tests/builder-upload-telemetry.unit.test.ts
Rama main · réplica de lectura del repositorio original.
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { mapBuilderUploadError, trackBuilderUpload } from '../cli/src/build/telemetry.ts'
const sendEventMock = vi.hoisted(() => vi.fn())
vi.mock('../cli/src/utils.ts', () => ({
sendEvent: sendEventMock,
}))
const httpError = (status: number) => ({ originalResponse: { getStatus: () => status } })
describe('mapBuilderUploadError', () => {
it.concurrent.each([
[httpError(401), 'unauthorized'],
[httpError(403), 'unauthorized'],
[httpError(413), 'payload_too_large'],
[httpError(500), 'storage_failure'],
[httpError(502), 'storage_failure'],
[httpError(599), 'storage_failure'],
// no-response (connection-level) errors
[new Error('ECONNRESET'), 'network_error'],
[{ originalResponse: undefined }, 'network_error'],
[null, 'network_error'],
[httpError(0), 'network_error'],
[httpError(418), 'unknown'],
[httpError(404), 'unknown'],
])('maps %o to %s', (error, expected) => {
expect(mapBuilderUploadError(error)).toBe(expected)
})
})
const base = { apikey: 'cap_test_key', appId: 'com.example.app', orgId: 'org-uuid-1', buildMode: 'release', jobId: 'job-abc' } as const
describe('trackBuilderUpload', () => {
beforeEach(() => {
sendEventMock.mockReset()
sendEventMock.mockResolvedValue(undefined)
})
it('emits Builder Upload Started with size but no duration or failure_category', async () => {
await trackBuilderUpload({
...base,
platform: 'ios',
sizeBytes: 12_345_678,
phase: 'started',
})
expect(sendEventMock).toHaveBeenCalledTimes(1)
const [, payload] = sendEventMock.mock.calls[0]
expect(payload).toMatchObject({
event: 'Builder Upload Started',
channel: 'build-lifecycle',
org_id: 'org-uuid-1',
tracking_version: 2,
tags: {
app_id: 'com.example.app',
platform: 'ios',
build_mode: 'release',
job_id: 'job-abc',
upload_size_bytes: '12345678',
},
})
expect(payload.tags.upload_duration_seconds).toBeUndefined()
expect(payload.tags.failure_category).toBeUndefined()
})
it('emits Builder Upload Succeeded with duration and size', async () => {
await trackBuilderUpload({
...base,
platform: 'android',
sizeBytes: 12_345_678,
phase: 'succeeded',
durationSeconds: 42.7,
})
const [, payload] = sendEventMock.mock.calls[0]
expect(payload).toMatchObject({
event: 'Builder Upload Succeeded',
tags: {
platform: 'android',
upload_duration_seconds: '43',
},
})
})
it('emits Builder Upload Failed with failure_category from a 413', async () => {
await trackBuilderUpload({
...base,
platform: 'ios',
sizeBytes: 999_999,
phase: 'failed',
durationSeconds: 5,
error: { originalResponse: { getStatus: () => 413 } },
})
const [, payload] = sendEventMock.mock.calls[0]
expect(payload).toMatchObject({
event: 'Builder Upload Failed',
tags: {
failure_category: 'payload_too_large',
upload_duration_seconds: '5',
},
})
})
it('swallows errors thrown by sendEvent', async () => {
sendEventMock.mockRejectedValueOnce(new Error('network down'))
await expect(trackBuilderUpload({
...base,
platform: 'ios',
sizeBytes: 1,
phase: 'started',
})).resolves.toBeUndefined()
})
})