banatie-service/tests/api/04-aliases.ts

284 lines
8.9 KiB
TypeScript

// tests/api/04-aliases.ts
// 3-Tier Alias Resolution System Tests
import { join } from 'path';
import { api, log, runTest, uploadFile, waitForGeneration, testContext, resolveAlias, createTestImage, exitWithTestResults } 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 RESOLUTION TESTS');
// Setup: Create a flow for testing
const setupGen = await createTestImage('Setup image for alias tests', {
flowAlias: '@alias-test-flow',
});
testContext.aliasFlowId = setupGen.flowId;
log.info(`Test flow created: ${testContext.aliasFlowId}`);
// Test 1: Technical alias @last
await runTest('Technical alias - @last', async () => {
const resolved = await resolveAlias('@last', testContext.aliasFlowId);
if (resolved.scope !== 'technical') {
throw new Error(`Wrong scope: ${resolved.scope}`);
}
if (!resolved.imageId) {
throw new Error('No image resolved');
}
log.detail('Scope', resolved.scope);
log.detail('Alias', '@last');
log.detail('Image ID', resolved.imageId);
});
// Test 2: Technical alias @first
await runTest('Technical alias - @first', async () => {
const resolved = await resolveAlias('@first', testContext.aliasFlowId);
if (resolved.scope !== 'technical') {
throw new Error(`Wrong scope: ${resolved.scope}`);
}
log.detail('Scope', resolved.scope);
log.detail('Alias', '@first');
});
// Test 3: Technical alias @upload
await runTest('Technical alias - @upload', async () => {
// First upload an image to the flow
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
await uploadFile(fixturePath, {
flowId: testContext.aliasFlowId,
description: 'Uploaded for @upload test',
});
const resolved = await resolveAlias('@upload', testContext.aliasFlowId);
if (resolved.scope !== 'technical') {
throw new Error(`Wrong scope: ${resolved.scope}`);
}
log.detail('Scope', resolved.scope);
log.detail('Alias', '@upload');
log.detail('Image source', 'uploaded');
});
// Test 4: Technical alias requires flowId
await runTest('Technical alias requires flow context', async () => {
try {
await resolveAlias('@last'); // No flowId
throw new Error('Should require flowId');
} catch (error: any) {
if (error.message.includes('Should require')) {
throw error;
}
log.detail('Correctly requires flowId', '✓');
}
});
// Test 5: Flow-scoped alias
await runTest('Flow-scoped alias resolution', async () => {
const gen = await createTestImage('Image for flow alias', {
flowId: testContext.aliasFlowId,
flowAlias: '@flow-hero',
});
const resolved = await resolveAlias('@flow-hero', testContext.aliasFlowId);
if (resolved.scope !== 'flow') {
throw new Error(`Wrong scope: ${resolved.scope}`);
}
log.detail('Scope', resolved.scope);
log.detail('Alias', '@flow-hero');
});
// Test 6: Project-scoped alias
await runTest('Project-scoped alias resolution', async () => {
const gen = await createTestImage('Image for project alias', {
alias: '@project-logo',
flowId: null, // Explicitly no flow
});
const resolved = await resolveAlias('@project-logo');
if (resolved.scope !== 'project') {
throw new Error(`Wrong scope: ${resolved.scope}`);
}
log.detail('Scope', resolved.scope);
log.detail('Alias', '@project-logo');
});
// Test 7: Alias priority - flow overrides project
await runTest('Alias precedence - flow > project', async () => {
// Create project alias
const projectGen = await createTestImage('Project scoped image', {
alias: '@priority-test',
flowId: null,
});
// Create flow alias with same name
const flowGen = await createTestImage('Flow scoped image', {
flowId: testContext.aliasFlowId,
flowAlias: '@priority-test',
});
// Without flow context - should get project
const projectResolved = await resolveAlias('@priority-test');
if (projectResolved.imageId !== projectGen.outputImageId) {
throw new Error('Should resolve to project alias');
}
log.detail('Without flow context', 'resolved to project ✓');
// With flow context - should get flow
const flowResolved = await resolveAlias('@priority-test', testContext.aliasFlowId);
if (flowResolved.imageId !== flowGen.outputImageId) {
throw new Error('Should resolve to flow alias');
}
log.detail('With flow context', 'resolved to flow ✓');
});
// Test 8: Reserved alias validation
await runTest('Reserved aliases cannot be assigned', async () => {
const reservedAliases = ['@last', '@first', '@upload'];
for (const reserved of reservedAliases) {
try {
const gen = await api(endpoints.generations, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: 'Test',
aspectRatio: '1:1',
alias: reserved,
}),
});
// If we get here, it didn't throw - that's bad
log.warning(`Reserved alias ${reserved} was allowed!`);
} catch (error: any) {
// Expected to fail
log.detail(`${reserved} correctly blocked`, '✓');
}
}
});
// Test 9: Alias reassignment
await runTest('Alias reassignment removes old', async () => {
const gen1 = await createTestImage('First image', {
alias: '@reassign-test',
flowId: null,
});
const gen2 = await createTestImage('Second image', {
alias: '@reassign-test',
flowId: null,
});
// Check that gen2 has the alias
const resolved = await resolveAlias('@reassign-test');
if (resolved.imageId !== gen2.outputImageId) {
throw new Error('Alias should be on second image');
}
// Check that gen1 lost the alias
const img1 = await api(`${endpoints.images}/${gen1.outputImageId}`);
if (img1.data.data.alias !== null) {
throw new Error('First image should have lost alias');
}
log.detail('Second image has alias', '✓');
log.detail('First image lost alias', '✓');
});
// Test 10: Same alias in different flows
await runTest('Same alias in different flows', async () => {
// Create two flows with same alias
const gen1 = await createTestImage('Flow 1 image', {
flowAlias: '@shared-name',
});
const gen2 = await createTestImage('Flow 2 image', {
flowAlias: '@shared-name',
});
// Resolve in each flow context
const resolved1 = await resolveAlias('@shared-name', gen1.flowId);
const resolved2 = await resolveAlias('@shared-name', gen2.flowId);
if (resolved1.imageId === resolved2.imageId) {
throw new Error('Should resolve to different images');
}
log.detail('Flow 1 image', resolved1.imageId.slice(0, 8));
log.detail('Flow 2 image', resolved2.imageId.slice(0, 8));
log.detail('Isolation confirmed', '✓');
});
// Test 11: Technical alias in generation prompt
await runTest('Use technical alias in prompt', async () => {
const result = await api(endpoints.generations, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: 'New variation based on @last',
aspectRatio: '1:1',
flowId: testContext.aliasFlowId,
}),
});
const generation = await waitForGeneration(result.data.data.id);
if (generation.status !== 'success') {
throw new Error('Generation failed');
}
// Check that @last was resolved
const hasLast = generation.referencedImages?.some((ref: any) => ref.alias === '@last');
if (!hasLast) {
throw new Error('Technical alias not resolved in prompt');
}
log.detail('Technical alias resolved', '✓');
});
// Test 12: Upload with dual aliases
await runTest('Upload with both project and flow alias', async () => {
const fixturePath = join(__dirname, config.fixturesDir, 'test-image.png');
const response = await uploadFile(fixturePath, {
alias: '@dual-project',
flowId: testContext.aliasFlowId,
flowAlias: '@dual-flow',
});
// Verify both aliases work
const projectResolved = await resolveAlias('@dual-project');
const flowResolved = await resolveAlias('@dual-flow', testContext.aliasFlowId);
if (projectResolved.imageId !== response.id || flowResolved.imageId !== response.id) {
throw new Error('Both aliases should resolve to same image');
}
log.detail('Project alias', '@dual-project ✓');
log.detail('Flow alias', '@dual-flow ✓');
});
log.section('ALIAS RESOLUTION TESTS COMPLETED');
}
main()
.then(() => exitWithTestResults())
.catch((error) => {
console.error('Unexpected error:', error);
process.exit(1);
});