Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
113 lines
4.5 KiB
TypeScript
113 lines
4.5 KiB
TypeScript
// src/tests/e2e/flyer-upload.e2e.test.ts
|
|
import { describe, it, expect, afterAll } from 'vitest';
|
|
import crypto from 'crypto';
|
|
import * as apiClient from '../../services/apiClient';
|
|
import { getPool } from '../../services/db/connection.db';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import { cleanupDb } from '../utils/cleanup';
|
|
import { poll } from '../utils/poll';
|
|
|
|
/**
|
|
* @vitest-environment node
|
|
*/
|
|
|
|
describe('E2E Flyer Upload and Processing Workflow', () => {
|
|
const uniqueId = Date.now();
|
|
const userEmail = `e2e-uploader-${uniqueId}@example.com`;
|
|
const userPassword = 'StrongPassword123!';
|
|
|
|
let authToken: string;
|
|
let userId: string | null = null;
|
|
let flyerId: number | null = null;
|
|
let storeId: number | null = null;
|
|
|
|
afterAll(async () => {
|
|
// Use the centralized cleanup utility for robustness.
|
|
await cleanupDb({
|
|
userIds: [userId],
|
|
flyerIds: [flyerId],
|
|
storeIds: [storeId],
|
|
});
|
|
});
|
|
|
|
it('should allow a user to upload a flyer and wait for processing to complete', async () => {
|
|
// 1. Register a new user
|
|
const registerResponse = await apiClient.registerUser(userEmail, userPassword, 'E2E Flyer Uploader');
|
|
expect(registerResponse.status).toBe(201);
|
|
|
|
// 2. Login to get the access token
|
|
const loginResponse = await apiClient.loginUser(userEmail, userPassword, false);
|
|
expect(loginResponse.status).toBe(200);
|
|
const loginData = await loginResponse.json();
|
|
authToken = loginData.token;
|
|
userId = loginData.userprofile.user.user_id;
|
|
expect(authToken).toBeDefined();
|
|
|
|
// 3. Prepare the flyer file
|
|
// We try to use the existing test asset if available, otherwise create a dummy buffer.
|
|
// Note: In a real E2E scenario against a live AI service, a valid image is required.
|
|
// If the AI service is mocked or stubbed in this environment, a dummy buffer might suffice.
|
|
let fileBuffer: Buffer;
|
|
let fileName = `e2e-test-flyer-${uniqueId}.jpg`;
|
|
|
|
const assetPath = path.resolve(__dirname, '../assets/test-flyer-image.jpg');
|
|
if (fs.existsSync(assetPath)) {
|
|
const rawBuffer = fs.readFileSync(assetPath);
|
|
// Append unique ID to ensure unique checksum for every test run
|
|
fileBuffer = Buffer.concat([rawBuffer, Buffer.from(uniqueId.toString())]);
|
|
} else {
|
|
// Fallback to a minimal valid JPEG header + random data if asset is missing
|
|
// (This might fail if the backend does strict image validation/processing)
|
|
fileBuffer = Buffer.concat([
|
|
Buffer.from([0xff, 0xd8, 0xff, 0xe0]), // JPEG Start of Image
|
|
Buffer.from(uniqueId.toString())
|
|
]);
|
|
}
|
|
|
|
// Create a File object for the apiClient
|
|
// FIX: The Node.js `Buffer` type can be incompatible with the web `File` API's
|
|
// expected `BlobPart` type in some TypeScript configurations. Explicitly creating
|
|
// a `Uint8Array` from the buffer ensures compatibility and resolves the type error.
|
|
// `Uint8Array` is a valid `BufferSource`, which is a valid `BlobPart`.
|
|
const flyerFile = new File([new Uint8Array(fileBuffer)], fileName, { type: 'image/jpeg' });
|
|
|
|
// Calculate checksum (required by the API)
|
|
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
|
|
|
// 4. Upload the flyer
|
|
const uploadResponse = await apiClient.uploadAndProcessFlyer(flyerFile, checksum, authToken);
|
|
|
|
expect(uploadResponse.status).toBe(202);
|
|
const uploadData = await uploadResponse.json();
|
|
const jobId = uploadData.jobId;
|
|
expect(jobId).toBeDefined();
|
|
|
|
// 5. Poll for job completion using the new utility
|
|
const jobStatus = await poll(
|
|
async () => {
|
|
const statusResponse = await apiClient.getJobStatus(jobId, authToken);
|
|
return statusResponse.json();
|
|
},
|
|
(status) => status.state === 'completed' || status.state === 'failed',
|
|
{ timeout: 180000, interval: 3000, description: 'flyer processing job completion' },
|
|
);
|
|
|
|
if (jobStatus.state === 'failed') {
|
|
// Log the failure reason for easier debugging in CI/CD environments.
|
|
console.error('E2E flyer processing job failed. Reason:', jobStatus.failedReason);
|
|
}
|
|
|
|
expect(jobStatus.state).toBe('completed');
|
|
flyerId = jobStatus.returnValue?.flyerId;
|
|
expect(flyerId).toBeTypeOf('number');
|
|
|
|
// Fetch the store_id associated with the created flyer for robust cleanup
|
|
if (flyerId) {
|
|
const flyerRes = await getPool().query('SELECT store_id FROM public.flyers WHERE flyer_id = $1', [flyerId]);
|
|
if (flyerRes.rows.length > 0) {
|
|
storeId = flyerRes.rows[0].store_id;
|
|
}
|
|
}
|
|
}, 240000); // Extended timeout for AI processing
|
|
}); |