Ctrl K
ComunidadGuías…

tests/app-id-validation.test.ts

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

Archivo original ↗Preguntar a KiBot
/**
 * App ID Validation Tests
 *
 * IMPORTANT: These integration tests require the backend to be running with the latest code.
 * If tests fail with unexpected status codes, restart Supabase Edge Functions:
 *   supabase functions serve --no-verify-jwt
 *
 * or for Cloudflare Workers:
 *   npm run dev:cloudflare (or restart the workers)
 */
import { randomUUID } from 'node:crypto'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { isValidAppId } from '../supabase/functions/_backend/utils/utils.ts'
import { BASE_URL, createDirectApiKeyWithBindings, getSupabaseClient, headers, resetAndSeedAppData, resetAppData, TEST_EMAIL, USER_ID } from './test-utils.ts'

const id = randomUUID()
const VALID_APPNAME = `com.app.valid.${id}`
const appCreationApiKey = randomUUID()
let testOrgId: string
let testOrgApiKeyId: number | null = null
let testOrgHeaders: Record<string, string>

beforeAll(async () => {
  await resetAndSeedAppData(VALID_APPNAME)

  // Create test organization
  const { data: orgData, error: orgError } = await getSupabaseClient().from('orgs').insert({
    id: randomUUID(),
    name: `Test App ID Validation Org ${id}`,
    management_email: TEST_EMAIL,
    created_by: USER_ID,
  }).select().single()

  if (orgError)
    throw orgError
  testOrgId = orgData.id

  const apiKey = await createDirectApiKeyWithBindings({
    key: appCreationApiKey,
    name: `app-id-validation-${id}`,
    orgId: testOrgId,
    roleName: 'org_super_admin',
  })
  if (!apiKey.key)
    throw new Error('Failed to create app id validation API key')

  testOrgApiKeyId = apiKey.id
  testOrgHeaders = {
    'Content-Type': 'application/json',
    'Authorization': apiKey.key,
  }
})

afterAll(async () => {
  await resetAppData(VALID_APPNAME)
  if (testOrgApiKeyId !== null)
    await getSupabaseClient().from('apikeys').delete().eq('id', testOrgApiKeyId)
  await getSupabaseClient().from('orgs').delete().eq('id', testOrgId)
})

describe('isValidAppId helper function', () => {
  it.concurrent('accepts reverse domain app IDs', () => {
    for (const appId of ['com.example.app', 'ee.forgr.demoapp', 'com.company.product-name', 'com.company.product_name', 'io.ionic.starter'])
      expect(isValidAppId(appId), appId).toBe(true)
  })

  it.concurrent('rejects malformed, empty or missing app IDs', () => {
    for (const appId of ['', 'app', 'app_name', '.com.example', 'com.example.', 'com..example', 'com.example.app+', '[appid]', null, undefined])
      expect(isValidAppId(appId as any), String(appId)).toBe(false)
  })
})

describe('public API app_id validation', () => {
  const appBody = (app_id: string) => () => ({ app_id, name: 'Test App', owner_org: testOrgId })
  const bundleBody = (app_id: string) => () => ({ app_id, version: '1.0.0', external_url: 'https://example.com/bundle.zip', checksum: 'abc123' })
  const channelBody = (app_id: string) => () => ({ app_id, channel: 'test-channel' })

  it.concurrent.each([
    ['POST', '/app', 'invalid-app-id', appBody('invalid-app-id')],
    ['POST', '/app', 'justappname', appBody('justappname')],
    ['POST', '/bundle', 'invalid-app', bundleBody('invalid-app')],
    ['POST', '/bundle', '.com.example.app', bundleBody('.com.example.app')],
    ['POST', '/channel', 'nodomainapp', channelBody('nodomainapp')],
    ['POST', '/channel', 'com.example.app+special', channelBody('com.example.app+special')],
    ['POST', '/device', 'badappid', () => ({ app_id: 'badappid', device_id: '00000000-0000-0000-0000-000000000000' })],
    ['GET', '/app/invalid-app-id', ''],
    ['GET', '/app/[appid]', ''],
    ['PUT', '/app/no-domain', 'no-domain', () => ({ name: 'Updated Name' })],
    ['DELETE', '/app/justname', ''],
    ['GET', '/bundle?app_id=invalid', ''],
    ['DELETE', '/bundle?app_id=badid&version=1.0.0', ''],
    ['GET', '/channel?app_id=wrongformat', ''],
    ['DELETE', '/channel?app_id=bad&channel=production', ''],
    ['GET', '/device?app_id=wrongformat', ''],
    ['DELETE', '/device?app_id=bad&device_id=00000000-0000-0000-0000-000000000000', ''],
  ] as [string, string, string, (() => Record<string, unknown>)?][])('rejects %s %s %s with invalid_app_id', async (method, path, _appId, body) => {
    const response = await fetch(`${BASE_URL}${path}`, {
      method,
      headers,
      ...(body ? { body: JSON.stringify(body()) } : {}),
    })
    expect(response.status).toBe(400)
    const data = await response.json() as { error: string }
    expect(data.error).toBe('invalid_app_id')
  })

  it('accepts app creation with a valid app_id', async () => {
    const validAppId = `com.test.valid.${randomUUID()}`
    const response = await fetch(`${BASE_URL}/app`, {
      method: 'POST',
      headers: testOrgHeaders,
      body: JSON.stringify({
        app_id: validAppId,
        name: 'Test Valid App',
        owner_org: testOrgId,
      }),
    })
    expect(response.status).toBe(200)
    const data = await response.json() as { app_id: string }
    expect(data.app_id).toBe(validAppId)

    await getSupabaseClient().from('apps').delete().eq('app_id', validAppId)
  })
})