11/* eslint-disable @typescript-eslint/no-explicit-any */
22
3- // npx vitest run src/__tests__/PostHogTelemetryClient.test.ts
3+ // pnpm --filter @roo-code/telemetry test src/__tests__/PostHogTelemetryClient.test.ts
44
55import * as vscode from "vscode"
66import { PostHog } from "posthog-node"
77
8- import { type TelemetryPropertiesProvider , TelemetryEventName } from "@roo-code/types"
8+ import { type TelemetryPropertiesProvider , TelemetryEventName , ApiProviderError } from "@roo-code/types"
99
1010import { PostHogTelemetryClient } from "../PostHogTelemetryClient"
1111
@@ -32,6 +32,7 @@ describe("PostHogTelemetryClient", () => {
3232
3333 mockPostHogClient = {
3434 capture : vi . fn ( ) ,
35+ captureException : vi . fn ( ) ,
3536 optIn : vi . fn ( ) ,
3637 optOut : vi . fn ( ) ,
3738 shutdown : vi . fn ( ) . mockResolvedValue ( undefined ) ,
@@ -373,4 +374,227 @@ describe("PostHogTelemetryClient", () => {
373374 expect ( mockPostHogClient . shutdown ) . toHaveBeenCalled ( )
374375 } )
375376 } )
377+
378+ describe ( "captureException" , ( ) => {
379+ it ( "should not capture exceptions when telemetry is disabled" , ( ) => {
380+ const client = new PostHogTelemetryClient ( )
381+ client . updateTelemetryState ( false )
382+
383+ const error = new Error ( "Test error" )
384+ client . captureException ( error )
385+
386+ expect ( mockPostHogClient . captureException ) . not . toHaveBeenCalled ( )
387+ } )
388+
389+ it ( "should capture exceptions when telemetry is enabled" , ( ) => {
390+ const client = new PostHogTelemetryClient ( )
391+ client . updateTelemetryState ( true )
392+
393+ const error = new Error ( "Test error" )
394+ client . captureException ( error , { provider : "TestProvider" } )
395+
396+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , {
397+ provider : "TestProvider" ,
398+ } )
399+ } )
400+
401+ it ( "should filter out 429 rate limit errors (via status property)" , ( ) => {
402+ const client = new PostHogTelemetryClient ( )
403+ client . updateTelemetryState ( true )
404+
405+ // Create an error with status property (like OpenAI SDK errors)
406+ const error = Object . assign ( new Error ( "Rate limit exceeded" ) , { status : 429 } )
407+ client . captureException ( error )
408+
409+ // Should NOT capture 429 errors
410+ expect ( mockPostHogClient . captureException ) . not . toHaveBeenCalled ( )
411+ } )
412+
413+ it ( "should filter out errors with '429' in message" , ( ) => {
414+ const client = new PostHogTelemetryClient ( )
415+ client . updateTelemetryState ( true )
416+
417+ const error = new Error ( "429 Rate limit exceeded: free-models-per-day" )
418+ client . captureException ( error )
419+
420+ // Should NOT capture errors with 429 in message
421+ expect ( mockPostHogClient . captureException ) . not . toHaveBeenCalled ( )
422+ } )
423+
424+ it ( "should filter out errors containing 'rate limit' (case insensitive)" , ( ) => {
425+ const client = new PostHogTelemetryClient ( )
426+ client . updateTelemetryState ( true )
427+
428+ const error = new Error ( "Request failed due to Rate Limit" )
429+ client . captureException ( error )
430+
431+ // Should NOT capture rate limit errors
432+ expect ( mockPostHogClient . captureException ) . not . toHaveBeenCalled ( )
433+ } )
434+
435+ it ( "should capture non-rate-limit errors" , ( ) => {
436+ const client = new PostHogTelemetryClient ( )
437+ client . updateTelemetryState ( true )
438+
439+ const error = new Error ( "Internal server error" )
440+ client . captureException ( error )
441+
442+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , undefined )
443+ } )
444+
445+ it ( "should capture errors with non-429 status codes" , ( ) => {
446+ const client = new PostHogTelemetryClient ( )
447+ client . updateTelemetryState ( true )
448+
449+ const error = Object . assign ( new Error ( "Internal server error" ) , { status : 500 } )
450+ client . captureException ( error )
451+
452+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , undefined )
453+ } )
454+
455+ it ( "should use nested error message from OpenAI SDK error structure for filtering" , ( ) => {
456+ const client = new PostHogTelemetryClient ( )
457+ client . updateTelemetryState ( true )
458+
459+ // Create an error with nested metadata (like OpenRouter upstream errors)
460+ const error = Object . assign ( new Error ( "Request failed" ) , {
461+ status : 429 ,
462+ error : {
463+ message : "Error details" ,
464+ metadata : { raw : "Rate limit exceeded: free-models-per-day" } ,
465+ } ,
466+ } )
467+ client . captureException ( error )
468+
469+ // Should NOT capture - the nested metadata.raw contains rate limit message
470+ expect ( mockPostHogClient . captureException ) . not . toHaveBeenCalled ( )
471+ } )
472+
473+ it ( "should modify error.message with extracted message from nested metadata.raw" , ( ) => {
474+ const client = new PostHogTelemetryClient ( )
475+ client . updateTelemetryState ( true )
476+
477+ // Create an OpenAI SDK-like error with nested metadata (non-rate-limit error)
478+ const error = Object . assign ( new Error ( "Generic request failed" ) , {
479+ status : 500 ,
480+ error : {
481+ message : "Nested error message" ,
482+ metadata : { raw : "Upstream provider error: model overloaded" } ,
483+ } ,
484+ } )
485+
486+ client . captureException ( error )
487+
488+ // Verify error message was modified to use metadata.raw (highest priority)
489+ expect ( error . message ) . toBe ( "Upstream provider error: model overloaded" )
490+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , undefined )
491+ } )
492+
493+ it ( "should modify error.message with nested error.message when metadata.raw is not available" , ( ) => {
494+ const client = new PostHogTelemetryClient ( )
495+ client . updateTelemetryState ( true )
496+
497+ // Create an OpenAI SDK-like error with nested message but no metadata.raw
498+ const error = Object . assign ( new Error ( "Generic request failed" ) , {
499+ status : 500 ,
500+ error : {
501+ message : "Upstream provider: connection timeout" ,
502+ } ,
503+ } )
504+
505+ client . captureException ( error )
506+
507+ // Verify error message was modified to use nested error.message
508+ expect ( error . message ) . toBe ( "Upstream provider: connection timeout" )
509+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , undefined )
510+ } )
511+
512+ it ( "should use primary message when no nested error structure exists" , ( ) => {
513+ const client = new PostHogTelemetryClient ( )
514+ client . updateTelemetryState ( true )
515+
516+ // Create an OpenAI SDK-like error without nested error object
517+ const error = Object . assign ( new Error ( "Primary error message" ) , {
518+ status : 500 ,
519+ } )
520+
521+ client . captureException ( error )
522+
523+ // Verify error message remains the primary message
524+ expect ( error . message ) . toBe ( "Primary error message" )
525+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , undefined )
526+ } )
527+
528+ it ( "should auto-extract properties from ApiProviderError" , ( ) => {
529+ const client = new PostHogTelemetryClient ( )
530+ client . updateTelemetryState ( true )
531+
532+ const error = new ApiProviderError ( "Test error" , "OpenRouter" , "gpt-4" , "createMessage" , 500 )
533+ client . captureException ( error )
534+
535+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , {
536+ provider : "OpenRouter" ,
537+ modelId : "gpt-4" ,
538+ operation : "createMessage" ,
539+ errorCode : 500 ,
540+ } )
541+ } )
542+
543+ it ( "should auto-extract properties from ApiProviderError without errorCode" , ( ) => {
544+ const client = new PostHogTelemetryClient ( )
545+ client . updateTelemetryState ( true )
546+
547+ const error = new ApiProviderError ( "Test error" , "OpenRouter" , "gpt-4" , "completePrompt" )
548+ client . captureException ( error )
549+
550+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , {
551+ provider : "OpenRouter" ,
552+ modelId : "gpt-4" ,
553+ operation : "completePrompt" ,
554+ } )
555+ } )
556+
557+ it ( "should merge auto-extracted properties with additionalProperties" , ( ) => {
558+ const client = new PostHogTelemetryClient ( )
559+ client . updateTelemetryState ( true )
560+
561+ const error = new ApiProviderError ( "Test error" , "OpenRouter" , "gpt-4" , "createMessage" )
562+ client . captureException ( error , { customProperty : "value" } )
563+
564+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , {
565+ provider : "OpenRouter" ,
566+ modelId : "gpt-4" ,
567+ operation : "createMessage" ,
568+ customProperty : "value" ,
569+ } )
570+ } )
571+
572+ it ( "should allow additionalProperties to override auto-extracted properties" , ( ) => {
573+ const client = new PostHogTelemetryClient ( )
574+ client . updateTelemetryState ( true )
575+
576+ const error = new ApiProviderError ( "Test error" , "OpenRouter" , "gpt-4" , "createMessage" )
577+ // Explicitly override the provider value
578+ client . captureException ( error , { provider : "OverriddenProvider" } )
579+
580+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , {
581+ provider : "OverriddenProvider" , // additionalProperties takes precedence
582+ modelId : "gpt-4" ,
583+ operation : "createMessage" ,
584+ } )
585+ } )
586+
587+ it ( "should not auto-extract for non-ApiProviderError errors" , ( ) => {
588+ const client = new PostHogTelemetryClient ( )
589+ client . updateTelemetryState ( true )
590+
591+ const error = new Error ( "Regular error" )
592+ client . captureException ( error , { customProperty : "value" } )
593+
594+ // Should only have the additionalProperties, not any auto-extracted ones
595+ expect ( mockPostHogClient . captureException ) . toHaveBeenCalledWith ( error , "test-machine-id" , {
596+ customProperty : "value" ,
597+ } )
598+ } )
599+ } )
376600} )
0 commit comments