import { describe, it, expect, vi, beforeEach } from 'vitest'; const { mockFind, mockFindOne, mockFindOneAndUpdate, mockFindOneAndDelete, mockSave, mockFindById, } = vi.hoisted(() => ({ mockFind: vi.fn(), mockFindOne: vi.fn(), mockFindOneAndUpdate: vi.fn(), mockFindOneAndDelete: vi.fn(), mockSave: vi.fn(), mockFindById: vi.fn(), })); vi.mock('../../../src/schemas/freshness-rule.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), lean: vi.fn().mockReturnThis(), exec: mockFind, }); const findOneChain = () => ({ lean: vi.fn().mockReturnThis(), exec: mockFindOne, }); const findByIdChain = () => ({ lean: vi.fn().mockReturnThis(), exec: mockFindById, }); const updateChain = () => ({ lean: vi.fn().mockReturnThis(), exec: mockFindOneAndUpdate, }); const deleteChain = () => ({ exec: mockFindOneAndDelete, }); class FakeModel { data: unknown; constructor(data: unknown) { this.data = data; } save() { mockSave(this.data); return Promise.resolve({ toObject: () => this.data }); } static find = vi.fn(() => chain()); static findOne = vi.fn(() => findOneChain()); static findById = vi.fn(() => findByIdChain()); static findOneAndUpdate = vi.fn(() => updateChain()); static findOneAndDelete = vi.fn(() => deleteChain()); } return { FreshnessRuleModel: FakeModel }; }); import { FreshnessRulesRepository } from '../../../src/modules/freshness-rules/freshness-rules.repository.js'; describe(FreshnessRulesRepository.name, () => { let repo: FreshnessRulesRepository; beforeEach(() => { vi.clearAllMocks(); repo = new FreshnessRulesRepository(); }); describe('findByHousehold', () => { it('returns paginated results', async () => { mockFind.mockResolvedValue([{ _id: { toString: () => 'id1' } }]); const result = await repo.findByHousehold('hh1', { limit: 50 }); expect(result.data).toHaveLength(1); expect(result.pagination.hasMore).toBe(false); }); it('applies category filter', async () => { mockFind.mockResolvedValue([]); await repo.findByHousehold('hh1', { category: 'dairy', limit: 50 }); expect(mockFind).toHaveBeenCalled(); }); it('applies storageLocation filter', async () => { mockFind.mockResolvedValue([]); await repo.findByHousehold('hh1', { storageLocation: 'fridge', limit: 50 }); expect(mockFind).toHaveBeenCalled(); }); it('applies cursor', async () => { const cursor = Buffer.from('abc').toString('base64'); mockFind.mockResolvedValue([]); await repo.findByHousehold('hh1', { cursor, limit: 50 }); expect(mockFind).toHaveBeenCalled(); }); }); describe('findById', () => { it('returns rule', async () => { mockFindById.mockResolvedValue({ _id: 'id1' }); const result = await repo.findById('id1'); expect(result).toEqual({ _id: 'id1' }); }); }); describe('findApplicableRule', () => { it('returns household rule when available', async () => { const rule = { _id: 'r1', householdId: 'hh1' }; mockFindOne.mockResolvedValueOnce(rule); const result = await repo.findApplicableRule('hh1', 'dairy', 'fridge'); expect(result).toEqual(rule); }); it('falls back to system rule', async () => { const systemRule = { _id: 'r2', householdId: null }; mockFindOne.mockResolvedValueOnce(null).mockResolvedValueOnce(systemRule); const result = await repo.findApplicableRule('hh1', 'dairy', 'fridge'); expect(result).toEqual(systemRule); }); it('returns null when no rule found', async () => { mockFindOne.mockResolvedValue(null); const result = await repo.findApplicableRule('hh1', 'dairy', 'fridge'); expect(result).toBeNull(); }); }); describe('create', () => { it('saves and returns rule', async () => { const data = { category: 'dairy', storageLocation: 'fridge', shelfLifeDays: 14, openedLifeDays: 7, }; const result = await repo.create(data); expect(mockSave).toHaveBeenCalled(); expect(result).toEqual(data); }); }); describe('update', () => { it('updates and returns rule', async () => { mockFindOneAndUpdate.mockResolvedValue({ _id: 'id1' }); const result = await repo.update('id1', 'hh1', { shelfLifeDays: 10 }); expect(result).toEqual({ _id: 'id1' }); }); }); describe('delete', () => { it('deletes rule', async () => { mockFindOneAndDelete.mockResolvedValue({ _id: 'id1' }); await repo.delete('id1', 'hh1'); expect(mockFindOneAndDelete).toHaveBeenCalled(); }); }); });