MeshiTrack/packages/api/tests/modules/medicines/medicines.repository.test.ts

216 lines
6.3 KiB
TypeScript
Raw Normal View History

2026-03-28 08:19:48 +09:00
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,
};
});
2026-05-19 11:06:03 +09:00
vi.mock('../../../src/schemas/medicine.schema.js', () => {
2026-03-28 08:19:48 +09:00
class MockMedicineModel {
_data: Record<string, unknown>;
constructor(data: Record<string, unknown>) {
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 };
});
2026-05-19 11:06:03 +09:00
import { MedicinesRepository } from '../../../src/modules/medicines/medicines.repository.js';
2026-03-28 08:19:48 +09:00
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 },
);
});
});
});