import { describe, it, expect, vi, beforeEach } from 'vitest'; const { mockFind, mockFindOne, mockAggregate, mockSave } = vi.hoisted(() => ({ mockFind: vi.fn(), mockFindOne: vi.fn(), mockAggregate: vi.fn(), mockSave: vi.fn(), })); vi.mock('../../schemas/medicine-price.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), lean: vi.fn().mockReturnThis(), exec: mockFind, }); const findOneChain = () => ({ sort: vi.fn().mockReturnThis(), lean: vi.fn().mockReturnThis(), exec: mockFindOne, }); const aggregateChain = () => ({ exec: mockAggregate }); 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 aggregate = vi.fn(() => aggregateChain()); } return { MedicinePriceModel: FakeModel }; }); import { MedicinePricesRepository } from './medicine-prices.repository.js'; describe(MedicinePricesRepository.name, () => { let repo: MedicinePricesRepository; beforeEach(() => { vi.clearAllMocks(); repo = new MedicinePricesRepository(); }); describe('create', () => { it('saves and returns price record', async () => { const data = { householdId: 'hh1', medicineProductId: 'mp-1', medicineProductBrand: 'Tylenol', medicineId: 'med-1', medicineName: 'Acetaminophen', storeId: 'st-1', storeName: 'Walgreens', price: 10, currency: 'USD', quantity: 100, unit: 'tablet', pricePerUnit: 0.1, date: new Date(), isInsurancePrice: false, createdBy: 'user-1', }; mockSave.mockImplementation(function (this: { toObject: () => unknown }) { return Promise.resolve(this); }); const result = await repo.create(data as never); expect(mockSave).toHaveBeenCalled(); expect(result).toBeTruthy(); }); }); describe('findByMedicine', () => { it('returns paginated items', async () => { const items = [{ _id: 'pr-1', medicineId: 'med-1' }]; mockFind.mockResolvedValue(items); const result = await repo.findByMedicine('hh1', 'med-1', { limit: 20 }); expect(result.data).toEqual(items); expect(result.pagination.hasMore).toBe(false); expect(result.pagination.cursor).toBeNull(); }); it('sets hasMore when more items exist', async () => { const items = [{ _id: 'pr-1' }, { _id: 'pr-2' }, { _id: 'pr-3' }]; mockFind.mockResolvedValue(items); const result = await repo.findByMedicine('hh1', 'med-1', { limit: 2 }); expect(result.data).toHaveLength(2); expect(result.pagination.hasMore).toBe(true); expect(result.pagination.cursor).toBeTruthy(); }); it('handles cursor', async () => { mockFind.mockResolvedValue([]); const cursor = Buffer.from('pr-1').toString('base64'); const result = await repo.findByMedicine('hh1', 'med-1', { cursor, limit: 20 }); expect(result.pagination.hasMore).toBe(false); }); it('returns null cursor when no data', async () => { mockFind.mockResolvedValue([]); const result = await repo.findByMedicine('hh1', 'med-1', { limit: 20 }); expect(result.pagination.cursor).toBeNull(); }); it('applies storeId filter', async () => { mockFind.mockResolvedValue([]); await repo.findByMedicine('hh1', 'med-1', { storeId: 'st-1', limit: 20 }); expect(mockFind).toHaveBeenCalled(); }); it('applies startDate-only filter', async () => { mockFind.mockResolvedValue([]); await repo.findByMedicine('hh1', 'med-1', { startDate: '2026-01-01T00:00:00.000Z', limit: 20, }); expect(mockFind).toHaveBeenCalled(); }); it('applies endDate-only filter', async () => { mockFind.mockResolvedValue([]); await repo.findByMedicine('hh1', 'med-1', { endDate: '2026-12-31T00:00:00.000Z', limit: 20, }); expect(mockFind).toHaveBeenCalled(); }); }); describe('compareStores', () => { it('returns store comparison results', async () => { const rows = [ { _id: 'st-1', storeName: 'Walgreens', latestPrice: 10, latestPricePerUnit: 0.1, currency: 'USD', date: new Date(), isInsurancePrice: false, }, ]; mockAggregate.mockResolvedValue(rows); const result = await repo.compareStores('hh1', 'med-1'); expect(result).toHaveLength(1); expect(result[0].storeId).toBe('st-1'); expect(result[0].storeName).toBe('Walgreens'); }); it('returns empty array when no records', async () => { mockAggregate.mockResolvedValue([]); const result = await repo.compareStores('hh1', 'med-1'); expect(result).toEqual([]); }); }); describe('getLatestForMedicine', () => { it('returns latest record', async () => { const record = { _id: 'pr-1', pricePerUnit: 0.1 }; mockFindOne.mockResolvedValue(record); const result = await repo.getLatestForMedicine('hh1', 'med-1'); expect(result).toEqual(record); }); it('filters by storeId when provided', async () => { mockFindOne.mockResolvedValue(null); const result = await repo.getLatestForMedicine('hh1', 'med-1', 'st-1'); expect(result).toBeNull(); }); it('returns null when not found', async () => { mockFindOne.mockResolvedValue(null); expect(await repo.getLatestForMedicine('hh1', 'med-1')).toBeNull(); }); }); describe('getAnalytics', () => { it('returns analytics object with all fields', async () => { mockAggregate.mockResolvedValue([]); const result = await repo.getAnalytics('hh1', { period: 'month' }); expect(result).toHaveProperty('spendingOverTime'); expect(result).toHaveProperty('topBySpending'); expect(result).toHaveProperty('spendingByStore'); expect(result).toHaveProperty('priceAlerts'); }); it('uses quarter date format', async () => { mockAggregate.mockResolvedValue([]); const result = await repo.getAnalytics('hh1', { period: 'quarter' }); expect(result).toHaveProperty('spendingOverTime'); }); it('uses year date format', async () => { mockAggregate.mockResolvedValue([]); const result = await repo.getAnalytics('hh1', { period: 'year' }); expect(result).toHaveProperty('spendingOverTime'); }); it('handles non-empty analytics results', async () => { mockAggregate .mockResolvedValueOnce([{ period: '2026-01', total: 50 }]) .mockResolvedValueOnce([{ medicineId: 'med-1', medicineName: 'Acetaminophen', totalSpent: 50, avgPricePerUnit: 0.1 }]) .mockResolvedValueOnce([{ storeId: 'st-1', storeName: 'Walgreens', totalSpent: 50, purchaseCount: 5 }]) .mockResolvedValueOnce([]); const result = await repo.getAnalytics('hh1', { period: 'month' }); expect(result.spendingOverTime).toHaveLength(1); expect(result.topBySpending).toHaveLength(1); expect(result.spendingByStore).toHaveLength(1); expect(result.priceAlerts).toHaveLength(0); }); }); });