import { describe, it, expect, vi, beforeEach } from 'vitest'; import { FreshnessRulesService } from './freshness-rules.service.js'; import { NotFoundError, BadRequestError } from '../../common/errors.js'; import { FreshnessRuleSource } from '@meshitrack/shared'; const mockRepo = { findByHousehold: vi.fn(), findById: vi.fn(), findApplicableRule: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), }; describe(FreshnessRulesService.name, () => { let service: FreshnessRulesService; beforeEach(() => { vi.clearAllMocks(); service = new FreshnessRulesService({ freshnessRulesRepository: mockRepo as never, }); }); describe('list', () => { it('delegates to repository', async () => { const expected = { data: [], pagination: { cursor: null, hasMore: false } }; mockRepo.findByHousehold.mockResolvedValue(expected); const result = await service.list('hh1', { limit: 50 }); expect(result).toEqual(expected); }); }); describe('create', () => { it('creates household rule', async () => { const data = { category: 'dairy' as never, storageLocation: 'fridge' as never, shelfLifeDays: 14, openedLifeDays: 7, spoilageSignsToCheck: [], }; mockRepo.create.mockResolvedValue({ ...data, _id: 'r1', householdId: 'hh1' }); const result = await service.create(data, 'hh1'); expect(mockRepo.create).toHaveBeenCalledWith({ ...data, householdId: 'hh1', source: FreshnessRuleSource.HOUSEHOLD, }); expect(result).toBeDefined(); }); }); describe('update', () => { it('updates household rule', async () => { mockRepo.findById.mockResolvedValue({ _id: 'r1', householdId: 'hh1', source: FreshnessRuleSource.HOUSEHOLD, }); mockRepo.update.mockResolvedValue({ _id: 'r1', shelfLifeDays: 10 }); const result = await service.update('r1', 'hh1', { shelfLifeDays: 10 }); expect(result).toBeDefined(); }); it('throws NotFoundError when rule not found', async () => { mockRepo.findById.mockResolvedValue(null); await expect(service.update('missing', 'hh1', {})).rejects.toThrow(NotFoundError); }); it('throws BadRequestError for system rules', async () => { mockRepo.findById.mockResolvedValue({ _id: 'r1', householdId: null, source: FreshnessRuleSource.SYSTEM, }); await expect(service.update('r1', 'hh1', {})).rejects.toThrow(BadRequestError); }); it('throws NotFoundError for another household rule', async () => { mockRepo.findById.mockResolvedValue({ _id: 'r1', householdId: 'other-hh', source: FreshnessRuleSource.HOUSEHOLD, }); await expect(service.update('r1', 'hh1', {})).rejects.toThrow(NotFoundError); }); it('throws NotFoundError when update returns null', async () => { mockRepo.findById.mockResolvedValue({ _id: 'r1', householdId: 'hh1', source: FreshnessRuleSource.HOUSEHOLD, }); mockRepo.update.mockResolvedValue(null); await expect(service.update('r1', 'hh1', {})).rejects.toThrow(NotFoundError); }); }); describe('delete', () => { it('deletes household rule', async () => { mockRepo.findById.mockResolvedValue({ _id: 'r1', householdId: 'hh1', source: FreshnessRuleSource.HOUSEHOLD, }); mockRepo.delete.mockResolvedValue({ _id: 'r1' }); await service.delete('r1', 'hh1'); expect(mockRepo.delete).toHaveBeenCalledWith('r1', 'hh1'); }); it('throws NotFoundError when rule not found', async () => { mockRepo.findById.mockResolvedValue(null); await expect(service.delete('missing', 'hh1')).rejects.toThrow(NotFoundError); }); it('throws BadRequestError for system rules', async () => { mockRepo.findById.mockResolvedValue({ _id: 'r1', householdId: null, source: FreshnessRuleSource.SYSTEM, }); await expect(service.delete('r1', 'hh1')).rejects.toThrow(BadRequestError); }); it('throws NotFoundError for another household rule', async () => { mockRepo.findById.mockResolvedValue({ _id: 'r1', householdId: 'other-hh', source: FreshnessRuleSource.HOUSEHOLD, }); await expect(service.delete('r1', 'hh1')).rejects.toThrow(NotFoundError); }); }); });