tests/bundle-error-cases.test.ts
Rama main · réplica de lectura del repositorio original.
import { randomUUID } from 'node:crypto'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { BASE_URL, createAppVersions, getSupabaseClient, headers, resetAndSeedAppData, resetAppData, USER_ID } from './test-utils.ts'
const id = randomUUID()
const APPNAME = `com.bundle.error.${id}`
let testOrgId: string
let metadataVersionId: number
let channelVersionId: number
let testChannelId: number
beforeAll(async () => {
await resetAndSeedAppData(APPNAME)
// Get the org that was created by resetAndSeedAppData
const { data: app } = await getSupabaseClient()
.from('apps')
.select('owner_org')
.eq('app_id', APPNAME)
.single()
if (!app)
throw new Error('App not found after seeding')
testOrgId = app.owner_org
metadataVersionId = (await createAppVersions(`1.0.0-test-metadata-${id}`, APPNAME)).id
channelVersionId = (await createAppVersions(`1.0.0-test-channel-${id}`, APPNAME)).id
const { data: channel, error: channelError } = await getSupabaseClient()
.from('channels')
.insert({
name: `test-channel-${id}`,
app_id: APPNAME,
version: channelVersionId,
created_by: USER_ID,
owner_org: testOrgId,
})
.select('id')
.single()
if (channelError || !channel)
throw channelError ?? new Error('Failed to create test channel')
testChannelId = channel.id
})
afterAll(async () => {
await resetAppData(APPNAME)
})
describe('[GET] /bundle - Error Cases', () => {
it('should return 400 when app_id is missing', async () => {
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'GET',
headers,
// 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 401 when the API key is missing', async () => {
const response = await fetch(`${BASE_URL}/bundle?app_id=${APPNAME}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
expect(response.status).toBe(401)
})
it('should return 404 when the app does not exist', async () => {
const response = await fetch(`${BASE_URL}/bundle?app_id=com.nonexistent.${id}.app`, {
method: 'GET',
headers,
})
expect(response.status).toBe(404)
const data = await response.json() as { error: string }
expect(data.error).toBe('app_not_found')
})
it('should return 404 when the app id is not visible to the caller', async () => {
const response = await fetch(`${BASE_URL}/bundle?app_id=${APPNAME}_no`, {
method: 'GET',
headers,
})
expect(response.status).toBe(404)
const data = await response.json() as { error: string }
expect(data.error).toBe('app_not_found')
})
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 return empty array for authorized app with no bundles', async () => {
const emptyAppId = `com.bundle.empty.${randomUUID()}`
await resetAndSeedAppData(emptyAppId)
try {
const { error: deleteError } = await getSupabaseClient()
.from('app_versions')
.delete()
.eq('app_id', emptyAppId)
if (deleteError)
throw deleteError
const response = await fetch(`${BASE_URL}/bundle?app_id=${emptyAppId}`, {
method: 'GET',
headers,
})
expect(response.status).toBe(200)
const data = await response.json()
expect(data).toEqual([])
}
finally {
await resetAppData(emptyAppId)
}
})
it('should return empty array for page past the last bundle', async () => {
const response = await fetch(`${BASE_URL}/bundle?app_id=${APPNAME}&page=99`, {
method: 'GET',
headers,
})
expect(response.status).toBe(200)
const data = await response.json()
expect(data).toEqual([])
})
})
describe('[DELETE] /bundle - Error Cases', () => {
it('should return 400 when app_id is missing', async () => {
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'DELETE',
headers,
body: JSON.stringify({
version: '1.0.0',
// Missing app_id
}),
})
expect(response.status).toBe(400)
const data = await response.json() as { error: string }
expect(data.error).toBe('missing_app_id') // Changed: validation now catches this first
})
it('should return 400 when user cannot access the app', async () => {
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'DELETE',
headers,
body: JSON.stringify({
app_id: 'nonexistent.app',
version: '1.0.0',
}),
})
expect(response.status).toBe(400)
const data = await response.json() as { error: string }
expect(data.error).toBe('cannot_delete_bundle')
})
it('should return 400 when version cannot be deleted', async () => {
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'DELETE',
headers,
body: JSON.stringify({
app_id: APPNAME,
version: 'nonexistent-version',
}),
})
expect(response.status).toBe(400)
const data = await response.json() as { error: string }
expect(data.error).toBe('cannot_delete_version')
})
it('should handle invalid JSON body', async () => {
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'DELETE',
headers,
body: 'invalid json',
})
expect(response.status).toBeGreaterThanOrEqual(400)
})
})
describe('[POST] /bundle/metadata - Extended Error Cases', () => {
it('should return 400 when no fields to update', async () => {
const response = await fetch(`${BASE_URL}/bundle/metadata`, {
method: 'POST',
headers,
body: JSON.stringify({
app_id: APPNAME,
version_id: metadataVersionId,
// No updateable fields provided
}),
})
expect(response.status).toBe(400)
const data = await response.json() as { error: string }
expect(data.error).toBe('no_fields_to_update')
})
it('should return 400 when update fails', async () => {
const response = await fetch(`${BASE_URL}/bundle/metadata`, {
method: 'POST',
headers,
body: JSON.stringify({
app_id: APPNAME,
version_id: 999999, // Non-existent version
link: 'https://example.com',
}),
})
expect(response.status).toBe(400)
const data = await response.json() as { error: string }
expect(data.error).toBe('cannot_find_version')
})
it('should handle invalid JSON body', async () => {
const response = await fetch(`${BASE_URL}/bundle/metadata`, {
method: 'POST',
headers,
body: 'invalid json',
})
expect(response.status).toBeGreaterThanOrEqual(400)
})
})
describe('[PUT] /bundle - Extended Error Cases', () => {
it('should return 400 when user cannot access the app', async () => {
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'PUT',
headers,
body: JSON.stringify({
app_id: 'nonexistent.app',
version_id: 1,
channel_id: 1,
}),
})
expect(response.status).toBe(400)
const data = await response.json() as { error: string }
expect(data.error).toBe('cannot_access_app')
})
it('should return 400 when app cannot be found', async () => {
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'PUT',
headers,
body: JSON.stringify({
app_id: APPNAME,
version_id: 999999, // Non-existent version
channel_id: 1,
}),
})
expect(response.status).toBe(400)
const data = await response.json() as { error: string }
expect(data.error).toBe('cannot_find_version')
})
it('should return 500 when bundle cannot be set to channel', async () => {
// Try to set bundle to channel with conflicting data
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'PUT',
headers,
body: JSON.stringify({
app_id: APPNAME,
version_id: 999999, // Non-existent version ID
channel_id: testChannelId,
}),
})
const data = await response.json() as { error: string }
expect(data.error).toBe('cannot_find_version')
})
it('should handle invalid JSON body', async () => {
const response = await fetch(`${BASE_URL}/bundle`, {
method: 'PUT',
headers,
body: 'invalid json',
})
expect(response.status).toBeGreaterThanOrEqual(400)
})
})