diff --git a/package.json b/package.json index 52621ee..e33e9bd 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "test:ui": "vitest --ui", "test:run": "vitest run", "test:coverage": "vitest run --coverage", + "test:api": "tsx tests/api/run-all.ts", "format": "prettier --write \"apps/**/*.{ts,tsx,js,jsx,json,css,md}\" \"packages/**/*.{ts,tsx,js,jsx,json,css,md}\" \"*.{ts,tsx,js,jsx,json,css,md}\" --ignore-unknown", "format:check": "prettier --check \"apps/**/*.{ts,tsx,js,jsx,json,css,md}\" \"packages/**/*.{ts,tsx,js,jsx,json,css,md}\" \"*.{ts,tsx,js,jsx,json,css,md}\" --ignore-unknown", "clean": "pnpm -r clean && rm -rf node_modules" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 409d721..c68a371 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@types/node': + specifier: ^20.11.0 + version: 20.19.17 '@vitest/ui': specifier: ^3.2.4 version: 3.2.4(vitest@3.2.4) @@ -23,12 +26,15 @@ importers: prettier: specifier: ^3.6.2 version: 3.6.2 + tsx: + specifier: ^4.7.0 + version: 4.20.5 typescript: specifier: ^5.9.2 version: 5.9.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.5.2)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) + version: 3.2.4(@types/node@20.19.17)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) apps/admin: dependencies: @@ -6891,13 +6897,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.9(@types/node@24.5.2)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(vite@7.1.9(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.9(@types/node@24.5.2)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) + vite: 7.1.9(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -6928,7 +6934,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@24.5.2)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) + vitest: 3.2.4(@types/node@20.19.17)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) '@vitest/utils@3.2.4': dependencies: @@ -7766,7 +7772,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -7800,7 +7806,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -7815,7 +7821,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.44.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -10464,13 +10470,13 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@3.2.4(@types/node@24.5.2)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1): + vite-node@3.2.4(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@5.5.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.9(@types/node@24.5.2)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) + vite: 7.1.9(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -10485,7 +10491,7 @@ snapshots: - tsx - yaml - vite@7.1.9(@types/node@24.5.2)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1): + vite@7.1.9(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1): dependencies: esbuild: 0.25.10 fdir: 6.5.0(picomatch@4.0.3) @@ -10494,18 +10500,18 @@ snapshots: rollup: 4.52.4 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.5.2 + '@types/node': 20.19.17 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.1 tsx: 4.20.5 yaml: 2.8.1 - vitest@3.2.4(@types/node@24.5.2)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1): + vitest@3.2.4(@types/node@20.19.17)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.9(@types/node@24.5.2)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.1.9(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -10523,11 +10529,11 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.9(@types/node@24.5.2)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@24.5.2)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) + vite: 7.1.9(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@20.19.17)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.5)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.5.2 + '@types/node': 20.19.17 '@vitest/ui': 3.2.4(vitest@3.2.4) transitivePeerDependencies: - jiti diff --git a/tests/api/test-01-basic.ts b/tests/api/01-basic.ts similarity index 97% rename from tests/api/test-01-basic.ts rename to tests/api/01-basic.ts index 3d020b7..6023a09 100644 --- a/tests/api/test-01-basic.ts +++ b/tests/api/01-basic.ts @@ -4,13 +4,19 @@ import { join } from 'path'; import { api, log, runTest, saveImage, uploadFile, waitForGeneration, testContext } 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('BASIC TESTS'); // Test 1: Upload image await runTest('Upload image', async () => { const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); - + const response = await uploadFile(fixturePath, { alias: '@test-logo', description: 'Test logo image', @@ -29,7 +35,7 @@ async function main() { // Test 2: List images await runTest('List images', async () => { const result = await api(endpoints.images); - + if (!result.data.images || !Array.isArray(result.data.images)) { throw new Error('No images array returned'); } @@ -41,7 +47,7 @@ async function main() { // Test 3: Get image by ID await runTest('Get image by ID', async () => { const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`); - + if (!result.data.image) { throw new Error('Image not found'); } @@ -74,7 +80,7 @@ async function main() { // Wait for completion log.info('Waiting for generation to complete...'); const generation = await waitForGeneration(testContext.generationId); - + if (generation.status !== 'success') { throw new Error(`Generation failed: ${generation.errorMessage}`); } @@ -85,12 +91,12 @@ async function main() { // Save generated image if (generation.outputImageId) { const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`); - + // Download image const imageUrl = imageResult.data.image.storageUrl; const imageResponse = await fetch(imageUrl); const imageBuffer = await imageResponse.arrayBuffer(); - + await saveImage(imageBuffer, 'simple-generation.png'); testContext.imageId = generation.outputImageId; } @@ -118,7 +124,7 @@ async function main() { // Wait for completion log.info('Waiting for generation to complete...'); const generation = await waitForGeneration(result.data.generation.id); - + if (generation.status !== 'success') { throw new Error(`Generation failed: ${generation.errorMessage}`); } @@ -129,7 +135,7 @@ async function main() { const imageUrl = imageResult.data.image.storageUrl; const imageResponse = await fetch(imageUrl); const imageBuffer = await imageResponse.arrayBuffer(); - + await saveImage(imageBuffer, 'with-reference.png'); } }); @@ -137,7 +143,7 @@ async function main() { // Test 6: List generations await runTest('List generations', async () => { const result = await api(endpoints.generations); - + if (!result.data.generations || !Array.isArray(result.data.generations)) { throw new Error('No generations array returned'); } @@ -150,7 +156,7 @@ async function main() { // Test 7: Get generation by ID await runTest('Get generation details', async () => { const result = await api(`${endpoints.generations}/${testContext.generationId}`); - + if (!result.data.generation) { throw new Error('Generation not found'); } diff --git a/tests/api/test-02-flows.ts b/tests/api/02-flows.ts similarity index 97% rename from tests/api/test-02-flows.ts rename to tests/api/02-flows.ts index 9d7d2f4..c091bc9 100644 --- a/tests/api/test-02-flows.ts +++ b/tests/api/02-flows.ts @@ -3,6 +3,12 @@ import { api, log, runTest, saveImage, waitForGeneration, testContext } from './utils'; import { 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('FLOW TESTS'); @@ -28,7 +34,7 @@ async function main() { // Test 2: List flows await runTest('List flows', async () => { const result = await api(endpoints.flows); - + if (!result.data.flows || !Array.isArray(result.data.flows)) { throw new Error('No flows array returned'); } @@ -59,7 +65,7 @@ async function main() { // Wait for completion log.info('Waiting for generation to complete...'); const generation = await waitForGeneration(result.data.generation.id); - + if (generation.status !== 'success') { throw new Error(`Generation failed: ${generation.errorMessage}`); } @@ -72,7 +78,7 @@ async function main() { const imageUrl = imageResult.data.image.storageUrl; const imageResponse = await fetch(imageUrl); const imageBuffer = await imageResponse.arrayBuffer(); - + await saveImage(imageBuffer, 'flow-gen-1.png'); } }); @@ -100,7 +106,7 @@ async function main() { // Wait for completion log.info('Waiting for generation to complete...'); const generation = await waitForGeneration(result.data.generation.id); - + if (generation.status !== 'success') { throw new Error(`Generation failed: ${generation.errorMessage}`); } @@ -111,7 +117,7 @@ async function main() { const imageUrl = imageResult.data.image.storageUrl; const imageResponse = await fetch(imageUrl); const imageBuffer = await imageResponse.arrayBuffer(); - + await saveImage(imageBuffer, 'flow-gen-2-with-last.png'); } }); @@ -119,7 +125,7 @@ async function main() { // Test 5: Get flow details await runTest('Get flow details', async () => { const result = await api(`${endpoints.flows}/${testContext.flowId}`); - + if (!result.data.flow) { throw new Error('Flow not found'); } @@ -135,7 +141,7 @@ async function main() { // First, get the latest generation's image ID const flowResult = await api(`${endpoints.flows}/${testContext.flowId}`); const lastGeneration = flowResult.data.generations[flowResult.data.generations.length - 1]; - + if (!lastGeneration.outputImageId) { throw new Error('No output image for alias assignment'); } @@ -181,7 +187,7 @@ async function main() { // Wait for completion log.info('Waiting for generation to complete...'); const generation = await waitForGeneration(result.data.generation.id); - + if (generation.status !== 'success') { throw new Error(`Generation failed: ${generation.errorMessage}`); } @@ -192,7 +198,7 @@ async function main() { const imageUrl = imageResult.data.image.storageUrl; const imageResponse = await fetch(imageUrl); const imageBuffer = await imageResponse.arrayBuffer(); - + await saveImage(imageBuffer, 'flow-gen-with-hero.png'); } }); @@ -206,7 +212,7 @@ async function main() { // Verify it's deleted const result = await api(`${endpoints.flows}/${testContext.flowId}`); const hasFeatureAlias = '@featured' in result.data.flow.aliases; - + if (hasFeatureAlias) { throw new Error('Alias was not deleted'); } diff --git a/tests/api/test-03-aliases.ts b/tests/api/03-aliases.ts similarity index 98% rename from tests/api/test-03-aliases.ts rename to tests/api/03-aliases.ts index 2eca4c4..81a4030 100644 --- a/tests/api/test-03-aliases.ts +++ b/tests/api/03-aliases.ts @@ -4,13 +4,19 @@ import { join } from 'path'; import { api, log, runTest, saveImage, uploadFile, waitForGeneration, testContext } 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('ALIAS TESTS'); // Test 1: Upload with project-scoped alias await runTest('Upload with project alias', async () => { const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); - + const response = await uploadFile(fixturePath, { alias: '@brand-logo', description: 'Brand logo for project-wide use', @@ -28,7 +34,7 @@ async function main() { // Test 2: Upload with flow-scoped alias await runTest('Upload with flow alias', async () => { const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); - + const response = await uploadFile(fixturePath, { flowAlias: '@temp-logo', flowId: testContext.flowId, @@ -47,7 +53,7 @@ async function main() { // Test 3: Upload with BOTH project and flow aliases await runTest('Upload with dual aliases', async () => { const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); - + const response = await uploadFile(fixturePath, { alias: '@global-asset', flowAlias: '@flow-asset', @@ -71,7 +77,7 @@ async function main() { // Test 4: Resolve project-scoped alias await runTest('Resolve project alias', async () => { const result = await api(`${endpoints.images}/resolve/@brand-logo`); - + if (!result.data.image) { throw new Error('Image not resolved'); } @@ -88,7 +94,7 @@ async function main() { // Test 5: Resolve flow-scoped alias await runTest('Resolve flow alias', async () => { const result = await api(`${endpoints.images}/resolve/@temp-logo?flowId=${testContext.flowId}`); - + if (!result.data.image) { throw new Error('Image not resolved'); } @@ -105,7 +111,7 @@ async function main() { // Test 6: Resolve @last technical alias await runTest('Resolve @last technical alias', async () => { const result = await api(`${endpoints.images}/resolve/@last?flowId=${testContext.flowId}`); - + if (!result.data.image) { throw new Error('Image not resolved'); } @@ -122,7 +128,7 @@ async function main() { // Test 7: Resolve @first technical alias await runTest('Resolve @first technical alias', async () => { const result = await api(`${endpoints.images}/resolve/@first?flowId=${testContext.flowId}`); - + if (!result.data.image) { throw new Error('Image not resolved'); } @@ -138,7 +144,7 @@ async function main() { // Test 8: Resolve @upload technical alias await runTest('Resolve @upload technical alias', async () => { const result = await api(`${endpoints.images}/resolve/@upload?flowId=${testContext.flowId}`); - + if (!result.data.image) { throw new Error('Image not resolved'); } @@ -166,14 +172,14 @@ async function main() { log.info('Waiting for generation to complete...'); const generation = await waitForGeneration(result.data.generation.id); - + if (generation.status !== 'success') { throw new Error(`Generation failed: ${generation.errorMessage}`); } // Check if alias was assigned const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`); - + if (imageResult.data.image.alias !== '@generated-logo') { throw new Error('Alias not assigned to generated image'); } @@ -203,14 +209,14 @@ async function main() { log.info('Waiting for generation to complete...'); const generation = await waitForGeneration(result.data.generation.id); - + if (generation.status !== 'success') { throw new Error(`Generation failed: ${generation.errorMessage}`); } // Check if flow alias was assigned const flowResult = await api(`${endpoints.flows}/${testContext.flowId}`); - + if (!flowResult.data.flow.aliases['@pattern']) { throw new Error('Flow alias not assigned'); } diff --git a/tests/api/test-04-live.ts b/tests/api/04-live.ts similarity index 97% rename from tests/api/test-04-live.ts rename to tests/api/04-live.ts index 6c6b3a2..cd433d4 100644 --- a/tests/api/test-04-live.ts +++ b/tests/api/04-live.ts @@ -3,6 +3,12 @@ import { api, log, runTest, saveImage, wait } from './utils'; import { 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('LIVE ENDPOINT TESTS'); @@ -145,7 +151,7 @@ async function main() { // Test 7: Verify cache works as URL await runTest('Live as direct URL (browser-like)', async () => { const url = `${endpoints.live}?prompt=${encodeURIComponent('A beautiful sunset')}&aspectRatio=16:9`; - + log.info('Testing URL format:'); log.detail('URL', url); @@ -178,7 +184,7 @@ async function main() { log.detail('Cache-Control', cacheControl || 'NOT SET'); log.detail('Content-Type', contentType || 'NOT SET'); - + if (!cacheControl || !cacheControl.includes('public')) { log.warning('Cache-Control should be set for CDN optimization'); } @@ -203,11 +209,11 @@ async function main() { // Rapid subsequent calls (all HITs) log.info('Making 5 rapid cache HIT calls...'); const durations: number[] = []; - + for (let i = 0; i < 5; i++) { const result = await api(`${endpoints.live}?${params}`); durations.push(result.duration); - + const cacheStatus = result.headers.get('X-Cache-Status'); if (cacheStatus !== 'HIT') { throw new Error(`Call ${i + 1} expected HIT, got ${cacheStatus}`); diff --git a/tests/api/test-05-edge-cases.ts b/tests/api/05-edge-cases.ts similarity index 98% rename from tests/api/test-05-edge-cases.ts rename to tests/api/05-edge-cases.ts index ec25a19..319dd9a 100644 --- a/tests/api/test-05-edge-cases.ts +++ b/tests/api/05-edge-cases.ts @@ -4,6 +4,12 @@ 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'); @@ -30,7 +36,7 @@ async function main() { // 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 @@ -44,7 +50,7 @@ async function main() { // 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', @@ -191,7 +197,7 @@ async function main() { // 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' }, @@ -292,9 +298,9 @@ async function main() { // 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}`); } @@ -305,13 +311,13 @@ async function main() { // 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}`); } diff --git a/tests/api/test-config.ts b/tests/api/config.ts similarity index 100% rename from tests/api/test-config.ts rename to tests/api/config.ts diff --git a/tests/api/fixture/test-image.png b/tests/api/fixture/image.png similarity index 100% rename from tests/api/fixture/test-image.png rename to tests/api/fixture/image.png diff --git a/tests/api/test-run-all.ts b/tests/api/run-all.ts similarity index 92% rename from tests/api/test-run-all.ts rename to tests/api/run-all.ts index 88724e5..9d63b04 100644 --- a/tests/api/test-run-all.ts +++ b/tests/api/run-all.ts @@ -4,6 +4,12 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import { log } from './utils'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + const execAsync = promisify(exec); const testFiles = [ @@ -16,24 +22,24 @@ const testFiles = [ async function runTest(file: string): Promise<{ success: boolean; duration: number }> { const startTime = Date.now(); - + try { log.section(`Running ${file}`); - + await execAsync(`tsx ${file}`, { cwd: __dirname, env: process.env, }); - + const duration = Date.now() - startTime; log.success(`${file} completed (${duration}ms)`); - + return { success: true, duration }; } catch (error) { const duration = Date.now() - startTime; log.error(`${file} failed (${duration}ms)`); console.error(error); - + return { success: false, duration }; } } @@ -42,42 +48,42 @@ async function main() { console.log('\n'); log.section('🚀 BANATIE API TEST SUITE'); console.log('\n'); - + const results: Array<{ file: string; success: boolean; duration: number }> = []; const startTime = Date.now(); - + for (const file of testFiles) { const result = await runTest(file); results.push({ file, ...result }); console.log('\n'); } - + const totalDuration = Date.now() - startTime; - + // Summary log.section('📊 TEST SUMMARY'); console.log('\n'); - + const passed = results.filter(r => r.success).length; const failed = results.filter(r => !r.success).length; - + results.forEach(result => { const icon = result.success ? '✓' : '✗'; const color = result.success ? '\x1b[32m' : '\x1b[31m'; console.log(`${color}${icon}\x1b[0m ${result.file} (${result.duration}ms)`); }); - + console.log('\n'); log.info(`Total: ${results.length} test suites`); log.success(`Passed: ${passed}`); - + if (failed > 0) { log.error(`Failed: ${failed}`); } - + log.info(`Duration: ${(totalDuration / 1000).toFixed(2)}s`); console.log('\n'); - + if (failed > 0) { process.exit(1); } diff --git a/tests/api/test-utils.ts b/tests/api/utils.ts similarity index 96% rename from tests/api/test-utils.ts rename to tests/api/utils.ts index 166d0f5..30dbf29 100644 --- a/tests/api/test-utils.ts +++ b/tests/api/utils.ts @@ -4,6 +4,12 @@ import { writeFile, mkdir } from 'fs/promises'; import { join } from 'path'; import { config } from './config'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + // Colors for console output const colors = { reset: '\x1b[0m', @@ -31,7 +37,7 @@ export const log = { // API fetch wrapper export async function api( endpoint: string, - options: RequestInit & { + options: RequestInit & { expectError?: boolean; timeout?: number; } = {} @@ -63,7 +69,7 @@ export async function api( let data: any; const contentType = response.headers.get('content-type'); - + if (contentType?.includes('application/json')) { data = await response.json(); } else if (contentType?.includes('image/')) { @@ -106,7 +112,7 @@ export async function saveImage( filename: string ): Promise { const resultsPath = join(__dirname, config.resultsDir); - + try { await mkdir(resultsPath, { recursive: true }); } catch (err) { @@ -118,7 +124,7 @@ export async function saveImage( const filepath = join(resultsPath, fullFilename); await writeFile(filepath, Buffer.from(buffer)); - + if (config.saveImages) { log.info(`Saved image: ${fullFilename}`); } @@ -132,7 +138,7 @@ export async function uploadFile( fields: Record = {} ): Promise { const formData = new FormData(); - + // Read file const fs = await import('fs/promises'); const fileBuffer = await fs.readFile(filepath);