x
This commit is contained in:
1 parent
70877882b0
commit
ce2f4cf0c8
93 files changed
+4200
-67
No files matched your search
@@ -0,0 +1,386 @@
|
||||
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();
|
||||
});
|
||||
Reference in new issue
Block a user