/**
* Contract Tests: search-posts tool
*
* Tests input validation and output schema compliance for the search-posts tool.
* These tests verify the tool's contract without making real API calls.
*/
import { describe, expect, it } from "vitest";
import { searchPostsTool } from "../../src/tools/search-posts.js";
import { SearchPostsInputSchema } from "../../src/utils/validators.js";
describe("search-posts tool contract", () => {
describe("Input Validation", () => {
it("should accept valid minimal input with query only", () => {
const input = { query: "AI" };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it("should accept valid input with all parameters", () => {
const input = {
query: "Python",
tags: ["story"],
numericFilters: ["points>=100"],
page: 0,
hitsPerPage: 20,
};
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it("should reject empty query string", () => {
const input = { query: "" };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it("should reject missing query parameter", () => {
const input = { page: 0 };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it("should accept optional tags array", () => {
const input = { query: "test", tags: ["story", "comment"] };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it("should reject negative page number", () => {
const input = { query: "test", page: -1 };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it("should accept page 0 (first page)", () => {
const input = { query: "test", page: 0 };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it("should reject hitsPerPage less than 1", () => {
const input = { query: "test", hitsPerPage: 0 };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it("should reject hitsPerPage greater than 1000", () => {
const input = { query: "test", hitsPerPage: 1001 };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it("should accept hitsPerPage within valid range (1-1000)", () => {
const input = { query: "test", hitsPerPage: 50 };
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it("should accept multiple numeric filters", () => {
const input = {
query: "test",
numericFilters: ["points>=100", "num_comments>=50"],
};
const result = SearchPostsInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it("should apply default values for optional parameters", () => {
const input = { query: "test" };
const result = SearchPostsInputSchema.parse(input);
expect(result.page).toBe(0);
expect(result.hitsPerPage).toBe(20);
});
});
describe("Output Schema", () => {
it("should return error result for invalid input", async () => {
const result = await searchPostsTool({ query: "" });
expect(result.isError).toBe(true);
expect(result.content).toHaveLength(1);
expect(result.content[0].type).toBe("text");
});
it("should return properly structured error for validation failure", async () => {
const result = await searchPostsTool({ page: -1 });
expect(result.isError).toBe(true);
const content = result.content[0];
if (content.type === "text") {
expect(content.text).toContain("Validation error");
}
});
it("should handle missing required parameters gracefully", async () => {
const result = await searchPostsTool({});
expect(result.isError).toBe(true);
const content = result.content[0];
if (content.type === "text") {
expect(content.text).toContain("query");
}
});
});
describe("Tool Metadata", () => {
it("should have correct tool name", async () => {
// Import metadata separately to test it
const { searchPostsToolMetadata } = await import("../../src/tools/search-posts.js");
expect(searchPostsToolMetadata.name).toBe("search-posts");
});
it("should have comprehensive description", async () => {
const { searchPostsToolMetadata } = await import("../../src/tools/search-posts.js");
expect(searchPostsToolMetadata.description).toBeTruthy();
expect(searchPostsToolMetadata.description.length).toBeGreaterThan(50);
});
it("should have properly defined input schema", async () => {
const { searchPostsToolMetadata } = await import("../../src/tools/search-posts.js");
expect(searchPostsToolMetadata.inputSchema).toBeDefined();
expect(searchPostsToolMetadata.inputSchema.type).toBe("object");
expect(searchPostsToolMetadata.inputSchema.properties).toHaveProperty("query");
expect(searchPostsToolMetadata.inputSchema.required).toContain("query");
});
});
});