fix: tests

This commit is contained in:
Oleg Proskurin 2025-11-10 00:09:40 +07:00
parent 874cc4fcba
commit 4e7eb7b5b5
11 changed files with 128 additions and 79 deletions

View File

@ -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"

View File

@ -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

View File

@ -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');
}

View File

@ -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');
}

View File

@ -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');
}

View File

@ -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}`);

View File

@ -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}`);
}

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -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);
}

View File

@ -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<T = any>(
endpoint: string,
options: RequestInit & {
options: RequestInit & {
expectError?: boolean;
timeout?: number;
} = {}
@ -63,7 +69,7 @@ export async function api<T = any>(
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<string> {
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<string, string> = {}
): Promise<any> {
const formData = new FormData();
// Read file
const fs = await import('fs/promises');
const fileBuffer = await fs.readFile(filepath);