import { describe, it, expect, vi, beforeEach } from 'vitest'; import { MedicineForm, StrengthUnit, MedicineCategory } from '@meshitrack/shared'; const { mockExec, _mockLean, mockFind, mockFindOne, mockFindOneAndUpdate, mockSave, _mockSort, _mockLimit, } = vi.hoisted(() => { const mockExec = vi.fn(); const mockLean = vi.fn(() => ({ exec: mockExec })); const mockLimit = vi.fn(() => ({ lean: mockLean })); const mockSort = vi.fn(() => ({ limit: mockLimit })); return { mockExec, mockLean, mockFind: vi.fn(() => ({ sort: mockSort })), mockFindOne: vi.fn(() => ({ lean: mockLean })), mockFindOneAndUpdate: vi.fn(() => ({ exec: mockExec })), mockSave: vi.fn(), mockSort, mockLimit, }; }); vi.mock('../../schemas/medicine.schema.js', () => { class MockMedicineModel { _data: Record; constructor(data: Record) { this._data = data; Object.assign(this, data); } save() { mockSave(); return Promise.resolve(this); } toObject() { return { _id: 'med-new', ...this._data }; } static find = mockFind; static findOne = mockFindOne; static findOneAndUpdate = mockFindOneAndUpdate; } return { MedicineModel: MockMedicineModel }; }); import { MedicinesRepository } from './medicines.repository.js'; describe(MedicinesRepository.name, () => { let repo: MedicinesRepository; beforeEach(() => { vi.clearAllMocks(); repo = new MedicinesRepository(); }); describe('findByHousehold', () => { it('returns paginated results', async () => { const items = [ { _id: 'med-1', name: 'Aspirin' }, { _id: 'med-2', name: 'Ibuprofen' }, ]; mockExec.mockResolvedValue(items); const result = await repo.findByHousehold('hh1', { limit: 20 }); expect(mockFind).toHaveBeenCalledWith({ householdId: 'hh1', isDeleted: false }); expect(result.data).toHaveLength(2); expect(result.pagination.hasMore).toBe(false); }); it('applies partial name search when q is provided', async () => { mockExec.mockResolvedValue([]); await repo.findByHousehold('hh1', { q: 'asp', limit: 20 }); expect(mockFind).toHaveBeenCalledWith( expect.objectContaining({ name: { $regex: 'asp', $options: 'i' } }), ); }); it('applies category filter', async () => { mockExec.mockResolvedValue([]); await repo.findByHousehold('hh1', { category: MedicineCategory.PRESCRIPTION, limit: 20 }); expect(mockFind).toHaveBeenCalledWith( expect.objectContaining({ category: MedicineCategory.PRESCRIPTION }), ); }); it('applies form filter', async () => { mockExec.mockResolvedValue([]); await repo.findByHousehold('hh1', { form: MedicineForm.TABLET, limit: 20 }); expect(mockFind).toHaveBeenCalledWith(expect.objectContaining({ form: MedicineForm.TABLET })); }); it('detects hasMore when extra item returned', async () => { const items = Array.from({ length: 3 }, (_, i) => ({ _id: `med-${i}`, name: `Med ${i}` })); mockExec.mockResolvedValue(items); 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('decodes cursor for pagination', async () => { mockExec.mockResolvedValue([]); const cursor = Buffer.from('med-5').toString('base64'); await repo.findByHousehold('hh1', { cursor, limit: 20 }); expect(mockFind).toHaveBeenCalledWith(expect.objectContaining({ _id: { $gt: 'med-5' } })); }); }); describe('findById', () => { it('finds by id and householdId', async () => { const medicine = { _id: 'med-1', name: 'Aspirin' }; mockExec.mockResolvedValue(medicine); const result = await repo.findById('med-1', 'hh1'); expect(mockFindOne).toHaveBeenCalledWith({ _id: 'med-1', householdId: 'hh1', isDeleted: false, }); expect(result).toEqual(medicine); }); }); describe('findDuplicate', () => { it('finds medicine with matching fields', async () => { mockExec.mockResolvedValue({ _id: 'med-1' }); const result = await repo.findDuplicate('hh1', 'Aspirin', 500, 'mg', 'tablet'); expect(mockFindOne).toHaveBeenCalledWith({ householdId: 'hh1', name: 'Aspirin', strength: 500, strengthUnit: 'mg', form: 'tablet', isDeleted: false, }); expect(result).toBeTruthy(); }); it('excludes specified id', async () => { mockExec.mockResolvedValue(null); await repo.findDuplicate('hh1', 'Aspirin', 500, 'mg', 'tablet', 'med-1'); expect(mockFindOne).toHaveBeenCalledWith(expect.objectContaining({ _id: { $ne: 'med-1' } })); }); }); describe('create', () => { it('creates a medicine', async () => { mockSave.mockResolvedValue({}); const data = { name: 'Aspirin', form: MedicineForm.TABLET, strength: 500, strengthUnit: StrengthUnit.MG, category: MedicineCategory.OTC, tags: [], }; const result = await repo.create(data, 'hh1', 'kc-1'); expect(mockSave).toHaveBeenCalled(); expect(result).toMatchObject({ name: 'Aspirin', householdId: 'hh1', createdBy: 'kc-1' }); }); }); describe('update', () => { it('updates a medicine', async () => { mockExec.mockResolvedValue({ _id: 'med-1', name: 'Updated' }); const result = await repo.update('med-1', 'hh1', { name: 'Updated' }); expect(mockFindOneAndUpdate).toHaveBeenCalledWith( { _id: 'med-1', householdId: 'hh1', isDeleted: false }, { $set: { name: 'Updated' } }, { new: true, lean: true }, ); expect(result).toEqual({ _id: 'med-1', name: 'Updated' }); }); }); describe('softDelete', () => { it('sets isDeleted to true', async () => { mockExec.mockResolvedValue({ _id: 'med-1', isDeleted: true }); await repo.softDelete('med-1', 'hh1'); expect(mockFindOneAndUpdate).toHaveBeenCalledWith( { _id: 'med-1', householdId: 'hh1', isDeleted: false }, { $set: { isDeleted: true } }, { new: true, lean: true }, ); }); }); });