|
|
|
|
@@ -37,7 +37,7 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
extractCoreDataFromFlyerImage: vi.fn(),
|
|
|
|
|
} as unknown as AIService;
|
|
|
|
|
mockPersonalizationRepo = {
|
|
|
|
|
getAllMasterItems: vi.fn().mockResolvedValue([]),
|
|
|
|
|
getAllMasterItems: vi.fn().mockResolvedValue({ items: [], total: 0 }),
|
|
|
|
|
} as unknown as PersonalizationRepository;
|
|
|
|
|
|
|
|
|
|
service = new FlyerAiProcessor(mockAiService, mockPersonalizationRepo);
|
|
|
|
|
@@ -86,9 +86,9 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
const imagePaths = [{ path: 'page1.jpg', mimetype: 'image/jpeg' }];
|
|
|
|
|
|
|
|
|
|
// Act & Assert
|
|
|
|
|
await expect(
|
|
|
|
|
service.extractAndValidateData(imagePaths, jobData, logger),
|
|
|
|
|
).rejects.toThrow(dbError);
|
|
|
|
|
await expect(service.extractAndValidateData(imagePaths, jobData, logger)).rejects.toThrow(
|
|
|
|
|
dbError,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Verify that the process stops before calling the AI service
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).not.toHaveBeenCalled();
|
|
|
|
|
@@ -103,8 +103,20 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_to: '2024-01-07',
|
|
|
|
|
store_address: '123 Good St',
|
|
|
|
|
items: [
|
|
|
|
|
{ item: 'Priced Item 1', price_in_cents: 199, price_display: '$1.99', quantity: '1', category_name: 'A' },
|
|
|
|
|
{ item: 'Priced Item 2', price_in_cents: 299, price_display: '$2.99', quantity: '1', category_name: 'B' },
|
|
|
|
|
{
|
|
|
|
|
item: 'Priced Item 1',
|
|
|
|
|
price_in_cents: 199,
|
|
|
|
|
price_display: '$1.99',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'A',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
item: 'Priced Item 2',
|
|
|
|
|
price_in_cents: 299,
|
|
|
|
|
price_display: '$2.99',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'B',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
vi.mocked(mockAiService.extractCoreDataFromFlyerImage).mockResolvedValue(mockAiResponse);
|
|
|
|
|
@@ -128,7 +140,9 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_to: null,
|
|
|
|
|
store_address: null,
|
|
|
|
|
};
|
|
|
|
|
vi.mocked(mockAiService.extractCoreDataFromFlyerImage).mockResolvedValue(invalidResponse as any);
|
|
|
|
|
vi.mocked(mockAiService.extractCoreDataFromFlyerImage).mockResolvedValue(
|
|
|
|
|
invalidResponse as any,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const imagePaths = [{ path: 'page1.jpg', mimetype: 'image/jpeg' }];
|
|
|
|
|
await expect(service.extractAndValidateData(imagePaths, jobData, logger)).rejects.toThrow(
|
|
|
|
|
@@ -140,7 +154,15 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
const jobData = createMockJobData({});
|
|
|
|
|
const mockAiResponse = {
|
|
|
|
|
store_name: null, // Missing store name
|
|
|
|
|
items: [{ item: 'Test Item', price_display: '$1.99', price_in_cents: 199, quantity: 'each', category_name: 'Grocery' }],
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
item: 'Test Item',
|
|
|
|
|
price_display: '$1.99',
|
|
|
|
|
price_in_cents: 199,
|
|
|
|
|
quantity: 'each',
|
|
|
|
|
category_name: 'Grocery',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
valid_from: '2024-01-01',
|
|
|
|
|
valid_to: '2024-01-07',
|
|
|
|
|
store_address: null,
|
|
|
|
|
@@ -187,9 +209,27 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_to: '2024-01-07',
|
|
|
|
|
store_address: '123 Test St',
|
|
|
|
|
items: [
|
|
|
|
|
{ item: 'Priced Item', price_in_cents: 199, price_display: '$1.99', quantity: '1', category_name: 'A' },
|
|
|
|
|
{ item: 'Unpriced Item 1', price_in_cents: null, price_display: 'See store', quantity: '1', category_name: 'B' },
|
|
|
|
|
{ item: 'Unpriced Item 2', price_in_cents: null, price_display: 'FREE', quantity: '1', category_name: 'C' },
|
|
|
|
|
{
|
|
|
|
|
item: 'Priced Item',
|
|
|
|
|
price_in_cents: 199,
|
|
|
|
|
price_display: '$1.99',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'A',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
item: 'Unpriced Item 1',
|
|
|
|
|
price_in_cents: null,
|
|
|
|
|
price_display: 'See store',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'B',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
item: 'Unpriced Item 2',
|
|
|
|
|
price_in_cents: null,
|
|
|
|
|
price_display: 'FREE',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'C',
|
|
|
|
|
},
|
|
|
|
|
], // 1/3 = 33% have price, which is < 50%
|
|
|
|
|
};
|
|
|
|
|
vi.mocked(mockAiService.extractCoreDataFromFlyerImage).mockResolvedValue(mockAiResponse);
|
|
|
|
|
@@ -200,7 +240,9 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
|
|
|
|
|
expect(result.needsReview).toBe(true);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ qualityIssues: ['Low price quality (33% of items have a price)'] }),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
qualityIssues: ['Low price quality (33% of items have a price)'],
|
|
|
|
|
}),
|
|
|
|
|
expect.stringContaining('AI response has quality issues.'),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
@@ -216,10 +258,34 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_to: '2024-01-07',
|
|
|
|
|
store_address: '123 Test St',
|
|
|
|
|
items: [
|
|
|
|
|
{ item: 'Priced Item 1', price_in_cents: 199, price_display: '$1.99', quantity: '1', category_name: 'A' },
|
|
|
|
|
{ item: 'Priced Item 2', price_in_cents: 299, price_display: '$2.99', quantity: '1', category_name: 'B' },
|
|
|
|
|
{ item: 'Priced Item 3', price_in_cents: 399, price_display: '$3.99', quantity: '1', category_name: 'C' },
|
|
|
|
|
{ item: 'Unpriced Item 1', price_in_cents: null, price_display: 'See store', quantity: '1', category_name: 'D' },
|
|
|
|
|
{
|
|
|
|
|
item: 'Priced Item 1',
|
|
|
|
|
price_in_cents: 199,
|
|
|
|
|
price_display: '$1.99',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'A',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
item: 'Priced Item 2',
|
|
|
|
|
price_in_cents: 299,
|
|
|
|
|
price_display: '$2.99',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'B',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
item: 'Priced Item 3',
|
|
|
|
|
price_in_cents: 399,
|
|
|
|
|
price_display: '$3.99',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'C',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
item: 'Unpriced Item 1',
|
|
|
|
|
price_in_cents: null,
|
|
|
|
|
price_display: 'See store',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'D',
|
|
|
|
|
},
|
|
|
|
|
], // 3/4 = 75% have price. This is > 50% (default) but < 80% (custom).
|
|
|
|
|
};
|
|
|
|
|
vi.mocked(mockAiService.extractCoreDataFromFlyerImage).mockResolvedValue(mockAiResponse);
|
|
|
|
|
@@ -233,7 +299,9 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
// Because 75% < 80%, it should be flagged for review.
|
|
|
|
|
expect(result.needsReview).toBe(true);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ qualityIssues: ['Low price quality (75% of items have a price)'] }),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
qualityIssues: ['Low price quality (75% of items have a price)'],
|
|
|
|
|
}),
|
|
|
|
|
expect.stringContaining('AI response has quality issues.'),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
@@ -243,9 +311,17 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
const mockAiResponse = {
|
|
|
|
|
store_name: 'Test Store',
|
|
|
|
|
valid_from: null, // Missing date
|
|
|
|
|
valid_to: null, // Missing date
|
|
|
|
|
valid_to: null, // Missing date
|
|
|
|
|
store_address: '123 Test St',
|
|
|
|
|
items: [{ item: 'Test Item', price_in_cents: 199, price_display: '$1.99', quantity: '1', category_name: 'A' }],
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
item: 'Test Item',
|
|
|
|
|
price_in_cents: 199,
|
|
|
|
|
price_display: '$1.99',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'A',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
vi.mocked(mockAiService.extractCoreDataFromFlyerImage).mockResolvedValue(mockAiResponse);
|
|
|
|
|
const { logger } = await import('./logger.server');
|
|
|
|
|
@@ -264,7 +340,7 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
const jobData = createMockJobData({});
|
|
|
|
|
const mockAiResponse = {
|
|
|
|
|
store_name: null, // Issue 1
|
|
|
|
|
items: [], // Issue 2
|
|
|
|
|
items: [], // Issue 2
|
|
|
|
|
valid_from: null, // Issue 3
|
|
|
|
|
valid_to: null,
|
|
|
|
|
store_address: null,
|
|
|
|
|
@@ -277,7 +353,14 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
|
|
|
|
|
expect(result.needsReview).toBe(true);
|
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
|
|
|
{ rawData: mockAiResponse, qualityIssues: ['Missing store name', 'No items were extracted', 'Missing both valid_from and valid_to dates'] },
|
|
|
|
|
{
|
|
|
|
|
rawData: mockAiResponse,
|
|
|
|
|
qualityIssues: [
|
|
|
|
|
'Missing store name',
|
|
|
|
|
'No items were extracted',
|
|
|
|
|
'Missing both valid_from and valid_to dates',
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
'AI response has quality issues. Flagging for review. Issues: Missing store name, No items were extracted, Missing both valid_from and valid_to dates',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
@@ -291,7 +374,15 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_from: '2024-01-01',
|
|
|
|
|
valid_to: '2024-01-07',
|
|
|
|
|
store_address: '123 Test St',
|
|
|
|
|
items: [{ item: 'Test Item', price_in_cents: 199, price_display: '$1.99', quantity: '1', category_name: 'A' }],
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
item: 'Test Item',
|
|
|
|
|
price_in_cents: 199,
|
|
|
|
|
price_display: '$1.99',
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'A',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
vi.mocked(mockAiService.extractCoreDataFromFlyerImage).mockResolvedValue(mockAiResponse);
|
|
|
|
|
|
|
|
|
|
@@ -300,7 +391,11 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenCalledWith(
|
|
|
|
|
imagePaths, [], undefined, '456 Fallback Ave', logger
|
|
|
|
|
imagePaths,
|
|
|
|
|
[],
|
|
|
|
|
undefined,
|
|
|
|
|
'456 Fallback Ave',
|
|
|
|
|
logger,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -323,8 +418,22 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_to: '2025-01-07',
|
|
|
|
|
store_address: '123 Batch St',
|
|
|
|
|
items: [
|
|
|
|
|
{ item: 'Item A', price_display: '$1', price_in_cents: 100, quantity: '1', category_name: 'Cat A', master_item_id: 1 },
|
|
|
|
|
{ item: 'Item B', price_display: '$2', price_in_cents: 200, quantity: '1', category_name: 'Cat B', master_item_id: 2 },
|
|
|
|
|
{
|
|
|
|
|
item: 'Item A',
|
|
|
|
|
price_display: '$1',
|
|
|
|
|
price_in_cents: 100,
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'Cat A',
|
|
|
|
|
master_item_id: 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
item: 'Item B',
|
|
|
|
|
price_display: '$2',
|
|
|
|
|
price_in_cents: 200,
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'Cat B',
|
|
|
|
|
master_item_id: 2,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -334,7 +443,14 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_to: null,
|
|
|
|
|
store_address: null,
|
|
|
|
|
items: [
|
|
|
|
|
{ item: 'Item C', price_display: '$3', price_in_cents: 300, quantity: '1', category_name: 'Cat C', master_item_id: 3 },
|
|
|
|
|
{
|
|
|
|
|
item: 'Item C',
|
|
|
|
|
price_display: '$3',
|
|
|
|
|
price_in_cents: 300,
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'Cat C',
|
|
|
|
|
master_item_id: 3,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -351,8 +467,22 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenCalledTimes(2);
|
|
|
|
|
|
|
|
|
|
// 2. Check the arguments for each call
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenNthCalledWith(1, imagePaths.slice(0, 4), [], undefined, undefined, logger);
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenNthCalledWith(2, imagePaths.slice(4, 5), [], undefined, undefined, logger);
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenNthCalledWith(
|
|
|
|
|
1,
|
|
|
|
|
imagePaths.slice(0, 4),
|
|
|
|
|
[],
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
logger,
|
|
|
|
|
);
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenNthCalledWith(
|
|
|
|
|
2,
|
|
|
|
|
imagePaths.slice(4, 5),
|
|
|
|
|
[],
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
logger,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 3. Check the merged data
|
|
|
|
|
expect(result.data.store_name).toBe('Batch 1 Store'); // Metadata from the first batch
|
|
|
|
|
@@ -362,11 +492,13 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
|
|
|
|
|
// 4. Check that items from both batches are merged
|
|
|
|
|
expect(result.data.items).toHaveLength(3);
|
|
|
|
|
expect(result.data.items).toEqual(expect.arrayContaining([
|
|
|
|
|
expect.objectContaining({ item: 'Item A' }),
|
|
|
|
|
expect.objectContaining({ item: 'Item B' }),
|
|
|
|
|
expect.objectContaining({ item: 'Item C' }),
|
|
|
|
|
]));
|
|
|
|
|
expect(result.data.items).toEqual(
|
|
|
|
|
expect.arrayContaining([
|
|
|
|
|
expect.objectContaining({ item: 'Item A' }),
|
|
|
|
|
expect.objectContaining({ item: 'Item B' }),
|
|
|
|
|
expect.objectContaining({ item: 'Item C' }),
|
|
|
|
|
]),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 5. Check that the job is not flagged for review
|
|
|
|
|
expect(result.needsReview).toBe(false);
|
|
|
|
|
@@ -376,7 +508,11 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
// Arrange
|
|
|
|
|
const jobData = createMockJobData({});
|
|
|
|
|
const imagePaths = [
|
|
|
|
|
{ path: 'page1.jpg', mimetype: 'image/jpeg' }, { path: 'page2.jpg', mimetype: 'image/jpeg' }, { path: 'page3.jpg', mimetype: 'image/jpeg' }, { path: 'page4.jpg', mimetype: 'image/jpeg' }, { path: 'page5.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page1.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page2.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page3.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page4.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page5.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const mockAiResponseBatch1 = {
|
|
|
|
|
@@ -385,7 +521,14 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_to: '2025-01-07',
|
|
|
|
|
store_address: '123 Good St',
|
|
|
|
|
items: [
|
|
|
|
|
{ item: 'Item A', price_display: '$1', price_in_cents: 100, quantity: '1', category_name: 'Cat A', master_item_id: 1 },
|
|
|
|
|
{
|
|
|
|
|
item: 'Item A',
|
|
|
|
|
price_display: '$1',
|
|
|
|
|
price_in_cents: 100,
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'Cat A',
|
|
|
|
|
master_item_id: 1,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -416,11 +559,45 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
// Arrange
|
|
|
|
|
const jobData = createMockJobData({});
|
|
|
|
|
const imagePaths = [
|
|
|
|
|
{ path: 'page1.jpg', mimetype: 'image/jpeg' }, { path: 'page2.jpg', mimetype: 'image/jpeg' }, { path: 'page3.jpg', mimetype: 'image/jpeg' }, { path: 'page4.jpg', mimetype: 'image/jpeg' }, { path: 'page5.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page1.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page2.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page3.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page4.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
{ path: 'page5.jpg', mimetype: 'image/jpeg' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const mockAiResponseBatch1 = { store_name: null, valid_from: '2025-01-01', valid_to: '2025-01-07', store_address: null, items: [{ item: 'Item A', price_display: '$1', price_in_cents: 100, quantity: '1', category_name: 'Cat A', master_item_id: 1 }] };
|
|
|
|
|
const mockAiResponseBatch2 = { store_name: 'Batch 2 Store', valid_from: '2025-01-02', valid_to: null, store_address: '456 Subsequent St', items: [{ item: 'Item C', price_display: '$3', price_in_cents: 300, quantity: '1', category_name: 'Cat C', master_item_id: 3 }] };
|
|
|
|
|
const mockAiResponseBatch1 = {
|
|
|
|
|
store_name: null,
|
|
|
|
|
valid_from: '2025-01-01',
|
|
|
|
|
valid_to: '2025-01-07',
|
|
|
|
|
store_address: null,
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
item: 'Item A',
|
|
|
|
|
price_display: '$1',
|
|
|
|
|
price_in_cents: 100,
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'Cat A',
|
|
|
|
|
master_item_id: 1,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
const mockAiResponseBatch2 = {
|
|
|
|
|
store_name: 'Batch 2 Store',
|
|
|
|
|
valid_from: '2025-01-02',
|
|
|
|
|
valid_to: null,
|
|
|
|
|
store_address: '456 Subsequent St',
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
item: 'Item C',
|
|
|
|
|
price_display: '$3',
|
|
|
|
|
price_in_cents: 300,
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'Cat C',
|
|
|
|
|
master_item_id: 3,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vi.mocked(mockAiService.extractCoreDataFromFlyerImage)
|
|
|
|
|
.mockResolvedValueOnce(mockAiResponseBatch1)
|
|
|
|
|
@@ -453,7 +630,14 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
valid_to: '2025-02-07',
|
|
|
|
|
store_address: '789 Single St',
|
|
|
|
|
items: [
|
|
|
|
|
{ item: 'Item X', price_display: '$10', price_in_cents: 1000, quantity: '1', category_name: 'Cat X', master_item_id: 10 },
|
|
|
|
|
{
|
|
|
|
|
item: 'Item X',
|
|
|
|
|
price_display: '$10',
|
|
|
|
|
price_in_cents: 1000,
|
|
|
|
|
quantity: '1',
|
|
|
|
|
category_name: 'Cat X',
|
|
|
|
|
master_item_id: 10,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -468,9 +652,15 @@ describe('FlyerAiProcessor', () => {
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
|
|
// 2. Check the arguments for the single call.
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenCalledWith(imagePaths, [], undefined, undefined, logger);
|
|
|
|
|
expect(mockAiService.extractCoreDataFromFlyerImage).toHaveBeenCalledWith(
|
|
|
|
|
imagePaths,
|
|
|
|
|
[],
|
|
|
|
|
undefined,
|
|
|
|
|
undefined,
|
|
|
|
|
logger,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 3. Check that the final data matches the single batch's data.
|
|
|
|
|
expect(result.data).toEqual(mockAiResponse);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|