feature/api-development #1
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
Loading…
Reference in New Issue