Ctrl K
ComunidadGuías…

tests/bundle-set-channel-rbac.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'

const checkPermissionPgMock = vi.fn()
const closeClientMock = vi.fn()
const getDrizzleClientMock = vi.fn()
const logPgErrorMock = vi.fn()
const queryMock = vi.fn()
const releaseMock = vi.fn()
const connectMock = vi.fn()
const pgClientMock = { connect: connectMock }
const transactionClientMock = { query: queryMock, release: releaseMock }
const transactionDrizzleMock = {}

vi.mock('../supabase/functions/_backend/utils/rbac.ts', () => ({
  checkPermissionPg: (...args: unknown[]) => checkPermissionPgMock(...args),
}))

vi.mock('../supabase/functions/_backend/utils/pg.ts', async () => ({
  ...(await import('./helpers/pg-checkout-release-mocks.ts')),
  closeClient: (...args: unknown[]) => closeClientMock(...args),
  getDrizzleClient: (...args: unknown[]) => getDrizzleClientMock(...args),
  getPgClient: () => pgClientMock,
  logPgError: (...args: unknown[]) => logPgErrorMock(...args),
}))

const { setChannel } = await import('../supabase/functions/_backend/public/bundle/set_channel.ts')

function context() {
  return {
    get: vi.fn((key: string) => {
      if (key === 'capgkey')
        return 'test-apikey'
      if (key === 'requestId')
        return 'test-request'
      return undefined
    }),
    json: vi.fn((body: unknown) => Response.json(body)),
  }
}

function apiKey() {
  return { key: 'test-apikey', user_id: 'user-test' } as any
}

