import { describe, it, expect } from 'vitest'; import { FreshnessCalculatorService } from '../../../src/modules/pantry/freshness-calculator.service.js'; import { ItemStatus, FreshnessUrgency, FreshnessSource, StorageLocation } from '@meshitrack/shared'; describe(FreshnessCalculatorService.name, () => { const service = new FreshnessCalculatorService(); const baseItem = { status: ItemStatus.SEALED, storageLocation: StorageLocation.FRIDGE, purchaseDate: new Date('2024-01-01'), expirationDate: undefined, openedDate: undefined, preparedDate: undefined, }; const rule = { shelfLifeDays: 14, openedLifeDays: 7, freezerLifeDays: 90, }; describe('calculate', () => { it('uses packaging expiration date when present', () => { const item = { ...baseItem, expirationDate: new Date('2099-12-31') }; const result = service.calculate(item, rule); expect(result.source).toBe(FreshnessSource.PACKAGING); expect(result.estimatedExpiryDate).toEqual(new Date('2099-12-31')); }); it('falls back to 7-day default when no rule provided', () => { const result = service.calculate(baseItem, null); expect(result.source).toBe(FreshnessSource.RULE); const expected = new Date('2024-01-01'); expected.setDate(expected.getDate() + 7); expect(result.estimatedExpiryDate).toEqual(expected); }); it('uses freezerLifeDays for freezer storage', () => { const item = { ...baseItem, storageLocation: StorageLocation.FREEZER }; const result = service.calculate(item, rule); expect(result.source).toBe(FreshnessSource.RULE); const expected = new Date('2024-01-01'); expected.setDate(expected.getDate() + 90); expect(result.estimatedExpiryDate).toEqual(expected); }); it('uses shelfLifeDays for sealed items in freezer without freezerLifeDays', () => { const item = { ...baseItem, storageLocation: StorageLocation.FREEZER }; const ruleNoFreezer = { shelfLifeDays: 14, openedLifeDays: 7 }; const result = service.calculate(item, ruleNoFreezer); const expected = new Date('2024-01-01'); expected.setDate(expected.getDate() + 14); expect(result.estimatedExpiryDate).toEqual(expected); }); it('uses openedLifeDays for opened items', () => { const openedDate = new Date('2024-01-05'); const item = { ...baseItem, status: ItemStatus.OPENED, openedDate, }; const result = service.calculate(item, rule); const expected = new Date('2024-01-05'); expected.setDate(expected.getDate() + 7); expect(result.estimatedExpiryDate).toEqual(expected); }); it('uses openedLifeDays for prepared items with openedDate', () => { const openedDate = new Date('2024-01-05'); const item = { ...baseItem, status: ItemStatus.PREPARED, openedDate, }; const result = service.calculate(item, rule); const expected = new Date('2024-01-05'); expected.setDate(expected.getDate() + 7); expect(result.estimatedExpiryDate).toEqual(expected); }); it('uses shelfLifeDays for opened item without openedDate', () => { const item = { ...baseItem, status: ItemStatus.OPENED }; const result = service.calculate(item, rule); const expected = new Date('2024-01-01'); expected.setDate(expected.getDate() + 14); expect(result.estimatedExpiryDate).toEqual(expected); }); it('uses shelfLifeDays for sealed items', () => { const result = service.calculate(baseItem, rule); const expected = new Date('2024-01-01'); expected.setDate(expected.getDate() + 14); expect(result.estimatedExpiryDate).toEqual(expected); }); it('computes daysRemaining and urgency', () => { const future = new Date(); future.setDate(future.getDate() + 10); const item = { ...baseItem, expirationDate: future }; const result = service.calculate(item, rule); expect(result.daysRemaining).toBeGreaterThan(5); expect(result.urgency).toBe(FreshnessUrgency.FRESH); }); }); describe('isActive', () => { it('returns true for sealed', () => { expect(service.isActive('sealed')).toBe(true); }); it('returns true for opened', () => { expect(service.isActive('opened')).toBe(true); }); it('returns true for prepared', () => { expect(service.isActive('prepared')).toBe(true); }); it('returns false for consumed', () => { expect(service.isActive('consumed')).toBe(false); }); it('returns false for discarded', () => { expect(service.isActive('discarded')).toBe(false); }); it('returns false for expired', () => { expect(service.isActive('expired')).toBe(false); }); }); describe('mapUrgency', () => { it('returns FRESH for > 5 days', () => { expect(service.mapUrgency(6)).toBe(FreshnessUrgency.FRESH); }); it('returns USE_SOON for 2-5 days', () => { expect(service.mapUrgency(3)).toBe(FreshnessUrgency.USE_SOON); }); it('returns URGENT for 0-1 days', () => { expect(service.mapUrgency(1)).toBe(FreshnessUrgency.URGENT); }); it('returns CHECK for -1 to -3 days', () => { expect(service.mapUrgency(-1)).toBe(FreshnessUrgency.CHECK); }); it('returns EXPIRED for < -3 days', () => { expect(service.mapUrgency(-4)).toBe(FreshnessUrgency.EXPIRED); }); it('returns USE_SOON for exactly 2', () => { expect(service.mapUrgency(2)).toBe(FreshnessUrgency.USE_SOON); }); it('returns USE_SOON for exactly 5', () => { expect(service.mapUrgency(5)).toBe(FreshnessUrgency.USE_SOON); }); it('returns URGENT for exactly 0', () => { expect(service.mapUrgency(0)).toBe(FreshnessUrgency.URGENT); }); it('returns CHECK for exactly -3', () => { expect(service.mapUrgency(-3)).toBe(FreshnessUrgency.CHECK); }); }); });