Implement regimens

This commit is contained in:
Aerilyn Weber 2026-03-28 18:25:49 +09:00
parent 1f66fab30f
commit 9f416903ef
66 changed files with 9130 additions and 189 deletions

View file

@ -29,6 +29,8 @@ const {
mockFindExpiringSoon,
mockSoftDelete,
mockCountByMedicineId,
mockDiscard,
mockFindActiveByMedicineForFEFO,
} = vi.hoisted(() => ({
mockFindByHousehold: vi.fn(),
mockFindById: vi.fn(),
@ -39,6 +41,8 @@ const {
mockFindExpiringSoon: vi.fn(),
mockSoftDelete: vi.fn(),
mockCountByMedicineId: vi.fn(),
mockDiscard: vi.fn(),
mockFindActiveByMedicineForFEFO: vi.fn(),
}));
vi.mock('./cabinet.repository.js', () => ({
@ -52,6 +56,8 @@ vi.mock('./cabinet.repository.js', () => ({
findExpiringSoon = mockFindExpiringSoon;
softDelete = mockSoftDelete;
countByMedicineId = mockCountByMedicineId;
discard = mockDiscard;
findActiveByMedicineForFEFO = mockFindActiveByMedicineForFEFO;
},
}));
@ -105,6 +111,28 @@ vi.mock('../medicines/medicines.service.js', () => ({
},
}));
vi.mock('../cabinet-events/cabinet-events.repository.js', () => ({
CabinetEventsRepository: class {
create = vi.fn();
createMany = vi.fn();
findByHousehold = vi.fn();
findByCabinetItem = vi.fn();
getSpendingSummary = vi.fn();
getAvgUnitPriceByMedicine = vi.fn();
},
}));
vi.mock('../cabinet-events/cabinet-events.service.js', () => ({
CabinetEventsService: class {
logEvent = vi.fn();
logEvents = vi.fn();
listEvents = vi.fn();
getEventsByItem = vi.fn();
getSpendingSummary = vi.fn();
getAvgUnitPrices = vi.fn();
},
}));
vi.mock('../users/users.repository.js', () => ({
UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
@ -117,6 +145,7 @@ import householdPlugin from '../../plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js';
import medicinesRoutes from '../medicines/medicines.routes.js';
import medicineProductsRoutes from '../medicine-products/medicine-products.routes.js';
import cabinetEventsRoutes from '../cabinet-events/cabinet-events.routes.js';
import cabinetRoutes from './cabinet.routes.js';
function makeFakeCabinetItem(overrides = {}) {
@ -155,6 +184,7 @@ describe('cabinet.routes', () => {
await instance.register(usersRoutes);
await instance.register(medicinesRoutes);
await instance.register(medicineProductsRoutes);
await instance.register(cabinetEventsRoutes);
await instance.register(cabinetRoutes);
await instance.ready();
return instance;
@ -242,6 +272,40 @@ describe('cabinet.routes', () => {
expect(body.data[0].expirationDate).toBe('2026-12-31T00:00:00.000Z');
expect(body.data[0].createdAt).toBe('2024-01-01T00:00:00.000Z');
});
it('includes optional purchase and store fields when present', async () => {
const item = makeFakeCabinetItem({
concentration: 5.0,
concentrationUnit: 'mg/mL',
purchaseDate: new Date('2024-03-01T00:00:00.000Z'),
unitPrice: 1.5,
totalPrice: 45.0,
currency: 'USD',
storeId: 'store-1',
storeName: 'Pharmacy Plus',
});
mockFindByHousehold.mockResolvedValue({
data: [item],
pagination: { cursor: null, hasMore: false },
});
const res = await app.inject({
method: 'GET',
url: '/api/v1/households/hh1/cabinet',
headers: authHeaders,
});
expect(res.statusCode).toBe(200);
const body = res.json();
expect(body.data[0].concentration).toBe(5.0);
expect(body.data[0].concentrationUnit).toBe('mg/mL');
expect(body.data[0].purchaseDate).toBe('2024-03-01T00:00:00.000Z');
expect(body.data[0].unitPrice).toBe(1.5);
expect(body.data[0].totalPrice).toBe(45.0);
expect(body.data[0].currency).toBe('USD');
expect(body.data[0].storeId).toBe('store-1');
expect(body.data[0].storeName).toBe('Pharmacy Plus');
});
});
describe('GET /api/v1/households/:householdId/cabinet/summary', () => {
@ -379,4 +443,32 @@ describe('cabinet.routes', () => {
expect(res.statusCode).toBe(204);
});
});
describe('POST /api/v1/households/:householdId/cabinet/:id/discard', () => {
it('discards a cabinet item', async () => {
mockFindById.mockResolvedValue(makeFakeCabinetItem({ quantity: 20 }));
mockDiscard.mockResolvedValue(makeFakeCabinetItem({ quantity: 0, isDeleted: true }));
const res = await app.inject({
method: 'POST',
url: '/api/v1/households/hh1/cabinet/ci-1/discard',
headers: authHeaders,
payload: { reason: 'expired' },
});
expect(res.statusCode).toBe(200);
expect(res.json().quantity).toBe(0);
});
it('returns 400 for missing reason', async () => {
const res = await app.inject({
method: 'POST',
url: '/api/v1/households/hh1/cabinet/ci-1/discard',
headers: authHeaders,
payload: {},
});
expect(res.statusCode).toBe(400);
});
});
});