"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const receipt_import_service_1 = require("./receipt-import.service"); function cat(id, name, path) { return { id, name, path }; } describe('ReceiptImportService test matrix', () => { const categories = [ cat(1, 'Bröd & Kakor', 'Bröd & Kakor'), cat(2, 'Kondis & fika', 'Bröd & Kakor > Kondis & fika'), cat(3, 'Kaffebröd', 'Bröd & Kakor > Kondis & fika > Kaffebröd'), cat(10, 'Skafferi', 'Skafferi'), cat(11, 'Pasta, ris & matgryn', 'Skafferi > Pasta, ris & matgryn'), cat(12, 'Pasta', 'Skafferi > Pasta, ris & matgryn > Pasta'), cat(20, 'Frukt & Grönt', 'Frukt & Grönt'), cat(21, 'Potatis & rotsaker', 'Frukt & Grönt > Potatis & rotsaker'), cat(22, 'Potatis', 'Frukt & Grönt > Potatis & rotsaker > Potatis'), cat(30, 'Mejeri, ost & ägg', 'Mejeri, ost & ägg'), cat(31, 'Matlagning', 'Mejeri, ost & ägg > Matlagning'), cat(32, 'Grädde', 'Mejeri, ost & ägg > Matlagning > Grädde'), cat(33, 'Ägg', 'Mejeri, ost & ägg > Ägg'), cat(40, 'Dryck', 'Dryck'), cat(41, 'Juice, fruktdryck & smoothie', 'Dryck > Juice, fruktdryck & smoothie'), cat(42, 'Kyld juice & nektar', 'Dryck > Juice, fruktdryck & smoothie > Kyld juice & nektar'), cat(50, 'Glass, godis & snacks', 'Glass, godis & snacks'), cat(51, 'Godis', 'Glass, godis & snacks > Godis'), cat(52, 'Godispåsar', 'Glass, godis & snacks > Godis > Godispåsar'), cat(53, 'Choklad', 'Glass, godis & snacks > Choklad'), cat(54, 'Chokladkakor & rullar', 'Glass, godis & snacks > Choklad > Chokladkakor & rullar'), ]; const prismaMock = { category: { findMany: jest.fn().mockResolvedValue([]) }, receiptAlias: { findMany: jest.fn().mockResolvedValue([]) }, product: { findMany: jest.fn().mockResolvedValue([]) }, }; const aiServiceMock = { suggestCategory: jest.fn(), }; const categoriesServiceMock = { findFlattened: jest.fn(), }; const service = new receipt_import_service_1.ReceiptImportService(prismaMock, aiServiceMock, categoriesServiceMock); beforeEach(() => { jest.clearAllMocks(); prismaMock.receiptAlias.findMany.mockResolvedValue([]); prismaMock.product.findMany.mockResolvedValue([]); }); describe('ignore patterns', () => { it.each([ 'Willys Plus:Bröd', 'willys plus: mjölk', 'WILLYS PLUS - ÄGG', 'Willys Plus : Ost', 'Rabatt kupong', 'Summa', ])('ignorerar "%s"', (raw) => { expect((0, receipt_import_service_1.isIgnoredReceiptName)(raw)).toBe(true); }); it.each([ 'Mezze Maniche', 'Snickers', 'Nappar Cola 80g', 'Vispgrädde 5DL', ])('ignorerar inte "%s"', (raw) => { expect((0, receipt_import_service_1.isIgnoredReceiptName)(raw)).toBe(false); }); }); describe('rule matrix', () => { const matrix = [ { raw: 'Mezze Maniche', expectedPath: 'Skafferi > Pasta, ris & matgryn > Pasta' }, { raw: 'Nappar Cola 80g', expectedPath: 'Glass, godis & snacks > Godis > Godispåsar' }, { raw: 'Snickers', expectedPath: 'Glass, godis & snacks > Choklad > Chokladkakor & rullar' }, { raw: 'Potatis Fast', expectedPath: 'Frukt & Grönt > Potatis & rotsaker > Potatis' }, { raw: 'Ägg 24p Inne M', expectedPath: 'Mejeri, ost & ägg > Ägg' }, { raw: 'Dryck Multivitamin', expectedPath: 'Dryck > Juice, fruktdryck & smoothie > Kyld juice & nektar' }, { raw: 'Vispgrädde 5DL', expectedPath: 'Mejeri, ost & ägg > Matlagning > Grädde' }, { raw: 'Wienerbröd', expectedPath: 'Bröd & Kakor > Kondis & fika > Kaffebröd' }, ]; it.each(matrix)('klassar "$raw" -> "$expectedPath"', ({ raw, expectedPath }) => { const suggestion = service.ruleBasedCategorySuggestion(raw, categories); expect(suggestion).not.toBeNull(); expect(suggestion?.path).toBe(expectedPath); }); }); describe('alias fallback och prioritet', () => { it('prioriterar user-alias före global alias för samma receiptName', async () => { prismaMock.receiptAlias.findMany.mockResolvedValue([ { receiptName: 'mjolk 1l', productId: 501, product: { id: 501, name: 'Mjolk user', canonicalName: 'Mjolk user', categoryId: 30, categoryRef: { id: 30, name: 'Mejeri' }, }, }, { receiptName: 'mjolk 1l', productId: 999, product: { id: 999, name: 'Mjolk global', canonicalName: 'Mjolk global', categoryId: 30, categoryRef: { id: 30, name: 'Mejeri' }, }, }, ]); prismaMock.product.findMany.mockResolvedValue([]); const result = await service.matchProducts([{ rawName: 'MJOLK 1L' }], 77); expect(prismaMock.receiptAlias.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: { OR: [ { ownerId: 77, isGlobal: false }, { isGlobal: true }, ], }, })); expect(result[0].matchedProductId).toBe(501); expect(result[0].matchedProductName).toBe('Mjolk user'); }); it('använder global alias när user-alias saknas', async () => { prismaMock.receiptAlias.findMany.mockResolvedValue([ { receiptName: 'snickers', productId: 222, product: { id: 222, name: 'Snickers', canonicalName: 'Snickers', categoryId: 53, categoryRef: { id: 53, name: 'Choklad' }, }, }, ]); prismaMock.product.findMany.mockResolvedValue([]); const result = await service.matchProducts([{ rawName: 'SNICKERS' }], 88); expect(result[0].matchedProductId).toBe(222); expect(result[0].matchedProductName).toBe('Snickers'); }); it('flöde: manuell korrigering lär alias och nästa import matchar direkt', async () => { const aliases = []; prismaMock.receiptAlias.findMany.mockImplementation(async () => aliases); prismaMock.product.findMany.mockResolvedValue([ { id: 700, name: 'Arla Mjolk 1l', canonicalName: 'Mjolk', categoryId: 30, categoryRef: { id: 30, name: 'Mejeri' }, }, ]); const first = await service.matchProducts([{ rawName: 'ARLA MJOLK 1L' }], 42); expect(first[0].matchedProductId).toBeUndefined(); expect(first[0].suggestedProductId).toBe(700); aliases.push({ receiptName: 'arla mjolk 1l', productId: 700, product: { id: 700, name: 'Arla Mjolk 1l', canonicalName: 'Mjolk', categoryId: 30, categoryRef: { id: 30, name: 'Mejeri' }, }, }); const second = await service.matchProducts([{ rawName: 'ARLA MJOLK 1L' }], 42); expect(second[0].matchedProductId).toBe(700); expect(second[0].matchedProductName).toBe('Mjolk'); expect(second[0].suggestedProductId).toBeUndefined(); }); }); }); //# sourceMappingURL=receipt-import.service.spec.js.map