Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
- Added a new DealsRepository class to interact with the database for fetching the best sale prices of watched items. - Created a new route `/api/users/deals/best-watched-prices` to handle requests for the best prices of items the authenticated user is watching. - Enhanced logging in the FlyerDataTransformer and FlyerProcessingService for better traceability. - Updated tests to ensure proper logging and functionality in the FlyerProcessingService. - Refactored logger client to support structured logging for better consistency across the application.
126 lines
4.7 KiB
TypeScript
126 lines
4.7 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 { z } from 'zod';
|
|
import type { AiFlyerDataSchema } from './flyerProcessingService.server';
|
|
|
|
// 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 extractedData: z.infer<typeof AiFlyerDataSchema> = {
|
|
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 },
|
|
],
|
|
};
|
|
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(extractedData, 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,
|
|
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,
|
|
}));
|
|
expect((itemsForDb[0] as any).updated_at).toBeTypeOf('string');
|
|
|
|
// 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 extractedData: z.infer<typeof AiFlyerDataSchema> = {
|
|
store_name: '', // Empty store name
|
|
valid_from: null,
|
|
valid_to: null,
|
|
store_address: null,
|
|
items: [], // No items
|
|
};
|
|
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(extractedData, imagePaths, originalFileName, checksum, undefined, mockLogger);
|
|
|
|
// Assert
|
|
// 0. Check logging
|
|
expect(mockLogger.info).toHaveBeenCalledWith('Starting data transformation from AI output to database format.');
|
|
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,
|
|
uploaded_by: undefined, // Should be undefined
|
|
});
|
|
});
|
|
}); |