Skip to main content
Glama
by wei
validators.test.ts8.13 kB
/** * Unit Tests for Input Validators * * Tests all Zod schemas and validation rules from data-model.md */ import { describe, expect, it } from "vitest"; import { ZodError } from "zod"; import { GetFrontPageInputSchema, GetItemInputSchema, GetLatestPostsInputSchema, GetUserInputSchema, SearchPostsInputSchema, safeValidateInput, validateInput, } from "../../src/utils/validators.js"; describe("SearchPostsInputSchema", () => { it("should accept valid input with all fields", () => { const input = { query: "AI", tags: ["story"], numericFilters: ["points>=100"], page: 0, hitsPerPage: 20, }; const result = validateInput(SearchPostsInputSchema, input); expect(result).toEqual(input); }); it("should accept valid input with only required fields", () => { const input = { query: "Python" }; const result = validateInput(SearchPostsInputSchema, input); expect(result.query).toBe("Python"); expect(result.page).toBe(0); // Default expect(result.hitsPerPage).toBe(20); // Default }); it("should reject empty query string", () => { const input = { query: "" }; expect(() => validateInput(SearchPostsInputSchema, input)).toThrow(ZodError); }); it("should reject negative page number", () => { const input = { query: "test", page: -1 }; expect(() => validateInput(SearchPostsInputSchema, input)).toThrow(ZodError); }); it("should reject hitsPerPage less than 1", () => { const input = { query: "test", hitsPerPage: 0 }; expect(() => validateInput(SearchPostsInputSchema, input)).toThrow(ZodError); }); it("should reject hitsPerPage greater than 1000", () => { const input = { query: "test", hitsPerPage: 1001 }; expect(() => validateInput(SearchPostsInputSchema, input)).toThrow(ZodError); }); it("should accept optional empty arrays for tags and numericFilters", () => { const input = { query: "test", tags: [], numericFilters: [], }; const result = validateInput(SearchPostsInputSchema, input); expect(result.tags).toEqual([]); expect(result.numericFilters).toEqual([]); }); }); describe("GetFrontPageInputSchema", () => { it("should accept valid input with all fields", () => { const input = { page: 0, hitsPerPage: 30 }; const result = validateInput(GetFrontPageInputSchema, input); expect(result).toEqual(input); }); it("should use defaults when no fields provided", () => { const result = validateInput(GetFrontPageInputSchema, {}); expect(result.page).toBe(0); expect(result.hitsPerPage).toBe(30); }); it("should reject negative page number", () => { const input = { page: -1 }; expect(() => validateInput(GetFrontPageInputSchema, input)).toThrow(ZodError); }); it("should accept page 0", () => { const input = { page: 0 }; const result = validateInput(GetFrontPageInputSchema, input); expect(result.page).toBe(0); }); it("should reject invalid hitsPerPage range", () => { expect(() => validateInput(GetFrontPageInputSchema, { hitsPerPage: 0 })).toThrow(ZodError); expect(() => validateInput(GetFrontPageInputSchema, { hitsPerPage: 1001 })).toThrow(ZodError); }); }); describe("GetLatestPostsInputSchema", () => { it("should accept valid input with all fields", () => { const input = { tags: ["story", "comment"], page: 1, hitsPerPage: 50, }; const result = validateInput(GetLatestPostsInputSchema, input); expect(result).toEqual(input); }); it("should use defaults for pagination", () => { const result = validateInput(GetLatestPostsInputSchema, {}); expect(result.page).toBe(0); expect(result.hitsPerPage).toBe(20); }); it("should accept optional tags array", () => { const input = { tags: ["story"] }; const result = validateInput(GetLatestPostsInputSchema, input); expect(result.tags).toEqual(["story"]); }); it("should accept empty input object", () => { const result = validateInput(GetLatestPostsInputSchema, {}); expect(result.page).toBe(0); expect(result.hitsPerPage).toBe(20); }); }); describe("GetItemInputSchema", () => { it("should accept valid item ID", () => { const input = { itemId: "38456789" }; const result = validateInput(GetItemInputSchema, input); expect(result.itemId).toBe("38456789"); }); it("should reject empty item ID", () => { const input = { itemId: "" }; expect(() => validateInput(GetItemInputSchema, input)).toThrow(ZodError); }); it("should reject missing item ID", () => { expect(() => validateInput(GetItemInputSchema, {})).toThrow(ZodError); }); it("should accept numeric string IDs", () => { const input = { itemId: "123" }; const result = validateInput(GetItemInputSchema, input); expect(result.itemId).toBe("123"); }); }); describe("GetUserInputSchema", () => { it("should accept valid alphanumeric username", () => { const input = { username: "pg" }; const result = validateInput(GetUserInputSchema, input); expect(result.username).toBe("pg"); }); it("should accept username with underscores", () => { const input = { username: "user_name_123" }; const result = validateInput(GetUserInputSchema, input); expect(result.username).toBe("user_name_123"); }); it("should accept username with numbers", () => { const input = { username: "user123" }; const result = validateInput(GetUserInputSchema, input); expect(result.username).toBe("user123"); }); it("should reject empty username", () => { const input = { username: "" }; expect(() => validateInput(GetUserInputSchema, input)).toThrow(ZodError); }); it("should reject username with spaces", () => { const input = { username: "user name" }; expect(() => validateInput(GetUserInputSchema, input)).toThrow(ZodError); }); it("should reject username with special characters", () => { expect(() => validateInput(GetUserInputSchema, { username: "user@name" })).toThrow(ZodError); expect(() => validateInput(GetUserInputSchema, { username: "user-name" })).toThrow(ZodError); expect(() => validateInput(GetUserInputSchema, { username: "user.name" })).toThrow(ZodError); }); it("should reject missing username", () => { expect(() => validateInput(GetUserInputSchema, {})).toThrow(ZodError); }); }); describe("safeValidateInput", () => { it("should return success for valid input", () => { const input = { query: "AI" }; const result = safeValidateInput(SearchPostsInputSchema, input); expect(result.success).toBe(true); if (result.success) { expect(result.data.query).toBe("AI"); } }); it("should return error for invalid input", () => { const input = { query: "" }; const result = safeValidateInput(SearchPostsInputSchema, input); expect(result.success).toBe(false); if (!result.success) { expect(result.error).toBeInstanceOf(ZodError); } }); it("should not throw on validation failure", () => { const input = { username: "user@invalid" }; expect(() => safeValidateInput(GetUserInputSchema, input)).not.toThrow(); }); }); describe("Edge Cases", () => { it("should handle maximum valid hitsPerPage (1000)", () => { const input = { query: "test", hitsPerPage: 1000 }; const result = validateInput(SearchPostsInputSchema, input); expect(result.hitsPerPage).toBe(1000); }); it("should handle minimum valid hitsPerPage (1)", () => { const input = { query: "test", hitsPerPage: 1 }; const result = validateInput(SearchPostsInputSchema, input); expect(result.hitsPerPage).toBe(1); }); it("should handle large page numbers", () => { const input = { query: "test", page: 999 }; const result = validateInput(SearchPostsInputSchema, input); expect(result.page).toBe(999); }); it("should handle complex numeric filters", () => { const input = { query: "AI", numericFilters: ["points>=100", "created_at_i>1640000000", "num_comments>=50"], }; const result = validateInput(SearchPostsInputSchema, input); expect(result.numericFilters).toHaveLength(3); }); it("should handle multiple tags", () => { const input = { query: "test", tags: ["story", "front_page", "show_hn"], }; const result = validateInput(SearchPostsInputSchema, input); expect(result.tags).toHaveLength(3); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wei/hn-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server