fix: service and tests
This commit is contained in:
parent
4e7eb7b5b5
commit
d6ca79152c
|
|
@ -1,6 +1,7 @@
|
|||
import express, { Application } from 'express';
|
||||
import cors from 'cors';
|
||||
import { config } from 'dotenv';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { Config } from './types/api';
|
||||
import { textToImageRouter } from './routes/textToImage';
|
||||
import { imagesRouter } from './routes/images';
|
||||
|
|
@ -43,7 +44,7 @@ export const createApp = (): Application => {
|
|||
|
||||
// Request ID middleware for logging
|
||||
app.use((req, res, next) => {
|
||||
req.requestId = Math.random().toString(36).substr(2, 9);
|
||||
req.requestId = randomUUID();
|
||||
res.setHeader('X-Request-ID', req.requestId);
|
||||
next();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ export class ImageGenService {
|
|||
filename: uploadResult.filename,
|
||||
filepath: uploadResult.path,
|
||||
url: uploadResult.url,
|
||||
size: uploadResult.size,
|
||||
model: this.primaryModel,
|
||||
geminiParams,
|
||||
...(generatedData.description && {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export class GenerationService {
|
|||
storageKey,
|
||||
storageUrl: genResult.url!,
|
||||
mimeType: 'image/jpeg',
|
||||
fileSize: 0, // TODO: Get actual file size from storage
|
||||
fileSize: genResult.size || 0,
|
||||
fileHash,
|
||||
source: 'generated',
|
||||
alias: params.assignAlias || null,
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ export interface ImageGenerationResult {
|
|||
filename?: string;
|
||||
filepath?: string;
|
||||
url?: string; // API URL for accessing the image
|
||||
size?: number; // File size in bytes
|
||||
description?: string;
|
||||
model: string;
|
||||
geminiParams?: GeminiParams; // Gemini SDK parameters used for generation
|
||||
|
|
|
|||
|
|
@ -22,39 +22,39 @@ async function main() {
|
|||
description: 'Test logo image',
|
||||
});
|
||||
|
||||
if (!response.image || !response.image.id) {
|
||||
if (!response || !response.id) {
|
||||
throw new Error('No image returned');
|
||||
}
|
||||
|
||||
testContext.uploadedImageId = response.image.id;
|
||||
log.detail('Image ID', response.image.id);
|
||||
log.detail('Storage Key', response.image.storageKey);
|
||||
log.detail('Alias', response.image.alias);
|
||||
testContext.uploadedImageId = response.id;
|
||||
log.detail('Image ID', response.id);
|
||||
log.detail('Storage Key', response.storageKey);
|
||||
log.detail('Alias', response.alias);
|
||||
});
|
||||
|
||||
// Test 2: List images
|
||||
await runTest('List images', async () => {
|
||||
const result = await api(endpoints.images);
|
||||
|
||||
if (!result.data.images || !Array.isArray(result.data.images)) {
|
||||
if (!result.data.data || !Array.isArray(result.data.data)) {
|
||||
throw new Error('No images array returned');
|
||||
}
|
||||
|
||||
log.detail('Total images', result.data.images.length);
|
||||
log.detail('Has uploaded', result.data.images.some((img: any) => img.source === 'uploaded'));
|
||||
log.detail('Total images', result.data.data.length);
|
||||
log.detail('Has uploaded', result.data.data.some((img: any) => img.source === 'uploaded'));
|
||||
});
|
||||
|
||||
// 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) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Image not found');
|
||||
}
|
||||
|
||||
log.detail('Image ID', result.data.image.id);
|
||||
log.detail('Source', result.data.image.source);
|
||||
log.detail('File size', `${result.data.image.fileSize} bytes`);
|
||||
log.detail('Image ID', result.data.data.id);
|
||||
log.detail('Source', result.data.data.source);
|
||||
log.detail('File size', `${result.data.data.fileSize} bytes`);
|
||||
});
|
||||
|
||||
// Test 4: Generate image without references
|
||||
|
|
@ -68,14 +68,14 @@ async function main() {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!result.data.generation) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('No generation returned');
|
||||
}
|
||||
|
||||
testContext.generationId = result.data.generation.id;
|
||||
log.detail('Generation ID', result.data.generation.id);
|
||||
log.detail('Status', result.data.generation.status);
|
||||
log.detail('Prompt', result.data.generation.originalPrompt);
|
||||
testContext.generationId = result.data.data.id;
|
||||
log.detail('Generation ID', result.data.data.id);
|
||||
log.detail('Status', result.data.data.status);
|
||||
log.detail('Prompt', result.data.data.originalPrompt);
|
||||
|
||||
// Wait for completion
|
||||
log.info('Waiting for generation to complete...');
|
||||
|
|
@ -93,7 +93,7 @@ async function main() {
|
|||
const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`);
|
||||
|
||||
// Download image
|
||||
const imageUrl = imageResult.data.image.storageUrl;
|
||||
const imageUrl = imageResult.data.data.storageUrl;
|
||||
const imageResponse = await fetch(imageUrl);
|
||||
const imageBuffer = await imageResponse.arrayBuffer();
|
||||
|
||||
|
|
@ -114,16 +114,16 @@ async function main() {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!result.data.generation) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('No generation returned');
|
||||
}
|
||||
|
||||
log.detail('Generation ID', result.data.generation.id);
|
||||
log.detail('Referenced images', result.data.generation.referencedImages?.length || 0);
|
||||
log.detail('Generation ID', result.data.data.id);
|
||||
log.detail('Referenced images', result.data.data.referencedImages?.length || 0);
|
||||
|
||||
// Wait for completion
|
||||
log.info('Waiting for generation to complete...');
|
||||
const generation = await waitForGeneration(result.data.generation.id);
|
||||
const generation = await waitForGeneration(result.data.data.id);
|
||||
|
||||
if (generation.status !== 'success') {
|
||||
throw new Error(`Generation failed: ${generation.errorMessage}`);
|
||||
|
|
@ -132,7 +132,7 @@ async function main() {
|
|||
// Save generated image
|
||||
if (generation.outputImageId) {
|
||||
const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`);
|
||||
const imageUrl = imageResult.data.image.storageUrl;
|
||||
const imageUrl = imageResult.data.data.storageUrl;
|
||||
const imageResponse = await fetch(imageUrl);
|
||||
const imageBuffer = await imageResponse.arrayBuffer();
|
||||
|
||||
|
|
@ -144,12 +144,12 @@ async function main() {
|
|||
await runTest('List generations', async () => {
|
||||
const result = await api(endpoints.generations);
|
||||
|
||||
if (!result.data.generations || !Array.isArray(result.data.generations)) {
|
||||
if (!result.data.data || !Array.isArray(result.data.data)) {
|
||||
throw new Error('No generations array returned');
|
||||
}
|
||||
|
||||
log.detail('Total generations', result.data.generations.length);
|
||||
log.detail('Successful', result.data.generations.filter((g: any) => g.status === 'success').length);
|
||||
log.detail('Total generations', result.data.data.length);
|
||||
log.detail('Successful', result.data.data.filter((g: any) => g.status === 'success').length);
|
||||
log.detail('Has pagination', !!result.data.pagination);
|
||||
});
|
||||
|
||||
|
|
@ -157,14 +157,14 @@ async function main() {
|
|||
await runTest('Get generation details', async () => {
|
||||
const result = await api(`${endpoints.generations}/${testContext.generationId}`);
|
||||
|
||||
if (!result.data.generation) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Generation not found');
|
||||
}
|
||||
|
||||
log.detail('Generation ID', result.data.generation.id);
|
||||
log.detail('Status', result.data.generation.status);
|
||||
log.detail('Has image', !!result.data.image);
|
||||
log.detail('Referenced images', result.data.referencedImages?.length || 0);
|
||||
log.detail('Generation ID', result.data.data.id);
|
||||
log.detail('Status', result.data.data.status);
|
||||
log.detail('Has output image', !!result.data.data.outputImage);
|
||||
log.detail('Referenced images', result.data.data.referencedImages?.length || 0);
|
||||
});
|
||||
|
||||
log.section('BASIC TESTS COMPLETED');
|
||||
|
|
|
|||
|
|
@ -22,24 +22,24 @@ async function main() {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!result.data.flow || !result.data.flow.id) {
|
||||
if (!result.data.data || !result.data.data.id) {
|
||||
throw new Error('No flow returned');
|
||||
}
|
||||
|
||||
testContext.flowId = result.data.flow.id;
|
||||
log.detail('Flow ID', result.data.flow.id);
|
||||
log.detail('Aliases', JSON.stringify(result.data.flow.aliases));
|
||||
testContext.flowId = result.data.data.id;
|
||||
log.detail('Flow ID', result.data.data.id);
|
||||
log.detail('Aliases', JSON.stringify(result.data.data.aliases));
|
||||
});
|
||||
|
||||
// Test 2: List flows
|
||||
await runTest('List flows', async () => {
|
||||
const result = await api(endpoints.flows);
|
||||
|
||||
if (!result.data.flows || !Array.isArray(result.data.flows)) {
|
||||
if (!result.data.data || !Array.isArray(result.data.data)) {
|
||||
throw new Error('No flows array returned');
|
||||
}
|
||||
|
||||
log.detail('Total flows', result.data.flows.length);
|
||||
log.detail('Total flows', result.data.data.length);
|
||||
log.detail('Has pagination', !!result.data.pagination);
|
||||
});
|
||||
|
||||
|
|
@ -55,16 +55,16 @@ async function main() {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!result.data.generation) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('No generation returned');
|
||||
}
|
||||
|
||||
log.detail('Generation ID', result.data.generation.id);
|
||||
log.detail('Flow ID', result.data.generation.flowId);
|
||||
log.detail('Generation ID', result.data.data.id);
|
||||
log.detail('Flow ID', result.data.data.flowId);
|
||||
|
||||
// Wait for completion
|
||||
log.info('Waiting for generation to complete...');
|
||||
const generation = await waitForGeneration(result.data.generation.id);
|
||||
const generation = await waitForGeneration(result.data.data.id);
|
||||
|
||||
if (generation.status !== 'success') {
|
||||
throw new Error(`Generation failed: ${generation.errorMessage}`);
|
||||
|
|
@ -96,16 +96,16 @@ async function main() {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!result.data.generation) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('No generation returned');
|
||||
}
|
||||
|
||||
log.detail('Generation ID', result.data.generation.id);
|
||||
log.detail('Referenced @last', result.data.generation.referencedImages?.some((r: any) => r.alias === '@last'));
|
||||
log.detail('Generation ID', result.data.data.id);
|
||||
log.detail('Referenced @last', result.data.data.referencedImages?.some((r: any) => r.alias === '@last'));
|
||||
|
||||
// Wait for completion
|
||||
log.info('Waiting for generation to complete...');
|
||||
const generation = await waitForGeneration(result.data.generation.id);
|
||||
const generation = await waitForGeneration(result.data.data.id);
|
||||
|
||||
if (generation.status !== 'success') {
|
||||
throw new Error(`Generation failed: ${generation.errorMessage}`);
|
||||
|
|
@ -126,13 +126,13 @@ async function main() {
|
|||
await runTest('Get flow details', async () => {
|
||||
const result = await api(`${endpoints.flows}/${testContext.flowId}`);
|
||||
|
||||
if (!result.data.flow) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Flow not found');
|
||||
}
|
||||
|
||||
log.detail('Flow ID', result.data.flow.id);
|
||||
log.detail('Generations count', result.data.generations?.length || 0);
|
||||
log.detail('Images count', result.data.images?.length || 0);
|
||||
log.detail('Flow ID', result.data.data.id);
|
||||
log.detail('Generations count', result.data.datas?.length || 0);
|
||||
log.detail('Images count', result.data.data?.length || 0);
|
||||
log.detail('Resolved aliases', Object.keys(result.data.resolvedAliases || {}).length);
|
||||
});
|
||||
|
||||
|
|
@ -157,11 +157,11 @@ async function main() {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!result.data.flow) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Flow not returned');
|
||||
}
|
||||
|
||||
log.detail('Updated aliases', JSON.stringify(result.data.flow.aliases));
|
||||
log.detail('Updated aliases', JSON.stringify(result.data.data.aliases));
|
||||
});
|
||||
|
||||
// Test 7: Generate with flow-scoped alias
|
||||
|
|
@ -177,16 +177,16 @@ async function main() {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!result.data.generation) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('No generation returned');
|
||||
}
|
||||
|
||||
log.detail('Generation ID', result.data.generation.id);
|
||||
log.detail('Referenced @hero', result.data.generation.referencedImages?.some((r: any) => r.alias === '@hero'));
|
||||
log.detail('Generation ID', result.data.data.id);
|
||||
log.detail('Referenced @hero', result.data.data.referencedImages?.some((r: any) => r.alias === '@hero'));
|
||||
|
||||
// Wait for completion
|
||||
log.info('Waiting for generation to complete...');
|
||||
const generation = await waitForGeneration(result.data.generation.id);
|
||||
const generation = await waitForGeneration(result.data.data.id);
|
||||
|
||||
if (generation.status !== 'success') {
|
||||
throw new Error(`Generation failed: ${generation.errorMessage}`);
|
||||
|
|
@ -211,13 +211,13 @@ async function main() {
|
|||
|
||||
// Verify it's deleted
|
||||
const result = await api(`${endpoints.flows}/${testContext.flowId}`);
|
||||
const hasFeatureAlias = '@featured' in result.data.flow.aliases;
|
||||
const hasFeatureAlias = '@featured' in result.data.data.aliases;
|
||||
|
||||
if (hasFeatureAlias) {
|
||||
throw new Error('Alias was not deleted');
|
||||
}
|
||||
|
||||
log.detail('Remaining aliases', JSON.stringify(result.data.flow.aliases));
|
||||
log.detail('Remaining aliases', JSON.stringify(result.data.data.aliases));
|
||||
});
|
||||
|
||||
log.section('FLOW TESTS COMPLETED');
|
||||
|
|
|
|||
|
|
@ -78,50 +78,50 @@ async function main() {
|
|||
await runTest('Resolve project alias', async () => {
|
||||
const result = await api(`${endpoints.images}/resolve/@brand-logo`);
|
||||
|
||||
if (!result.data.image) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Image not resolved');
|
||||
}
|
||||
|
||||
if (result.data.scope !== 'project') {
|
||||
throw new Error(`Wrong scope: ${result.data.scope}`);
|
||||
if (result.data.data.scope !== 'project') {
|
||||
throw new Error(`Wrong scope: ${result.data.data.scope}`);
|
||||
}
|
||||
|
||||
log.detail('Image ID', result.data.image.id);
|
||||
log.detail('Scope', result.data.scope);
|
||||
log.detail('Alias', result.data.image.alias);
|
||||
log.detail('Image ID', result.data.data.id);
|
||||
log.detail('Scope', result.data.data.scope);
|
||||
log.detail('Alias', result.data.data.alias);
|
||||
});
|
||||
|
||||
// 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) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Image not resolved');
|
||||
}
|
||||
|
||||
if (result.data.scope !== 'flow') {
|
||||
throw new Error(`Wrong scope: ${result.data.scope}`);
|
||||
if (result.data.data.scope !== 'flow') {
|
||||
throw new Error(`Wrong scope: ${result.data.data.scope}`);
|
||||
}
|
||||
|
||||
log.detail('Image ID', result.data.image.id);
|
||||
log.detail('Scope', result.data.scope);
|
||||
log.detail('Flow ID', result.data.flow?.id);
|
||||
log.detail('Image ID', result.data.data.id);
|
||||
log.detail('Scope', result.data.data.scope);
|
||||
log.detail('Flow ID', result.data.data?.id);
|
||||
});
|
||||
|
||||
// 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) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Image not resolved');
|
||||
}
|
||||
|
||||
if (result.data.scope !== 'technical') {
|
||||
throw new Error(`Wrong scope: ${result.data.scope}`);
|
||||
if (result.data.data.scope !== 'technical') {
|
||||
throw new Error(`Wrong scope: ${result.data.data.scope}`);
|
||||
}
|
||||
|
||||
log.detail('Image ID', result.data.image.id);
|
||||
log.detail('Scope', result.data.scope);
|
||||
log.detail('Image ID', result.data.data.id);
|
||||
log.detail('Scope', result.data.data.scope);
|
||||
log.detail('Technical alias', '@last');
|
||||
});
|
||||
|
||||
|
|
@ -129,33 +129,33 @@ async function main() {
|
|||
await runTest('Resolve @first technical alias', async () => {
|
||||
const result = await api(`${endpoints.images}/resolve/@first?flowId=${testContext.flowId}`);
|
||||
|
||||
if (!result.data.image) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Image not resolved');
|
||||
}
|
||||
|
||||
if (result.data.scope !== 'technical') {
|
||||
throw new Error(`Wrong scope: ${result.data.scope}`);
|
||||
if (result.data.data.scope !== 'technical') {
|
||||
throw new Error(`Wrong scope: ${result.data.data.scope}`);
|
||||
}
|
||||
|
||||
log.detail('Image ID', result.data.image.id);
|
||||
log.detail('Scope', result.data.scope);
|
||||
log.detail('Image ID', result.data.data.id);
|
||||
log.detail('Scope', result.data.data.scope);
|
||||
});
|
||||
|
||||
// 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) {
|
||||
if (!result.data.data) {
|
||||
throw new Error('Image not resolved');
|
||||
}
|
||||
|
||||
if (result.data.scope !== 'technical') {
|
||||
throw new Error(`Wrong scope: ${result.data.scope}`);
|
||||
if (result.data.data.scope !== 'technical') {
|
||||
throw new Error(`Wrong scope: ${result.data.data.scope}`);
|
||||
}
|
||||
|
||||
log.detail('Image ID', result.data.image.id);
|
||||
log.detail('Scope', result.data.scope);
|
||||
log.detail('Source', result.data.image.source);
|
||||
log.detail('Image ID', result.data.data.id);
|
||||
log.detail('Scope', result.data.data.scope);
|
||||
log.detail('Source', result.data.data.source);
|
||||
});
|
||||
|
||||
// Test 9: Generate with assignAlias (project-scoped)
|
||||
|
|
@ -171,7 +171,7 @@ async function main() {
|
|||
});
|
||||
|
||||
log.info('Waiting for generation to complete...');
|
||||
const generation = await waitForGeneration(result.data.generation.id);
|
||||
const generation = await waitForGeneration(result.data.data.id);
|
||||
|
||||
if (generation.status !== 'success') {
|
||||
throw new Error(`Generation failed: ${generation.errorMessage}`);
|
||||
|
|
@ -208,7 +208,7 @@ async function main() {
|
|||
});
|
||||
|
||||
log.info('Waiting for generation to complete...');
|
||||
const generation = await waitForGeneration(result.data.generation.id);
|
||||
const generation = await waitForGeneration(result.data.data.id);
|
||||
|
||||
if (generation.status !== 'success') {
|
||||
throw new Error(`Generation failed: ${generation.errorMessage}`);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const config = {
|
|||
|
||||
// Paths
|
||||
resultsDir: '../../results',
|
||||
fixturesDir: './fixtures',
|
||||
fixturesDir: './fixture',
|
||||
|
||||
// Timeouts
|
||||
requestTimeout: 30000,
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { writeFile, mkdir } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { config } from './config';
|
||||
import { config, endpoints } from './config';
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
|
@ -150,7 +150,7 @@ export async function uploadFile(
|
|||
formData.append(key, value);
|
||||
}
|
||||
|
||||
const result = await api(config.endpoints.images + '/upload', {
|
||||
const result = await api(endpoints.images + '/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
|
|
@ -158,7 +158,7 @@ export async function uploadFile(
|
|||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
return result.data.data;
|
||||
}
|
||||
|
||||
// Wait helper
|
||||
|
|
@ -172,8 +172,8 @@ export async function waitForGeneration(
|
|||
maxAttempts = 20
|
||||
): Promise<any> {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const result = await api(`${config.endpoints.generations}/${generationId}`);
|
||||
const generation = result.data.generation;
|
||||
const result = await api(`${endpoints.generations}/${generationId}`);
|
||||
const generation = result.data.data;
|
||||
|
||||
if (generation.status === 'success' || generation.status === 'failed') {
|
||||
return generation;
|
||||
|
|
|
|||
Loading…
Reference in New Issue