Ctrl K
ComunidadGuías…

tests/builder-payload.unit.test.ts

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

Archivo original ↗Preguntar a KiBot
import { describe, expect, it } from 'vitest'
import { builderPayloadTestUtils } from '../supabase/functions/_backend/public/build/request.ts'

const { buildBuilderPayload, normalizeIosDependencyManager, parseMachineVersionUnavailable } = builderPayloadTestUtils

const baseInput = {
  orgId: 'org-123',
  actorUserId: 'user-1',
  appId: 'com.test.app',
  uploadPath: 'orgs/org-123/apps/com.test/native-builds/session.zip',
  platform: 'ios',
  buildOptions: {},
  buildCredentials: {},
  priority: 10,
}

describe('builder payload shape', () => {
  it.concurrent('maps build_options (snake_case input) to buildOptions (camelCase output)', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      buildOptions: { platform: 'ios', buildMode: 'release', cliVersion: '7.83.0' },
    })

    expect(payload).toHaveProperty('buildOptions')
    expect(payload.buildOptions).toEqual({ platform: 'ios', buildMode: 'release', cliVersion: '7.83.0' })
    // Must NOT contain the snake_case input key
    expect(payload).not.toHaveProperty('build_options')
  })

  it.concurrent('maps build_credentials (snake_case input) to buildCredentials (camelCase output)', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      platform: 'android',
      buildCredentials: { KEYSTORE_KEY_ALIAS: 'alias', KEYSTORE_KEY_PASSWORD: 'val' },
    })

    expect(payload).toHaveProperty('buildCredentials')
    expect(payload.buildCredentials).toEqual({ KEYSTORE_KEY_ALIAS: 'alias', KEYSTORE_KEY_PASSWORD: 'val' })
    // Must NOT contain the snake_case input key
    expect(payload).not.toHaveProperty('build_credentials')
  })

  it.concurrent('does not include a legacy flat credentials field', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      uploadPath: 'path.zip',
      buildCredentials: { SOME_SECRET: 'val' },
    })

    expect(payload).not.toHaveProperty('credentials')
  })

  it.concurrent('forwards plan queue priority to the builder job payload', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      priority: 40,
    })
    expect(payload.priority).toBe(40)
  })

  it.concurrent('includes userId (org), actorUserId (human), appId, artifactKey, and fastlane with correct values', () => {
    const payload = buildBuilderPayload({
      orgId: 'org-456',
      actorUserId: 'user-789',
      appId: 'com.example.app',
      uploadPath: 'orgs/org-456/apps/com.example/native-builds/uuid.zip',
      platform: 'android',
      buildOptions: {},
      buildCredentials: {},
      priority: 20,
    })

    expect(payload.userId).toBe('org-456')
    expect(payload.actorUserId).toBe('user-789')
    expect(payload.appId).toBe('com.example.app')
    expect(payload.artifactKey).toBe('orgs/org-456/apps/com.example/native-builds/uuid.zip')
    expect(payload.fastlane).toEqual({ lane: 'android' })
  })

  it.concurrent('contains exactly the expected top-level keys when cache is enabled (default)', () => {
    const payload = buildBuilderPayload({
      orgId: 'org-789',
      actorUserId: 'user-1',
      appId: 'com.test.app',
      uploadPath: 'path/to/artifact.zip',
      platform: 'ios',
      buildOptions: { foo: 'bar' },
      buildCredentials: { baz: 'qux' },
      priority: 10,
    })

    const keys = Object.keys(payload).sort()
    expect(keys).toEqual([
      'actorUserId',
      'appId',
      'artifactKey',
      'buildCredentials',
      'buildOptions',
      'fastlane',
      'priority',
      'userId',
    ])
  })

  it.concurrent('forwards cache_key to builder payload for custom cache namespaces', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      cacheKey: 'prod',
    })

    expect(payload.cache_key).toBe('prod')
    expect(payload.cache_fingerprint_extra).toBe('prod')

    const keys = Object.keys(payload).sort()
    expect(keys).toEqual([
      'actorUserId',
      'appId',
      'artifactKey',
      'buildCredentials',
      'buildOptions',
      'cache_fingerprint_extra',
      'cache_key',
      'fastlane',
      'priority',
      'userId',
    ])
  })

  it.concurrent('omits cache_key when unset', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
    })

    expect(payload).not.toHaveProperty('cache_key')
    expect(payload).not.toHaveProperty('cache_fingerprint_extra')
  })

  it.concurrent('forwards cache_enabled false when CLI opts out with --no-cache', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      cacheEnabled: false,
    })

    expect(payload.cache_enabled).toBe(false)
    expect(payload.appId).toBe('com.test.app')

    const keys = Object.keys(payload).sort()
    expect(keys).toEqual([
      'actorUserId',
      'appId',
      'artifactKey',
      'buildCredentials',
      'buildOptions',
      'cache_enabled',
      'fastlane',
      'priority',
      'userId',
    ])
  })

  it.concurrent('omits cache_enabled when cache is enabled (default)', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      cacheEnabled: true,
    })

    expect(payload).not.toHaveProperty('cache_enabled')
    expect(payload.appId).toBe('com.test.app')
  })

  it.concurrent('drops timeoutSeconds from buildOptions', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      uploadPath: 'path/to/artifact.zip',
      buildOptions: { platform: 'ios', timeoutSeconds: 999999 },
    })

    expect(payload.buildOptions).toEqual({ platform: 'ios' })
  })

  it.concurrent('passes through buildOptions and buildCredentials contents unchanged', () => {
    const complexOptions = {
      platform: 'ios',
      buildMode: 'debug',
      cliVersion: '7.84.0',
      nested: { deep: { value: 42 } },
      array: [1, 2, 3],
    }
    const complexCredentials = {
      BUILD_CERTIFICATE_BASE64: 'base64data',
      P12_PASSWORD: 'test-val',
    }

    const payload = buildBuilderPayload({
      orgId: 'org-test',
      actorUserId: 'user-1',
      appId: 'com.complex.app',
      uploadPath: 'test/path.zip',
      platform: 'ios',
      buildOptions: complexOptions,
      buildCredentials: complexCredentials,
      priority: 100,
    })

    expect(payload.buildOptions).toEqual(complexOptions)
    expect(payload.buildCredentials).toEqual(complexCredentials)
  })
})

