Ctrl K
ComunidadGuías…

tests/bundle-create.test.ts

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

Archivo original ↗Preguntar a KiBot
import type { Database } from '../src/types/database.types'
import { randomUUID } from 'node:crypto'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { BASE_URL, getSupabaseClient, headers, resetAndSeedAppData, resetAppData, TEST_EMAIL, USER_ID } from './test-utils.ts'

const id = randomUUID()
const APPNAME = `com.bundle.create.${id}`
let testOrgId: string
type AppInsert = Database['public']['Tables']['apps']['Insert']

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

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

  if (orgError)
    throw orgError
  testOrgId = orgData.id

  // Create test app
  const appInsert: AppInsert = {
    app_id: APPNAME,
    name: `Test Bundle Create App`,
    icon_url: 'https://example.com/icon.png',
    owner_org: testOrgId,
  }
  await getSupabaseClient().from('apps').insert(appInsert)
})

afterAll(async () => {
  await resetAppData(APPNAME)
  await getSupabaseClient().from('orgs').delete().eq('id', testOrgId)
})

describe('[POST] /bundle - Create Bundle with External URL', () => {
  it('should create bundle with valid GitHub ZIP URL', async () => {
    const response = await fetch(`${BASE_URL}/bundle`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        app_id: APPNAME,
        checksum: 'a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcd',
        version: '1.0.0-github-zip',
        external_url: 'https://github.com/Cap-go/capgo/archive/refs/tags/v12.12.32.zip', // Valid ZIP file from GitHub
      }),
    })

    // This should succeed as it's a valid HTTPS ZIP file
    expect(response.status).toBe(200)
    const data = await response.json() as { status: string, bundle: any }
    expect(data.status).toBe('success')
    expect(data.bundle).toBeTruthy()
    expect(data.bundle.name).toBe('1.0.0-github-zip')
    expect(data.bundle.external_url).toBe('https://github.com/Cap-go/capgo/archive/refs/tags/v12.12.32.zip')
    expect(data.bundle.storage_provider).toBe('external')
  })

  // it.only('should reject non-ZIP files from GitHub', async () => {
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-tar-gz',
  //       external_url: 'https://github.com/Cap-go/capgo/archive/refs/tags/v12.12.32.tar.gz', // tar.gz instead of zip
  //     }),
  //   })

  //   // This should fail because .tar.gz is not a ZIP file
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   console.log(data)
  //   expect(data.error).toBe('url_not_zip')
  // })

  it('should return 400 when app_id is missing', async () => {
    const response = await fetch(`${BASE_URL}/bundle`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        checksum: 'a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcd',
        version: '1.0.0',
        external_url: 'https://example.com/test.zip',
        // Missing app_id
      }),
    })
    expect(response.status).toBe(400)
    const data = await response.json() as { error: string }
    expect(data.error).toBe('missing_app_id')
  })

  it('should return 400 when version is missing', async () => {
    const response = await fetch(`${BASE_URL}/bundle`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        checksum: 'a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcd',
        app_id: APPNAME,
        external_url: 'https://example.com/test.zip',
        // Missing version
      }),
    })
    expect(response.status).toBe(400)
    const data = await response.json() as { error: string }
    expect(data.error).toBe('missing_version')
  })

  it('should return 400 when external_url is missing', async () => {
    const response = await fetch(`${BASE_URL}/bundle`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        checksum: 'a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcd',
        app_id: APPNAME,
        version: '1.0.0',
        // Missing external_url
      }),
    })
    expect(response.status).toBe(400)
    const data = await response.json() as { error: string }
    expect(data.error).toBe('missing_external_url')
  })

  it('should return 400 when URL is not HTTPS', async () => {
    const response = await fetch(`${BASE_URL}/bundle`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        checksum: 'a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcd',
        app_id: APPNAME,
        version: '1.0.0-http',
        external_url: 'http://example.com/test.zip', // HTTP instead of HTTPS
      }),
    })
    expect(response.status).toBe(400)
    const data = await response.json() as { error: string }
    expect(data.error).toBe('invalid_protocol')
  })

  it('should return 400 when user cannot access the app', async () => {
    const response = await fetch(`${BASE_URL}/bundle`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        checksum: 'a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcd',
        app_id: 'nonexistent.app',
        version: '1.0.0',
        external_url: 'https://example.com/test.zip',
      }),
    })
    expect(response.status).toBe(400)
    const data = await response.json() as { error: string }
    expect(data.error).toBe('cannot_create_bundle')
  })

  it('should return 400 when version already exists', async () => {
    // First create a version
    const capgo = getSupabaseClient()
    await capgo
      .from('app_versions')
      .insert({
        checksum: 'a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcd',
        app_id: APPNAME,
        name: '1.0.0-duplicate',
        owner_org: testOrgId,
        storage_provider: 'r2',
      })

    // Try to create the same version again
    const response = await fetch(`${BASE_URL}/bundle`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        checksum: 'a1b2c3d4e5f6789abcdef123456789abcdef123456789abcdef123456789abcd',
        app_id: APPNAME,
        version: '1.0.0-duplicate',
        external_url: 'https://example.com/test.zip',
      }),
    })
    expect(response.status).toBe(400)
    const data = await response.json() as { error: string }
    expect(data.error).toBe('version_already_exists')
  })

  // it('should return 400 when URL does not point to ZIP file', async () => {
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-not-zip',
  //       external_url: 'https://httpbin.org/json', // Returns JSON, not ZIP
  //     }),
  //   })
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   expect(['url_not_zip', 'url_fetch_error'].includes(data.error)).toBe(true)
  // })

  // it('should return 400 when URL points to HTML page', async () => {
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-html',
  //       external_url: 'https://httpbin.org/html', // Returns HTML
  //     }),
  //   })
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   expect(['url_not_file', 'url_fetch_error'].includes(data.error)).toBe(true)
  // })

  it('should handle invalid JSON body', async () => {
    const response = await fetch(`${BASE_URL}/bundle`, {
      method: 'POST',
      headers,
      body: 'invalid json',
    })
    expect(response.status).toBeGreaterThanOrEqual(400)
  })

  // it('should validate URL accessibility', async () => {
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-404',
  //       external_url: 'https://httpbin.org/status/404', // Returns 404
  //     }),
  //   })
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   expect(['url_not_accessible', 'url_fetch_error'].includes(data.error)).toBe(true)
  // })

  // it('should handle network errors gracefully', async () => {
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-network-error',
  //       external_url: 'https://nonexistent-domain-12345.com/test.zip',
  //     }),
  //   })
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   expect(['url_fetch_error', 'url_not_accessible'].includes(data.error)).toBe(true)
  // })

  // it('should follow redirects properly', async () => {
  //   // Using httpbin.org/redirect-to which redirects to the target URL
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-redirect-test',
  //       external_url: 'https://httpbin.org/redirect-to?url=https://httpbin.org/json&status_code=302',
  //     }),
  //   })
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   // Should fail with url_not_zip (because it redirects to JSON), not url_not_accessible
  //   // This proves the redirect was followed successfully
  //   expect(['url_not_zip', 'url_fetch_error'].includes(data.error)).toBe(true)
  // })

  // it('should handle multiple redirects (up to 5)', async () => {
  //   // Test with multiple redirects
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-multi-redirect',
  //       external_url: 'https://httpbin.org/redirect/3', // 3 redirects chain
  //     }),
  //   })
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   // Should follow the redirects and then fail on final content validation
  //   expect(['url_not_zip', 'url_fetch_error', 'url_not_file'].includes(data.error)).toBe(true)
  // })

  // it('should reject too many redirects', async () => {
  //   // Test with more than 5 redirects
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-too-many-redirects',
  //       external_url: 'https://httpbin.org/redirect/10', // 10 redirects (should exceed limit)
  //     }),
  //   })
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   // Should fail with too many redirects error
  //   expect(['url_fetch_error'].includes(data.error)).toBe(true)
  // })

  // it('should validate Content-Disposition header for filename', async () => {
  //   // This test demonstrates that Content-Disposition header checking is implemented
  //   // Most real-world URLs with proper Content-Disposition headers would be inaccessible in tests
  //   const response = await fetch(`${BASE_URL}/bundle`, {
  //     method: 'POST',
  //     headers,
  //     body: JSON.stringify({
  //       app_id: APPNAME,
  //       version: '1.0.0-content-disposition',
  //       external_url: 'https://httpbin.org/response-headers?Content-Disposition=attachment%3B%20filename%3D%22bundle.zip%22',
  //     }),
  //   })
  //   expect(response.status).toBe(400)
  //   const data = await response.json() as { error: string }
  //   // Should check Content-Disposition but still fail on content-type or accessibility
  //   expect(['url_not_zip', 'url_fetch_error', 'url_not_file'].includes(data.error)).toBe(true)
  // })
})