import { describe, expect, test } from "bun:test"; import { getCollectionSummary } from "../../src/server/mcp/resources/collection.ts"; import { registerCategoryTools } from "../../src/server/mcp/tools/categories.ts"; import { registerItemTools } from "../../src/server/mcp/tools/items.ts"; import { registerSetupTools } from "../../src/server/mcp/tools/setups.ts"; import { registerThreadTools } from "../../src/server/mcp/tools/threads.ts"; import { createTestDb } from "../helpers/db.ts"; function parseResult(result: { content: Array<{ type: string; text: string }>; }) { return JSON.parse(result.content[0].text); } describe("MCP Item Tools", () => { test("list_items returns array", async () => { const db = createTestDb(); const tools = registerItemTools(db); const result = await tools.list_items({}); const data = parseResult(result); expect(Array.isArray(data)).toBe(true); }); test("create_item creates and returns item", async () => { const db = createTestDb(); const tools = registerItemTools(db); const result = await tools.create_item({ name: "Test Tent", categoryId: 1, weightGrams: 1200, priceCents: 35000, }); const data = parseResult(result); expect(data.name).toBe("Test Tent"); expect(data.weightGrams).toBe(1200); expect(data.priceCents).toBe(35000); expect(data.id).toBeDefined(); }); test("get_item retrieves by ID", async () => { const db = createTestDb(); const tools = registerItemTools(db); const created = parseResult( await tools.create_item({ name: "Sleeping Bag", categoryId: 1 }), ); const result = await tools.get_item({ id: created.id }); const data = parseResult(result); expect(data.name).toBe("Sleeping Bag"); expect(data.id).toBe(created.id); }); test("get_item returns error for missing item", async () => { const db = createTestDb(); const tools = registerItemTools(db); const result = await tools.get_item({ id: 999 }); const data = parseResult(result); expect(data.error).toContain("not found"); }); test("delete_item removes item", async () => { const db = createTestDb(); const tools = registerItemTools(db); const created = parseResult( await tools.create_item({ name: "To Delete", categoryId: 1 }), ); const deleteResult = await tools.delete_item({ id: created.id }); const data = parseResult(deleteResult); expect(data.deleted).toBe(true); // Verify it's gone const getResult = await tools.get_item({ id: created.id }); const getData = parseResult(getResult); expect(getData.error).toContain("not found"); }); }); describe("MCP Category Tools", () => { test("list_categories returns array with Uncategorized", async () => { const db = createTestDb(); const tools = registerCategoryTools(db); const result = await tools.list_categories(); const data = parseResult(result); expect(Array.isArray(data)).toBe(true); expect(data.length).toBeGreaterThanOrEqual(1); expect(data.some((c: any) => c.name === "Uncategorized")).toBe(true); }); test("create_category creates a new category", async () => { const db = createTestDb(); const tools = registerCategoryTools(db); const result = await tools.create_category({ name: "Shelter", icon: "tent", }); const data = parseResult(result); expect(data.name).toBe("Shelter"); expect(data.icon).toBe("tent"); }); }); describe("MCP Thread Tools", () => { test("create_thread starts a thread with status active", async () => { const db = createTestDb(); const tools = registerThreadTools(db); const result = await tools.create_thread({ name: "Handlebar Bag", categoryId: 1, }); const data = parseResult(result); expect(data.name).toBe("Handlebar Bag"); expect(data.status).toBe("active"); }); test("add_candidate adds to thread", async () => { const db = createTestDb(); const tools = registerThreadTools(db); const thread = parseResult( await tools.create_thread({ name: "Saddle Bag", categoryId: 1 }), ); const result = await tools.add_candidate({ threadId: thread.id, name: "Apidura Racing", categoryId: 1, priceCents: 8500, pros: "Lightweight", cons: "Expensive", }); const data = parseResult(result); expect(data.name).toBe("Apidura Racing"); expect(data.threadId).toBe(thread.id); expect(data.pros).toBe("Lightweight"); }); test("resolve_thread picks winner and creates item", async () => { const db = createTestDb(); const threadTools = registerThreadTools(db); const itemTools = registerItemTools(db); // Create thread with two candidates const thread = parseResult( await threadTools.create_thread({ name: "Frame Bag", categoryId: 1 }), ); const candidate1 = parseResult( await threadTools.add_candidate({ threadId: thread.id, name: "Revelate Tangle", categoryId: 1, priceCents: 12000, }), ); await threadTools.add_candidate({ threadId: thread.id, name: "Ortlieb Frame Pack", categoryId: 1, priceCents: 9000, }); // Resolve with first candidate const resolveResult = await threadTools.resolve_thread({ threadId: thread.id, candidateId: candidate1.id, }); const resolveData = parseResult(resolveResult); expect(resolveData.success).toBe(true); expect(resolveData.item.name).toBe("Revelate Tangle"); // Check item was added to collection const items = parseResult(await itemTools.list_items({})); expect(items.some((i: any) => i.name === "Revelate Tangle")).toBe(true); // Check thread is now resolved const threadList = parseResult( await threadTools.list_threads({ includeResolved: true }), ); const resolved = threadList.find((t: any) => t.id === thread.id); expect(resolved.status).toBe("resolved"); }); }); describe("MCP Setup Tools", () => { test("create_setup and list_setups", async () => { const db = createTestDb(); const tools = registerSetupTools(db); await tools.create_setup({ name: "Weekend Trip" }); const result = await tools.list_setups(); const data = parseResult(result); expect(data.length).toBe(1); expect(data[0].name).toBe("Weekend Trip"); }); test("get_setup returns setup with items", async () => { const db = createTestDb(); const setupTools = registerSetupTools(db); const itemTools = registerItemTools(db); const setup = parseResult( await setupTools.create_setup({ name: "Overnighter" }), ); const item = parseResult( await itemTools.create_item({ name: "Bivvy", categoryId: 1 }), ); await setupTools.update_setup({ id: setup.id, itemIds: [item.id] }); const result = await setupTools.get_setup({ id: setup.id }); const data = parseResult(result); expect(data.name).toBe("Overnighter"); expect(data.items.length).toBe(1); expect(data.items[0].name).toBe("Bivvy"); }); }); describe("MCP Collection Summary Resource", () => { test("returns overview with correct counts", () => { const db = createTestDb(); const summary = getCollectionSummary(db); expect(summary.overview).toBeDefined(); expect(summary.overview.totalItems).toBe(0); expect(summary.overview.categoryCount).toBe(1); // Uncategorized expect(summary.itemsByCategory).toBeDefined(); expect(summary.activeThreads).toBeDefined(); expect(Array.isArray(summary.activeThreads)).toBe(true); }); test("reflects items and threads after creation", async () => { const db = createTestDb(); const itemTools = registerItemTools(db); const threadTools = registerThreadTools(db); await itemTools.create_item({ name: "Tent", categoryId: 1, weightGrams: 1500, }); await itemTools.create_item({ name: "Sleeping Pad", categoryId: 1, weightGrams: 500, }); await threadTools.create_thread({ name: "Cook System", categoryId: 1, }); const summary = getCollectionSummary(db); expect(summary.overview.totalItems).toBe(2); expect(summary.overview.totalWeightGrams).toBe(2000); expect(summary.overview.activeThreadCount).toBe(1); expect(summary.itemsByCategory.Uncategorized).toBe(2); expect(summary.activeThreads.length).toBe(1); expect(summary.activeThreads[0].name).toBe("Cook System"); }); });