Skip to content

Commit c0b8d7a

Browse files
committed
add tests
1 parent 7eb0143 commit c0b8d7a

File tree

3 files changed

+176
-11
lines changed

3 files changed

+176
-11
lines changed

src/common/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export class McpLogger extends LoggerBase {
218218
}
219219
}
220220

221-
class CompositeLogger extends LoggerBase {
221+
export class CompositeLogger extends LoggerBase {
222222
// This is not a real logger type - it should not be used anyway.
223223
protected type: LoggerType = "composite" as unknown as LoggerType;
224224

tests/unit/logger.test.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { describe, beforeEach, afterEach, vi, MockInstance, it, expect } from "vitest";
2+
import { CompositeLogger, ConsoleLogger, LoggerType, LogId, McpLogger } from "../../src/common/logger.js";
3+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4+
5+
describe("Logger", () => {
6+
let consoleErrorSpy: MockInstance<typeof console.error>;
7+
let consoleLogger: ConsoleLogger;
8+
9+
let mcpLoggerSpy: MockInstance;
10+
let mcpLogger: McpLogger;
11+
12+
beforeEach(() => {
13+
// Mock console.error before creating the ConsoleLogger
14+
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
15+
16+
consoleLogger = new ConsoleLogger();
17+
18+
mcpLoggerSpy = vi.fn();
19+
mcpLogger = new McpLogger({
20+
server: {
21+
sendLoggingMessage: mcpLoggerSpy,
22+
},
23+
isConnected: () => true,
24+
} as unknown as McpServer);
25+
});
26+
27+
afterEach(() => {
28+
vi.restoreAllMocks();
29+
});
30+
31+
const getLastMcpLogMessage = (): string => {
32+
return (mcpLoggerSpy.mock.lastCall?.[0] as { data: string }).data;
33+
};
34+
35+
const getLastConsoleMessage = (): string => {
36+
return consoleErrorSpy.mock.lastCall?.[0] as string;
37+
};
38+
39+
const mockSensitivePayload = {
40+
id: LogId.serverInitialized,
41+
context: "test",
42+
message: "My email is [email protected]",
43+
};
44+
45+
const expectLogMessageRedaction = (logMessage: string, expectRedacted: boolean): void => {
46+
const expectedContain = expectRedacted ? "<email>" : "[email protected]";
47+
const expectedNotContain = expectRedacted ? "[email protected]" : "<email>";
48+
49+
expect(logMessage).to.contain(expectedContain);
50+
expect(logMessage).to.not.contain(expectedNotContain);
51+
};
52+
53+
describe("redaction", () => {
54+
it("redacts sensitive information by default", () => {
55+
consoleLogger.info(mockSensitivePayload);
56+
57+
expect(consoleErrorSpy).toHaveBeenCalledOnce();
58+
59+
expectLogMessageRedaction(getLastConsoleMessage(), true);
60+
});
61+
62+
it("does not redact sensitive information for mcp logger by default", () => {
63+
mcpLogger.info(mockSensitivePayload);
64+
65+
expect(mcpLoggerSpy).toHaveBeenCalledOnce();
66+
67+
expectLogMessageRedaction(getLastMcpLogMessage(), false);
68+
});
69+
70+
it("allows disabling redaction for all loggers", () => {
71+
const payload = {
72+
...mockSensitivePayload,
73+
noRedaction: true,
74+
};
75+
76+
consoleLogger.debug(payload);
77+
mcpLogger.error(payload);
78+
79+
expect(consoleErrorSpy).toHaveBeenCalledOnce();
80+
expectLogMessageRedaction(getLastConsoleMessage(), false);
81+
82+
expect(mcpLoggerSpy).toHaveBeenCalledOnce();
83+
expectLogMessageRedaction(getLastMcpLogMessage(), false);
84+
});
85+
86+
it("allows forcing redaction for all loggers", () => {
87+
const payload = {
88+
...mockSensitivePayload,
89+
noRedaction: false,
90+
};
91+
92+
consoleLogger.warning(payload);
93+
mcpLogger.warning(payload);
94+
95+
expect(consoleErrorSpy).toHaveBeenCalledOnce();
96+
expectLogMessageRedaction(getLastConsoleMessage(), true);
97+
98+
expect(mcpLoggerSpy).toHaveBeenCalledOnce();
99+
expectLogMessageRedaction(getLastMcpLogMessage(), true);
100+
});
101+
102+
it("allows disabling redaction for specific loggers", () => {
103+
const payload = {
104+
...mockSensitivePayload,
105+
noRedaction: "console" as LoggerType,
106+
};
107+
108+
consoleLogger.debug(payload);
109+
mcpLogger.debug(payload);
110+
111+
expect(consoleErrorSpy).toHaveBeenCalledOnce();
112+
expectLogMessageRedaction(getLastConsoleMessage(), false);
113+
114+
expect(mcpLoggerSpy).toHaveBeenCalledOnce();
115+
expectLogMessageRedaction(getLastMcpLogMessage(), true);
116+
});
117+
118+
it("allows disabling redaction for multiple loggers", () => {
119+
const payload = {
120+
...mockSensitivePayload,
121+
noRedaction: ["console", "mcp"] as LoggerType[],
122+
};
123+
124+
consoleLogger.notice(payload);
125+
mcpLogger.notice(payload);
126+
127+
expect(consoleErrorSpy).toHaveBeenCalledOnce();
128+
expectLogMessageRedaction(getLastConsoleMessage(), false);
129+
130+
expect(mcpLoggerSpy).toHaveBeenCalledOnce();
131+
expectLogMessageRedaction(getLastMcpLogMessage(), false);
132+
});
133+
134+
describe("CompositeLogger", () => {
135+
it("propagates noRedaction config to child loggers", () => {
136+
const compositeLogger = new CompositeLogger(consoleLogger, mcpLogger);
137+
compositeLogger.info({
138+
...mockSensitivePayload,
139+
noRedaction: true,
140+
});
141+
142+
expect(consoleErrorSpy).toHaveBeenCalledOnce();
143+
expectLogMessageRedaction(getLastConsoleMessage(), false);
144+
145+
expect(mcpLoggerSpy).toHaveBeenCalledOnce();
146+
expectLogMessageRedaction(getLastMcpLogMessage(), false);
147+
});
148+
149+
it("supports redaction for a subset of its child loggers", () => {
150+
const compositeLogger = new CompositeLogger(consoleLogger, mcpLogger);
151+
compositeLogger.info({
152+
...mockSensitivePayload,
153+
noRedaction: ["console", "disk"],
154+
});
155+
156+
expect(consoleErrorSpy).toHaveBeenCalledOnce();
157+
expectLogMessageRedaction(getLastConsoleMessage(), false);
158+
159+
expect(mcpLoggerSpy).toHaveBeenCalledOnce();
160+
expectLogMessageRedaction(getLastMcpLogMessage(), true);
161+
});
162+
});
163+
});
164+
});

tests/unit/telemetry.test.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,11 @@ describe("Telemetry", () => {
250250
expect(telemetry["isBufferingEvents"]).toBe(false);
251251
expect(telemetry.getCommonProperties().device_id).toBe("unknown");
252252

253-
expect(loggerSpy).toHaveBeenCalledWith(
254-
LogId.telemetryDeviceIdFailure,
255-
"telemetry",
256-
"Error: Failed to get device ID"
257-
);
253+
expect(loggerSpy).toHaveBeenCalledWith({
254+
id: LogId.telemetryDeviceIdFailure,
255+
context: "telemetry",
256+
message: "Error: Failed to get device ID",
257+
});
258258
});
259259

260260
it("should timeout if machine ID resolution takes too long", async () => {
@@ -277,11 +277,12 @@ describe("Telemetry", () => {
277277

278278
expect(telemetry.getCommonProperties().device_id).toBe("unknown");
279279
expect(telemetry["isBufferingEvents"]).toBe(false);
280-
expect(loggerSpy).toHaveBeenCalledWith(
281-
LogId.telemetryDeviceIdTimeout,
282-
"telemetry",
283-
"Device ID retrieval timed out"
284-
);
280+
expect(loggerSpy).toHaveBeenCalledWith({
281+
id: LogId.telemetryDeviceIdTimeout,
282+
context: "telemetry",
283+
message: "Device ID retrieval timed out",
284+
noRedaction: true,
285+
});
285286
});
286287
});
287288
});

0 commit comments

Comments
 (0)