Files
glips--figma-context-mcp/src/tests/telemetry-redaction.test.ts
T
wehub-resource-sync 588cfc5bb3
Release / release-please (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:45:31 +08:00

102 lines
3.5 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
// Mock posthog-node so we can observe what the telemetry client sends without
// hitting the network. We're testing OUR code (withRequestSecrets, ALS
// propagation, redactErrorMessage merge logic) end-to-end — only the system
// boundary is mocked.
const captureSpy = vi.fn();
const shutdownSpy = vi.fn(async () => {});
vi.mock("posthog-node", () => ({
PostHog: class {
capture = captureSpy;
shutdown = shutdownSpy;
},
}));
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
import type { Server } from "http";
import type { AddressInfo } from "net";
import { startHttpServer, stopHttpServer } from "~/server.js";
import { initTelemetry, shutdown as shutdownTelemetry } from "~/telemetry/index.js";
const PER_REQUEST_KEY = "figd_TENANT_SECRET_xyz789";
describe("per-request telemetry redaction", () => {
let client: Client;
let httpServer: Server | undefined;
beforeEach(() => {
captureSpy.mockClear();
// Init with NO global redaction secrets so the assertion proves the
// per-request AsyncLocalStorage path is what's doing the scrubbing.
initTelemetry({ optOut: false, immediateFlush: true, redactFromErrors: [] });
// Stub fetch to fail with the per-request token embedded in the error
// message. FigmaService wraps the original message into a new Error, so
// the secret survives into `outcome.error.message` and reaches captureEvent.
const realFetch = globalThis.fetch;
vi.stubGlobal(
"fetch",
vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
if (String(input).startsWith("https://api.figma.com")) {
throw new Error(`upstream failure (token=${PER_REQUEST_KEY})`);
}
return realFetch(input, init);
}),
);
});
afterEach(async () => {
await client?.close();
if (httpServer) {
await stopHttpServer();
httpServer = undefined;
}
await shutdownTelemetry();
vi.unstubAllGlobals();
});
it("scrubs per-request X-Figma-Token from telemetry error_message", async () => {
httpServer = await startHttpServer(
"127.0.0.1",
0,
{ figmaApiKey: "", figmaOAuthToken: "", useOAuth: false },
{},
);
const port = (httpServer.address() as AddressInfo).port;
client = new Client({ name: "redaction-test", version: "1.0.0" });
await client.connect(
new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${port}/mcp`), {
requestInit: { headers: { "X-Figma-Token": PER_REQUEST_KEY } },
}),
);
const result = await client.request(
{
method: "tools/call",
params: { name: "get_figma_data", arguments: { fileKey: "abc123" } },
},
CallToolResultSchema,
);
// Sanity: the tool call should fail (fetch threw), so we know the error
// path actually fired.
expect(result.isError).toBe(true);
const errorEvents = captureSpy.mock.calls
.map(([args]) => args)
.filter((args) => args?.properties?.is_error === true);
expect(errorEvents.length).toBeGreaterThan(0);
for (const event of errorEvents) {
const message = String(event.properties.error_message ?? "");
expect(message, `event ${event.event} leaked the per-request token`).not.toContain(
PER_REQUEST_KEY,
);
expect(message).toContain("[REDACTED]");
}
});
});