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:ui": "vitest --ui",
"test:run": "vitest run", "test:run": "vitest run",
"test:coverage": "vitest run --coverage", "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": "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", "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" "clean": "pnpm -r clean && rm -rf node_modules"

View File

@ -8,6 +8,9 @@ importers:
.: .:
devDependencies: devDependencies:
'@types/node':
specifier: ^20.11.0
version: 20.19.17
'@vitest/ui': '@vitest/ui':
specifier: ^3.2.4 specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4) version: 3.2.4(vitest@3.2.4)
@ -23,12 +26,15 @@ importers:
prettier: prettier:
specifier: ^3.6.2 specifier: ^3.6.2
version: 3.6.2 version: 3.6.2
tsx:
specifier: ^4.7.0
version: 4.20.5
typescript: typescript:
specifier: ^5.9.2 specifier: ^5.9.2
version: 5.9.2 version: 5.9.2
vitest: vitest:
specifier: ^3.2.4 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: apps/admin:
dependencies: dependencies:
@ -6891,13 +6897,13 @@ snapshots:
chai: 5.3.3 chai: 5.3.3
tinyrainbow: 2.0.0 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: dependencies:
'@vitest/spy': 3.2.4 '@vitest/spy': 3.2.4
estree-walker: 3.0.3 estree-walker: 3.0.3
magic-string: 0.30.19 magic-string: 0.30.19
optionalDependencies: 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': '@vitest/pretty-format@3.2.4':
dependencies: dependencies:
@ -6928,7 +6934,7 @@ snapshots:
sirv: 3.0.2 sirv: 3.0.2
tinyglobby: 0.2.15 tinyglobby: 0.2.15
tinyrainbow: 2.0.0 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': '@vitest/utils@3.2.4':
dependencies: dependencies:
@ -7766,7 +7772,7 @@ snapshots:
eslint: 8.57.1 eslint: 8.57.1
eslint-import-resolver-node: 0.3.9 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-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-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.5(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) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1)
@ -7800,7 +7806,7 @@ snapshots:
tinyglobby: 0.2.15 tinyglobby: 0.2.15
unrs-resolver: 1.11.1 unrs-resolver: 1.11.1
optionalDependencies: 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: transitivePeerDependencies:
- supports-color - supports-color
@ -7815,7 +7821,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - 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: dependencies:
'@rtsao/scc': 1.1.0 '@rtsao/scc': 1.1.0
array-includes: 3.1.9 array-includes: 3.1.9
@ -10464,13 +10470,13 @@ snapshots:
d3-time: 3.1.0 d3-time: 3.1.0
d3-timer: 3.0.1 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: dependencies:
cac: 6.7.14 cac: 6.7.14
debug: 4.4.3(supports-color@5.5.0) debug: 4.4.3(supports-color@5.5.0)
es-module-lexer: 1.7.0 es-module-lexer: 1.7.0
pathe: 2.0.3 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: transitivePeerDependencies:
- '@types/node' - '@types/node'
- jiti - jiti
@ -10485,7 +10491,7 @@ snapshots:
- tsx - tsx
- yaml - 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: dependencies:
esbuild: 0.25.10 esbuild: 0.25.10
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
@ -10494,18 +10500,18 @@ snapshots:
rollup: 4.52.4 rollup: 4.52.4
tinyglobby: 0.2.15 tinyglobby: 0.2.15
optionalDependencies: optionalDependencies:
'@types/node': 24.5.2 '@types/node': 20.19.17
fsevents: 2.3.3 fsevents: 2.3.3
jiti: 2.6.1 jiti: 2.6.1
lightningcss: 1.30.1 lightningcss: 1.30.1
tsx: 4.20.5 tsx: 4.20.5
yaml: 2.8.1 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: dependencies:
'@types/chai': 5.2.2 '@types/chai': 5.2.2
'@vitest/expect': 3.2.4 '@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/pretty-format': 3.2.4
'@vitest/runner': 3.2.4 '@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4 '@vitest/snapshot': 3.2.4
@ -10523,11 +10529,11 @@ snapshots:
tinyglobby: 0.2.15 tinyglobby: 0.2.15
tinypool: 1.1.1 tinypool: 1.1.1
tinyrainbow: 2.0.0 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: 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@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)
why-is-node-running: 2.3.0 why-is-node-running: 2.3.0
optionalDependencies: optionalDependencies:
'@types/node': 24.5.2 '@types/node': 20.19.17
'@vitest/ui': 3.2.4(vitest@3.2.4) '@vitest/ui': 3.2.4(vitest@3.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- jiti - jiti

View File

@ -4,13 +4,19 @@ import { join } from 'path';
import { api, log, runTest, saveImage, uploadFile, waitForGeneration, testContext } from './utils'; import { api, log, runTest, saveImage, uploadFile, waitForGeneration, testContext } from './utils';
import { config, endpoints } from './config'; 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() { async function main() {
log.section('BASIC TESTS'); log.section('BASIC TESTS');
// Test 1: Upload image // Test 1: Upload image
await runTest('Upload image', async () => { await runTest('Upload image', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
const response = await uploadFile(fixturePath, { const response = await uploadFile(fixturePath, {
alias: '@test-logo', alias: '@test-logo',
description: 'Test logo image', description: 'Test logo image',
@ -29,7 +35,7 @@ async function main() {
// Test 2: List images // Test 2: List images
await runTest('List images', async () => { await runTest('List images', async () => {
const result = await api(endpoints.images); const result = await api(endpoints.images);
if (!result.data.images || !Array.isArray(result.data.images)) { if (!result.data.images || !Array.isArray(result.data.images)) {
throw new Error('No images array returned'); throw new Error('No images array returned');
} }
@ -41,7 +47,7 @@ async function main() {
// Test 3: Get image by ID // Test 3: Get image by ID
await runTest('Get image by ID', async () => { await runTest('Get image by ID', async () => {
const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`); const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`);
if (!result.data.image) { if (!result.data.image) {
throw new Error('Image not found'); throw new Error('Image not found');
} }
@ -74,7 +80,7 @@ async function main() {
// Wait for completion // Wait for completion
log.info('Waiting for generation to complete...'); log.info('Waiting for generation to complete...');
const generation = await waitForGeneration(testContext.generationId); const generation = await waitForGeneration(testContext.generationId);
if (generation.status !== 'success') { if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`); throw new Error(`Generation failed: ${generation.errorMessage}`);
} }
@ -85,12 +91,12 @@ async function main() {
// Save generated image // Save generated image
if (generation.outputImageId) { if (generation.outputImageId) {
const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`); const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`);
// Download image // Download image
const imageUrl = imageResult.data.image.storageUrl; const imageUrl = imageResult.data.image.storageUrl;
const imageResponse = await fetch(imageUrl); const imageResponse = await fetch(imageUrl);
const imageBuffer = await imageResponse.arrayBuffer(); const imageBuffer = await imageResponse.arrayBuffer();
await saveImage(imageBuffer, 'simple-generation.png'); await saveImage(imageBuffer, 'simple-generation.png');
testContext.imageId = generation.outputImageId; testContext.imageId = generation.outputImageId;
} }
@ -118,7 +124,7 @@ async function main() {
// Wait for completion // Wait for completion
log.info('Waiting for generation to complete...'); log.info('Waiting for generation to complete...');
const generation = await waitForGeneration(result.data.generation.id); const generation = await waitForGeneration(result.data.generation.id);
if (generation.status !== 'success') { if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`); throw new Error(`Generation failed: ${generation.errorMessage}`);
} }
@ -129,7 +135,7 @@ async function main() {
const imageUrl = imageResult.data.image.storageUrl; const imageUrl = imageResult.data.image.storageUrl;
const imageResponse = await fetch(imageUrl); const imageResponse = await fetch(imageUrl);
const imageBuffer = await imageResponse.arrayBuffer(); const imageBuffer = await imageResponse.arrayBuffer();
await saveImage(imageBuffer, 'with-reference.png'); await saveImage(imageBuffer, 'with-reference.png');
} }
}); });
@ -137,7 +143,7 @@ async function main() {
// Test 6: List generations // Test 6: List generations
await runTest('List generations', async () => { await runTest('List generations', async () => {
const result = await api(endpoints.generations); const result = await api(endpoints.generations);
if (!result.data.generations || !Array.isArray(result.data.generations)) { if (!result.data.generations || !Array.isArray(result.data.generations)) {
throw new Error('No generations array returned'); throw new Error('No generations array returned');
} }
@ -150,7 +156,7 @@ async function main() {
// Test 7: Get generation by ID // Test 7: Get generation by ID
await runTest('Get generation details', async () => { await runTest('Get generation details', async () => {
const result = await api(`${endpoints.generations}/${testContext.generationId}`); const result = await api(`${endpoints.generations}/${testContext.generationId}`);
if (!result.data.generation) { if (!result.data.generation) {
throw new Error('Generation not found'); throw new Error('Generation not found');
} }

View File

@ -3,6 +3,12 @@
import { api, log, runTest, saveImage, waitForGeneration, testContext } from './utils'; import { api, log, runTest, saveImage, waitForGeneration, testContext } from './utils';
import { endpoints } from './config'; 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() { async function main() {
log.section('FLOW TESTS'); log.section('FLOW TESTS');
@ -28,7 +34,7 @@ async function main() {
// Test 2: List flows // Test 2: List flows
await runTest('List flows', async () => { await runTest('List flows', async () => {
const result = await api(endpoints.flows); const result = await api(endpoints.flows);
if (!result.data.flows || !Array.isArray(result.data.flows)) { if (!result.data.flows || !Array.isArray(result.data.flows)) {
throw new Error('No flows array returned'); throw new Error('No flows array returned');
} }
@ -59,7 +65,7 @@ async function main() {
// Wait for completion // Wait for completion
log.info('Waiting for generation to complete...'); log.info('Waiting for generation to complete...');
const generation = await waitForGeneration(result.data.generation.id); const generation = await waitForGeneration(result.data.generation.id);
if (generation.status !== 'success') { if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`); throw new Error(`Generation failed: ${generation.errorMessage}`);
} }
@ -72,7 +78,7 @@ async function main() {
const imageUrl = imageResult.data.image.storageUrl; const imageUrl = imageResult.data.image.storageUrl;
const imageResponse = await fetch(imageUrl); const imageResponse = await fetch(imageUrl);
const imageBuffer = await imageResponse.arrayBuffer(); const imageBuffer = await imageResponse.arrayBuffer();
await saveImage(imageBuffer, 'flow-gen-1.png'); await saveImage(imageBuffer, 'flow-gen-1.png');
} }
}); });
@ -100,7 +106,7 @@ async function main() {
// Wait for completion // Wait for completion
log.info('Waiting for generation to complete...'); log.info('Waiting for generation to complete...');
const generation = await waitForGeneration(result.data.generation.id); const generation = await waitForGeneration(result.data.generation.id);
if (generation.status !== 'success') { if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`); throw new Error(`Generation failed: ${generation.errorMessage}`);
} }
@ -111,7 +117,7 @@ async function main() {
const imageUrl = imageResult.data.image.storageUrl; const imageUrl = imageResult.data.image.storageUrl;
const imageResponse = await fetch(imageUrl); const imageResponse = await fetch(imageUrl);
const imageBuffer = await imageResponse.arrayBuffer(); const imageBuffer = await imageResponse.arrayBuffer();
await saveImage(imageBuffer, 'flow-gen-2-with-last.png'); await saveImage(imageBuffer, 'flow-gen-2-with-last.png');
} }
}); });
@ -119,7 +125,7 @@ async function main() {
// Test 5: Get flow details // Test 5: Get flow details
await runTest('Get flow details', async () => { await runTest('Get flow details', async () => {
const result = await api(`${endpoints.flows}/${testContext.flowId}`); const result = await api(`${endpoints.flows}/${testContext.flowId}`);
if (!result.data.flow) { if (!result.data.flow) {
throw new Error('Flow not found'); throw new Error('Flow not found');
} }
@ -135,7 +141,7 @@ async function main() {
// First, get the latest generation's image ID // First, get the latest generation's image ID
const flowResult = await api(`${endpoints.flows}/${testContext.flowId}`); const flowResult = await api(`${endpoints.flows}/${testContext.flowId}`);
const lastGeneration = flowResult.data.generations[flowResult.data.generations.length - 1]; const lastGeneration = flowResult.data.generations[flowResult.data.generations.length - 1];
if (!lastGeneration.outputImageId) { if (!lastGeneration.outputImageId) {
throw new Error('No output image for alias assignment'); throw new Error('No output image for alias assignment');
} }
@ -181,7 +187,7 @@ async function main() {
// Wait for completion // Wait for completion
log.info('Waiting for generation to complete...'); log.info('Waiting for generation to complete...');
const generation = await waitForGeneration(result.data.generation.id); const generation = await waitForGeneration(result.data.generation.id);
if (generation.status !== 'success') { if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`); throw new Error(`Generation failed: ${generation.errorMessage}`);
} }
@ -192,7 +198,7 @@ async function main() {
const imageUrl = imageResult.data.image.storageUrl; const imageUrl = imageResult.data.image.storageUrl;
const imageResponse = await fetch(imageUrl); const imageResponse = await fetch(imageUrl);
const imageBuffer = await imageResponse.arrayBuffer(); const imageBuffer = await imageResponse.arrayBuffer();
await saveImage(imageBuffer, 'flow-gen-with-hero.png'); await saveImage(imageBuffer, 'flow-gen-with-hero.png');
} }
}); });
@ -206,7 +212,7 @@ async function main() {
// Verify it's deleted // Verify it's deleted
const result = await api(`${endpoints.flows}/${testContext.flowId}`); const result = await api(`${endpoints.flows}/${testContext.flowId}`);
const hasFeatureAlias = '@featured' in result.data.flow.aliases; const hasFeatureAlias = '@featured' in result.data.flow.aliases;
if (hasFeatureAlias) { if (hasFeatureAlias) {
throw new Error('Alias was not deleted'); 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 { api, log, runTest, saveImage, uploadFile, waitForGeneration, testContext } from './utils';
import { config, endpoints } from './config'; 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() { async function main() {
log.section('ALIAS TESTS'); log.section('ALIAS TESTS');
// Test 1: Upload with project-scoped alias // Test 1: Upload with project-scoped alias
await runTest('Upload with project alias', async () => { await runTest('Upload with project alias', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
const response = await uploadFile(fixturePath, { const response = await uploadFile(fixturePath, {
alias: '@brand-logo', alias: '@brand-logo',
description: 'Brand logo for project-wide use', description: 'Brand logo for project-wide use',
@ -28,7 +34,7 @@ async function main() {
// Test 2: Upload with flow-scoped alias // Test 2: Upload with flow-scoped alias
await runTest('Upload with flow alias', async () => { await runTest('Upload with flow alias', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
const response = await uploadFile(fixturePath, { const response = await uploadFile(fixturePath, {
flowAlias: '@temp-logo', flowAlias: '@temp-logo',
flowId: testContext.flowId, flowId: testContext.flowId,
@ -47,7 +53,7 @@ async function main() {
// Test 3: Upload with BOTH project and flow aliases // Test 3: Upload with BOTH project and flow aliases
await runTest('Upload with dual aliases', async () => { await runTest('Upload with dual aliases', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
const response = await uploadFile(fixturePath, { const response = await uploadFile(fixturePath, {
alias: '@global-asset', alias: '@global-asset',
flowAlias: '@flow-asset', flowAlias: '@flow-asset',
@ -71,7 +77,7 @@ async function main() {
// Test 4: Resolve project-scoped alias // Test 4: Resolve project-scoped alias
await runTest('Resolve project alias', async () => { await runTest('Resolve project alias', async () => {
const result = await api(`${endpoints.images}/resolve/@brand-logo`); const result = await api(`${endpoints.images}/resolve/@brand-logo`);
if (!result.data.image) { if (!result.data.image) {
throw new Error('Image not resolved'); throw new Error('Image not resolved');
} }
@ -88,7 +94,7 @@ async function main() {
// Test 5: Resolve flow-scoped alias // Test 5: Resolve flow-scoped alias
await runTest('Resolve flow alias', async () => { await runTest('Resolve flow alias', async () => {
const result = await api(`${endpoints.images}/resolve/@temp-logo?flowId=${testContext.flowId}`); const result = await api(`${endpoints.images}/resolve/@temp-logo?flowId=${testContext.flowId}`);
if (!result.data.image) { if (!result.data.image) {
throw new Error('Image not resolved'); throw new Error('Image not resolved');
} }
@ -105,7 +111,7 @@ async function main() {
// Test 6: Resolve @last technical alias // Test 6: Resolve @last technical alias
await runTest('Resolve @last technical alias', async () => { await runTest('Resolve @last technical alias', async () => {
const result = await api(`${endpoints.images}/resolve/@last?flowId=${testContext.flowId}`); const result = await api(`${endpoints.images}/resolve/@last?flowId=${testContext.flowId}`);
if (!result.data.image) { if (!result.data.image) {
throw new Error('Image not resolved'); throw new Error('Image not resolved');
} }
@ -122,7 +128,7 @@ async function main() {
// Test 7: Resolve @first technical alias // Test 7: Resolve @first technical alias
await runTest('Resolve @first technical alias', async () => { await runTest('Resolve @first technical alias', async () => {
const result = await api(`${endpoints.images}/resolve/@first?flowId=${testContext.flowId}`); const result = await api(`${endpoints.images}/resolve/@first?flowId=${testContext.flowId}`);
if (!result.data.image) { if (!result.data.image) {
throw new Error('Image not resolved'); throw new Error('Image not resolved');
} }
@ -138,7 +144,7 @@ async function main() {
// Test 8: Resolve @upload technical alias // Test 8: Resolve @upload technical alias
await runTest('Resolve @upload technical alias', async () => { await runTest('Resolve @upload technical alias', async () => {
const result = await api(`${endpoints.images}/resolve/@upload?flowId=${testContext.flowId}`); const result = await api(`${endpoints.images}/resolve/@upload?flowId=${testContext.flowId}`);
if (!result.data.image) { if (!result.data.image) {
throw new Error('Image not resolved'); throw new Error('Image not resolved');
} }
@ -166,14 +172,14 @@ async function main() {
log.info('Waiting for generation to complete...'); log.info('Waiting for generation to complete...');
const generation = await waitForGeneration(result.data.generation.id); const generation = await waitForGeneration(result.data.generation.id);
if (generation.status !== 'success') { if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`); throw new Error(`Generation failed: ${generation.errorMessage}`);
} }
// Check if alias was assigned // Check if alias was assigned
const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`); const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`);
if (imageResult.data.image.alias !== '@generated-logo') { if (imageResult.data.image.alias !== '@generated-logo') {
throw new Error('Alias not assigned to generated image'); throw new Error('Alias not assigned to generated image');
} }
@ -203,14 +209,14 @@ async function main() {
log.info('Waiting for generation to complete...'); log.info('Waiting for generation to complete...');
const generation = await waitForGeneration(result.data.generation.id); const generation = await waitForGeneration(result.data.generation.id);
if (generation.status !== 'success') { if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`); throw new Error(`Generation failed: ${generation.errorMessage}`);
} }
// Check if flow alias was assigned // Check if flow alias was assigned
const flowResult = await api(`${endpoints.flows}/${testContext.flowId}`); const flowResult = await api(`${endpoints.flows}/${testContext.flowId}`);
if (!flowResult.data.flow.aliases['@pattern']) { if (!flowResult.data.flow.aliases['@pattern']) {
throw new Error('Flow alias not assigned'); throw new Error('Flow alias not assigned');
} }

View File

@ -3,6 +3,12 @@
import { api, log, runTest, saveImage, wait } from './utils'; import { api, log, runTest, saveImage, wait } from './utils';
import { endpoints } from './config'; 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() { async function main() {
log.section('LIVE ENDPOINT TESTS'); log.section('LIVE ENDPOINT TESTS');
@ -145,7 +151,7 @@ async function main() {
// Test 7: Verify cache works as URL // Test 7: Verify cache works as URL
await runTest('Live as direct URL (browser-like)', async () => { await runTest('Live as direct URL (browser-like)', async () => {
const url = `${endpoints.live}?prompt=${encodeURIComponent('A beautiful sunset')}&aspectRatio=16:9`; const url = `${endpoints.live}?prompt=${encodeURIComponent('A beautiful sunset')}&aspectRatio=16:9`;
log.info('Testing URL format:'); log.info('Testing URL format:');
log.detail('URL', url); log.detail('URL', url);
@ -178,7 +184,7 @@ async function main() {
log.detail('Cache-Control', cacheControl || 'NOT SET'); log.detail('Cache-Control', cacheControl || 'NOT SET');
log.detail('Content-Type', contentType || 'NOT SET'); log.detail('Content-Type', contentType || 'NOT SET');
if (!cacheControl || !cacheControl.includes('public')) { if (!cacheControl || !cacheControl.includes('public')) {
log.warning('Cache-Control should be set for CDN optimization'); log.warning('Cache-Control should be set for CDN optimization');
} }
@ -203,11 +209,11 @@ async function main() {
// Rapid subsequent calls (all HITs) // Rapid subsequent calls (all HITs)
log.info('Making 5 rapid cache HIT calls...'); log.info('Making 5 rapid cache HIT calls...');
const durations: number[] = []; const durations: number[] = [];
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
const result = await api(`${endpoints.live}?${params}`); const result = await api(`${endpoints.live}?${params}`);
durations.push(result.duration); durations.push(result.duration);
const cacheStatus = result.headers.get('X-Cache-Status'); const cacheStatus = result.headers.get('X-Cache-Status');
if (cacheStatus !== 'HIT') { if (cacheStatus !== 'HIT') {
throw new Error(`Call ${i + 1} expected HIT, got ${cacheStatus}`); 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 { api, log, runTest, uploadFile } from './utils';
import { config, endpoints } from './config'; 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() { async function main() {
log.section('EDGE CASES & VALIDATION TESTS'); log.section('EDGE CASES & VALIDATION TESTS');
@ -30,7 +36,7 @@ async function main() {
// Test 2: Reserved technical alias // Test 2: Reserved technical alias
await runTest('Reserved technical alias', async () => { await runTest('Reserved technical alias', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
try { try {
await uploadFile(fixturePath, { await uploadFile(fixturePath, {
alias: '@last', // Reserved alias: '@last', // Reserved
@ -44,7 +50,7 @@ async function main() {
// Test 3: Duplicate project alias // Test 3: Duplicate project alias
await runTest('Duplicate project alias', async () => { await runTest('Duplicate project alias', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png'); const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
// First upload // First upload
await uploadFile(fixturePath, { await uploadFile(fixturePath, {
alias: '@duplicate-test', alias: '@duplicate-test',
@ -191,7 +197,7 @@ async function main() {
// Test 10: Extremely long prompt (over 2000 chars) // Test 10: Extremely long prompt (over 2000 chars)
await runTest('Prompt too long', async () => { await runTest('Prompt too long', async () => {
const longPrompt = 'A'.repeat(2001); const longPrompt = 'A'.repeat(2001);
const result = await api(endpoints.generations, { const result = await api(endpoints.generations, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -292,9 +298,9 @@ async function main() {
// Test 16: Missing API key // Test 16: Missing API key
await runTest('Missing API key', async () => { await runTest('Missing API key', async () => {
const url = `${config.baseURL}${endpoints.images}`; const url = `${config.baseURL}${endpoints.images}`;
const response = await fetch(url); // No API key header const response = await fetch(url); // No API key header
if (response.status !== 401) { if (response.status !== 401) {
throw new Error(`Expected 401, got ${response.status}`); throw new Error(`Expected 401, got ${response.status}`);
} }
@ -305,13 +311,13 @@ async function main() {
// Test 17: Invalid API key // Test 17: Invalid API key
await runTest('Invalid API key', async () => { await runTest('Invalid API key', async () => {
const url = `${config.baseURL}${endpoints.images}`; const url = `${config.baseURL}${endpoints.images}`;
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers: {
'X-API-Key': 'invalid_key_123', 'X-API-Key': 'invalid_key_123',
}, },
}); });
if (response.status !== 401) { if (response.status !== 401) {
throw new Error(`Expected 401, got ${response.status}`); 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 { promisify } from 'util';
import { log } from './utils'; 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 execAsync = promisify(exec);
const testFiles = [ const testFiles = [
@ -16,24 +22,24 @@ const testFiles = [
async function runTest(file: string): Promise<{ success: boolean; duration: number }> { async function runTest(file: string): Promise<{ success: boolean; duration: number }> {
const startTime = Date.now(); const startTime = Date.now();
try { try {
log.section(`Running ${file}`); log.section(`Running ${file}`);
await execAsync(`tsx ${file}`, { await execAsync(`tsx ${file}`, {
cwd: __dirname, cwd: __dirname,
env: process.env, env: process.env,
}); });
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
log.success(`${file} completed (${duration}ms)`); log.success(`${file} completed (${duration}ms)`);
return { success: true, duration }; return { success: true, duration };
} catch (error) { } catch (error) {
const duration = Date.now() - startTime; const duration = Date.now() - startTime;
log.error(`${file} failed (${duration}ms)`); log.error(`${file} failed (${duration}ms)`);
console.error(error); console.error(error);
return { success: false, duration }; return { success: false, duration };
} }
} }
@ -42,42 +48,42 @@ async function main() {
console.log('\n'); console.log('\n');
log.section('🚀 BANATIE API TEST SUITE'); log.section('🚀 BANATIE API TEST SUITE');
console.log('\n'); console.log('\n');
const results: Array<{ file: string; success: boolean; duration: number }> = []; const results: Array<{ file: string; success: boolean; duration: number }> = [];
const startTime = Date.now(); const startTime = Date.now();
for (const file of testFiles) { for (const file of testFiles) {
const result = await runTest(file); const result = await runTest(file);
results.push({ file, ...result }); results.push({ file, ...result });
console.log('\n'); console.log('\n');
} }
const totalDuration = Date.now() - startTime; const totalDuration = Date.now() - startTime;
// Summary // Summary
log.section('📊 TEST SUMMARY'); log.section('📊 TEST SUMMARY');
console.log('\n'); console.log('\n');
const passed = results.filter(r => r.success).length; const passed = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length; const failed = results.filter(r => !r.success).length;
results.forEach(result => { results.forEach(result => {
const icon = result.success ? '✓' : '✗'; const icon = result.success ? '✓' : '✗';
const color = result.success ? '\x1b[32m' : '\x1b[31m'; const color = result.success ? '\x1b[32m' : '\x1b[31m';
console.log(`${color}${icon}\x1b[0m ${result.file} (${result.duration}ms)`); console.log(`${color}${icon}\x1b[0m ${result.file} (${result.duration}ms)`);
}); });
console.log('\n'); console.log('\n');
log.info(`Total: ${results.length} test suites`); log.info(`Total: ${results.length} test suites`);
log.success(`Passed: ${passed}`); log.success(`Passed: ${passed}`);
if (failed > 0) { if (failed > 0) {
log.error(`Failed: ${failed}`); log.error(`Failed: ${failed}`);
} }
log.info(`Duration: ${(totalDuration / 1000).toFixed(2)}s`); log.info(`Duration: ${(totalDuration / 1000).toFixed(2)}s`);
console.log('\n'); console.log('\n');
if (failed > 0) { if (failed > 0) {
process.exit(1); process.exit(1);
} }

View File

@ -4,6 +4,12 @@ import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path'; import { join } from 'path';
import { config } from './config'; 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 // Colors for console output
const colors = { const colors = {
reset: '\x1b[0m', reset: '\x1b[0m',
@ -31,7 +37,7 @@ export const log = {
// API fetch wrapper // API fetch wrapper
export async function api<T = any>( export async function api<T = any>(
endpoint: string, endpoint: string,
options: RequestInit & { options: RequestInit & {
expectError?: boolean; expectError?: boolean;
timeout?: number; timeout?: number;
} = {} } = {}
@ -63,7 +69,7 @@ export async function api<T = any>(
let data: any; let data: any;
const contentType = response.headers.get('content-type'); const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) { if (contentType?.includes('application/json')) {
data = await response.json(); data = await response.json();
} else if (contentType?.includes('image/')) { } else if (contentType?.includes('image/')) {
@ -106,7 +112,7 @@ export async function saveImage(
filename: string filename: string
): Promise<string> { ): Promise<string> {
const resultsPath = join(__dirname, config.resultsDir); const resultsPath = join(__dirname, config.resultsDir);
try { try {
await mkdir(resultsPath, { recursive: true }); await mkdir(resultsPath, { recursive: true });
} catch (err) { } catch (err) {
@ -118,7 +124,7 @@ export async function saveImage(
const filepath = join(resultsPath, fullFilename); const filepath = join(resultsPath, fullFilename);
await writeFile(filepath, Buffer.from(buffer)); await writeFile(filepath, Buffer.from(buffer));
if (config.saveImages) { if (config.saveImages) {
log.info(`Saved image: ${fullFilename}`); log.info(`Saved image: ${fullFilename}`);
} }
@ -132,7 +138,7 @@ export async function uploadFile(
fields: Record<string, string> = {} fields: Record<string, string> = {}
): Promise<any> { ): Promise<any> {
const formData = new FormData(); const formData = new FormData();
// Read file // Read file
const fs = await import('fs/promises'); const fs = await import('fs/promises');
const fileBuffer = await fs.readFile(filepath); const fileBuffer = await fs.readFile(filepath);