import { Test, TestingModule } from '@nestjs/testing'; import { FlyerNormalizerService } from './flyer-normalizer.service'; describe('FlyerNormalizerService', () => { let service: FlyerNormalizerService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [FlyerNormalizerService], }).compile(); service = module.get(FlyerNormalizerService); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('normalize', () => { it('should normalize a valid item', () => { const items = [ { rawName: 'KALLRÖKT LAX, GRAVAD LAX', normalizedName: 'kallrökt lax gravad lax', category: 'Fisk', price: 39.9, comparisonPrice: 266.0, unit: 'kg', offer: ['Max 3 köp/hushåll'], confidence: 0.85, reasonCodes: ['ai_parsed'], }, ]; const result = service.normalize(items); expect(result).toHaveLength(1); expect(result[0].rawName).toBe('KALLRÖKT LAX, GRAVAD LAX'); expect(result[0].price).toBe(39.9); expect(result[0].priceUnit).toBe('kg'); expect(result[0].categoryHint).toBe('Fisk'); }); it('should handle missing fields gracefully', () => { const items = [ { name: 'PRODUKT', // andra fält saknas }, ]; const result = service.normalize(items); expect(result).toHaveLength(1); expect(result[0].rawName).toBe('PRODUKT'); expect(result[0].price).toBeNull(); expect(result[0].categoryHint).toBeNull(); }); it('should skip items without name', () => { const items = [ { price: 100 }, // no name { rawName: 'VALID PRODUCT', price: 50 }, ]; const result = service.normalize(items); expect(result).toHaveLength(1); expect(result[0].rawName).toBe('VALID PRODUCT'); }); it('should normalize units correctly', () => { const items = [ { rawName: 'Mjölk', unit: 'L' }, { rawName: 'Smör', unit: 'styck' }, { rawName: 'Socker', unit: 'KG' }, ]; const result = service.normalize(items); expect(result).toHaveLength(3); expect(result[0].priceUnit).toBe('l'); expect(result[1].priceUnit).toBe('st'); expect(result[2].priceUnit).toBe('kg'); }); it('should parse Swedish prices correctly', () => { const items = [ { rawName: 'Produkt1', price: '39,90' }, { rawName: 'Produkt2', price: 39.9 }, { rawName: 'Produkt3', price: '100' }, ]; const result = service.normalize(items); expect(result[0].price).toBe(39.9); expect(result[1].price).toBe(39.9); expect(result[2].price).toBe(100); }); it('should return empty list for non-array input', () => { const result = service.normalize(null as any); expect(result).toEqual([]); const result2 = service.normalize(undefined as any); expect(result2).toEqual([]); }); it('splits listed cheese variants into separate products', () => { const items = [ { rawName: 'PRÄST®, HERRGÅRD®, GREVÉ®', brand: 'ARLA KO', unit: 'kg', comparisonPrice: '79,90', offer: ['Max 3 förp/hushåll'], }, ]; const result = service.normalize(items); expect(result).toHaveLength(3); expect(result.map((item) => item.rawName)).toEqual(['Prästost', 'Herrgårdsost', 'Grevéost']); expect(result.every((item) => item.brand === 'Arla Ko')).toBe(true); expect(result.every((item) => item.categoryHint === 'Hårdost')).toBe(true); expect(result[0].parseReasons).toContain('split_cheese_variants'); }); it('normalizes PRAST token to Prästost', () => { const items = [{ rawName: 'PRAST, GREVE', brand: 'ARLA KO' }]; const result = service.normalize(items); expect(result.map((item) => item.rawName)).toContain('Prästost'); }); it('normalizes GREVE token to Grevéost', () => { const items = [{ rawName: 'GREVE, PRAST', brand: 'ARLA KO' }]; const result = service.normalize(items); expect(result.map((item) => item.rawName)).toContain('Grevéost'); }); it('keeps single cheese item unsplit but normalizes brand/category', () => { const items = [ { rawName: 'Prästost', brand: 'arla ko', }, ]; const result = service.normalize(items); expect(result).toHaveLength(1); expect(result[0].rawName).toBe('Prästost'); expect(result[0].brand).toBe('Arla Ko'); expect(result[0].categoryHint).toBe('Hårdost'); }); it('fixes known OCR typo for spröd', () => { const items = [ { rawName: 'Pröd Bakad Firre', brand: 'Findus', }, ]; const result = service.normalize(items); expect(result).toHaveLength(1); expect(result[0].rawName).toBe('Spröd Bakad Firre'); expect(result[0].normalizedName).toBe('spröd bakad firre'); }); it('does not apply spröd typo fix outside known fish context', () => { const items = [ { rawName: 'Pröd tvättmedel', brand: 'Test', }, ]; const result = service.normalize(items); expect(result).toHaveLength(1); expect(result[0].rawName).toBe('Pröd tvättmedel'); }); it('fixes herggårdsost only in cheese context', () => { const items = [ { rawName: 'Herggårdsost 31%', brand: 'Arla Ko', }, ]; const result = service.normalize(items); expect(result).toHaveLength(1); expect(result[0].rawName).toContain('Herrgårdsost'); }); it('fixes greveost typo in cheese context and preserves é', () => { const items = [ { rawName: 'Greveost skivad', brand: 'Arla Ko', }, ]; const result = service.normalize(items); expect(result).toHaveLength(1); expect(result[0].rawName).toContain('Grevéost'); expect(result[0].normalizedName).toContain('grevéost'); }); }); });