describe('builder machine version rejection', () => {
  it.concurrent('forwards only the builder runner_version_unavailable message', () => {
    const body = JSON.stringify({ error: 'No build machine has Xcode 27. Available: macOS 26.1 · Xcode 26.0.1.', code: 'runner_version_unavailable' })
    expect(parseMachineVersionUnavailable(400, body)).toBe('No build machine has Xcode 27. Available: macOS 26.1 · Xcode 26.0.1.')
    expect(parseMachineVersionUnavailable(500, body)).toBeNull()
    expect(parseMachineVersionUnavailable(400, JSON.stringify({ error: 'invalid payload' }))).toBeNull()
    expect(parseMachineVersionUnavailable(400, 'not json')).toBeNull()
  })

  it.concurrent('passes the requested machine versions through to the builder', () => {
    const payload = buildBuilderPayload({
      ...baseInput,
      buildOptions: { platform: 'ios', iosXcodeVersion: '26', iosMacosVersion: '26.1' },
    })
    expect(payload.buildOptions).toMatchObject({ iosXcodeVersion: '26', iosMacosVersion: '26.1' })
  })
})

describe('ios dependency manager normalization', () => {
  it.concurrent('keeps cocoapods and spm for iOS builds', () => {
    expect(normalizeIosDependencyManager('ios', 'cocoapods')).toBe('cocoapods')
    expect(normalizeIosDependencyManager('ios', 'spm')).toBe('spm')
  })

  it.concurrent('drops unknown values and non-iOS platforms', () => {
    expect(normalizeIosDependencyManager('ios', 'carthage')).toBeNull()
    expect(normalizeIosDependencyManager('ios', undefined)).toBeNull()
    expect(normalizeIosDependencyManager('ios', 1)).toBeNull()
    expect(normalizeIosDependencyManager('android', 'spm')).toBeNull()
  })
})