/**
* Integration Tests: get-item tool
*
* Tests the get-item tool with real HackerNews API calls.
* Verifies correct behavior for retrieving items with nested comments.
*/
import { describe, expect, it } from "vitest";
import { getItemHandler } from "../../../src/tools/get-item.js";
/**
* Helper: Check if item has nested children
*/
function hasNestedChildren(children: { children?: unknown[] }[]): boolean {
return children.some((child) => child.children && child.children.length > 0);
}
/**
* Helper: Find child with children
*/
function findChildWithChildren(
children: { children?: unknown[] }[]
): { children?: unknown[] } | undefined {
return children.find((child) => child.children && child.children.length > 0);
}
/**
* Helper: Validate children structure recursively
*/
function validateChildren(item: { children?: unknown[] }): boolean {
if (!item.children) return true;
if (!Array.isArray(item.children)) return false;
return item.children.every((child: unknown) => {
if (typeof child !== "object" || child === null) return false;
const childObj = child as { children?: unknown[] };
return validateChildren(childObj);
});
}
describe("get-item integration", () => {
describe("Story Retrieval", () => {
it("should retrieve a story by ID", async () => {
// Using a well-known story ID (pg's "How to Start a Startup" is 8863)
const result = await getItemHandler({ itemId: "8863" });
expect(result.isError).toBe(false);
expect(result.content).toHaveLength(1);
const content = result.content[0];
expect(content.type).toBe("text");
if (content.type === "text") {
const data = JSON.parse(content.text);
expect(data).toHaveProperty("id");
expect(data).toHaveProperty("type");
expect(data).toHaveProperty("author");
expect(data).toHaveProperty("title");
expect(data).toHaveProperty("created_at");
expect(data).toHaveProperty("children");
expect(data.type).toBe("story");
expect(Array.isArray(data.children)).toBe(true);
}
});
it("should include story metadata", async () => {
const result = await getItemHandler({ itemId: "8863" });
expect(result.isError).toBe(false);
const content = result.content[0];
if (content.type === "text") {
const data = JSON.parse(content.text);
expect(data.id).toBeTruthy();
expect(data.author).toBeTruthy();
expect(typeof data.created_at_i).toBe("number");
// Story should have a title
if (data.type === "story") {
expect(data.title).toBeTruthy();
}
}
});
it("should retrieve children array for stories", async () => {
const result = await getItemHandler({ itemId: "8863" });
expect(result.isError).toBe(false);
const content = result.content[0];
if (content.type === "text") {
const data = JSON.parse(content.text);
expect(data).toHaveProperty("children");
expect(Array.isArray(data.children)).toBe(true);
}
});
});
describe("Comment Retrieval", () => {
it("should retrieve a comment by ID", async () => {
// Using a known comment ID from HN
const result = await getItemHandler({ itemId: "2921983" });
expect(result.isError).toBe(false);
const content = result.content[0];
if (content.type === "text") {
const data = JSON.parse(content.text);
expect(data.type).toBe("comment");
expect(data).toHaveProperty("text");
expect(data).toHaveProperty("author");
expect(data).toHaveProperty("parent_id");
}
});
it("should include comment text and author", async () => {
const result = await getItemHandler({ itemId: "2921983" });
expect(result.isError).toBe(false);
const content = result.content[0];
if (content.type === "text") {
const data = JSON.parse(content.text);
expect(data.text).toBeTruthy();
expect(data.author).toBeTruthy();
}
});
});
describe("Nested Comments", () => {
it("should fetch nested comment structure", async () => {
// Item 8863 is a story with comments
const result = await getItemHandler({ itemId: "8863" });
expect(result.isError).toBe(false);
const content = result.content[0];
if (content.type === "text") {
const data = JSON.parse(content.text);
expect(Array.isArray(data.children)).toBe(true);
// If there are children, verify they have the expected structure
if (data.children.length > 0) {
const firstChild = data.children[0];
expect(firstChild).toHaveProperty("id");
expect(firstChild).toHaveProperty("author");
}
}
});
it("should handle multiple levels of nesting", async () => {
const result = await getItemHandler({ itemId: "8863" });
expect(result.isError).toBe(false);
const content = result.content[0];
if (content.type === "text") {
const data = JSON.parse(content.text);
// Check if there are nested children (comments with replies)
if (data.children && data.children.length > 0) {
const nested = hasNestedChildren(data.children);
// If there are nested comments, verify structure
if (nested) {
const childWithChildren = findChildWithChildren(data.children);
expect(Array.isArray(childWithChildren?.children)).toBe(true);
}
}
}
});
it("should preserve comment tree structure", async () => {
const result = await getItemHandler({ itemId: "8863" });
expect(result.isError).toBe(false);
const content = result.content[0];
if (content.type === "text") {
const data = JSON.parse(content.text);
expect(validateChildren(data)).toBe(true);
}
});
});
describe("Error Handling", () => {
it("should return error for empty itemId", async () => {
const result = await getItemHandler({ itemId: "" });
expect(result.isError).toBe(true);
const content = result.content[0];
if (content.type === "text") {
expect(content.text).toContain("itemId");
}
});
it("should return error for missing itemId", async () => {
const result = await getItemHandler({});
expect(result.isError).toBe(true);
});
it("should handle non-existent item ID", async () => {
// Using a very high number that likely doesn't exist
const result = await getItemHandler({ itemId: "999999999999" });
expect(result.isError).toBe(true);
const content = result.content[0];
if (content.type === "text") {
expect(content.text).toBeTruthy();
}
});
});
});