one moar time - we can do it?
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 34m45s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 34m45s
This commit is contained in:
@@ -21,26 +21,34 @@ vi.mock('./logger.client', () => ({
|
||||
vi.mock('./apiClient', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./apiClient')>();
|
||||
return {
|
||||
apiFetch: (url: string, options: RequestInit = {}, apiOptions: import('./apiClient').ApiOptions = {}) => {
|
||||
// The base URL must match what MSW is expecting.
|
||||
const fullUrl = url.startsWith('/')
|
||||
? `http://localhost/api${url}`
|
||||
: url;
|
||||
// FIX: Correctly handle the tokenOverride by merging it into the request headers.
|
||||
if (apiOptions.tokenOverride) {
|
||||
options.headers = { ...options.headers, Authorization: `Bearer ${apiOptions.tokenOverride}` };
|
||||
}
|
||||
apiFetch: (url: string, options: RequestInit = {}, apiOptions: import('./apiClient').ApiOptions = {}) => {
|
||||
const fullUrl = url.startsWith('/') ? `http://localhost/api${url}` : url;
|
||||
options.headers = new Headers(options.headers); // Ensure headers is a Headers object
|
||||
|
||||
// FIX: Manually construct a Request object. This ensures that when `options.body`
|
||||
// is FormData, the contained File objects are correctly processed by MSW's parsers,
|
||||
// preserving their original filenames instead of defaulting to "blob".
|
||||
return fetch(new Request(fullUrl, options));
|
||||
// FIX: Manually construct a Request object. This is a critical step. When `fetch` is
|
||||
// called directly with FormData, some environments (like JSDOM) can lose the filename.
|
||||
// Wrapping it in `new Request()` helps preserve the metadata.
|
||||
const request = new Request(fullUrl, options);
|
||||
console.log(`[apiFetch MOCK] Created Request object for URL: ${request.url}. Content-Type will be set by browser/fetch.`);
|
||||
return fetch(request);
|
||||
if (apiOptions.tokenOverride) {
|
||||
options.headers.set('Authorization', `Bearer ${apiOptions.tokenOverride}`);
|
||||
}
|
||||
|
||||
// ================================= WORKAROUND FOR JSDOM FILE NAME BUG =================================
|
||||
// JSDOM's fetch implementation (undici) loses filenames in FormData.
|
||||
// SOLUTION: Before fetch is called, we find the file, extract its real name,
|
||||
// and add it to a custom header. The MSW handler will read this header.
|
||||
if (options.body instanceof FormData) {
|
||||
console.log(`[apiFetch MOCK] FormData detected. Searching for file to preserve its name.`);
|
||||
for (const value of (options.body as FormData).values()) {
|
||||
if (value instanceof File) {
|
||||
console.log(`[apiFetch MOCK] Found file: '${value.name}'. Setting 'X-Test-Filename' header.`);
|
||||
options.headers.set('X-Test-Filename', value.name);
|
||||
// We only expect one file per request in these tests, so we can break.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ======================================= END WORKAROUND ===============================================
|
||||
|
||||
const request = new Request(fullUrl, options);
|
||||
console.log(`[apiFetch MOCK] Executing fetch for URL: ${request.url}.`);
|
||||
return fetch(request);
|
||||
},
|
||||
// Add a mock for ApiOptions to satisfy the compiler
|
||||
ApiOptions: vi.fn()
|
||||
@@ -55,32 +63,30 @@ const server = setupServer(
|
||||
let body: Record<string, unknown> | FormData = {};
|
||||
let bodyForSpy: Record<string, unknown> = {};
|
||||
const contentType = request.headers.get('Content-Type');
|
||||
console.log(`\n--- [MSW HANDLER] Intercepted POST to '${String(params.endpoint)}'. Content-Type: ${contentType} ---`);
|
||||
console.log(`\n--- [MSW HANDLER] Intercepted POST to '${String(params.endpoint)}' ---`);
|
||||
|
||||
if (contentType?.includes('application/json')) {
|
||||
const parsedBody = await request.json();
|
||||
console.log('[MSW HANDLER] Body is JSON. Parsed:', parsedBody);
|
||||
if (typeof parsedBody === 'object' && parsedBody !== null && !Array.isArray(parsedBody)) {
|
||||
body = parsedBody as Record<string, unknown>;
|
||||
bodyForSpy = body; // For JSON, the body is already a plain object.
|
||||
}
|
||||
} else if (contentType?.includes('multipart/form-data')) {
|
||||
body = await request.formData();
|
||||
console.log('[MSW HANDLER] Body is FormData. Iterating entries...');
|
||||
// FIX: The `instanceof File` check is unreliable in JSDOM.
|
||||
// We will use "duck typing" to check if an object looks like a file.
|
||||
// WORKAROUND PART 2: Read the filename from our custom header.
|
||||
const preservedFilename = request.headers.get('X-Test-Filename');
|
||||
console.log(`[MSW HANDLER] Reading 'X-Test-Filename' header. Value: '${preservedFilename}'`);
|
||||
|
||||
for (const [key, value] of (body as FormData).entries()) {
|
||||
// A robust check for a File-like object.
|
||||
const isFile = typeof value === 'object' && value !== null && 'name' in value && 'size' in value && 'type' in value;
|
||||
console.log(`[MSW HANDLER] FormData Entry -> Key: '${key}', Type: ${typeof value}, IsFile: ${isFile}`);
|
||||
if (isFile) {
|
||||
const file = value as File;
|
||||
console.log(`[MSW HANDLER DEBUG] -> Identified as File. Name: '${file.name}', Size: ${file.size}, Type: '${file.type}'`);
|
||||
const finalName = preservedFilename || file.name;
|
||||
console.log(`[MSW HANDLER DEBUG] Found file-like object for key '${key}'. Original name: '${file.name}'. Using preserved name: '${finalName}'`);
|
||||
if (!bodyForSpy[key]) {
|
||||
bodyForSpy[key] = { name: file.name, size: file.size, type: file.type };
|
||||
bodyForSpy[key] = { name: finalName, size: file.size, type: file.type };
|
||||
}
|
||||
} else {
|
||||
console.log(`[MSW HANDLER DEBUG] Found text field. Key: '${key}', Value: '${String(value)}'`);
|
||||
bodyForSpy[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +127,7 @@ describe('AI API Client (Network Mocking with MSW)', () => {
|
||||
|
||||
describe('uploadAndProcessFlyer', () => {
|
||||
it('should construct FormData with file and checksum and send a POST request', async () => {
|
||||
const mockFile = new File(['dummy-flyer-content'], 'flyer.pdf', { type: 'application/pdf' });
|
||||
const mockFile = new File(['this is a test pdf'], 'flyer.pdf', { type: 'application/pdf' });
|
||||
const checksum = 'checksum-abc-123';
|
||||
console.log(`\n--- [TEST START] uploadAndProcessFlyer ---`);
|
||||
console.log('[TEST ARRANGE] Created mock file:', { name: mockFile.name, size: mockFile.size, type: mockFile.type });
|
||||
@@ -168,7 +174,7 @@ describe('AI API Client (Network Mocking with MSW)', () => {
|
||||
|
||||
describe('isImageAFlyer', () => {
|
||||
it('should construct FormData and send a POST request', async () => {
|
||||
const mockFile = new File(['dummy'], 'flyer.jpg', { type: 'image/jpeg' });
|
||||
const mockFile = new File(['dummy image content'], 'flyer.jpg', { type: 'image/jpeg' });
|
||||
console.log(`\n--- [TEST START] isImageAFlyer ---`);
|
||||
await aiApiClient.isImageAFlyer(mockFile, 'test-token');
|
||||
|
||||
@@ -189,7 +195,7 @@ describe('AI API Client (Network Mocking with MSW)', () => {
|
||||
|
||||
describe('extractAddressFromImage', () => {
|
||||
it('should construct FormData and send a POST request', async () => {
|
||||
const mockFile = new File(['dummy'], 'flyer.jpg', { type: 'image/jpeg' });
|
||||
const mockFile = new File(['dummy image content'], 'flyer.jpg', { type: 'image/jpeg' });
|
||||
console.log(`\n--- [TEST START] extractAddressFromImage ---`);
|
||||
await aiApiClient.extractAddressFromImage(mockFile, 'test-token');
|
||||
|
||||
@@ -209,7 +215,7 @@ describe('AI API Client (Network Mocking with MSW)', () => {
|
||||
|
||||
describe('extractLogoFromImage', () => {
|
||||
it('should construct FormData and send a POST request', async () => {
|
||||
const mockFile = new File(['logo'], 'logo.jpg', { type: 'image/jpeg' });
|
||||
const mockFile = new File(['dummy image content'], 'logo.jpg', { type: 'image/jpeg' });
|
||||
console.log(`\n--- [TEST START] extractLogoFromImage ---`);
|
||||
await aiApiClient.extractLogoFromImage([mockFile], 'test-token');
|
||||
|
||||
@@ -345,7 +351,7 @@ describe('AI API Client (Network Mocking with MSW)', () => {
|
||||
|
||||
describe('rescanImageArea', () => {
|
||||
it('should construct FormData with image, cropArea, and extractionType', async () => {
|
||||
const mockFile = new File(['dummy-content'], 'flyer-page.jpg', { type: 'image/jpeg' });
|
||||
const mockFile = new File(['dummy image content'], 'flyer-page.jpg', { type: 'image/jpeg' });
|
||||
const cropArea = { x: 10, y: 20, width: 100, height: 50 };
|
||||
const extractionType = 'item_details' as const;
|
||||
console.log(`\n--- [TEST START] rescanImageArea ---`);
|
||||
|
||||
Reference in New Issue
Block a user