describe('bundle set channel RBAC guard', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    checkPermissionPgMock.mockResolvedValue(true)
    getDrizzleClientMock.mockReturnValue(transactionDrizzleMock)
    queryMock.mockImplementation(async (text: string) => {
      if (text.includes('FROM public.channels')) {
        return { rowCount: 1, rows: [{ name: 'production', owner_org: '046a36ac-e03c-4590-9257-bd6c9dba9ee8' }] }
      }
      if (text.includes('FROM public.app_versions')) {
        return { rowCount: 1, rows: [{ name: '1.0.0' }] }
      }
      return {
        rowCount: text.includes('UPDATE public.channels') ? 1 : undefined,
        rows: [],
      }
    })
    connectMock.mockResolvedValue(transactionClientMock)
  })

  it('checks promotion permission against the target channel on its transaction connection', async () => {
    const c = context()

    const response = await setChannel(c as any, {
      app_id: 'com.example.app',
      version_id: 7,
      channel_id: 42,
    }, apiKey())

    expect(response.status).toBe(200)
    expect(checkPermissionPgMock).toHaveBeenCalledTimes(1)
    expect(getDrizzleClientMock).toHaveBeenCalledWith(transactionClientMock)
    expect(checkPermissionPgMock).toHaveBeenCalledWith(
      c,
      'channel.promote_bundle',
      { appId: 'com.example.app', channelId: 42 },
      transactionDrizzleMock,
      'user-test',
      'test-apikey',
    )
    expect(queryMock).toHaveBeenCalledWith(expect.stringContaining('FROM public.app_versions'), [
      7,
      'com.example.app',
    ])
    expect(queryMock).toHaveBeenCalledWith(expect.stringContaining('FROM public.channels'), [
      42,
      'com.example.app',
    ])
    expect(queryMock).toHaveBeenCalledWith(expect.stringContaining('UPDATE public.channels'), [
      7,
      42,
      'com.example.app',
      '046a36ac-e03c-4590-9257-bd6c9dba9ee8',
    ])

    const queryTexts = queryMock.mock.calls.map(([text]) => String(text))
    const targetChannelQueryIndex = queryTexts.findIndex(text => text.includes('FROM public.channels'))
    const bundleLockQueryIndex = queryTexts.findIndex(text => text.includes('pg_advisory_xact_lock'))
    const versionQueryIndex = queryTexts.findIndex(text => text.includes('FROM public.app_versions'))

    expect(queryTexts[targetChannelQueryIndex]).toContain('FOR UPDATE')
    expect(bundleLockQueryIndex).toBeGreaterThan(targetChannelQueryIndex)
    expect(bundleLockQueryIndex).toBeLessThan(versionQueryIndex)
    expect(queryMock).toHaveBeenCalledWith(
      'SELECT pg_catalog.pg_advisory_xact_lock($1::bigint)',
      [7],
    )
  })

  it('rolls back without updating when channel-scoped promotion is denied', async () => {
    const c = context()
    checkPermissionPgMock.mockResolvedValueOnce(false)

    await expect(setChannel(c as any, {
      app_id: 'com.example.app',
      version_id: 7,
      channel_id: 42,
    }, apiKey())).rejects.toHaveProperty('status', 400)

    expect(checkPermissionPgMock).toHaveBeenCalledTimes(1)
    expect(checkPermissionPgMock).toHaveBeenCalledWith(
      c,
      'channel.promote_bundle',
      { appId: 'com.example.app', channelId: 42 },
      expect.anything(),
      'user-test',
      'test-apikey',
    )
    expect(connectMock).toHaveBeenCalledTimes(1)
    expect(queryMock).toHaveBeenCalledWith(expect.stringContaining('FROM public.channels'), [
      42,
      'com.example.app',
    ])
    expect(queryMock).toHaveBeenCalledWith('ROLLBACK')
    expect(queryMock).not.toHaveBeenCalledWith(expect.stringContaining('FROM public.app_versions'), expect.anything())
    expect(queryMock).not.toHaveBeenCalledWith(expect.stringContaining('UPDATE public.channels'), expect.anything())
  })

  it('uses app-scoped promotion permission when the target channel is missing', async () => {
    const c = context()
    queryMock.mockImplementation(async (text: string) => {
      if (text.includes('FROM public.channels'))
        return { rowCount: 0, rows: [] }
      return { rowCount: undefined, rows: [] }
    })
    checkPermissionPgMock.mockResolvedValueOnce(false)

    await expect(setChannel(c as any, {
      app_id: 'com.example.app',
      version_id: 7,
      channel_id: 42,
    }, apiKey())).rejects.toMatchObject({
      status: 400,
      cause: expect.objectContaining({ error: 'cannot_access_app' }),
    })

    expect(checkPermissionPgMock).toHaveBeenCalledTimes(1)
    expect(checkPermissionPgMock).toHaveBeenCalledWith(
      c,
      'channel.promote_bundle',
      { appId: 'com.example.app' },
      expect.anything(),
      'user-test',
      'test-apikey',
    )
    expect(queryMock).toHaveBeenCalledWith('ROLLBACK')
    expect(queryMock).not.toHaveBeenCalledWith(expect.stringContaining('FROM public.app_versions'), expect.anything())
    expect(queryMock).not.toHaveBeenCalledWith(expect.stringContaining('UPDATE public.channels'), expect.anything())
  })

  it('checks the version before returning a missing-channel error', async () => {
    const c = context()
    queryMock.mockImplementation(async (text: string) => {
      if (text.includes('FROM public.channels'))
        return { rowCount: 0, rows: [] }
      if (text.includes('FROM public.app_versions'))
        return { rowCount: 0, rows: [] }
      return { rowCount: undefined, rows: [] }
    })

    await expect(setChannel(c as any, {
      app_id: 'com.example.app',
      version_id: 7,
      channel_id: 42,
    }, apiKey())).rejects.toMatchObject({
      status: 400,
      cause: expect.objectContaining({ error: 'cannot_find_version' }),
    })

    expect(checkPermissionPgMock).toHaveBeenCalledWith(
      c,
      'channel.promote_bundle',
      { appId: 'com.example.app' },
      expect.anything(),
      'user-test',
      'test-apikey',
    )
    expect(queryMock).toHaveBeenCalledWith(expect.stringContaining('FROM public.app_versions'), [
      7,
      'com.example.app',
    ])
    expect(queryMock).toHaveBeenCalledWith('ROLLBACK')
    expect(queryMock).not.toHaveBeenCalledWith(expect.stringContaining('UPDATE public.channels'), expect.anything())
  })
})