11import { ApiClient } from "../../src/common/atlas/apiClient.js" ;
22import type { Session } from "../../src/common/session.js" ;
33import { Telemetry } from "../../src/telemetry/telemetry.js" ;
4- import type { BaseEvent , TelemetryResult } from "../../src/telemetry/types.js" ;
4+ import type { BaseEvent , CommonProperties , TelemetryEvent , TelemetryResult } from "../../src/telemetry/types.js" ;
55import { EventCache } from "../../src/telemetry/eventCache.js" ;
66import { config } from "../../src/common/config.js" ;
77import { afterEach , beforeEach , describe , it , vi , expect } from "vitest" ;
88import { NullLogger } from "../../src/common/logger.js" ;
99import type { MockedFunction } from "vitest" ;
1010import type { DeviceId } from "../../src/helpers/deviceId.js" ;
11+ import { expectDefined } from "../integration/helpers.js" ;
1112
1213// Mock the ApiClient to avoid real API calls
1314vi . mock ( "../../src/common/atlas/apiClient.js" ) ;
@@ -29,6 +30,7 @@ describe("Telemetry", () => {
2930 } ;
3031 let session : Session ;
3132 let telemetry : Telemetry ;
33+ let mockDeviceId : DeviceId ;
3234
3335 // Helper function to create properly typed test events
3436 function createTestEvent ( options ?: {
@@ -115,7 +117,7 @@ describe("Telemetry", () => {
115117 mockEventCache . appendEvents = vi . fn ( ) . mockResolvedValue ( undefined ) ;
116118 MockEventCache . getInstance = vi . fn ( ) . mockReturnValue ( mockEventCache as unknown as EventCache ) ;
117119
118- const mockDeviceId = {
120+ mockDeviceId = {
119121 get : vi . fn ( ) . mockResolvedValue ( "test-device-id" ) ,
120122 } as unknown as DeviceId ;
121123
@@ -137,183 +139,200 @@ describe("Telemetry", () => {
137139 config . telemetry = "enabled" ;
138140 } ) ;
139141
140- describe ( "sending events" , ( ) => {
141- describe ( "when telemetry is enabled" , ( ) => {
142- it ( "should send events successfully" , async ( ) => {
143- const testEvent = createTestEvent ( ) ;
142+ describe ( "when telemetry is enabled" , ( ) => {
143+ it ( "should send events successfully" , async ( ) => {
144+ const testEvent = createTestEvent ( ) ;
144145
145- await telemetry . setupPromise ;
146+ await telemetry . setupPromise ;
146147
147- await telemetry . emitEvents ( [ testEvent ] ) ;
148+ await telemetry . emitEvents ( [ testEvent ] ) ;
148149
149- verifyMockCalls ( {
150- sendEventsCalls : 1 ,
151- clearEventsCalls : 1 ,
152- sendEventsCalledWith : [ testEvent ] ,
153- } ) ;
150+ verifyMockCalls ( {
151+ sendEventsCalls : 1 ,
152+ clearEventsCalls : 1 ,
153+ sendEventsCalledWith : [ testEvent ] ,
154154 } ) ;
155+ } ) ;
155156
156- it ( "should cache events when sending fails" , async ( ) => {
157- mockApiClient . sendEvents . mockRejectedValueOnce ( new Error ( "API error" ) ) ;
157+ it ( "should cache events when sending fails" , async ( ) => {
158+ mockApiClient . sendEvents . mockRejectedValueOnce ( new Error ( "API error" ) ) ;
158159
159- const testEvent = createTestEvent ( ) ;
160+ const testEvent = createTestEvent ( ) ;
160161
161- await telemetry . setupPromise ;
162+ await telemetry . setupPromise ;
162163
163- await telemetry . emitEvents ( [ testEvent ] ) ;
164+ await telemetry . emitEvents ( [ testEvent ] ) ;
164165
165- verifyMockCalls ( {
166- sendEventsCalls : 1 ,
167- appendEventsCalls : 1 ,
168- appendEventsCalledWith : [ testEvent ] ,
169- } ) ;
166+ verifyMockCalls ( {
167+ sendEventsCalls : 1 ,
168+ appendEventsCalls : 1 ,
169+ appendEventsCalledWith : [ testEvent ] ,
170170 } ) ;
171+ } ) ;
171172
172- it ( "should include cached events when sending" , async ( ) => {
173- const cachedEvent = createTestEvent ( {
174- command : "cached-command" ,
175- component : "cached-component" ,
176- } ) ;
173+ it ( "should include cached events when sending" , async ( ) => {
174+ const cachedEvent = createTestEvent ( {
175+ command : "cached-command" ,
176+ component : "cached-component" ,
177+ } ) ;
177178
178- const newEvent = createTestEvent ( {
179- command : "new-command" ,
180- component : "new-component" ,
181- } ) ;
179+ const newEvent = createTestEvent ( {
180+ command : "new-command" ,
181+ component : "new-component" ,
182+ } ) ;
182183
183- // Set up mock to return cached events
184- mockEventCache . getEvents . mockReturnValueOnce ( [ cachedEvent ] ) ;
184+ // Set up mock to return cached events
185+ mockEventCache . getEvents . mockReturnValueOnce ( [ cachedEvent ] ) ;
185186
186- await telemetry . setupPromise ;
187+ await telemetry . setupPromise ;
187188
188- await telemetry . emitEvents ( [ newEvent ] ) ;
189+ await telemetry . emitEvents ( [ newEvent ] ) ;
189190
190- verifyMockCalls ( {
191- sendEventsCalls : 1 ,
192- clearEventsCalls : 1 ,
193- sendEventsCalledWith : [ cachedEvent , newEvent ] ,
194- } ) ;
191+ verifyMockCalls ( {
192+ sendEventsCalls : 1 ,
193+ clearEventsCalls : 1 ,
194+ sendEventsCalledWith : [ cachedEvent , newEvent ] ,
195195 } ) ;
196+ } ) ;
196197
197- it ( "should correctly add common properties to events" , async ( ) => {
198- await telemetry . setupPromise ;
198+ it ( "should correctly add common properties to events" , async ( ) => {
199+ await telemetry . setupPromise ;
200+
201+ const commonProps = telemetry . getCommonProperties ( ) ;
199202
200- const commonProps = telemetry . getCommonProperties ( ) ;
203+ // Use explicit type assertion
204+ const expectedProps : Record < string , string > = {
205+ mcp_client_version : "1.0.0" ,
206+ mcp_client_name : "test-agent" ,
207+ session_id : "test-session-id" ,
208+ config_atlas_auth : "true" ,
209+ config_connection_string : expect . any ( String ) as unknown as string ,
210+ device_id : "test-device-id" ,
211+ } ;
201212
202- // Use explicit type assertion
203- const expectedProps : Record < string , string > = {
204- mcp_client_version : "1.0.0" ,
205- mcp_client_name : "test-agent" ,
206- session_id : "test-session-id" ,
207- config_atlas_auth : "true" ,
208- config_connection_string : expect . any ( String ) as unknown as string ,
209- device_id : "test-device-id" ,
210- } ;
213+ expect ( commonProps ) . toMatchObject ( expectedProps ) ;
214+ } ) ;
211215
212- expect ( commonProps ) . toMatchObject ( expectedProps ) ;
216+ it ( "should add hostingMode to events if set" , async ( ) => {
217+ Telemetry . hostingMode = "vscode-extension" ;
218+ telemetry = Telemetry . create ( session , config , mockDeviceId , {
219+ eventCache : mockEventCache as unknown as EventCache ,
213220 } ) ;
221+ await telemetry . setupPromise ;
222+
223+ const commonProps = telemetry . getCommonProperties ( ) ;
224+ expect ( commonProps . hosting_mode ) . toBe ( "vscode-extension" ) ;
225+
226+ await telemetry . emitEvents ( [ createTestEvent ( ) ] ) ;
227+
228+ const calls = mockApiClient . sendEvents . mock . calls ;
229+ expect ( calls ) . toHaveLength ( 1 ) ;
230+ const event = calls [ 0 ] ?. [ 0 ] [ 0 ] ;
231+ expectDefined ( event ) ;
232+ expect ( ( event as TelemetryEvent < CommonProperties > ) . properties . hosting_mode ) . toBe ( "vscode-extension" ) ;
233+ } ) ;
214234
215- describe ( "device ID resolution" , ( ) => {
216- beforeEach ( ( ) => {
217- vi . clearAllMocks ( ) ;
218- } ) ;
235+ describe ( "device ID resolution" , ( ) => {
236+ beforeEach ( ( ) => {
237+ vi . clearAllMocks ( ) ;
238+ } ) ;
219239
220- afterEach ( ( ) => {
221- vi . clearAllMocks ( ) ;
222- } ) ;
240+ afterEach ( ( ) => {
241+ vi . clearAllMocks ( ) ;
242+ } ) ;
223243
224- it ( "should successfully resolve the device ID" , async ( ) => {
225- const mockDeviceId = {
226- get : vi . fn ( ) . mockResolvedValue ( "test-device-id" ) ,
227- } as unknown as DeviceId ;
244+ it ( "should successfully resolve the device ID" , async ( ) => {
245+ const mockDeviceId = {
246+ get : vi . fn ( ) . mockResolvedValue ( "test-device-id" ) ,
247+ } as unknown as DeviceId ;
228248
229- telemetry = Telemetry . create ( session , config , mockDeviceId ) ;
249+ telemetry = Telemetry . create ( session , config , mockDeviceId ) ;
230250
231- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
232- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
251+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
252+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
233253
234- await telemetry . setupPromise ;
254+ await telemetry . setupPromise ;
235255
236- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
237- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( "test-device-id" ) ;
238- } ) ;
256+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
257+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( "test-device-id" ) ;
258+ } ) ;
239259
240- it ( "should handle device ID resolution failure gracefully" , async ( ) => {
241- const mockDeviceId = {
242- get : vi . fn ( ) . mockResolvedValue ( "unknown" ) ,
243- } as unknown as DeviceId ;
260+ it ( "should handle device ID resolution failure gracefully" , async ( ) => {
261+ const mockDeviceId = {
262+ get : vi . fn ( ) . mockResolvedValue ( "unknown" ) ,
263+ } as unknown as DeviceId ;
244264
245- telemetry = Telemetry . create ( session , config , mockDeviceId ) ;
265+ telemetry = Telemetry . create ( session , config , mockDeviceId ) ;
246266
247- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
248- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
267+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
268+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
249269
250- await telemetry . setupPromise ;
270+ await telemetry . setupPromise ;
251271
252- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
253- // Should use "unknown" as fallback when device ID resolution fails
254- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( "unknown" ) ;
255- } ) ;
272+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
273+ // Should use "unknown" as fallback when device ID resolution fails
274+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( "unknown" ) ;
275+ } ) ;
256276
257- it ( "should handle device ID timeout gracefully" , async ( ) => {
258- const mockDeviceId = {
259- get : vi . fn ( ) . mockResolvedValue ( "unknown" ) ,
260- } as unknown as DeviceId ;
277+ it ( "should handle device ID timeout gracefully" , async ( ) => {
278+ const mockDeviceId = {
279+ get : vi . fn ( ) . mockResolvedValue ( "unknown" ) ,
280+ } as unknown as DeviceId ;
261281
262- telemetry = Telemetry . create ( session , config , mockDeviceId ) ;
282+ telemetry = Telemetry . create ( session , config , mockDeviceId ) ;
263283
264- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
265- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
284+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( true ) ;
285+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( undefined ) ;
266286
267- await telemetry . setupPromise ;
287+ await telemetry . setupPromise ;
268288
269- expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
270- // Should use "unknown" as fallback when device ID times out
271- expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( "unknown" ) ;
272- } ) ;
289+ expect ( telemetry [ "isBufferingEvents" ] ) . toBe ( false ) ;
290+ // Should use "unknown" as fallback when device ID times out
291+ expect ( telemetry . getCommonProperties ( ) . device_id ) . toBe ( "unknown" ) ;
273292 } ) ;
274293 } ) ;
294+ } ) ;
275295
276- describe ( "when telemetry is disabled" , ( ) => {
277- beforeEach ( ( ) => {
278- config . telemetry = "disabled" ;
279- } ) ;
296+ describe ( "when telemetry is disabled" , ( ) => {
297+ beforeEach ( ( ) => {
298+ config . telemetry = "disabled" ;
299+ } ) ;
280300
281- afterEach ( ( ) => {
282- config . telemetry = "enabled" ;
283- } ) ;
301+ afterEach ( ( ) => {
302+ config . telemetry = "enabled" ;
303+ } ) ;
284304
285- it ( "should not send events" , async ( ) => {
286- const testEvent = createTestEvent ( ) ;
305+ it ( "should not send events" , async ( ) => {
306+ const testEvent = createTestEvent ( ) ;
287307
288- await telemetry . emitEvents ( [ testEvent ] ) ;
308+ await telemetry . emitEvents ( [ testEvent ] ) ;
289309
290- verifyMockCalls ( ) ;
291- } ) ;
310+ verifyMockCalls ( ) ;
292311 } ) ;
312+ } ) ;
293313
294- describe ( "when DO_NOT_TRACK environment variable is set" , ( ) => {
295- let originalEnv : string | undefined ;
314+ describe ( "when DO_NOT_TRACK environment variable is set" , ( ) => {
315+ let originalEnv : string | undefined ;
296316
297- beforeEach ( ( ) => {
298- originalEnv = process . env . DO_NOT_TRACK ;
299- process . env . DO_NOT_TRACK = "1" ;
300- } ) ;
317+ beforeEach ( ( ) => {
318+ originalEnv = process . env . DO_NOT_TRACK ;
319+ process . env . DO_NOT_TRACK = "1" ;
320+ } ) ;
301321
302- afterEach ( ( ) => {
303- if ( originalEnv ) {
304- process . env . DO_NOT_TRACK = originalEnv ;
305- } else {
306- delete process . env . DO_NOT_TRACK ;
307- }
308- } ) ;
322+ afterEach ( ( ) => {
323+ if ( originalEnv ) {
324+ process . env . DO_NOT_TRACK = originalEnv ;
325+ } else {
326+ delete process . env . DO_NOT_TRACK ;
327+ }
328+ } ) ;
309329
310- it ( "should not send events" , async ( ) => {
311- const testEvent = createTestEvent ( ) ;
330+ it ( "should not send events" , async ( ) => {
331+ const testEvent = createTestEvent ( ) ;
312332
313- await telemetry . emitEvents ( [ testEvent ] ) ;
333+ await telemetry . emitEvents ( [ testEvent ] ) ;
314334
315- verifyMockCalls ( ) ;
316- } ) ;
335+ verifyMockCalls ( ) ;
317336 } ) ;
318337 } ) ;
319338} ) ;
0 commit comments