387 lines
11 KiB
TypeScript
387 lines
11 KiB
TypeScript
// 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);
|