import { describe, it, expect, vi, beforeEach } from 'vitest'; import { MedicinePricesService } from './medicine-prices.service.js'; describe(MedicinePricesService.name, () => { const mockPricesRepo = { create: vi.fn(), findByMedicine: vi.fn(), compareStores: vi.fn(), getLatestForMedicine: vi.fn(), getAnalytics: vi.fn(), }; const mockProductsRepo = { findById: vi.fn(), }; const mockStoresRepo = { findById: vi.fn(), }; let service: MedicinePricesService; beforeEach(() => { vi.clearAllMocks(); service = new MedicinePricesService({ medicinePricesRepository: mockPricesRepo as never, medicineProductsRepository: mockProductsRepo as never, storesRepository: mockStoresRepo as never, }); }); describe('recordPrice', () => { const validInput = { medicineProductId: 'mp-1', medicineId: 'med-1', storeId: 'st-1', price: 10, currency: 'USD', quantity: 100, unit: 'tablet' as never, isInsurancePrice: false, }; it('creates price record with computed pricePerUnit', async () => { mockProductsRepo.findById.mockResolvedValue({ medicineName: 'Acetaminophen', brand: 'Tylenol', }); mockStoresRepo.findById.mockResolvedValue({ name: 'Walgreens' }); const record = { _id: 'pr-1', pricePerUnit: 0.1 }; mockPricesRepo.create.mockResolvedValue(record); const result = await service.recordPrice(validInput, 'hh1', 'user-1'); expect(result).toEqual(record); expect(mockPricesRepo.create).toHaveBeenCalledWith( expect.objectContaining({ pricePerUnit: 0.1, medicineName: 'Acetaminophen', storeName: 'Walgreens', }), ); }); it('uses medicineName as brand fallback when brand is not set', async () => { mockProductsRepo.findById.mockResolvedValue({ medicineName: 'Generic', brand: undefined }); mockStoresRepo.findById.mockResolvedValue({ name: 'CVS' }); mockPricesRepo.create.mockResolvedValue({ _id: 'pr-1' }); await service.recordPrice(validInput, 'hh1', 'user-1'); expect(mockPricesRepo.create).toHaveBeenCalledWith( expect.objectContaining({ medicineProductBrand: 'Generic' }), ); }); it('uses provided date when given', async () => { mockProductsRepo.findById.mockResolvedValue({ medicineName: 'Acetaminophen', brand: 'Tylenol', }); mockStoresRepo.findById.mockResolvedValue({ name: 'Walgreens' }); mockPricesRepo.create.mockResolvedValue({ _id: 'pr-1' }); await service.recordPrice( { ...validInput, date: '2026-01-15T00:00:00.000Z' }, 'hh1', 'user-1', ); expect(mockPricesRepo.create).toHaveBeenCalledWith( expect.objectContaining({ date: new Date('2026-01-15T00:00:00.000Z') }), ); }); it('throws NotFoundError when product not found', async () => { mockProductsRepo.findById.mockResolvedValue(null); await expect(service.recordPrice(validInput, 'hh1', 'user-1')).rejects.toThrow( 'Medicine product not found', ); }); it('throws NotFoundError when store not found', async () => { mockProductsRepo.findById.mockResolvedValue({ medicineName: 'Acetaminophen', brand: 'Tylenol', }); mockStoresRepo.findById.mockResolvedValue(null); await expect(service.recordPrice(validInput, 'hh1', 'user-1')).rejects.toThrow( 'Store not found', ); }); }); describe('getPriceHistory', () => { it('delegates to repository', async () => { const result = { data: [], pagination: { cursor: null, hasMore: false } }; mockPricesRepo.findByMedicine.mockResolvedValue(result); const response = await service.getPriceHistory('hh1', 'med-1', { limit: 20 }); expect(response).toEqual(result); expect(mockPricesRepo.findByMedicine).toHaveBeenCalledWith('hh1', 'med-1', { limit: 20 }); }); }); describe('compareStores', () => { it('delegates to repository', async () => { const comparisons = [{ storeId: 'st-1', storeName: 'Walgreens', latestPrice: 10 }]; mockPricesRepo.compareStores.mockResolvedValue(comparisons); const result = await service.compareStores('hh1', 'med-1'); expect(result).toEqual(comparisons); expect(mockPricesRepo.compareStores).toHaveBeenCalledWith('hh1', 'med-1'); }); }); describe('estimatePrice', () => { it('returns pricePerUnit of latest record', async () => { mockPricesRepo.getLatestForMedicine.mockResolvedValue({ pricePerUnit: 0.15 }); const result = await service.estimatePrice('hh1', 'med-1'); expect(result).toBe(0.15); }); it('returns null when no records', async () => { mockPricesRepo.getLatestForMedicine.mockResolvedValue(null); const result = await service.estimatePrice('hh1', 'med-1'); expect(result).toBeNull(); }); it('filters by storeId when provided', async () => { mockPricesRepo.getLatestForMedicine.mockResolvedValue({ pricePerUnit: 0.2 }); await service.estimatePrice('hh1', 'med-1', 'st-1'); expect(mockPricesRepo.getLatestForMedicine).toHaveBeenCalledWith('hh1', 'med-1', 'st-1'); }); }); describe('getAnalytics', () => { it('delegates to repository', async () => { const analytics = { spendingOverTime: [], topBySpending: [], spendingByStore: [], priceAlerts: [], }; mockPricesRepo.getAnalytics.mockResolvedValue(analytics); const result = await service.getAnalytics('hh1', { period: 'month' }); expect(result).toEqual(analytics); expect(mockPricesRepo.getAnalytics).toHaveBeenCalledWith('hh1', { period: 'month' }); }); }); });