Ctrl K
ComunidadGuías…

tests/builder-upload-telemetry.unit.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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()
  })
})