banatie-service/tests/api/02-basic.ts

424 lines
13 KiB
TypeScript

// tests/api/02-basic.ts
// Image Upload and CRUD Operations
import { join } from 'path';
import { api, log, runTest, saveImage, uploadFile, waitForGeneration, testContext, verifyImageAccessible, resolveAlias } 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('IMAGE UPLOAD & CRUD TESTS');
// Test 1: Upload image with project-scoped alias
await runTest('Upload image with project alias', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
const response = await uploadFile(fixturePath, {
alias: '@test-logo',
description: 'Test logo image for CRUD tests',
});
if (!response || !response.id) {
throw new Error('No image returned');
}
if (response.alias !== '@test-logo') {
throw new Error('Alias not set correctly');
}
if (response.source !== 'uploaded') {
throw new Error('Source should be "uploaded"');
}
testContext.uploadedImageId = response.id;
log.detail('Image ID', response.id);
log.detail('Storage Key', response.storageKey);
log.detail('Alias', response.alias);
log.detail('Source', response.source);
});
// Test 2: Upload image without alias
await runTest('Upload image without alias', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
const response = await uploadFile(fixturePath, {
description: 'Image without alias',
});
if (!response || !response.id) {
throw new Error('No image returned');
}
if (response.alias !== null) {
throw new Error('Alias should be null');
}
log.detail('Image ID', response.id);
log.detail('Alias', 'null (as expected)');
});
// Test 3: List all images
await runTest('List all images', async () => {
const result = await api(endpoints.images);
if (!result.data.data || !Array.isArray(result.data.data)) {
throw new Error('No images array returned');
}
log.detail('Total images', result.data.data.length);
// Find our uploaded image
const found = result.data.data.find((img: any) => img.id === testContext.uploadedImageId);
if (!found) {
throw new Error('Uploaded image not in list');
}
log.detail('Found our image', '✓');
});
// Test 4: List images with source filter
await runTest('List images - filter by source=uploaded', async () => {
const result = await api(`${endpoints.images}?source=uploaded`);
if (!result.data.data || !Array.isArray(result.data.data)) {
throw new Error('No images array returned');
}
// All should be uploaded
const allUploaded = result.data.data.every((img: any) => img.source === 'uploaded');
if (!allUploaded) {
throw new Error('Source filter not working');
}
log.detail('Uploaded images', result.data.data.length);
});
// Test 5: List images with pagination
await runTest('List images with pagination', async () => {
const result = await api(`${endpoints.images}?limit=3&offset=0`);
if (!result.data.pagination) {
throw new Error('No pagination data');
}
log.detail('Limit', result.data.pagination.limit);
log.detail('Offset', result.data.pagination.offset);
log.detail('Total', result.data.pagination.total);
log.detail('Has more', result.data.pagination.hasMore);
});
// Test 6: Get image by ID
await runTest('Get image by ID', async () => {
const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`);
if (!result.data.data) {
throw new Error('Image not found');
}
const image = result.data.data;
// Verify fields
if (!image.id) throw new Error('Missing id');
if (!image.storageKey) throw new Error('Missing storageKey');
if (!image.storageUrl) throw new Error('Missing storageUrl');
if (!image.source) throw new Error('Missing source');
log.detail('Image ID', image.id);
log.detail('Source', image.source);
log.detail('File size', `${image.fileSize || 0} bytes`);
log.detail('Alias', image.alias || 'null');
});
// Test 7: Get image by alias (using resolve endpoint)
await runTest('Resolve project-scoped alias', async () => {
const resolved = await resolveAlias('@test-logo');
if (!resolved.imageId) {
throw new Error('Alias not resolved');
}
if (resolved.imageId !== testContext.uploadedImageId) {
throw new Error('Resolved to wrong image');
}
if (resolved.scope !== 'project') {
throw new Error(`Wrong scope: ${resolved.scope}`);
}
log.detail('Resolved image ID', resolved.imageId);
log.detail('Scope', resolved.scope);
log.detail('Alias', resolved.alias);
});
// Test 8: Update image metadata
await runTest('Update image metadata', async () => {
const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
focalPoint: { x: 0.5, y: 0.3 },
meta: {
description: 'Updated description',
tags: ['test', 'logo', 'updated'],
},
}),
});
if (!result.data.data) {
throw new Error('No image returned');
}
// Verify update by fetching again
const updated = await api(`${endpoints.images}/${testContext.uploadedImageId}`);
const image = updated.data.data;
if (!image.focalPoint || image.focalPoint.x !== 0.5 || image.focalPoint.y !== 0.3) {
throw new Error('Focal point not updated');
}
log.detail('Focal point', JSON.stringify(image.focalPoint));
log.detail('Meta', JSON.stringify(image.meta));
});
// Test 9: Update image alias (dedicated endpoint)
await runTest('Update image alias', async () => {
const result = await api(`${endpoints.images}/${testContext.uploadedImageId}/alias`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
alias: '@new-test-logo',
}),
});
if (!result.data.data) {
throw new Error('No image returned');
}
// Verify new alias works
const resolved = await resolveAlias('@new-test-logo');
if (resolved.imageId !== testContext.uploadedImageId) {
throw new Error('New alias not working');
}
log.detail('New alias', '@new-test-logo');
log.detail('Resolved', '✓');
});
// Test 10: Verify old alias doesn't work after update
await runTest('Old alias should not resolve after update', async () => {
try {
await resolveAlias('@test-logo');
throw new Error('Old alias should not resolve');
} catch (error: any) {
// Expected to fail
if (error.message.includes('should not resolve')) {
throw error;
}
log.detail('Old alias correctly invalid', '✓');
}
});
// Test 11: Remove image alias
await runTest('Remove image alias', async () => {
await api(`${endpoints.images}/${testContext.uploadedImageId}/alias`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
alias: null,
}),
});
// Verify image exists but has no alias
const result = await api(`${endpoints.images}/${testContext.uploadedImageId}`);
if (result.data.data.alias !== null) {
throw new Error('Alias should be null');
}
// Verify alias resolution fails
try {
await resolveAlias('@new-test-logo');
throw new Error('Removed alias should not resolve');
} catch (error: any) {
if (error.message.includes('should not resolve')) {
throw error;
}
log.detail('Alias removed', '✓');
}
});
// Test 12: Generate image with manual reference
await runTest('Generate with manual reference image', async () => {
// First, reassign alias for reference
await api(`${endpoints.images}/${testContext.uploadedImageId}/alias`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
alias: '@reference-logo',
}),
});
const result = await api(endpoints.generations, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: 'A product photo with the logo in corner',
aspectRatio: '1:1',
referenceImages: ['@reference-logo'],
}),
});
const generation = await waitForGeneration(result.data.data.id);
if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`);
}
// Verify referenced images tracked
if (!generation.referencedImages || generation.referencedImages.length === 0) {
throw new Error('Referenced images not tracked');
}
const refFound = generation.referencedImages.some(
(ref: any) => ref.alias === '@reference-logo'
);
if (!refFound) {
throw new Error('Reference image not found in referencedImages');
}
log.detail('Generation ID', generation.id);
log.detail('Referenced images', generation.referencedImages.length);
// Save generated image
if (generation.outputImageId) {
const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`);
const imageUrl = imageResult.data.data.storageUrl;
const imageResponse = await fetch(imageUrl);
const imageBuffer = await imageResponse.arrayBuffer();
await saveImage(imageBuffer, 'gen-with-reference.png');
}
});
// Test 13: Generate with auto-detected reference in prompt
await runTest('Generate with auto-detected reference', async () => {
const result = await api(endpoints.generations, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: 'Create banner using @reference-logo with blue background',
aspectRatio: '16:9',
// NOTE: referenceImages NOT provided, should auto-detect
}),
});
const generation = await waitForGeneration(result.data.data.id);
if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`);
}
// Verify auto-detection worked
if (!generation.referencedImages || generation.referencedImages.length === 0) {
throw new Error('Auto-detection did not work');
}
const autoDetected = generation.referencedImages.some(
(ref: any) => ref.alias === '@reference-logo'
);
if (!autoDetected) {
throw new Error('Reference not auto-detected from prompt');
}
log.detail('Auto-detected references', generation.referencedImages.length);
});
// Test 14: Generate with project alias assignment
await runTest('Generate with project alias assignment', async () => {
const result = await api(endpoints.generations, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: 'A hero banner image',
aspectRatio: '21:9',
alias: '@hero-banner',
}),
});
const generation = await waitForGeneration(result.data.data.id);
if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`);
}
// Verify alias assigned to output image
const imageResult = await api(`${endpoints.images}/${generation.outputImageId}`);
const image = imageResult.data.data;
if (image.alias !== '@hero-banner') {
throw new Error('Alias not assigned to output image');
}
// Verify alias resolution works
const resolved = await resolveAlias('@hero-banner');
if (resolved.imageId !== generation.outputImageId) {
throw new Error('Alias resolution failed');
}
log.detail('Output image alias', image.alias);
log.detail('Alias resolution', '✓');
testContext.heroBannerId = generation.outputImageId;
});
// Test 15: Alias conflict - new generation overwrites
await runTest('Alias conflict resolution', async () => {
// First generation has @hero alias (from previous test)
const firstImageId = testContext.heroBannerId;
// Create second generation with same alias
const result = await api(endpoints.generations, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: 'A different hero image',
aspectRatio: '21:9',
alias: '@hero-banner',
}),
});
const generation = await waitForGeneration(result.data.data.id);
if (generation.status !== 'success') {
throw new Error(`Generation failed: ${generation.errorMessage}`);
}
const secondImageId = generation.outputImageId;
// Verify second image has the alias
const resolved = await resolveAlias('@hero-banner');
if (resolved.imageId !== secondImageId) {
throw new Error('Second image should have the alias');
}
// Verify first image lost the alias but still exists
const firstImage = await api(`${endpoints.images}/${firstImageId}`);
if (firstImage.data.data.alias !== null) {
throw new Error('First image should have lost the alias');
}
log.detail('Second image has alias', '✓');
log.detail('First image preserved', '✓');
log.detail('First image alias removed', '✓');
});
log.section('IMAGE UPLOAD & CRUD TESTS COMPLETED');
}
main().catch(console.error);