/* eslint-disable @typescript-eslint/no-explicit-any */ // npx vitest run src/__tests__/RooCodeTelemetryClient.test.ts import { describe, it, expect, beforeEach, afterEach, vi } from "vitest" import { type TelemetryPropertiesProvider, TelemetryEventName } from "@roo-code/types" import { RooCodeTelemetryClient } from "../RooCodeTelemetryClient" const mockFetch = vi.fn() global.fetch = mockFetch as any describe("RooCodeTelemetryClient", () => { const getPrivateProperty = (instance: any, propertyName: string): T => { return instance[propertyName] } let mockAuthService: any beforeEach(() => { vi.clearAllMocks() // Create a mock AuthService instead of using the singleton mockAuthService = { getSessionToken: vi.fn().mockReturnValue("mock-token"), getState: vi.fn().mockReturnValue("active-session"), isAuthenticated: vi.fn().mockReturnValue(true), hasActiveSession: vi.fn().mockReturnValue(true), } mockFetch.mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({}), }) vi.spyOn(console, "info").mockImplementation(() => {}) vi.spyOn(console, "error").mockImplementation(() => {}) }) afterEach(() => { vi.restoreAllMocks() }) describe("isEventCapturable", () => { it("should return true for events not in exclude list", () => { const client = new RooCodeTelemetryClient(mockAuthService) const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>( client, "isEventCapturable", ).bind(client) expect(isEventCapturable(TelemetryEventName.TASK_CREATED)).toBe(true) expect(isEventCapturable(TelemetryEventName.LLM_COMPLETION)).toBe(true) expect(isEventCapturable(TelemetryEventName.MODE_SWITCH)).toBe(true) expect(isEventCapturable(TelemetryEventName.TOOL_USED)).toBe(true) }) it("should return false for events in exclude list", () => { const client = new RooCodeTelemetryClient(mockAuthService) const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>( client, "isEventCapturable", ).bind(client) expect(isEventCapturable(TelemetryEventName.TASK_CONVERSATION_MESSAGE)).toBe(false) }) }) describe("getEventProperties", () => { it("should merge provider properties with event properties", async () => { const client = new RooCodeTelemetryClient(mockAuthService) const mockProvider: TelemetryPropertiesProvider = { getTelemetryProperties: vi.fn().mockResolvedValue({ appVersion: "1.0.0", vscodeVersion: "1.60.0", platform: "darwin", editorName: "vscode", language: "en", mode: "code", }), } client.setProvider(mockProvider) const getEventProperties = getPrivateProperty< (event: { event: TelemetryEventName; properties?: Record }) => Promise> >(client, "getEventProperties").bind(client) const result = await getEventProperties({ event: TelemetryEventName.TASK_CREATED, properties: { customProp: "value", mode: "override", // This should override the provider's mode. }, }) expect(result).toEqual({ appVersion: "1.0.0", vscodeVersion: "1.60.0", platform: "darwin", editorName: "vscode", language: "en", mode: "override", // Event property takes precedence. customProp: "value", }) expect(mockProvider.getTelemetryProperties).toHaveBeenCalledTimes(1) }) it("should handle errors from provider gracefully", async () => { const client = new RooCodeTelemetryClient(mockAuthService) const mockProvider: TelemetryPropertiesProvider = { getTelemetryProperties: vi.fn().mockRejectedValue(new Error("Provider error")), } const consoleErrorSpy = vi.spyOn(console, "error") client.setProvider(mockProvider) const getEventProperties = getPrivateProperty< (event: { event: TelemetryEventName; properties?: Record }) => Promise> >(client, "getEventProperties").bind(client) const result = await getEventProperties({ event: TelemetryEventName.TASK_CREATED, properties: { customProp: "value" }, }) expect(result).toEqual({ customProp: "value" }) expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining("Error getting telemetry properties: Provider error"), ) }) it("should return event properties when no provider is set", async () => { const client = new RooCodeTelemetryClient(mockAuthService) const getEventProperties = getPrivateProperty< (event: { event: TelemetryEventName; properties?: Record }) => Promise> >(client, "getEventProperties").bind(client) const result = await getEventProperties({ event: TelemetryEventName.TASK_CREATED, properties: { customProp: "value" }, }) expect(result).toEqual({ customProp: "value" }) }) }) describe("capture", () => { it("should not capture events that are not capturable", async () => { const client = new RooCodeTelemetryClient(mockAuthService) await client.capture({ event: TelemetryEventName.TASK_CONVERSATION_MESSAGE, // In exclude list. properties: { test: "value" }, }) expect(mockFetch).not.toHaveBeenCalled() }) it("should not send request when schema validation fails", async () => { const client = new RooCodeTelemetryClient(mockAuthService) await client.capture({ event: TelemetryEventName.TASK_CREATED, properties: { test: "value" }, }) expect(mockFetch).not.toHaveBeenCalled() expect(console.error).toHaveBeenCalledWith(expect.stringContaining("Invalid telemetry event")) }) it("should send request when event is capturable and validation passes", async () => { const client = new RooCodeTelemetryClient(mockAuthService) const providerProperties = { appVersion: "1.0.0", vscodeVersion: "1.60.0", platform: "darwin", editorName: "vscode", language: "en", mode: "code", } const eventProperties = { taskId: "test-task-id", } const mockValidatedData = { type: TelemetryEventName.TASK_CREATED, properties: { ...providerProperties, taskId: "test-task-id", }, } const mockProvider: TelemetryPropertiesProvider = { getTelemetryProperties: vi.fn().mockResolvedValue(providerProperties), } client.setProvider(mockProvider) await client.capture({ event: TelemetryEventName.TASK_CREATED, properties: eventProperties, }) expect(mockFetch).toHaveBeenCalledWith( "https://app.roocode.com/api/events", expect.objectContaining({ method: "POST", body: JSON.stringify(mockValidatedData), }), ) }) it("should handle fetch errors gracefully", async () => { const client = new RooCodeTelemetryClient(mockAuthService) mockFetch.mockRejectedValue(new Error("Network error")) await expect( client.capture({ event: TelemetryEventName.TASK_CREATED, properties: { test: "value" }, }), ).resolves.not.toThrow() }) }) describe("telemetry state methods", () => { it("should always return true for isTelemetryEnabled", () => { const client = new RooCodeTelemetryClient(mockAuthService) expect(client.isTelemetryEnabled()).toBe(true) }) it("should have empty implementations for updateTelemetryState and shutdown", async () => { const client = new RooCodeTelemetryClient(mockAuthService) client.updateTelemetryState(true) await client.shutdown() }) }) })