import { ReceiptImportService } from './receipt-import.service'; import { FlatCategory } from '../categories/categories.service'; function cat(id: number, name: string, path: string): FlatCategory { return { id, name, path }; } describe('ReceiptImportService parseReceipt flow', () => { const categories: FlatCategory[] = [ cat(30, 'Mejeri, ost & ägg', 'Mejeri, ost & ägg'), cat(53, 'Choklad', 'Glass, godis & snacks > Choklad'), cat(51, 'Godis', 'Glass, godis & snacks > Godis'), ]; const prismaMock = { aiTrace: { create: jest.fn() }, receiptAlias: { findMany: jest.fn() }, product: { findMany: jest.fn() }, unitMapping: { findMany: jest.fn() }, user: { findUnique: jest.fn() }, }; const aiServiceMock = { suggestCategory: jest.fn(), }; const categoriesServiceMock = { findFlattened: jest.fn(), }; const service = new ReceiptImportService( prismaMock as any, aiServiceMock as any, categoriesServiceMock as any, { commitReceiptMatches: jest.fn() } as any, ); beforeEach(() => { jest.clearAllMocks(); categoriesServiceMock.findFlattened.mockResolvedValue(categories); prismaMock.unitMapping.findMany.mockResolvedValue([]); prismaMock.user.findUnique.mockResolvedValue({ aiEngineEnabled: true }); }); it('kör prioriteringskedjan i parseReceipt: user alias -> global alias -> wordmatch -> AI', async () => { prismaMock.receiptAlias.findMany.mockResolvedValue([ { receiptName: 'mixad vara', product: { id: 901, name: 'User Produkt', canonicalName: 'User Produkt', categoryRef: { id: 30, name: 'Mejeri, ost & ägg' }, }, }, { receiptName: 'global choklad', product: { id: 900, name: 'Global Produkt', canonicalName: 'Global Produkt', categoryRef: { id: 53, name: 'Choklad' }, }, }, ]); prismaMock.product.findMany.mockResolvedValue([ { id: 777, name: 'Specialprodukt', canonicalName: 'Specialprodukt', categoryRef: { id: 30, name: 'Mejeri, ost & ägg' }, }, ]); aiServiceMock.suggestCategory.mockResolvedValue({ categoryId: 51, categoryName: 'Godis', path: 'Glass, godis & snacks > Godis', confidence: 'low', }); jest .spyOn(service as any, 'parseReceiptViaImporter') .mockResolvedValue({ items: [ { rawName: 'MIXAD VARA', quantity: 1, unit: 'st' }, { rawName: 'GLOBAL CHOKLAD', quantity: 1, unit: 'st' }, { rawName: 'SPECIALPRODUKT 1st', quantity: 1, unit: 'st' }, { rawName: 'helt okänd vara', quantity: 1, unit: 'st' }, ], trace: { prompt: 'test prompt', rawOutput: '{"items":[]}', normalizedOutput: { items: [] }, }, }); const file = { buffer: Buffer.from('dummy'), mimetype: 'image/jpeg', originalname: 'receipt.jpg', } as any; const result = await service.parseReceipt(file, false, 10); expect(result).toHaveLength(4); expect(result[0].matchedVia).toBe('alias'); expect(result[0].matchedProductId).toBe(901); expect(result[1].matchedVia).toBe('alias'); expect(result[1].matchedProductId).toBe(900); expect(result[2].matchedVia).toBe('wordmatch'); expect(result[2].suggestedProductId).toBe(777); expect(result[2].categorySuggestion?.categoryId).toBe(30); expect(result[3].matchedVia).toBe('none'); expect(result[3].categorySuggestion?.categoryId).toBe(51); expect(aiServiceMock.suggestCategory).toHaveBeenCalledTimes(1); expect(aiServiceMock.suggestCategory).toHaveBeenCalledWith('helt okänd vara', categories); }); });