import { describe, it, expect, vi, beforeEach } from 'vitest'; const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(() => ({ mockFind: vi.fn(), mockFindOne: vi.fn(), mockFindOneAndUpdate: vi.fn(), mockSave: vi.fn(), })); vi.mock('../../../src/schemas/refill-list.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 updateChain = () => ({ exec: mockFindOneAndUpdate }); class FakeModel { data: unknown; constructor(data: unknown) { this.data = data; } save = mockSave; toObject() { return this.data; } static find = vi.fn(() => chain()); static findOne = vi.fn(() => findOneChain()); static findOneAndUpdate = vi.fn(() => updateChain()); } return { RefillListModel: FakeModel }; }); import { RefillsRepository } from '../../../src/modules/refills/refills.repository.js'; describe(RefillsRepository.name, () => { let repo: RefillsRepository; beforeEach(() => { vi.clearAllMocks(); repo = new RefillsRepository(); }); describe('create', () => { it('saves and returns refill list', async () => { const data = { householdId: 'hh1', name: 'Monthly Refills', status: 'active', createdBy: 'user-1', items: [], }; mockSave.mockImplementation(function (this: { toObject: () => unknown }) { return Promise.resolve(this); }); const result = await repo.create(data); expect(mockSave).toHaveBeenCalled(); expect(result).toBeTruthy(); }); }); describe('findByHousehold', () => { it('returns paginated lists', async () => { const lists = [{ _id: 'rl-1', name: 'Monthly Refills' }]; mockFind.mockResolvedValue(lists); const result = await repo.findByHousehold('hh1', { limit: 20 }); expect(result.data).toEqual(lists); expect(result.pagination.hasMore).toBe(false); expect(result.pagination.cursor).toBeNull(); }); it('sets hasMore when more lists exist', async () => { const lists = [{ _id: 'rl-1' }, { _id: 'rl-2' }, { _id: 'rl-3' }]; mockFind.mockResolvedValue(lists); const result = await repo.findByHousehold('hh1', { limit: 2 }); expect(result.data).toHaveLength(2); expect(result.pagination.hasMore).toBe(true); expect(result.pagination.cursor).toBeTruthy(); }); it('handles cursor pagination', async () => { mockFind.mockResolvedValue([]); const cursor = Buffer.from('rl-1').toString('base64'); const result = await repo.findByHousehold('hh1', { cursor, limit: 20 }); expect(result.pagination.hasMore).toBe(false); }); it('filters by status', async () => { mockFind.mockResolvedValue([]); await repo.findByHousehold('hh1', { status: 'active' as never, limit: 20 }); expect(mockFind).toHaveBeenCalled(); }); it('returns null cursor when no data', async () => { mockFind.mockResolvedValue([]); const result = await repo.findByHousehold('hh1', { limit: 20 }); expect(result.pagination.cursor).toBeNull(); }); }); describe('findById', () => { it('returns list when found', async () => { const list = { _id: 'rl-1', name: 'Monthly Refills' }; mockFindOne.mockResolvedValue(list); const result = await repo.findById('rl-1', 'hh1'); expect(result).toEqual(list); }); it('returns null when not found', async () => { mockFindOne.mockResolvedValue(null); expect(await repo.findById('missing', 'hh1')).toBeNull(); }); }); describe('update', () => { it('updates and returns list', async () => { const updated = { _id: 'rl-1', name: 'Updated' }; mockFindOneAndUpdate.mockResolvedValue(updated); const result = await repo.update('rl-1', 'hh1', { name: 'Updated' }); expect(result).toEqual(updated); }); it('updates status field', async () => { mockFindOneAndUpdate.mockResolvedValue({ _id: 'rl-1', status: 'shopping' }); const result = await repo.update('rl-1', 'hh1', { status: 'shopping' as never }); expect(result).toBeTruthy(); }); it('updates preferredStoreId field', async () => { mockFindOneAndUpdate.mockResolvedValue({ _id: 'rl-1', preferredStoreId: 'st-1' }); const result = await repo.update('rl-1', 'hh1', { preferredStoreId: 'st-1' }); expect(result).toBeTruthy(); }); it('returns null when not found', async () => { mockFindOneAndUpdate.mockResolvedValue(null); expect(await repo.update('missing', 'hh1', {})).toBeNull(); }); }); describe('updateItem', () => { it('updates item and returns list', async () => { const updated = { _id: 'rl-1', items: [{ _id: 'item-1', checked: true }] }; mockFindOneAndUpdate.mockResolvedValue(updated); const result = await repo.updateItem('rl-1', 'hh1', 'item-1', { checked: true }); expect(result).toEqual(updated); }); it('updates actualPrice, storeId, and notes', async () => { mockFindOneAndUpdate.mockResolvedValue({ _id: 'rl-1', items: [] }); await repo.updateItem('rl-1', 'hh1', 'item-1', { actualPrice: 9.99, storeId: 'st-1', notes: 'picked up at CVS', }); expect(mockFindOneAndUpdate).toHaveBeenCalled(); }); it('includes checkedAt when provided', async () => { mockFindOneAndUpdate.mockResolvedValue({ _id: 'rl-1', items: [] }); const checkedAt = new Date(); await repo.updateItem('rl-1', 'hh1', 'item-1', { checked: true, checkedAt }); expect(mockFindOneAndUpdate).toHaveBeenCalled(); }); }); describe('markItemsAddedToCabinet', () => { it('marks items and returns list', async () => { const updated = { _id: 'rl-1', items: [] }; mockFindOneAndUpdate.mockResolvedValue(updated); const result = await repo.markItemsAddedToCabinet('rl-1', 'hh1', ['item-1', 'item-2']); expect(result).toEqual(updated); }); }); });