Skip to content

Commit 0e6bd02

Browse files
committed
chore: add tests for telemetry
1 parent 3cc473a commit 0e6bd02

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

tests/integration/telemetry.test.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { ApiClient } from "../../src/common/atlas/apiClient.js";
2+
import { Session } from "../../src/session.js";
3+
import { Telemetry } from "../../src/telemetry/telemetry.js";
4+
import { BaseEvent, TelemetryResult } from "../../src/telemetry/types.js";
5+
import { EventCache } from "../../src/telemetry/eventCache.js";
6+
import { config } from "../../src/config.js";
7+
8+
// Mock the ApiClient to avoid real API calls
9+
jest.mock("../../src/common/atlas/apiClient.js");
10+
const MockApiClient = ApiClient as jest.MockedClass<typeof ApiClient>;
11+
12+
// Mock EventCache to control and verify caching behavior
13+
jest.mock("../../src/telemetry/eventCache.js");
14+
const MockEventCache = EventCache as jest.MockedClass<typeof EventCache>;
15+
16+
describe("Telemetry", () => {
17+
let mockApiClient: jest.Mocked<ApiClient>;
18+
let mockEventCache: jest.Mocked<EventCache>;
19+
let session: Session;
20+
let telemetry: Telemetry;
21+
22+
// Helper function to create properly typed test events
23+
function createTestEvent(options?: {
24+
source?: string;
25+
result?: TelemetryResult;
26+
component?: string;
27+
category?: string;
28+
command?: string;
29+
duration_ms?: number;
30+
}): BaseEvent {
31+
return {
32+
timestamp: new Date().toISOString(),
33+
source: options?.source || "mdbmcp",
34+
properties: {
35+
component: options?.component || "test-component",
36+
duration_ms: options?.duration_ms || 100,
37+
result: options?.result || "success",
38+
category: options?.category || "test",
39+
command: options?.command || "test-command",
40+
},
41+
};
42+
}
43+
44+
// Helper function to verify mock calls to reduce duplication
45+
function verifyMockCalls({
46+
sendEventsCalls = 0,
47+
clearEventsCalls = 0,
48+
appendEventsCalls = 0,
49+
sendEventsCalledWith = undefined,
50+
appendEventsCalledWith = undefined,
51+
} = {}) {
52+
const { calls: sendEvents } = mockApiClient.sendEvents.mock;
53+
const { calls: clearEvents } = mockEventCache.clearEvents.mock;
54+
const { calls: appendEvents } = mockEventCache.appendEvents.mock;
55+
56+
expect(sendEvents.length).toBe(sendEventsCalls);
57+
expect(clearEvents.length).toBe(clearEventsCalls);
58+
expect(appendEvents.length).toBe(appendEventsCalls);
59+
60+
if (sendEventsCalledWith) {
61+
expect(sendEvents[0]?.[0]).toEqual(sendEventsCalledWith);
62+
}
63+
64+
if (appendEventsCalledWith) {
65+
expect(appendEvents[0]?.[0]).toEqual(appendEventsCalledWith);
66+
}
67+
}
68+
69+
beforeEach(() => {
70+
// Reset mocks before each test
71+
jest.clearAllMocks();
72+
73+
// Setup mocked API client
74+
mockApiClient = new MockApiClient() as jest.Mocked<ApiClient>;
75+
mockApiClient.sendEvents = jest.fn().mockResolvedValue(undefined);
76+
mockApiClient.hasCredentials = jest.fn().mockReturnValue(true);
77+
78+
// Setup mocked EventCache
79+
mockEventCache = new MockEventCache() as jest.Mocked<EventCache>;
80+
mockEventCache.getEvents = jest.fn().mockReturnValue([]);
81+
mockEventCache.clearEvents = jest.fn().mockResolvedValue(undefined);
82+
mockEventCache.appendEvents = jest.fn().mockResolvedValue(undefined);
83+
MockEventCache.getInstance = jest.fn().mockReturnValue(mockEventCache);
84+
85+
// Create a simplified session with our mocked API client
86+
session = {
87+
apiClient: mockApiClient,
88+
sessionId: "test-session-id",
89+
agentRunner: { name: "test-agent", version: "1.0.0" } as const,
90+
close: jest.fn().mockResolvedValue(undefined),
91+
setAgentRunner: jest.fn().mockResolvedValue(undefined),
92+
} as unknown as Session;
93+
94+
// Create the telemetry instance with mocked dependencies
95+
telemetry = new Telemetry(session, mockEventCache);
96+
97+
// Set telemetry to enabled by default
98+
config.telemetry = "enabled";
99+
});
100+
101+
describe("when telemetry is enabled", () => {
102+
it("should send events successfully", async () => {
103+
const testEvent = createTestEvent();
104+
105+
await telemetry.emitEvents([testEvent]);
106+
107+
verifyMockCalls({
108+
sendEventsCalls: 1,
109+
clearEventsCalls: 1,
110+
sendEventsCalledWith: [testEvent],
111+
});
112+
});
113+
114+
it("should cache events when sending fails", async () => {
115+
mockApiClient.sendEvents.mockRejectedValueOnce(new Error("API error"));
116+
117+
const testEvent = createTestEvent();
118+
119+
await telemetry.emitEvents([testEvent]);
120+
121+
verifyMockCalls({
122+
sendEventsCalls: 1,
123+
appendEventsCalls: 1,
124+
appendEventsCalledWith: [testEvent],
125+
});
126+
});
127+
128+
it("should include cached events when sending", async () => {
129+
const cachedEvent = createTestEvent({
130+
command: "cached-command",
131+
component: "cached-component",
132+
});
133+
134+
const newEvent = createTestEvent({
135+
command: "new-command",
136+
component: "new-component",
137+
});
138+
139+
// Set up mock to return cached events
140+
mockEventCache.getEvents.mockReturnValueOnce([cachedEvent]);
141+
142+
await telemetry.emitEvents([newEvent]);
143+
144+
verifyMockCalls({
145+
sendEventsCalls: 1,
146+
clearEventsCalls: 1,
147+
sendEventsCalledWith: [cachedEvent, newEvent],
148+
});
149+
});
150+
});
151+
152+
describe("when telemetry is disabled", () => {
153+
beforeEach(() => {
154+
config.telemetry = "disabled";
155+
});
156+
157+
it("should not send events", async () => {
158+
const testEvent = createTestEvent();
159+
160+
await telemetry.emitEvents([testEvent]);
161+
162+
verifyMockCalls();
163+
});
164+
});
165+
166+
it("should correctly add common properties to events", () => {
167+
const commonProps = telemetry.getCommonProperties();
168+
169+
// Use explicit type assertion
170+
const expectedProps: Record<string, string> = {
171+
mcp_client_version: "1.0.0",
172+
mcp_client_name: "test-agent",
173+
session_id: "test-session-id",
174+
config_atlas_auth: "true",
175+
config_connection_string: expect.any(String) as unknown as string,
176+
};
177+
178+
expect(commonProps).toMatchObject(expectedProps);
179+
});
180+
181+
describe("when DO_NOT_TRACK environment variable is set", () => {
182+
let originalEnv: string | undefined;
183+
184+
beforeEach(() => {
185+
originalEnv = process.env.DO_NOT_TRACK;
186+
process.env.DO_NOT_TRACK = "1";
187+
});
188+
189+
afterEach(() => {
190+
process.env.DO_NOT_TRACK = originalEnv;
191+
});
192+
193+
it("should not send events", async () => {
194+
const testEvent = createTestEvent();
195+
196+
await telemetry.emitEvents([testEvent]);
197+
198+
verifyMockCalls();
199+
});
200+
});
201+
});

0 commit comments

Comments
 (0)