This commit is contained in:
Your Name committed 2026-10-11 06:43:08 +07:00
1 parent 9237c0eded
commit f5893c3fad
16 files changed
+1230 -114

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
import { expect, test } from "bun:test";
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { loadTargetRegistry } from "./target-profiles";
test("profile registry rejects unsafe or ambiguous configuration", () => {
expect(() => loadTargetRegistry({ MCP_DEFAULT_TARGET_PROFILE: "tenant-a" })).toThrow(/requires MCP_TARGET_PROFILES_FILE/);
const dir = mkdtempSync(join(tmpdir(), "dbk-mcp-profiles-"));
const file = join(dir, "profiles.json");
const base = { id: "tenant-a", displayName: "Tenant A", kind: "production", baseUrl: "https://a.example/api", usernameEnv: "A_USER", passwordEnv: "A_PASS" };
const env = { MCP_TARGET_PROFILES_FILE: file, MCP_DEFAULT_TARGET_PROFILE: "tenant-a", A_USER: "alice", A_PASS: "secret", B_USER: "bob", B_PASS: "secret2" };
try {
writeFileSync(file, JSON.stringify({ profiles: [base] }));
expect(loadTargetRegistry(env)?.profiles[0]?.id).toBe("tenant-a");
writeFileSync(file, JSON.stringify({ profiles: [base, base] }));
expect(() => loadTargetRegistry(env)).toThrow(/duplicated/);
writeFileSync(file, JSON.stringify({ profiles: [{ ...base, baseUrl: "http://customer.example/api" }] }));
expect(() => loadTargetRegistry(env)).toThrow(/HTTPS/);
for (const baseUrl of ["https://a.example/api?", "https://a.example/api#"]) {
writeFileSync(file, JSON.stringify({ profiles: [{ ...base, baseUrl }] }));
expect(() => loadTargetRegistry(env)).toThrow(/query, or fragment/);
}
writeFileSync(file, JSON.stringify({ profiles: [base, { ...base, id: "tenant-b", baseUrl: "https://A.EXAMPLE:443/api/", usernameEnv: "B_USER", passwordEnv: "B_PASS" }] }));
expect(() => loadTargetRegistry(env)).toThrow(/duplicated/);
writeFileSync(file, JSON.stringify({ profiles: [{ ...base, password: "literal" }] }));
expect(() => loadTargetRegistry(env)).toThrow(/literal credentials/);
writeFileSync(file, JSON.stringify({ profiles: [base] }));
expect(() => loadTargetRegistry({ ...env, A_PASS: "", ADMIN_PASSWORD: "legacy-fallback" })).toThrow(/missing credential/);
expect(() => loadTargetRegistry({ ...env, MCP_DEFAULT_TARGET_PROFILE: "missing" })).toThrow(/MCP_DEFAULT_TARGET_PROFILE/);
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test("MCP switches between two backends without sharing credentials or tokens", async () => {
const dir = mkdtempSync(join(tmpdir(), "dbk-mcp-protocol-"));
const file = join(dir, "profiles.json");
const seenA: Array<{ path: string; auth: string; username?: string; password?: string }> = [];
const seenB: Array<{ path: string; auth: string; username?: string; password?: string }> = [];
const bSettingsBodies: unknown[] = [];
let holdA = false;
let signalAStarted: (() => void) | undefined;
let releaseA: (() => void) | undefined;
const aStarted = new Promise<void>((resolve) => { signalAStarted = resolve; });
const aReleased = new Promise<void>((resolve) => { releaseA = resolve; });
const createBackend = (seen: typeof seenA, token: string) => Bun.serve({
port: 0,
fetch: async (request) => {
const path = new URL(request.url).pathname;
if (path === "/api/auth/login") {
const body = await request.json() as { username: string; password: string };
seen.push({ path, auth: request.headers.get("authorization") ?? "", ...body });
return Response.json({ token });
}
seen.push({ path, auth: request.headers.get("authorization") ?? "" });
if (token === "token-b" && path === "/api/settings" && request.method === "PUT") {
const body = await request.json();
bSettingsBodies.push(body);
return Response.json(body);
}
if (token === "token-a" && path === "/api/products/brands" && holdA) {
signalAStarted?.();
await aReleased;
}
if (path === "/api/products/brands") return Response.json([{ id: token }]);
return new Response("Not found", { status: 404 });
},
});
const a = createBackend(seenA, "token-a");
const b = createBackend(seenB, "token-b");
let client: Client | undefined;
try {
writeFileSync(file, JSON.stringify({ profiles: [
{ id: "tenant-a", displayName: "Tenant A", kind: "local", baseUrl: `http://localhost:${a.port}/api`, usernameEnv: "A_USER", passwordEnv: "A_PASS" },
{ id: "tenant-b", displayName: "Tenant B", kind: "local", baseUrl: `http://localhost:${b.port}/api`, usernameEnv: "B_USER", passwordEnv: "B_PASS" },
] }));
const env = { ...process.env, MCP_TARGET_PROFILES_FILE: file, MCP_DEFAULT_TARGET_PROFILE: "tenant-a", A_USER: "alice", A_PASS: "pass-a", B_USER: "bob", B_PASS: "pass-b" } as Record<string, string>;
const transport = new StdioClientTransport({ command: process.execPath, args: [join(import.meta.dir, "index.ts")], cwd: import.meta.dir, env, stderr: "pipe" });
client = new Client({ name: "profile-test", version: "1.0.0" });
await client.connect(transport);
const tools = (await client.listTools()).tools;
expect(tools.some((tool) => tool.name === "list_target_profiles")).toBe(true);
expect(tools.some((tool) => tool.name === "select_target_profile")).toBe(true);
expect(tools.some((tool) => tool.name === "upload_product_image_from_url")).toBe(false);
expect(tools.find((tool) => tool.name === "list_product_brands")?.inputSchema.required).toContain("expectedProfileId");
expect(tools.find((tool) => tool.name === "update_brand_name")?.inputSchema.required).toContain("expectedProfileId");
const listed = await client.callTool({ name: "list_target_profiles", arguments: {} });
expect(JSON.stringify(listed)).not.toContain("pass-a");
expect(JSON.stringify(listed)).not.toContain("pass-b");
const missing = await client.callTool({ name: "list_product_brands", arguments: {} });
expect(missing.isError).toBe(true);
const blockedUpload = await client.callTool({ name: "upload_product_image_from_url", arguments: { expectedProfileId: "tenant-a", imageUrl: "https://example.com/photo.jpg", productId: "p1" } });
expect(blockedUpload.isError).toBe(true);
expect(JSON.stringify(blockedUpload)).toContain("PROFILE_TOOL_UNAVAILABLE");
expect(seenA.filter((item) => item.path === "/api/products/brands")).toHaveLength(0);
const first = await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-a" } });
expect(first.structuredContent).toEqual({ data: [{ id: "token-a" }] });
const legacy = await client.callTool({ name: "set_target_environment", arguments: { env: "production", customUrl: "https://evil.example/api" } });
expect(legacy.isError).toBe(true);
expect((await client.callTool({ name: "get_target_environment", arguments: {} })).structuredContent).toMatchObject({ activeProfile: { id: "tenant-a" } });
const unknown = await client.callTool({ name: "select_target_profile", arguments: { profileId: "unknown" } });
expect(unknown.isError).toBe(true);
await client.callTool({ name: "select_target_profile", arguments: { profileId: "tenant-b" } });
const staleRead = await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-a" } });
expect(staleRead.isError).toBe(true);
const staleWrite = await client.callTool({ name: "update_brand_name", arguments: { expectedProfileId: "tenant-a", brandName: "Wrong tenant" } });
expect(staleWrite.isError).toBe(true);
expect(seenB).toHaveLength(0); // no login, stale read, or stale write reached backend
const second = await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-b" } });
expect(second.structuredContent).toEqual({ data: [{ id: "token-b" }] });
const validWrite = await client.callTool({ name: "update_brand_name", arguments: { expectedProfileId: "tenant-b", brandName: "Tenant B" } });
expect(validWrite.isError).toBeFalsy();
expect(bSettingsBodies).toEqual([{ brandName: "Tenant B" }]);
await client.callTool({ name: "select_target_profile", arguments: { profileId: "tenant-a" } });
await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-a" } });
holdA = true;
const inFlight = client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-a" } });
await aStarted;
const switching = client.callTool({ name: "select_target_profile", arguments: { profileId: "tenant-b" } });
releaseA?.();
expect((await inFlight).structuredContent).toEqual({ data: [{ id: "token-a" }] });
expect((await switching).structuredContent).toMatchObject({ activeProfile: { id: "tenant-b" } });
expect((await client.callTool({ name: "list_product_brands", arguments: { expectedProfileId: "tenant-b" } })).structuredContent).toEqual({ data: [{ id: "token-b" }] });
expect(seenA.filter((item) => item.path === "/api/auth/login")).toEqual([{ path: "/api/auth/login", auth: "", username: "alice", password: "pass-a" }]);
expect(seenB.filter((item) => item.path === "/api/auth/login")).toEqual([{ path: "/api/auth/login", auth: "", username: "bob", password: "pass-b" }]);
expect(seenA.filter((item) => item.path === "/api/products/brands").every((item) => item.auth === "Bearer token-a")).toBe(true);
expect(seenB.filter((item) => item.path === "/api/products/brands").every((item) => item.auth === "Bearer token-b")).toBe(true);
await client.close();
client = undefined;
const restartTransport = new StdioClientTransport({ command: process.execPath, args: [join(import.meta.dir, "index.ts")], cwd: import.meta.dir, env, stderr: "pipe" });
client = new Client({ name: "profile-restart-test", version: "1.0.0" });
await client.connect(restartTransport);
expect((await client.callTool({ name: "get_target_environment", arguments: {} })).structuredContent).toMatchObject({ mode: "profiles", activeProfile: { id: "tenant-a" } });
} finally {
await client?.close();
a.stop(); b.stop();
rmSync(dir, { recursive: true, force: true });
}
});