Skip to content

Commit 8575d9a

Browse files
committed
fix: adjust mocks to fit ESM
1 parent 189e97d commit 8575d9a

File tree

3 files changed

+125
-133
lines changed

3 files changed

+125
-133
lines changed

src/telemetry/telemetry.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,35 @@ export class Telemetry {
2020
private isBufferingEvents: boolean = true;
2121
/** Resolves when the device ID is retrieved or timeout occurs */
2222
public deviceIdPromise: DeferredPromise<string> | undefined;
23+
private eventCache: EventCache;
24+
private getRawMachineId: () => Promise<string>;
2325

2426
private constructor(
2527
private readonly session: Session,
2628
private readonly userConfig: UserConfig,
2729
private readonly commonProperties: CommonProperties,
28-
private readonly eventCache: EventCache
29-
) {}
30+
{ eventCache, getRawMachineId }: { eventCache: EventCache; getRawMachineId: () => Promise<string> }
31+
) {
32+
this.eventCache = eventCache;
33+
this.getRawMachineId = getRawMachineId;
34+
}
3035

3136
static create(
3237
session: Session,
3338
userConfig: UserConfig,
34-
commonProperties: CommonProperties = { ...MACHINE_METADATA },
35-
eventCache: EventCache = EventCache.getInstance()
39+
{
40+
commonProperties = { ...MACHINE_METADATA },
41+
eventCache = EventCache.getInstance(),
42+
43+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
44+
getRawMachineId = () => nodeMachineId.machineId(true),
45+
}: {
46+
eventCache?: EventCache;
47+
getRawMachineId?: () => Promise<string>;
48+
commonProperties?: CommonProperties;
49+
} = {}
3650
): Telemetry {
37-
const instance = new Telemetry(session, userConfig, commonProperties, eventCache);
51+
const instance = new Telemetry(session, userConfig, commonProperties, { eventCache, getRawMachineId });
3852

3953
void instance.start();
4054
return instance;
@@ -71,8 +85,7 @@ export class Telemetry {
7185
return this.commonProperties.device_id;
7286
}
7387

74-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
75-
const originalId: string = await nodeMachineId.machineId(true);
88+
const originalId: string = await this.getRawMachineId();
7689

7790
// Create a hashed format from the all uppercase version of the machine ID
7891
// to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.

tests/integration/telemetry.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { createHmac } from "crypto";
2-
import nodeMachineId from "node-machine-id";
32
import { Telemetry } from "../../src/telemetry/telemetry.js";
43
import { Session } from "../../src/session.js";
54
import { config } from "../../src/config.js";
5+
import nodeMachineId from "node-machine-id";
66

77
describe("Telemetry", () => {
88
it("should resolve the actual machine ID", async () => {
9-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
9+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1010
const actualId: string = await nodeMachineId.machineId(true);
11+
1112
const actualHashedId = createHmac("sha256", actualId.toUpperCase()).update("atlascli").digest("hex");
1213

1314
const telemetry = Telemetry.create(

tests/unit/telemetry.test.ts

Lines changed: 102 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
// Mock node-machine-id to simulate machine ID resolution
2-
jest.mock("node-machine-id", () => ({
3-
machineId: jest.fn(),
4-
}));
5-
61
import { ApiClient } from "../../src/common/atlas/apiClient.js";
72
import { Session } from "../../src/session.js";
83
import { DEVICE_ID_TIMEOUT, Telemetry } from "../../src/telemetry/telemetry.js";
94
import { BaseEvent, TelemetryResult } from "../../src/telemetry/types.js";
105
import { EventCache } from "../../src/telemetry/eventCache.js";
116
import { config } from "../../src/config.js";
12-
import { MACHINE_METADATA } from "../../src/telemetry/constants.js";
137
import { jest } from "@jest/globals";
14-
import { createHmac } from "crypto";
158
import logger, { LogId } from "../../src/logger.js";
16-
import nodeMachineId from "node-machine-id";
9+
import { createHmac } from "crypto";
1710

1811
// Mock the ApiClient to avoid real API calls
1912
jest.mock("../../src/common/atlas/apiClient.js");
@@ -23,9 +16,10 @@ const MockApiClient = ApiClient as jest.MockedClass<typeof ApiClient>;
2316
jest.mock("../../src/telemetry/eventCache.js");
2417
const MockEventCache = EventCache as jest.MockedClass<typeof EventCache>;
2518

26-
const mockMachineId = jest.spyOn(nodeMachineId, "machineId");
27-
2819
describe("Telemetry", () => {
20+
const machineId = "test-machine-id";
21+
const hashedMachineId = createHmac("sha256", machineId.toUpperCase()).update("atlascli").digest("hex");
22+
2923
let mockApiClient: jest.Mocked<ApiClient>;
3024
let mockEventCache: jest.Mocked<EventCache>;
3125
let session: Session;
@@ -131,26 +125,15 @@ describe("Telemetry", () => {
131125
setAgentRunner: jest.fn().mockResolvedValue(undefined),
132126
} as unknown as Session;
133127

134-
// Create the telemetry instance with mocked dependencies
135-
telemetry = Telemetry.create(
136-
session,
137-
config,
138-
{
139-
...MACHINE_METADATA,
140-
},
141-
mockEventCache
142-
);
128+
telemetry = Telemetry.create(session, config, {
129+
eventCache: mockEventCache,
130+
getRawMachineId: () => Promise.resolve(machineId),
131+
});
143132

144133
config.telemetry = "enabled";
145134
});
146135

147136
describe("sending events", () => {
148-
beforeEach(() => {
149-
// @ts-expect-error This is a workaround
150-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
151-
mockMachineId.mockResolvedValue("test-machine-id");
152-
});
153-
154137
describe("when telemetry is enabled", () => {
155138
it("should send events successfully", async () => {
156139
const testEvent = createTestEvent();
@@ -200,141 +183,136 @@ describe("Telemetry", () => {
200183
sendEventsCalledWith: [cachedEvent, newEvent],
201184
});
202185
});
203-
});
204186

205-
describe("when telemetry is disabled", () => {
206-
beforeEach(() => {
207-
config.telemetry = "disabled";
208-
});
187+
it("should correctly add common properties to events", () => {
188+
const commonProps = telemetry.getCommonProperties();
209189

210-
it("should not send events", async () => {
211-
const testEvent = createTestEvent();
212-
213-
await telemetry.emitEvents([testEvent]);
190+
// Use explicit type assertion
191+
const expectedProps: Record<string, string> = {
192+
mcp_client_version: "1.0.0",
193+
mcp_client_name: "test-agent",
194+
session_id: "test-session-id",
195+
config_atlas_auth: "true",
196+
config_connection_string: expect.any(String) as unknown as string,
197+
device_id: hashedMachineId,
198+
};
214199

215-
verifyMockCalls();
200+
expect(commonProps).toMatchObject(expectedProps);
216201
});
217-
});
218202

219-
it("should correctly add common properties to events", () => {
220-
const commonProps = telemetry.getCommonProperties();
203+
describe("machine ID resolution", () => {
204+
beforeEach(() => {
205+
jest.clearAllMocks();
206+
jest.useFakeTimers();
207+
});
221208

222-
// Use explicit type assertion
223-
const expectedProps: Record<string, string> = {
224-
mcp_client_version: "1.0.0",
225-
mcp_client_name: "test-agent",
226-
session_id: "test-session-id",
227-
config_atlas_auth: "true",
228-
config_connection_string: expect.any(String) as unknown as string,
229-
};
209+
afterEach(() => {
210+
jest.clearAllMocks();
211+
jest.useRealTimers();
212+
});
230213

231-
expect(commonProps).toMatchObject(expectedProps);
232-
});
214+
it("should successfully resolve the machine ID", async () => {
215+
telemetry = Telemetry.create(session, config, {
216+
getRawMachineId: () => Promise.resolve(machineId),
217+
});
233218

234-
describe("when DO_NOT_TRACK environment variable is set", () => {
235-
let originalEnv: string | undefined;
219+
expect(telemetry["isBufferingEvents"]).toBe(true);
220+
expect(telemetry.getCommonProperties().device_id).toBe(undefined);
236221

237-
beforeEach(() => {
238-
originalEnv = process.env.DO_NOT_TRACK;
239-
process.env.DO_NOT_TRACK = "1";
240-
});
222+
await telemetry.deviceIdPromise;
241223

242-
afterEach(() => {
243-
process.env.DO_NOT_TRACK = originalEnv;
244-
});
224+
expect(telemetry["isBufferingEvents"]).toBe(false);
225+
expect(telemetry.getCommonProperties().device_id).toBe(hashedMachineId);
226+
});
245227

246-
it("should not send events", async () => {
247-
const testEvent = createTestEvent();
228+
it("should handle machine ID resolution failure", async () => {
229+
const loggerSpy = jest.spyOn(logger, "debug");
248230

249-
await telemetry.emitEvents([testEvent]);
231+
telemetry = Telemetry.create(session, config, {
232+
getRawMachineId: () => Promise.reject(new Error("Failed to get device ID")),
233+
});
250234

251-
verifyMockCalls();
252-
});
253-
});
254-
});
235+
expect(telemetry["isBufferingEvents"]).toBe(true);
236+
expect(telemetry.getCommonProperties().device_id).toBe(undefined);
255237

256-
describe("machine ID resolution", () => {
257-
const machineId = "test-machine-id";
258-
const hashedMachineId = createHmac("sha256", machineId.toUpperCase()).update("atlascli").digest("hex");
238+
await telemetry.deviceIdPromise;
259239

260-
beforeEach(() => {
261-
jest.useFakeTimers();
262-
jest.clearAllMocks();
263-
});
240+
expect(telemetry["isBufferingEvents"]).toBe(false);
241+
expect(telemetry.getCommonProperties().device_id).toBe("unknown");
264242

265-
afterEach(() => {
266-
jest.useRealTimers();
267-
jest.clearAllMocks();
268-
});
243+
expect(loggerSpy).toHaveBeenCalledWith(
244+
LogId.telemetryDeviceIdFailure,
245+
"telemetry",
246+
"Error: Failed to get device ID"
247+
);
248+
});
269249

270-
it("should successfully resolve the machine ID", async () => {
271-
// @ts-expect-error This is a workaround
272-
mockMachineId.mockResolvedValue(machineId);
273-
telemetry = Telemetry.create(session, config);
250+
it("should timeout if machine ID resolution takes too long", async () => {
251+
const loggerSpy = jest.spyOn(logger, "debug");
274252

275-
expect(telemetry["isBufferingEvents"]).toBe(true);
276-
expect(telemetry.getCommonProperties().device_id).toBe(undefined);
253+
telemetry = Telemetry.create(session, config, { getRawMachineId: () => new Promise(() => {}) });
277254

278-
await telemetry.deviceIdPromise;
255+
expect(telemetry["isBufferingEvents"]).toBe(true);
256+
expect(telemetry.getCommonProperties().device_id).toBe(undefined);
279257

280-
expect(telemetry["isBufferingEvents"]).toBe(false);
281-
expect(telemetry.getCommonProperties().device_id).toBe(hashedMachineId);
282-
});
258+
jest.advanceTimersByTime(DEVICE_ID_TIMEOUT / 2);
283259

284-
it("should handle machine ID resolution failure", async () => {
285-
const loggerSpy = jest.spyOn(logger, "debug");
260+
// Make sure the timeout doesn't happen prematurely.
261+
expect(telemetry["isBufferingEvents"]).toBe(true);
262+
expect(telemetry.getCommonProperties().device_id).toBe(undefined);
286263

287-
// @ts-expect-error This is a workaround
288-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
289-
mockMachineId.mockRejectedValue(new Error("Failed to get device ID"));
264+
jest.advanceTimersByTime(DEVICE_ID_TIMEOUT);
290265

291-
telemetry = Telemetry.create(session, config);
266+
await telemetry.deviceIdPromise;
292267

293-
expect(telemetry["isBufferingEvents"]).toBe(true);
294-
expect(telemetry.getCommonProperties().device_id).toBe(undefined);
268+
expect(telemetry.getCommonProperties().device_id).toBe("unknown");
269+
expect(telemetry["isBufferingEvents"]).toBe(false);
270+
expect(loggerSpy).toHaveBeenCalledWith(
271+
LogId.telemetryDeviceIdTimeout,
272+
"telemetry",
273+
"Device ID retrieval timed out"
274+
);
275+
});
276+
});
277+
});
295278

296-
await telemetry.deviceIdPromise;
279+
describe("when telemetry is disabled", () => {
280+
beforeEach(() => {
281+
config.telemetry = "disabled";
282+
});
297283

298-
expect(telemetry["isBufferingEvents"]).toBe(false);
299-
expect(telemetry.getCommonProperties().device_id).toBe("unknown");
284+
afterEach(() => {
285+
config.telemetry = "enabled";
286+
});
300287

301-
expect(loggerSpy).toHaveBeenCalledWith(
302-
LogId.telemetryDeviceIdFailure,
303-
"telemetry",
304-
"Error: Failed to get device ID"
305-
);
306-
});
288+
it("should not send events", async () => {
289+
const testEvent = createTestEvent();
307290

308-
it("should timeout if machine ID resolution takes too long", async () => {
309-
const loggerSpy = jest.spyOn(logger, "debug");
291+
await telemetry.emitEvents([testEvent]);
310292

311-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
312-
mockMachineId.mockImplementation(() => {
313-
return new Promise(() => {});
293+
verifyMockCalls();
314294
});
295+
});
315296

316-
telemetry = Telemetry.create(session, config);
317-
318-
expect(telemetry["isBufferingEvents"]).toBe(true);
319-
expect(telemetry.getCommonProperties().device_id).toBe(undefined);
297+
describe("when DO_NOT_TRACK environment variable is set", () => {
298+
let originalEnv: string | undefined;
320299

321-
jest.advanceTimersByTime(DEVICE_ID_TIMEOUT / 2);
300+
beforeEach(() => {
301+
originalEnv = process.env.DO_NOT_TRACK;
302+
process.env.DO_NOT_TRACK = "1";
303+
});
322304

323-
// Make sure the timeout doesn't happen prematurely.
324-
expect(telemetry["isBufferingEvents"]).toBe(true);
325-
expect(telemetry.getCommonProperties().device_id).toBe(undefined);
305+
afterEach(() => {
306+
process.env.DO_NOT_TRACK = originalEnv;
307+
});
326308

327-
jest.advanceTimersByTime(DEVICE_ID_TIMEOUT);
309+
it("should not send events", async () => {
310+
const testEvent = createTestEvent();
328311

329-
await telemetry.deviceIdPromise;
312+
await telemetry.emitEvents([testEvent]);
330313

331-
expect(telemetry.getCommonProperties().device_id).toBe("unknown");
332-
expect(telemetry["isBufferingEvents"]).toBe(false);
333-
expect(loggerSpy).toHaveBeenCalledWith(
334-
LogId.telemetryDeviceIdTimeout,
335-
"telemetry",
336-
"Device ID retrieval timed out"
337-
);
314+
verifyMockCalls();
315+
});
338316
});
339317
});
340318
});

0 commit comments

Comments
 (0)