tests/builder-onboarding-telemetry.unit.test.ts
Rama main · réplica de lectura del repositorio original.
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { trackBuilderOnboardingStep } from '../cli/src/build/onboarding/telemetry.ts'
const sendEventMock = vi.hoisted(() => vi.fn())
vi.mock('../cli/src/utils.ts', () => ({
defaultApiHost: 'https://api.capgo.app',
getRemoteConfig: vi.fn().mockResolvedValue({ hostApi: 'https://api.capgo.app' }),
sendEvent: sendEventMock,
}))
const base = { apikey: 'cap_test_key', platform: 'ios', appId: 'com.example.app', orgId: 'org-uuid-1' } as const
describe('trackBuilderOnboardingStep', () => {
beforeEach(() => {
sendEventMock.mockReset()
sendEventMock.mockResolvedValue(undefined)
})
it('builds the expected payload and calls sendEvent once', async () => {
await trackBuilderOnboardingStep({
...base,
step: 'api-key-instructions',
durationMs: 1234,
durationStep: 'welcome',
})
expect(sendEventMock).toHaveBeenCalledTimes(1)
const [calledKey, payload] = sendEventMock.mock.calls[0]
expect(calledKey).toBe('cap_test_key')
expect(payload).toMatchObject({
event: 'Builder Onboarding Step',
channel: 'builder-onboarding',
org_id: 'org-uuid-1',
tracking_version: 2,
tags: {
step: 'api-key-instructions',
platform: 'ios',
app_id: 'com.example.app',
duration_ms: '1234',
duration_step: 'welcome',
},
})
expect(payload.tags.error_category).toBeUndefined()
})
it('includes error_category only when an error is provided', async () => {
await trackBuilderOnboardingStep({
...base,
step: 'error',
error: Object.assign(new Error('Unauthorized'), { status: 401 }),
})
const [, payload] = sendEventMock.mock.calls[0]
expect(payload.tags.error_category).toBe('apple_api_unauthorized')
})
it('uses the Android mapper when platform is android', async () => {
await trackBuilderOnboardingStep({
...base,
step: 'error',
platform: 'android',
error: Object.assign(new Error('Bad keystore'), { phase: 'keystore' }),
})
const [, payload] = sendEventMock.mock.calls[0]
expect(payload.tags.error_category).toBe('keystore_invalid')
})
it('swallows errors thrown by sendEvent', async () => {
sendEventMock.mockRejectedValueOnce(new Error('network down'))
await expect(trackBuilderOnboardingStep({
...base,
step: 'welcome',
})).resolves.toBeUndefined()
})
it('does not include duration_ms when undefined', async () => {
await trackBuilderOnboardingStep({
...base,
step: 'welcome',
})
const [, payload] = sendEventMock.mock.calls[0]
expect(payload.tags.duration_ms).toBeUndefined()
})
it('uses pre-computed errorCategory when provided (skipping the mapper)', async () => {
await trackBuilderOnboardingStep({
...base,
step: 'error',
errorCategory: 'profile_creation_failed',
// error intentionally omitted
})
const [, payload] = sendEventMock.mock.calls[0]
expect(payload.tags.error_category).toBe('profile_creation_failed')
})
})