Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 41s
244 lines
6.9 KiB
TypeScript
244 lines
6.9 KiB
TypeScript
// src/services/flyerDataTransformer.test.ts
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { FlyerDataTransformer } from './flyerDataTransformer';
|
|
import { logger as mockLogger } from './logger.server';
|
|
import { generateFlyerIcon } from '../utils/imageProcessor';
|
|
import type { AiProcessorResult } from './flyerAiProcessor.server';
|
|
import type { FlyerItemInsert } from '../types';
|
|
|
|
// Mock the dependencies
|
|
vi.mock('../utils/imageProcessor', () => ({
|
|
generateFlyerIcon: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('./logger.server', () => ({
|
|
logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
|
|
}));
|
|
|
|
describe('FlyerDataTransformer', () => {
|
|
let transformer: FlyerDataTransformer;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
transformer = new FlyerDataTransformer();
|
|
|
|
// Provide a default mock implementation for generateFlyerIcon
|
|
vi.mocked(generateFlyerIcon).mockResolvedValue('icon-flyer-page-1.webp');
|
|
});
|
|
|
|
it('should transform AI data into database-ready format with a user ID', async () => {
|
|
// Arrange
|
|
const aiResult: AiProcessorResult = {
|
|
data: {
|
|
store_name: 'Test Store',
|
|
valid_from: '2024-01-01',
|
|
valid_to: '2024-01-07',
|
|
store_address: '123 Test St',
|
|
items: [
|
|
{
|
|
item: 'Milk',
|
|
price_display: '$3.99',
|
|
price_in_cents: 399,
|
|
quantity: '1L',
|
|
category_name: 'Dairy',
|
|
master_item_id: 10,
|
|
},
|
|
{
|
|
item: 'Bread',
|
|
price_display: '$2.49',
|
|
price_in_cents: 249,
|
|
quantity: '1 loaf',
|
|
category_name: 'Bakery',
|
|
master_item_id: null,
|
|
},
|
|
],
|
|
},
|
|
needsReview: false,
|
|
};
|
|
const imagePaths = [{ path: '/uploads/flyer-page-1.jpg', mimetype: 'image/jpeg' }];
|
|
const originalFileName = 'my-flyer.pdf';
|
|
const checksum = 'checksum-abc-123';
|
|
const userId = 'user-xyz-456';
|
|
|
|
// Act
|
|
const { flyerData, itemsForDb } = await transformer.transform(
|
|
aiResult,
|
|
imagePaths,
|
|
originalFileName,
|
|
checksum,
|
|
userId,
|
|
mockLogger,
|
|
);
|
|
|
|
// Assert
|
|
// 0. Check logging
|
|
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
'Starting data transformation from AI output to database format.',
|
|
);
|
|
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
{ itemCount: 2, storeName: 'Test Store' },
|
|
'Data transformation complete.',
|
|
);
|
|
|
|
// 1. Check flyer data
|
|
expect(flyerData).toEqual({
|
|
file_name: originalFileName,
|
|
image_url: '/flyer-images/flyer-page-1.jpg',
|
|
icon_url: '/flyer-images/icons/icon-flyer-page-1.webp',
|
|
checksum,
|
|
store_name: 'Test Store',
|
|
valid_from: '2024-01-01',
|
|
valid_to: '2024-01-07',
|
|
store_address: '123 Test St',
|
|
item_count: 2,
|
|
status: 'processed',
|
|
uploaded_by: userId,
|
|
});
|
|
|
|
// 2. Check item data
|
|
expect(itemsForDb).toHaveLength(2);
|
|
expect(itemsForDb[0]).toEqual(
|
|
expect.objectContaining({
|
|
item: 'Milk',
|
|
master_item_id: 10, // Should be passed through
|
|
view_count: 0,
|
|
click_count: 0,
|
|
}),
|
|
);
|
|
expect(itemsForDb[1]).toEqual(
|
|
expect.objectContaining({
|
|
item: 'Bread',
|
|
master_item_id: undefined, // null should be converted to undefined
|
|
view_count: 0,
|
|
click_count: 0,
|
|
}),
|
|
);
|
|
|
|
// 3. Check that generateFlyerIcon was called correctly
|
|
expect(generateFlyerIcon).toHaveBeenCalledWith(
|
|
'/uploads/flyer-page-1.jpg',
|
|
'/uploads/icons',
|
|
mockLogger,
|
|
);
|
|
});
|
|
|
|
it('should handle missing optional data gracefully', async () => {
|
|
// Arrange
|
|
const aiResult: AiProcessorResult = {
|
|
data: {
|
|
store_name: '', // Empty store name
|
|
valid_from: null,
|
|
valid_to: null,
|
|
store_address: null,
|
|
items: [], // No items
|
|
},
|
|
needsReview: true,
|
|
};
|
|
const imagePaths = [{ path: '/uploads/another.png', mimetype: 'image/png' }];
|
|
const originalFileName = 'another.png';
|
|
const checksum = 'checksum-def-456';
|
|
// No userId provided
|
|
|
|
vi.mocked(generateFlyerIcon).mockResolvedValue('icon-another.webp');
|
|
|
|
// Act
|
|
const { flyerData, itemsForDb } = await transformer.transform(
|
|
aiResult,
|
|
imagePaths,
|
|
originalFileName,
|
|
checksum,
|
|
undefined,
|
|
mockLogger,
|
|
);
|
|
|
|
// Assert
|
|
// 0. Check logging
|
|
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
'Starting data transformation from AI output to database format.',
|
|
);
|
|
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
'AI did not return a store name. Using fallback "Unknown Store (auto)".',
|
|
);
|
|
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
{ itemCount: 0, storeName: 'Unknown Store (auto)' },
|
|
'Data transformation complete.',
|
|
);
|
|
|
|
expect(itemsForDb).toHaveLength(0);
|
|
expect(flyerData).toEqual({
|
|
file_name: originalFileName,
|
|
image_url: '/flyer-images/another.png',
|
|
icon_url: '/flyer-images/icons/icon-another.webp',
|
|
checksum,
|
|
store_name: 'Unknown Store (auto)', // Should use fallback
|
|
valid_from: null,
|
|
valid_to: null,
|
|
store_address: null,
|
|
item_count: 0,
|
|
status: 'needs_review',
|
|
uploaded_by: undefined, // Should be undefined
|
|
});
|
|
});
|
|
|
|
it('should correctly normalize item fields with null, undefined, or empty values', async () => {
|
|
// Arrange
|
|
const aiResult: AiProcessorResult = {
|
|
data: {
|
|
store_name: 'Test Store',
|
|
valid_from: '2024-01-01',
|
|
valid_to: '2024-01-07',
|
|
store_address: '123 Test St',
|
|
items: [
|
|
// Case 1: All fields are null or undefined
|
|
{
|
|
item: null,
|
|
price_display: null,
|
|
price_in_cents: null,
|
|
quantity: null,
|
|
category_name: null,
|
|
master_item_id: null,
|
|
},
|
|
// Case 2: Fields are empty strings
|
|
{
|
|
item: ' ', // whitespace only
|
|
price_display: '',
|
|
price_in_cents: 200,
|
|
quantity: '',
|
|
category_name: '',
|
|
master_item_id: 20,
|
|
},
|
|
],
|
|
},
|
|
needsReview: false,
|
|
};
|
|
const imagePaths = [{ path: '/uploads/flyer-page-1.jpg', mimetype: 'image/jpeg' }];
|
|
|
|
// Act
|
|
const { itemsForDb } = await transformer.transform(
|
|
aiResult,
|
|
imagePaths,
|
|
'file.pdf',
|
|
'checksum',
|
|
'user-1',
|
|
mockLogger,
|
|
);
|
|
|
|
// Assert
|
|
expect(itemsForDb).toHaveLength(2);
|
|
|
|
// Check Case 1 (null/undefined values)
|
|
expect(itemsForDb[0]).toEqual(
|
|
expect.objectContaining({
|
|
item: 'Unknown Item', price_display: '', quantity: '', category_name: 'Other/Miscellaneous', master_item_id: undefined,
|
|
}),
|
|
);
|
|
|
|
// Check Case 2 (empty string values)
|
|
expect(itemsForDb[1]).toEqual(
|
|
expect.objectContaining({
|
|
item: 'Unknown Item', price_display: '', quantity: '', category_name: 'Other/Miscellaneous', master_item_id: 20,
|
|
}),
|
|
);
|
|
});
|
|
});
|