1
- // Mock node-machine-id to simulate machine ID resolution
2
- jest . mock ( "node-machine-id" , ( ) => ( {
3
- machineId : jest . fn ( ) ,
4
- } ) ) ;
5
-
6
1
import { ApiClient } from "../../src/common/atlas/apiClient.js" ;
7
2
import { Session } from "../../src/session.js" ;
8
3
import { DEVICE_ID_TIMEOUT , Telemetry } from "../../src/telemetry/telemetry.js" ;
9
4
import { BaseEvent , TelemetryResult } from "../../src/telemetry/types.js" ;
10
5
import { EventCache } from "../../src/telemetry/eventCache.js" ;
11
6
import { config } from "../../src/config.js" ;
12
- import { MACHINE_METADATA } from "../../src/telemetry/constants.js" ;
13
7
import { jest } from "@jest/globals" ;
14
- import { createHmac } from "crypto" ;
15
8
import logger , { LogId } from "../../src/logger.js" ;
16
- import nodeMachineId from "node-machine-id " ;
9
+ import { createHmac } from "crypto " ;
17
10
18
11
// Mock the ApiClient to avoid real API calls
19
12
jest . mock ( "../../src/common/atlas/apiClient.js" ) ;
@@ -23,9 +16,10 @@ const MockApiClient = ApiClient as jest.MockedClass<typeof ApiClient>;
23
16
jest . mock ( "../../src/telemetry/eventCache.js" ) ;
24
17
const MockEventCache = EventCache as jest . MockedClass < typeof EventCache > ;
25
18
26
- const mockMachineId = jest . spyOn ( nodeMachineId , "machineId" ) ;
27
-
28
19
describe ( "Telemetry" , ( ) => {
20
+ const machineId = "test-machine-id" ;
21
+ const hashedMachineId = createHmac ( "sha256" , machineId . toUpperCase ( ) ) . update ( "atlascli" ) . digest ( "hex" ) ;
22
+
29
23
let mockApiClient : jest . Mocked < ApiClient > ;
30
24
let mockEventCache : jest . Mocked < EventCache > ;
31
25
let session : Session ;
@@ -131,26 +125,15 @@ describe("Telemetry", () => {
131
125
setAgentRunner : jest . fn ( ) . mockResolvedValue ( undefined ) ,
132
126
} as unknown as Session ;
133
127
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
+ } ) ;
143
132
144
133
config . telemetry = "enabled" ;
145
134
} ) ;
146
135
147
136
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
-
154
137
describe ( "when telemetry is enabled" , ( ) => {
155
138
it ( "should send events successfully" , async ( ) => {
156
139
const testEvent = createTestEvent ( ) ;
@@ -200,141 +183,136 @@ describe("Telemetry", () => {
200
183
sendEventsCalledWith : [ cachedEvent , newEvent ] ,
201
184
} ) ;
202
185
} ) ;
203
- } ) ;
204
186
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 ( ) ;
209
189
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
+ } ;
214
199
215
- verifyMockCalls ( ) ;
200
+ expect ( commonProps ) . toMatchObject ( expectedProps ) ;
216
201
} ) ;
217
- } ) ;
218
202
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
+ } ) ;
221
208
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
+ } ) ;
230
213
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
+ } ) ;
233
218
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 ) ;
236
221
237
- beforeEach ( ( ) => {
238
- originalEnv = process . env . DO_NOT_TRACK ;
239
- process . env . DO_NOT_TRACK = "1" ;
240
- } ) ;
222
+ await telemetry . deviceIdPromise ;
241
223
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
+ } ) ;
245
227
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" ) ;
248
230
249
- await telemetry . emitEvents ( [ testEvent ] ) ;
231
+ telemetry = Telemetry . create ( session , config , {
232
+ getRawMachineId : ( ) => Promise . reject ( new Error ( "Failed to get device ID" ) ) ,
233
+ } ) ;
250
234
251
- verifyMockCalls ( ) ;
252
- } ) ;
253
- } ) ;
254
- } ) ;
235
+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
236
+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
255
237
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 ;
259
239
260
- beforeEach ( ( ) => {
261
- jest . useFakeTimers ( ) ;
262
- jest . clearAllMocks ( ) ;
263
- } ) ;
240
+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
241
+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( "unknown" ) ;
264
242
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
+ } ) ;
269
249
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" ) ;
274
252
275
- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
276
- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
253
+ telemetry = Telemetry . create ( session , config , { getRawMachineId : ( ) => new Promise ( ( ) => { } ) } ) ;
277
254
278
- await telemetry . deviceIdPromise ;
255
+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
256
+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
279
257
280
- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
281
- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( hashedMachineId ) ;
282
- } ) ;
258
+ jest . advanceTimersByTime ( DEVICE_ID_TIMEOUT / 2 ) ;
283
259
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 ) ;
286
263
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 ) ;
290
265
291
- telemetry = Telemetry . create ( session , config ) ;
266
+ await telemetry . deviceIdPromise ;
292
267
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
+ } ) ;
295
278
296
- await telemetry . deviceIdPromise ;
279
+ describe ( "when telemetry is disabled" , ( ) => {
280
+ beforeEach ( ( ) => {
281
+ config . telemetry = "disabled" ;
282
+ } ) ;
297
283
298
- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
299
- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( "unknown" ) ;
284
+ afterEach ( ( ) => {
285
+ config . telemetry = "enabled" ;
286
+ } ) ;
300
287
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 ( ) ;
307
290
308
- it ( "should timeout if machine ID resolution takes too long" , async ( ) => {
309
- const loggerSpy = jest . spyOn ( logger , "debug" ) ;
291
+ await telemetry . emitEvents ( [ testEvent ] ) ;
310
292
311
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
312
- mockMachineId . mockImplementation ( ( ) => {
313
- return new Promise ( ( ) => { } ) ;
293
+ verifyMockCalls ( ) ;
314
294
} ) ;
295
+ } ) ;
315
296
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 ;
320
299
321
- jest . advanceTimersByTime ( DEVICE_ID_TIMEOUT / 2 ) ;
300
+ beforeEach ( ( ) => {
301
+ originalEnv = process . env . DO_NOT_TRACK ;
302
+ process . env . DO_NOT_TRACK = "1" ;
303
+ } ) ;
322
304
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
+ } ) ;
326
308
327
- jest . advanceTimersByTime ( DEVICE_ID_TIMEOUT ) ;
309
+ it ( "should not send events" , async ( ) => {
310
+ const testEvent = createTestEvent ( ) ;
328
311
329
- await telemetry . deviceIdPromise ;
312
+ await telemetry . emitEvents ( [ testEvent ] ) ;
330
313
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
+ } ) ;
338
316
} ) ;
339
317
} ) ;
340
318
} ) ;
0 commit comments