// tests/api/05-edge-cases.ts import { join } from 'path'; import { api, log, runTest, uploadFile } from './utils'; import { config, endpoints } from './config'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); async function main() { log.section('EDGE CASES & VALIDATION TESTS'); // Test 1: Invalid alias format await runTest('Invalid alias format', async () => { const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Test image', assignAlias: 'invalid-no-at-sign', }), expectError: true, }); if (result.status !== 400 && result.status !== 422) { throw new Error(`Expected 400/422, got ${result.status}`); } log.detail('Status', result.status); log.detail('Error', result.data.error || result.data.message); }); // Test 2: Reserved technical alias await runTest('Reserved technical alias', async () => { const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); try { await uploadFile(fixturePath, { alias: '@last', // Reserved }); throw new Error('Should have failed with reserved alias'); } catch (error) { log.detail('Correctly rejected', '@last is reserved'); } }); // Test 3: Duplicate project alias await runTest('Duplicate project alias', async () => { const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); // First upload await uploadFile(fixturePath, { alias: '@duplicate-test', }); // Try duplicate const result = await api(endpoints.images + '/upload', { method: 'POST', body: (() => { const formData = new FormData(); formData.append('alias', '@duplicate-test'); return formData; })(), expectError: true, }); if (result.status !== 409) { throw new Error(`Expected 409 Conflict, got ${result.status}`); } log.detail('Status', '409 Conflict'); log.detail('Message', result.data.message); }); // Test 4: Missing prompt await runTest('Missing required prompt', async () => { const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ aspectRatio: '16:9', // No prompt }), expectError: true, }); if (result.status !== 400 && result.status !== 422) { throw new Error(`Expected 400/422, got ${result.status}`); } log.detail('Status', result.status); log.detail('Validation error', 'prompt is required'); }); // Test 5: Invalid aspect ratio format await runTest('Invalid aspect ratio format', async () => { const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Test image', aspectRatio: 'invalid', }), expectError: true, }); if (result.status !== 400 && result.status !== 422) { throw new Error(`Expected 400/422, got ${result.status}`); } log.detail('Status', result.status); log.detail('Error', 'Invalid aspect ratio format'); }); // Test 6: Non-existent reference image await runTest('Non-existent reference image', async () => { const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Test with invalid reference', referenceImages: ['@non-existent-alias'], }), expectError: true, }); if (result.status !== 404) { throw new Error(`Expected 404, got ${result.status}`); } log.detail('Status', '404 Not Found'); log.detail('Error', 'Reference image not found'); }); // Test 7: Invalid flow ID await runTest('Invalid flow ID', async () => { const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Test image', flowId: '00000000-0000-0000-0000-000000000000', }), expectError: true, }); if (result.status !== 404) { throw new Error(`Expected 404, got ${result.status}`); } log.detail('Status', '404 Not Found'); log.detail('Error', 'Flow not found'); }); // Test 8: assignFlowAlias without flowId await runTest('Flow alias without flow ID', async () => { const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Test image', assignFlowAlias: '@test', // No flowId provided }), expectError: true, }); if (result.status !== 400 && result.status !== 422) { throw new Error(`Expected 400/422, got ${result.status}`); } log.detail('Status', result.status); log.detail('Error', 'assignFlowAlias requires flowId'); }); // Test 9: Empty prompt await runTest('Empty prompt', async () => { const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: '', }), expectError: true, }); if (result.status !== 400 && result.status !== 422) { throw new Error(`Expected 400/422, got ${result.status}`); } log.detail('Status', result.status); log.detail('Error', 'Prompt cannot be empty'); }); // Test 10: Extremely long prompt (over 2000 chars) await runTest('Prompt too long', async () => { const longPrompt = 'A'.repeat(2001); const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: longPrompt, }), expectError: true, }); if (result.status !== 400 && result.status !== 422) { throw new Error(`Expected 400/422, got ${result.status}`); } log.detail('Status', result.status); log.detail('Error', 'Prompt exceeds max length'); }); // Test 11: Dimensions out of range await runTest('Invalid dimensions', async () => { const result = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Test image', width: 10000, // Over max height: 10000, }), expectError: true, }); if (result.status !== 400 && result.status !== 422) { throw new Error(`Expected 400/422, got ${result.status}`); } log.detail('Status', result.status); log.detail('Error', 'Dimensions exceed max 8192'); }); // Test 12: Invalid image ID await runTest('Non-existent image ID', async () => { const result = await api(`${endpoints.images}/00000000-0000-0000-0000-000000000000`, { expectError: true, }); if (result.status !== 404) { throw new Error(`Expected 404, got ${result.status}`); } log.detail('Status', '404 Not Found'); }); // Test 13: Update non-existent image await runTest('Update non-existent image', async () => { const result = await api(`${endpoints.images}/00000000-0000-0000-0000-000000000000`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ description: 'Updated', }), expectError: true, }); if (result.status !== 404) { throw new Error(`Expected 404, got ${result.status}`); } log.detail('Status', '404 Not Found'); }); // Test 14: Delete non-existent flow await runTest('Delete non-existent flow', async () => { const result = await api(`${endpoints.flows}/00000000-0000-0000-0000-000000000000`, { method: 'DELETE', expectError: true, }); if (result.status !== 404) { throw new Error(`Expected 404, got ${result.status}`); } log.detail('Status', '404 Not Found'); }); // Test 15: Invalid pagination params await runTest('Invalid pagination params', async () => { const result = await api(`${endpoints.images}?limit=1000`, { expectError: true, }); if (result.status !== 400 && result.status !== 422) { throw new Error(`Expected 400/422, got ${result.status}`); } log.detail('Status', result.status); log.detail('Error', 'Limit exceeds max 100'); }); // Test 16: Missing API key await runTest('Missing API key', async () => { const url = `${config.baseURL}${endpoints.images}`; const response = await fetch(url); // No API key header if (response.status !== 401) { throw new Error(`Expected 401, got ${response.status}`); } log.detail('Status', '401 Unauthorized'); }); // Test 17: Invalid API key await runTest('Invalid API key', async () => { const url = `${config.baseURL}${endpoints.images}`; const response = await fetch(url, { headers: { 'X-API-Key': 'invalid_key_123', }, }); if (response.status !== 401) { throw new Error(`Expected 401, got ${response.status}`); } log.detail('Status', '401 Unauthorized'); }); // Test 18: Retry non-failed generation await runTest('Retry non-failed generation', async () => { // Create a successful generation first const genResult = await api(endpoints.generations, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: 'Test for retry', aspectRatio: '1:1', }), }); // Try to retry it (should fail) const retryResult = await api(`${endpoints.generations}/${genResult.data.generation.id}/retry`, { method: 'POST', expectError: true, }); if (retryResult.status !== 422) { throw new Error(`Expected 422, got ${retryResult.status}`); } log.detail('Status', '422 Unprocessable Entity'); log.detail('Error', 'Can only retry failed generations'); }); // Test 19: Resolve alias without context await runTest('Resolve flow alias without flow context', async () => { // Try to resolve a flow-only alias without flowId const result = await api(`${endpoints.images}/resolve/@temp-logo`, { expectError: true, }); if (result.status !== 404) { throw new Error(`Expected 404, got ${result.status}`); } log.detail('Status', '404 Not Found'); log.detail('Error', 'Alias requires flow context'); }); // Test 20: Live endpoint without prompt await runTest('Live endpoint without prompt', async () => { const result = await api(`${endpoints.live}?aspectRatio=16:9`, { expectError: true, }); if (result.status !== 400) { throw new Error(`Expected 400, got ${result.status}`); } log.detail('Status', '400 Bad Request'); log.detail('Error', 'Prompt is required'); }); log.section('EDGE CASES TESTS COMPLETED'); } main().catch(console.error);