mono/packages/media/cpp/ref/images/__tests__/e2e.test.ts
2026-04-12 22:38:43 +02:00

294 lines
11 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { app } from '@/index.js';
import fs from 'fs/promises';
import path from 'path';
describe('Images Product E2E', async () => {
const TEST_DIR = path.resolve(process.cwd(), 'tests/resize');
// Get all JPG files
// Note: This needs to be done before tests or inside a test that generates others,
// but in Vitest clean way is to maintain list or just map array.
// Since we need async fs, we can just do it in the suite body if top-level await is supported,
// or inside a single test that loops.
// Better pattern for dynamic tests:
const files = await fs.readdir(TEST_DIR);
const jpgFiles = files.filter(f => f.toLowerCase().endsWith('.jpg'));
if (jpgFiles.length === 0) {
it('should have test assets', () => {
console.warn('No JPG files found in tests/resize');
});
}
jpgFiles.forEach(filename => {
it(`should upload and resize ${filename}`, async () => {
const filePath = path.join(TEST_DIR, filename);
const stats = await fs.stat(filePath);
expect(stats.isFile()).toBe(true);
const fileContent = await fs.readFile(filePath);
const file = new File([fileContent], filename, { type: 'image/jpeg' });
const form = new FormData();
form.append('file', file);
form.append('width', '200');
form.append('height', '200');
form.append('format', 'webp');
const serverUrl = process.env.SERVER_URL || 'http://localhost:3333';
const postReq = new Request(`${serverUrl}/api/images`, {
method: 'POST',
body: form
});
// Execute Request
const res = await app.request(postReq);
// Verify Redirect
expect(res.status).toBe(303);
const location = res.headers.get('location');
expect(location).toBeTruthy();
expect(location).toContain('/api/images/cache/');
// Follow Redirect
// console.log(`Following redirect to: ${location}`);
const getReq = new Request(`${serverUrl}${location}`);
const res2 = await app.request(getReq);
console.log(res2.url);
// Verify Image Response
expect(res2.status).toBe(200);
expect(res2.headers.get('content-type')).toBe('image/webp');
const blob = await res2.blob();
expect(blob.size).toBeGreaterThan(0);
});
});
it('should use preset if provided', async () => {
const filename = jpgFiles[0]; // Use first available test file
const filePath = path.join(TEST_DIR, filename);
const form = new FormData();
const fileContent = await fs.readFile(filePath);
form.append('file', new File([fileContent], filename, { type: 'image/jpeg' }));
// form.append('preset', 'desktop:thumb'); // Now URL param
const serverUrl = process.env.SERVER_URL || 'http://localhost:3333';
const postReq = new Request(`${serverUrl}/api/images?preset=desktop:thumb`, {
method: 'POST',
body: form
});
const res = await app.request(postReq);
expect(res.status).toBe(303);
const location = res.headers.get('location');
expect(location).toContain('.avif'); // Preset uses avif
const getReq = new Request(`${serverUrl}${location}`);
const res2 = await app.request(getReq);
expect(res2.status).toBe(200);
expect(res2.headers.get('content-type')).toBe('image/avif'); // Verify format override
});
it('should allow overriding preset values', async () => {
const filename = jpgFiles[0];
const filePath = path.join(TEST_DIR, filename);
const form = new FormData();
const fileContent = await fs.readFile(filePath);
form.append('file', new File([fileContent], filename, { type: 'image/jpeg' }));
form.append('format', 'webp'); // Override 'avif' from desktop:thumb
form.append('width', '50'); // Override 150 from desktop:thumb
const serverUrl = process.env.SERVER_URL || 'http://localhost:3333';
// preset=desktop:thumb normally implies 150x150 avif
const postReq = new Request(`${serverUrl}/api/images?preset=desktop:thumb`, {
method: 'POST',
body: form
});
const res = await app.request(postReq);
expect(res.status).toBe(303);
const location = res.headers.get('location');
expect(location).toContain('.webp'); // Should be webp
const getReq = new Request(`${serverUrl}${location}`);
const res2 = await app.request(getReq);
expect(res2.status).toBe(200);
expect(res2.headers.get('content-type')).toBe('image/webp');
// We could also check dimensions if we parsed the image, but the cache hash check implicitly checks params
// hash includes `w50`
});
it('should forward to supabase and return valid json', async () => {
const filename = jpgFiles[0];
const filePath = path.join(TEST_DIR, filename);
const form = new FormData();
const fileContent = await fs.readFile(filePath);
form.append('file', new File([fileContent], filename, { type: 'image/jpeg' }));
const serverUrl = process.env.SERVER_URL || 'http://localhost:3333';
const postReq = new Request(`${serverUrl}/api/images?preset=desktop:thumb&forward=supabase`, {
method: 'POST',
body: form
});
const res = await app.request(postReq);
// Ensure we actually got a success response, otherwise fail
if (res.status !== 200) {
const text = await res.text();
console.error('Supabase response:', res.status, text);
}
expect(res.status).toBe(200);
if (res.status === 200) {
const data: any = await res.json();
expect(data.url).toBeTruthy();
expect(data.width).toBe(150); // desktop:thumb
expect(data.filename).toBeTruthy();
console.log('Supabase Upload URL:', data.url);
// Cleanup: Delete from Supabase
// Import dynamically to avoid top-level dependency if not needed
const { supabase } = await import('../../../commons/supabase.js');
const bucket = process.env.SUPABASE_BUCKET || 'pictures';
// data.filename should be 'cache/...' or just filename depending on implementation
// In index.ts we returned `storagePath` as `filename` field? Let's verify index.ts
// Yes: `filename: storagePath` which is `cache/${filename}`
const { error } = await supabase.storage
.from(bucket)
.remove([data.filename]);
if (error) {
console.error('Failed to cleanup Supabase test file:', error);
} else {
console.log('Cleaned up Supabase test file:', data.filename);
}
} else {
const text = await res.text();
console.warn('Supabase test skipped or failed (check env vars):', res.status, text);
// If internal server error due to missing creds, we might want to skip or fail gently
// But user asked for this test, so failing is appropriate if creds are missing but expected.
}
});
it('should transform image with operations', async () => {
const filename = jpgFiles[0];
const filePath = path.join(TEST_DIR, filename);
const form = new FormData();
const fileContent = await fs.readFile(filePath);
const file = new File([fileContent], filename, { type: 'image/jpeg' });
const operations = [
{ type: 'rotate', angle: 90 },
{ type: 'resize', width: 100, height: 100, fit: 'cover' }
];
form.append('file', file);
form.append('operations', JSON.stringify(operations));
const serverUrl = process.env.SERVER_URL || 'http://localhost:3333';
const postReq = new Request(`${serverUrl}/api/images/transform`, {
method: 'POST',
body: form
});
const res = await app.request(postReq);
expect(res.status).toBe(200);
const contentType = res.headers.get('content-type');
expect(contentType).toMatch(/image\/.*/);
const blob = await res.blob();
expect(blob.size).toBeGreaterThan(0);
// Basic check that it returned *something* successfully
});
it('should adjust image brightness and contrast', async () => {
const filename = jpgFiles[0];
const filePath = path.join(TEST_DIR, filename);
const form = new FormData();
const fileContent = await fs.readFile(filePath);
const file = new File([fileContent], filename, { type: 'image/jpeg' });
const operations = [
{ type: 'adjust', brightness: 1.2, contrast: 1.1 }
];
form.append('file', file);
form.append('operations', JSON.stringify(operations));
const serverUrl = process.env.SERVER_URL || 'http://localhost:3333';
const postReq = new Request(`${serverUrl}/api/images/transform`, {
method: 'POST',
body: form
});
const res = await app.request(postReq);
expect(res.status).toBe(200);
const contentType = res.headers.get('content-type');
expect(contentType).toMatch(/image\/.*/);
const blob = await res.blob();
expect(blob.size).toBeGreaterThan(0);
});
it('should extract exif data during supabase forward', async () => {
const filenames = jpgFiles.filter(f => f.includes('exif'));
if (filenames.length === 0) {
console.warn('Skipping EXIF test, test asset exif.jpg not found');
return;
}
const filename = filenames[0];
const filePath = path.join(TEST_DIR, filename);
const form = new FormData();
const fileContent = await fs.readFile(filePath);
form.append('file', new File([fileContent], filename, { type: 'image/jpeg' }));
const serverUrl = process.env.SERVER_URL || 'http://localhost:3333';
const postReq = new Request(`${serverUrl}/api/images?forward=supabase`, {
method: 'POST',
body: form
});
const res = await app.request(postReq);
expect(res.status).toBe(200);
const data: any = await res.json();
expect(data.url).toBeTruthy();
expect(data.meta).toBeTruthy();
console.log('Returned Meta:', data.meta);
// Verify some expected EXIF data if possible
// The file IMG20250911123721.jpg implies a date 2025-09-11
if (data.meta.dateTaken) {
const date = new Date(data.meta.dateTaken);
expect(date.getFullYear()).toBe(2025);
}
// Cleanup
if (data.filename) {
const { supabase } = await import('../../../commons/supabase.js');
const bucket = process.env.SUPABASE_BUCKET || 'pictures';
await supabase.storage.from(bucket).remove([data.filename]);
}
});
});