import prisma from "./src/lib/prisma"; import axios from "axios"; import { handleMenuTool } from "../mcp-server/menu-toolkit"; const API_BASE = "http://localhost:3051/api"; const TEST_MENU_NAME_API = "challenger-test-menu-api"; const TEST_MENU_NAME_MCP = "challenger-test-menu-mcp"; async function loginAndGetToken() { const loginRes = await fetch(`${API_BASE}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "admin", password: "homaroma123" }) }); if (!loginRes.ok) { throw new Error(`Failed to log in: ${loginRes.statusText}`); } const data = await loginRes.json() as { success: boolean; token: string }; if (!data.success || !data.token) { throw new Error(`Invalid login response: ${JSON.stringify(data)}`); } return data.token; } async function cleanDatabase() { console.log("Cleaning up previous test data in the database..."); await prisma.menu.deleteMany({ where: { name: { in: [TEST_MENU_NAME_API, TEST_MENU_NAME_MCP] } } }); console.log("Cleanup finished."); } async function run() { const token = await loginAndGetToken(); console.log("Obtained Admin Authorization Token successfully."); await cleanDatabase(); console.log("\n============================================="); console.log("PART 1: Direct Elysia API Routes Verification"); console.log("============================================="); // 1. Post a new menu via API console.log("1.1 POST /api/menus - Creating a menu with rootImage and items with columnImage & hoverImage..."); const createPayload = { name: TEST_MENU_NAME_API, rootImage: "https://example.com/api-root-original.png", items: [ { label: "Link 1", url: "/link1", order: 1, columnImage: "https://example.com/api-col1-original.png", hoverImage: "https://example.com/api-hover1-original.png" }, { label: "Link 2", url: "/link2", order: 2, columnImage: null, hoverImage: null } ] }; const postRes = await fetch(`${API_BASE}/menus`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify(createPayload) }); if (!postRes.ok) { throw new Error(`POST /menus failed: ${postRes.status} ${await postRes.text()}`); } const createdMenu = await postRes.json() as any; console.log("Successfully created menu via API:", JSON.stringify(createdMenu, null, 2)); // 2. Query the backend API and verify stored fields console.log("\n1.2 GET /api/menus/:name - Querying the backend API to verify fields..."); const getRes = await fetch(`${API_BASE}/menus/${TEST_MENU_NAME_API}`); if (!getRes.ok) { throw new Error(`GET /menus/${TEST_MENU_NAME_API} failed: ${getRes.status}`); } const retrievedMenu = await getRes.json() as any; console.log("Retrieved menu from API:", JSON.stringify(retrievedMenu, null, 2)); if (retrievedMenu.rootImage !== createPayload.rootImage) { throw new Error(`API Verification Error: Menu rootImage mismatch. Expected '${createPayload.rootImage}', got '${retrievedMenu.rootImage}'`); } const items = retrievedMenu.items || []; if (items.length !== 2) { throw new Error(`API Verification Error: Expected 2 items, got ${items.length}`); } const item1 = items.find((i: any) => i.label === "Link 1"); const item2 = items.find((i: any) => i.label === "Link 2"); if (!item1 || item1.columnImage !== "https://example.com/api-col1-original.png" || item1.hoverImage !== "https://example.com/api-hover1-original.png") { throw new Error(`API Verification Error: Item 1 image fields mismatch. Got: col='${item1?.columnImage}', hover='${item1?.hoverImage}'`); } if (!item2 || item2.columnImage !== null || item2.hoverImage !== null) { throw new Error(`API Verification Error: Item 2 image fields mismatch. Got: col='${item2?.columnImage}', hover='${item2?.hoverImage}'`); } console.log("✅ GET API fields are stored and retrieved correctly!"); // 3. Update the menu via PUT /api/menus/:name console.log("\n1.3 PUT /api/menus/:name - Updating the menu and items..."); const updatePayload = { rootImage: "https://example.com/api-root-updated.png", items: [ { id: item1.id, label: "Link 1 Updated", url: "/link1-updated", order: 10, columnImage: "https://example.com/api-col1-updated.png", hoverImage: "https://example.com/api-hover1-updated.png" }, { label: "Link 3 Added", url: "/link3", order: 3, columnImage: "https://example.com/api-col3-new.png", hoverImage: null } ] }; const putRes = await fetch(`${API_BASE}/menus/${TEST_MENU_NAME_API}`, { method: "PUT", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify(updatePayload) }); if (!putRes.ok) { throw new Error(`PUT /menus failed: ${putRes.status} ${await putRes.text()}`); } const updatedMenu = await putRes.json() as any; console.log("Successfully updated menu via PUT:", JSON.stringify(updatedMenu, null, 2)); if (updatedMenu.rootImage !== updatePayload.rootImage) { throw new Error(`API Verification Error: Updated Menu rootImage mismatch. Expected '${updatePayload.rootImage}', got '${updatedMenu.rootImage}'`); } const updatedItems = updatedMenu.items || []; const updatedItem1 = updatedItems.find((i: any) => i.id === item1.id); const addedItem3 = updatedItems.find((i: any) => i.label === "Link 3 Added"); if (!updatedItem1 || updatedItem1.columnImage !== "https://example.com/api-col1-updated.png" || updatedItem1.hoverImage !== "https://example.com/api-hover1-updated.png") { throw new Error(`API Verification Error: Updated Item 1 image fields mismatch.`); } if (!addedItem3 || addedItem3.columnImage !== "https://example.com/api-col3-new.png" || addedItem3.hoverImage !== null) { throw new Error(`API Verification Error: Added Item 3 image fields mismatch.`); } console.log("✅ PUT API fields update verified successfully!"); // 4. Verify validation errors for incorrect types in Elysia routes console.log("\n1.4 Verify Elysia Route Validation Errors for Incorrect Types..."); const invalidPayloads = [ { description: "Invalid rootImage type (number instead of string/null)", payload: { name: "challenger-invalid-1", rootImage: 12345, items: [] } }, { description: "Invalid columnImage type (boolean instead of string/null)", payload: { name: "challenger-invalid-2", rootImage: null, items: [ { label: "Link", url: "/url", order: 1, columnImage: true } ] } }, { description: "Invalid order type (string instead of number)", payload: { name: "challenger-invalid-3", rootImage: null, items: [ { label: "Link", url: "/url", order: "first", columnImage: null } ] } } ]; for (const { description, payload } of invalidPayloads) { const invalidRes = await fetch(`${API_BASE}/menus`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify(payload) }); console.log(`- Testing: ${description}`); console.log(` Response Status: ${invalidRes.status} (Expected: 422)`); if (invalidRes.status !== 422) { throw new Error(`API Validation Verification Error: Expected 422 status, got ${invalidRes.status}`); } const errorBody = await invalidRes.json(); console.log(` Validation Message: ${JSON.stringify((errorBody as any).summary || (errorBody as any).message || errorBody)}`); } console.log("✅ Elysia Route Validation Errors verification passed!"); console.log("\n============================================="); console.log("PART 2: Direct Database Schema Verification"); console.log("============================================="); console.log("Querying database directly using Prisma client..."); const dbMenu = await prisma.menu.findUnique({ where: { name: TEST_MENU_NAME_API }, include: { items: true } }); if (!dbMenu) { throw new Error("DB Verification Error: Menu not found in database."); } console.log("Direct DB Menu Record:", JSON.stringify(dbMenu, null, 2)); if (dbMenu.rootImage !== "https://example.com/api-root-updated.png") { throw new Error(`DB Verification Error: Menu rootImage column mismatch. Expected 'https://example.com/api-root-updated.png', got '${dbMenu.rootImage}'`); } const dbItem1 = dbMenu.items.find(i => i.url === "/link1-updated"); if (!dbItem1 || dbItem1.columnImage !== "https://example.com/api-col1-updated.png" || dbItem1.hoverImage !== "https://example.com/api-hover1-updated.png") { throw new Error("DB Verification Error: Menu item 1 columns mismatch in PostgreSQL."); } const dbItem3 = dbMenu.items.find(i => i.url === "/link3"); if (!dbItem3 || dbItem3.columnImage !== "https://example.com/api-col3-new.png" || dbItem3.hoverImage !== null) { throw new Error("DB Verification Error: Menu item 3 columns mismatch in PostgreSQL."); } console.log("✅ PostgreSQL schema verification passed!"); console.log("\n============================================="); console.log("PART 3: MCP Tools Layer Verification"); console.log("============================================="); // Create an Axios instance mimicking the MCP server environment const apiAxios = axios.create({ baseURL: API_BASE, headers: { Authorization: `Bearer ${token}` } }); // 1. Test upsert_menu MCP Tool console.log("3.1 Testing upsert_menu MCP Tool..."); const mcpPayload = { name: TEST_MENU_NAME_MCP, rootImage: "https://example.com/mcp-root.png", items: [ { label: "MCP Link 1", url: "/mcp-link1", order: 1, columnImage: "https://example.com/mcp-col1.png", hoverImage: "https://example.com/mcp-hover1.png" } ] }; const upsertToolRes = await handleMenuTool(apiAxios, "upsert_menu", mcpPayload); console.log("upsert_menu MCP response:", JSON.stringify(upsertToolRes, null, 2)); if (upsertToolRes?.isError) { throw new Error(`MCP Tool Error: upsert_menu failed: ${JSON.stringify(upsertToolRes)}`); } // Verify stored fields via direct API / DB const mcpDbMenu = await prisma.menu.findUnique({ where: { name: TEST_MENU_NAME_MCP }, include: { items: true } }); if (!mcpDbMenu) { throw new Error("MCP DB Verification Error: Menu created by MCP not found."); } if (mcpDbMenu.rootImage !== mcpPayload.rootImage) { throw new Error(`MCP DB Verification Error: rootImage mismatch. Got '${mcpDbMenu.rootImage}'`); } const mcpItem = mcpDbMenu.items[0]; if (!mcpItem || mcpItem.columnImage !== "https://example.com/mcp-col1.png" || mcpItem.hoverImage !== "https://example.com/mcp-hover1.png") { throw new Error(`MCP DB Verification Error: Item columns mismatch. Got col='${mcpItem?.columnImage}', hover='${mcpItem?.hoverImage}'`); } console.log("✅ MCP upsert_menu successfully created & stored correct fields!"); // 2. Test get_menu MCP Tool console.log("\n3.2 Testing get_menu MCP Tool..."); const getToolRes = await handleMenuTool(apiAxios, "get_menu", { name: TEST_MENU_NAME_MCP }); console.log("get_menu MCP response:", JSON.stringify(getToolRes, null, 2)); if (getToolRes?.isError) { throw new Error(`MCP Tool Error: get_menu failed: ${JSON.stringify(getToolRes)}`); } const mcpContentText = getToolRes?.content?.[0]?.text; if (!mcpContentText) { throw new Error("MCP Tool Error: No content returned by get_menu."); } const mcpMenuData = JSON.parse(mcpContentText); if (mcpMenuData.rootImage !== mcpPayload.rootImage) { throw new Error(`MCP Tool Verification Error: rootImage mismatch in get_menu response.`); } if (mcpMenuData.items?.[0]?.columnImage !== "https://example.com/mcp-col1.png") { throw new Error(`MCP Tool Verification Error: columnImage mismatch in get_menu response.`); } console.log("✅ MCP get_menu successfully retrieved correct fields!"); // 3. Verify validation errors in MCP Tools Layer console.log("\n3.3 Verify MCP Tool Validation Errors for Incorrect Types..."); const invalidMcpPayloads = [ { description: "Invalid items parameter type (not an array)", payload: { name: TEST_MENU_NAME_MCP, items: "not-an-array" } }, { description: "Invalid columnImage parameter type (number instead of string/null)", payload: { name: TEST_MENU_NAME_MCP, items: [ { label: "Invalid Item", url: "/invalid", order: 1, columnImage: 999 } ] } } ]; for (const { description, payload } of invalidMcpPayloads) { console.log(`- Testing MCP Tool: ${description}`); try { await handleMenuTool(apiAxios, "upsert_menu", payload); throw new Error(`Expected MCP Tool to throw validation error, but it succeeded.`); } catch (e: any) { console.log(` Caught expected validation error: ${e.message}`); } } console.log("✅ MCP Tool validation error checking passed!"); console.log("\n============================================="); console.log("ALL TESTS COMPLETED SUCCESSFULLY!"); console.log("============================================="); } run() .catch((err) => { console.error("\n❌ VERIFICATION TEST FAILED!"); console.error(err); process.exit(1); }) .finally(() => { prisma.$disconnect(); });