231 lines
6.5 KiB
TypeScript
231 lines
6.5 KiB
TypeScript
|
|
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('../../schemas/regimen.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 { RegimenModel: FakeModel };
|
||
|
|
});
|
||
|
|
|
||
|
|
import { RegimensRepository } from './regimens.repository.js';
|
||
|
|
|
||
|
|
describe(RegimensRepository.name, () => {
|
||
|
|
let repo: RegimensRepository;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.clearAllMocks();
|
||
|
|
repo = new RegimensRepository();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('findByHousehold', () => {
|
||
|
|
it('returns paginated items', async () => {
|
||
|
|
const items = [{ _id: 'reg-1', name: 'Morning' }];
|
||
|
|
mockFind.mockResolvedValue(items);
|
||
|
|
|
||
|
|
const result = await repo.findByHousehold('hh1', 'user-1', { limit: 20 });
|
||
|
|
|
||
|
|
expect(result.data).toEqual(items);
|
||
|
|
expect(result.pagination.hasMore).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('handles cursor-based pagination', async () => {
|
||
|
|
const items = [{ _id: 'reg-2', name: 'Evening' }];
|
||
|
|
mockFind.mockResolvedValue(items);
|
||
|
|
|
||
|
|
const cursor = Buffer.from('reg-1').toString('base64');
|
||
|
|
const result = await repo.findByHousehold('hh1', 'user-1', { cursor, limit: 20 });
|
||
|
|
|
||
|
|
expect(result.data).toEqual(items);
|
||
|
|
expect(result.pagination.hasMore).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('sets hasMore when more items exist', async () => {
|
||
|
|
const items = Array.from({ length: 3 }, (_, i) => ({ _id: `reg-${i}`, name: `Reg ${i}` }));
|
||
|
|
mockFind.mockResolvedValue(items);
|
||
|
|
|
||
|
|
const result = await repo.findByHousehold('hh1', 'user-1', { limit: 2 });
|
||
|
|
|
||
|
|
expect(result.data).toHaveLength(2);
|
||
|
|
expect(result.pagination.hasMore).toBe(true);
|
||
|
|
expect(result.pagination.cursor).toBeTruthy();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns null cursor when no data', async () => {
|
||
|
|
mockFind.mockResolvedValue([]);
|
||
|
|
|
||
|
|
const result = await repo.findByHousehold('hh1', 'user-1', { limit: 20 });
|
||
|
|
|
||
|
|
expect(result.pagination.cursor).toBeNull();
|
||
|
|
expect(result.pagination.hasMore).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('filters by isActive', async () => {
|
||
|
|
mockFind.mockResolvedValue([]);
|
||
|
|
|
||
|
|
await repo.findByHousehold('hh1', 'user-1', { isActive: true, limit: 20 });
|
||
|
|
|
||
|
|
expect(mockFind).toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('does not add isActive filter when undefined', async () => {
|
||
|
|
mockFind.mockResolvedValue([]);
|
||
|
|
|
||
|
|
await repo.findByHousehold('hh1', 'user-1', { limit: 20 });
|
||
|
|
|
||
|
|
expect(mockFind).toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns cursor only when hasMore is true', async () => {
|
||
|
|
const items = [{ _id: 'reg-1', name: 'Morning' }];
|
||
|
|
mockFind.mockResolvedValue(items);
|
||
|
|
|
||
|
|
const result = await repo.findByHousehold('hh1', 'user-1', { limit: 20 });
|
||
|
|
|
||
|
|
expect(result.pagination.cursor).toBeNull();
|
||
|
|
expect(result.pagination.hasMore).toBe(false);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('findById', () => {
|
||
|
|
it('returns regimen by id, householdId, and userId', async () => {
|
||
|
|
const regimen = { _id: 'reg-1', householdId: 'hh1', userId: 'user-1', name: 'Morning' };
|
||
|
|
mockFindOne.mockResolvedValue(regimen);
|
||
|
|
|
||
|
|
const result = await repo.findById('reg-1', 'hh1', 'user-1');
|
||
|
|
|
||
|
|
expect(result).toEqual(regimen);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns null when not found', async () => {
|
||
|
|
mockFindOne.mockResolvedValue(null);
|
||
|
|
|
||
|
|
const result = await repo.findById('reg-missing', 'hh1', 'user-1');
|
||
|
|
|
||
|
|
expect(result).toBeNull();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('findActiveByUser', () => {
|
||
|
|
it('returns active regimens for user', async () => {
|
||
|
|
const regimens = [
|
||
|
|
{ _id: 'reg-1', isActive: true },
|
||
|
|
{ _id: 'reg-2', isActive: true },
|
||
|
|
];
|
||
|
|
mockFind.mockResolvedValue(regimens);
|
||
|
|
|
||
|
|
const result = await repo.findActiveByUser('hh1', 'user-1');
|
||
|
|
|
||
|
|
expect(result).toEqual(regimens);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns empty array when no active regimens', async () => {
|
||
|
|
mockFind.mockResolvedValue([]);
|
||
|
|
|
||
|
|
const result = await repo.findActiveByUser('hh1', 'user-1');
|
||
|
|
|
||
|
|
expect(result).toEqual([]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('create', () => {
|
||
|
|
it('creates and returns regimen', async () => {
|
||
|
|
const data = {
|
||
|
|
name: 'Morning Routine',
|
||
|
|
isActive: true,
|
||
|
|
medications: [
|
||
|
|
{
|
||
|
|
medicineId: 'med-1',
|
||
|
|
medicineName: 'Metformin',
|
||
|
|
medicineStrength: 500,
|
||
|
|
medicineStrengthUnit: 'mg',
|
||
|
|
medicineForm: 'tablet',
|
||
|
|
dosage: 1,
|
||
|
|
dosageUnit: 'tablet',
|
||
|
|
frequency: 'daily',
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
mockSave.mockImplementation(function (this: { toObject: () => unknown }) {
|
||
|
|
return Promise.resolve(this);
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = await repo.create(data, 'hh1', 'user-1', 'user-1');
|
||
|
|
|
||
|
|
expect(result).toBeTruthy();
|
||
|
|
expect(mockSave).toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('update', () => {
|
||
|
|
it('updates and returns regimen', async () => {
|
||
|
|
const updated = { _id: 'reg-1', name: 'Updated' };
|
||
|
|
mockFindOneAndUpdate.mockResolvedValue(updated);
|
||
|
|
|
||
|
|
const result = await repo.update('reg-1', 'hh1', 'user-1', { name: 'Updated' });
|
||
|
|
|
||
|
|
expect(result).toEqual(updated);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns null when regimen not found', async () => {
|
||
|
|
mockFindOneAndUpdate.mockResolvedValue(null);
|
||
|
|
|
||
|
|
const result = await repo.update('reg-missing', 'hh1', 'user-1', { name: 'Updated' });
|
||
|
|
|
||
|
|
expect(result).toBeNull();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('softDelete', () => {
|
||
|
|
it('soft deletes and returns regimen', async () => {
|
||
|
|
const deleted = { _id: 'reg-1', isDeleted: true };
|
||
|
|
mockFindOneAndUpdate.mockResolvedValue(deleted);
|
||
|
|
|
||
|
|
const result = await repo.softDelete('reg-1', 'hh1', 'user-1');
|
||
|
|
|
||
|
|
expect(result).toEqual(deleted);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns null when regimen not found', async () => {
|
||
|
|
mockFindOneAndUpdate.mockResolvedValue(null);
|
||
|
|
|
||
|
|
const result = await repo.softDelete('reg-missing', 'hh1', 'user-1');
|
||
|
|
|
||
|
|
expect(result).toBeNull();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|