1- // Mock node-machine-id to simulate machine ID resolution
2- jest . mock ( "node-machine-id" , ( ) => ( {
3- machineId : jest . fn ( ) ,
4- } ) ) ;
5-
61import { ApiClient } from "../../src/common/atlas/apiClient.js" ;
72import { Session } from "../../src/session.js" ;
83import { DEVICE_ID_TIMEOUT , Telemetry } from "../../src/telemetry/telemetry.js" ;
94import { BaseEvent , TelemetryResult } from "../../src/telemetry/types.js" ;
105import { EventCache } from "../../src/telemetry/eventCache.js" ;
116import { config } from "../../src/config.js" ;
12- import { MACHINE_METADATA } from "../../src/telemetry/constants.js" ;
137import { jest } from "@jest/globals" ;
14- import { createHmac } from "crypto" ;
158import 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
1912jest . mock ( "../../src/common/atlas/apiClient.js" ) ;
@@ -23,9 +16,10 @@ const MockApiClient = ApiClient as jest.MockedClass<typeof ApiClient>;
2316jest . mock ( "../../src/telemetry/eventCache.js" ) ;
2417const MockEventCache = EventCache as jest . MockedClass < typeof EventCache > ;
2518
26- const mockMachineId = jest . spyOn ( nodeMachineId , "machineId" ) ;
27-
2819describe ( "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