ai: validate flyer payload, prevent DB crashes, and add tests for /api/ai/flyers/process
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m29s

This commit is contained in:
2025-12-02 09:00:17 -08:00
parent 95d1b1798b
commit 79d095d6b9

View File

@@ -156,6 +156,66 @@ describe('AI Routes (/api/ai)', () => {
expect(response.status).toBe(400);
expect(response.body.message).toBe('Flyer image file is required.');
});
it('should return 400 if extractedData is missing from payload', async () => {
const badPayload = { checksum: 'c2', originalFileName: 'noflyer.jpg' }; // no extractedData
const response = await supertest(app)
.post('/api/ai/flyers/process')
.field('data', JSON.stringify(badPayload))
.attach('flyerImage', imagePath);
expect(response.status).toBe(400);
expect(response.body.message).toBe('Invalid request: extractedData is required.');
expect(mockedDb.createFlyerAndItems).not.toHaveBeenCalled();
});
it('should accept payload when extractedData.items is missing and save with empty items', async () => {
// Arrange: extractedData present but items missing
const partialPayload = {
checksum: 'test-checksum-2',
originalFileName: 'flyer2.jpg',
extractedData: { store_name: 'Partial Store' } // no items key
};
mockedDb.findFlyerByChecksum.mockResolvedValue(undefined);
mockedDb.createFlyerAndItems.mockResolvedValue({ flyer_id: 2, created_at: new Date().toISOString(), file_name: partialPayload.originalFileName, image_url: '/flyer-images/flyer2.jpg' } as any);
const response = await supertest(app)
.post('/api/ai/flyers/process')
.field('data', JSON.stringify(partialPayload))
.attach('flyerImage', imagePath);
expect(response.status).toBe(201);
expect(mockedDb.createFlyerAndItems).toHaveBeenCalledTimes(1);
// verify the items array passed to DB was an empty array
const callArgs = mockedDb.createFlyerAndItems.mock.calls[0]?.[1];
expect(callArgs).toBeDefined();
expect(Array.isArray(callArgs)).toBe(true);
expect(callArgs.length).toBe(0);
});
it('should fallback to a safe store name when store_name is missing', async () => {
const payloadNoStore = {
checksum: 'test-checksum-3',
originalFileName: 'flyer3.jpg',
extractedData: { items: [] } // store_name missing
};
mockedDb.findFlyerByChecksum.mockResolvedValue(undefined);
mockedDb.createFlyerAndItems.mockResolvedValue({ flyer_id: 3, created_at: new Date().toISOString(), file_name: payloadNoStore.originalFileName, image_url: '/flyer-images/flyer3.jpg' } as any);
const response = await supertest(app)
.post('/api/ai/flyers/process')
.field('data', JSON.stringify(payloadNoStore))
.attach('flyerImage', imagePath);
expect(response.status).toBe(201);
expect(mockedDb.createFlyerAndItems).toHaveBeenCalledTimes(1);
// verify the flyerData.store_name passed to DB was the fallback string
const flyerDataArg = mockedDb.createFlyerAndItems.mock.calls[0][0];
expect(flyerDataArg.store_name).toContain('Unknown Store');
});
});
describe('when user is authenticated', () => {