2026-04-18 12:36:29 +09:00
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
2026-05-19 11:06:03 +09:00
|
|
|
import { RefillsService } from '../../../src/modules/refills/refills.service.js';
|
2026-04-18 12:36:29 +09:00
|
|
|
|
|
|
|
|
describe(RefillsService.name, () => {
|
|
|
|
|
const mockRepo = {
|
|
|
|
|
create: vi.fn(),
|
|
|
|
|
findByHousehold: vi.fn(),
|
|
|
|
|
findById: vi.fn(),
|
|
|
|
|
update: vi.fn(),
|
|
|
|
|
updateItem: vi.fn(),
|
|
|
|
|
markItemsAddedToCabinet: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
const mockRegimensService = {
|
|
|
|
|
calculateBurnRates: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
const mockCabinetRepo = {
|
|
|
|
|
getAggregateSummary: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
const mockCabinetService = {
|
|
|
|
|
addItem: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
const mockPricesRepo = {
|
|
|
|
|
getLatestForMedicine: vi.fn(),
|
|
|
|
|
compareStores: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
const mockPurchasesRepo = {
|
|
|
|
|
getPendingMedicineStock: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let service: RefillsService;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
mockPurchasesRepo.getPendingMedicineStock.mockResolvedValue([]);
|
|
|
|
|
service = new RefillsService({
|
|
|
|
|
refillsRepository: mockRepo as never,
|
|
|
|
|
regimensService: mockRegimensService as never,
|
|
|
|
|
cabinetRepository: mockCabinetRepo as never,
|
|
|
|
|
cabinetService: mockCabinetService as never,
|
|
|
|
|
medicinePricesRepository: mockPricesRepo as never,
|
|
|
|
|
purchasesRepository: mockPurchasesRepo as never,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('getAlerts', () => {
|
|
|
|
|
it('returns empty array when no medicines are running low', async () => {
|
|
|
|
|
mockRegimensService.calculateBurnRates.mockResolvedValue([
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
dailyConsumption: 1,
|
|
|
|
|
totalInCabinet: 100,
|
|
|
|
|
daysUntilEmpty: 100,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getAlerts('hh1', 'user-1', 7);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual([]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns alerts for medicines below threshold', async () => {
|
|
|
|
|
mockRegimensService.calculateBurnRates.mockResolvedValue([
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
dailyConsumption: 2,
|
|
|
|
|
totalInCabinet: 10,
|
|
|
|
|
daysUntilEmpty: 5,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
]);
|
|
|
|
|
mockCabinetRepo.getAggregateSummary.mockResolvedValue([
|
|
|
|
|
{ _id: 'med-1', medicineStrength: 500, medicineStrengthUnit: 'mg' },
|
|
|
|
|
]);
|
|
|
|
|
mockPricesRepo.getLatestForMedicine.mockResolvedValue(null);
|
|
|
|
|
mockPricesRepo.compareStores.mockResolvedValue([]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getAlerts('hh1', 'user-1', 7);
|
|
|
|
|
|
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
|
expect(result[0].medicineId).toBe('med-1');
|
|
|
|
|
expect(result[0].daysUntilEmpty).toBe(5);
|
|
|
|
|
expect(result[0].suggestedQuantity).toBe(60); // ceil(2 * 30)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('attaches lastKnownPrice when available', async () => {
|
|
|
|
|
mockRegimensService.calculateBurnRates.mockResolvedValue([
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
dailyConsumption: 2,
|
|
|
|
|
totalInCabinet: 5,
|
|
|
|
|
daysUntilEmpty: 2,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
]);
|
|
|
|
|
mockCabinetRepo.getAggregateSummary.mockResolvedValue([]);
|
|
|
|
|
mockPricesRepo.getLatestForMedicine.mockResolvedValue({
|
|
|
|
|
price: 10,
|
|
|
|
|
pricePerUnit: 0.1,
|
|
|
|
|
storeName: 'Walgreens',
|
|
|
|
|
storeId: 'st-1',
|
|
|
|
|
date: new Date('2026-01-01T00:00:00.000Z'),
|
|
|
|
|
});
|
|
|
|
|
mockPricesRepo.compareStores.mockResolvedValue([]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getAlerts('hh1', 'user-1', 7);
|
|
|
|
|
|
|
|
|
|
expect(result[0].lastKnownPrice).toBeDefined();
|
|
|
|
|
expect(result[0].lastKnownPrice?.storeName).toBe('Walgreens');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('attaches cheapestOption from compareStores', async () => {
|
|
|
|
|
mockRegimensService.calculateBurnRates.mockResolvedValue([
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
dailyConsumption: 2,
|
|
|
|
|
totalInCabinet: 5,
|
|
|
|
|
daysUntilEmpty: 2,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
]);
|
|
|
|
|
mockCabinetRepo.getAggregateSummary.mockResolvedValue([]);
|
|
|
|
|
mockPricesRepo.getLatestForMedicine.mockResolvedValue(null);
|
|
|
|
|
mockPricesRepo.compareStores.mockResolvedValue([
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
storeId: 'st-1',
|
|
|
|
|
storeName: 'CVS',
|
|
|
|
|
latestPrice: 8,
|
|
|
|
|
latestPricePerUnit: 0.08,
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
date: new Date(),
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getAlerts('hh1', 'user-1', 7);
|
|
|
|
|
|
|
|
|
|
expect(result[0].cheapestOption).toBeDefined();
|
|
|
|
|
expect(result[0].cheapestOption?.storeName).toBe('CVS');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('includes pendingOrderStock and daysUntilEmptyWithOrders from ordered purchases', async () => {
|
|
|
|
|
mockRegimensService.calculateBurnRates.mockResolvedValue([
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
dailyConsumption: 2,
|
|
|
|
|
totalInCabinet: 4,
|
|
|
|
|
daysUntilEmpty: 2,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
]);
|
|
|
|
|
mockCabinetRepo.getAggregateSummary.mockResolvedValue([]);
|
|
|
|
|
mockPricesRepo.getLatestForMedicine.mockResolvedValue(null);
|
|
|
|
|
mockPricesRepo.compareStores.mockResolvedValue([]);
|
|
|
|
|
mockPurchasesRepo.getPendingMedicineStock.mockResolvedValue([
|
|
|
|
|
{ medicineId: 'med-1', totalUnits: 60 },
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getAlerts('hh1', 'user-1', 7);
|
|
|
|
|
|
|
|
|
|
expect(result[0].pendingOrderStock).toBe(60);
|
|
|
|
|
expect(result[0].daysUntilEmptyWithOrders).toBe(32); // (4 + 60) / 2
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('excludes medicines with null daysUntilEmpty', async () => {
|
|
|
|
|
mockRegimensService.calculateBurnRates.mockResolvedValue([
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
dailyConsumption: 2,
|
|
|
|
|
totalInCabinet: 0,
|
|
|
|
|
daysUntilEmpty: null,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getAlerts('hh1', 'user-1', 7);
|
|
|
|
|
|
|
|
|
|
expect(result).toHaveLength(0);
|
|
|
|
|
});
|
2026-05-19 14:09:03 +09:00
|
|
|
|
|
|
|
|
it('handles zero dailyConsumption gracefully in daysUntilEmptyWithOrders calculation', async () => {
|
|
|
|
|
mockRegimensService.calculateBurnRates.mockResolvedValue([
|
|
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
dailyConsumption: 0,
|
|
|
|
|
totalInCabinet: 10,
|
|
|
|
|
daysUntilEmpty: 2,
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
mockCabinetRepo.getAggregateSummary.mockResolvedValue([]);
|
|
|
|
|
mockPricesRepo.getLatestForMedicine.mockResolvedValue(null);
|
|
|
|
|
mockPricesRepo.compareStores.mockResolvedValue([]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getAlerts('hh1', 'user-1', 7);
|
|
|
|
|
|
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
|
expect(result[0].daysUntilEmptyWithOrders).toBeNull();
|
|
|
|
|
});
|
2026-04-18 12:36:29 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('createList', () => {
|
|
|
|
|
it('creates list with provided items', async () => {
|
|
|
|
|
const list = { _id: 'rl-1', name: 'My List', items: [], status: 'active' };
|
|
|
|
|
mockRepo.create.mockResolvedValue(list);
|
|
|
|
|
|
|
|
|
|
const result = await service.createList(
|
|
|
|
|
{
|
|
|
|
|
name: 'My List',
|
|
|
|
|
fromAlerts: false,
|
|
|
|
|
thresholdDays: 7,
|
2026-04-26 18:44:59 +09:00
|
|
|
items: [
|
|
|
|
|
{ medicineId: 'med-1', medicineName: 'Aspirin', quantity: 30, unit: 'tablet' as never },
|
|
|
|
|
],
|
2026-04-18 12:36:29 +09:00
|
|
|
},
|
|
|
|
|
'hh1',
|
|
|
|
|
'user-1',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual(list);
|
|
|
|
|
expect(mockRepo.create).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ name: 'My List', householdId: 'hh1' }),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('creates list with no items when neither fromAlerts nor items provided', async () => {
|
|
|
|
|
mockRepo.create.mockResolvedValue({ _id: 'rl-1', items: [] });
|
|
|
|
|
|
2026-04-26 18:44:59 +09:00
|
|
|
await service.createList(
|
|
|
|
|
{ name: 'Empty List', fromAlerts: false, thresholdDays: 7 },
|
|
|
|
|
'hh1',
|
|
|
|
|
'user-1',
|
|
|
|
|
);
|
2026-04-18 12:36:29 +09:00
|
|
|
|
|
|
|
|
expect(mockRepo.create).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ items: [], totalEstimatedCost: undefined }),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('computes totalEstimatedCost from items with estimatedPrice', async () => {
|
|
|
|
|
mockRepo.create.mockResolvedValue({ _id: 'rl-1', items: [] });
|
|
|
|
|
|
|
|
|
|
await service.createList(
|
|
|
|
|
{
|
|
|
|
|
name: 'Priced List',
|
|
|
|
|
fromAlerts: false,
|
|
|
|
|
thresholdDays: 7,
|
|
|
|
|
items: [
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
quantity: 30,
|
|
|
|
|
unit: 'tablet' as never,
|
|
|
|
|
estimatedPrice: 10,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
medicineId: 'med-2',
|
|
|
|
|
medicineName: 'Ibuprofen',
|
|
|
|
|
quantity: 20,
|
|
|
|
|
unit: 'tablet' as never,
|
|
|
|
|
estimatedPrice: 8,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
'hh1',
|
|
|
|
|
'user-1',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(mockRepo.create).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ totalEstimatedCost: 18 }),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('creates list from alerts when fromAlerts is true', async () => {
|
|
|
|
|
mockRegimensService.calculateBurnRates.mockResolvedValue([
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
dailyConsumption: 2,
|
|
|
|
|
totalInCabinet: 5,
|
|
|
|
|
daysUntilEmpty: 2,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
]);
|
|
|
|
|
mockCabinetRepo.getAggregateSummary.mockResolvedValue([]);
|
|
|
|
|
mockPricesRepo.getLatestForMedicine.mockResolvedValue(null);
|
|
|
|
|
mockPricesRepo.compareStores.mockResolvedValue([]);
|
|
|
|
|
mockRepo.create.mockResolvedValue({ _id: 'rl-1', items: [] });
|
|
|
|
|
|
2026-04-26 18:44:59 +09:00
|
|
|
await service.createList(
|
|
|
|
|
{ name: 'Auto List', fromAlerts: true, thresholdDays: 7 },
|
|
|
|
|
'hh1',
|
|
|
|
|
'user-1',
|
|
|
|
|
);
|
2026-04-18 12:36:29 +09:00
|
|
|
|
|
|
|
|
expect(mockRepo.create).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
2026-04-26 18:44:59 +09:00
|
|
|
items: expect.arrayContaining([expect.objectContaining({ medicineId: 'med-1' })]),
|
2026-04-18 12:36:29 +09:00
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('list', () => {
|
|
|
|
|
it('delegates to repository', async () => {
|
|
|
|
|
const result = { data: [], pagination: { cursor: null, hasMore: false } };
|
|
|
|
|
mockRepo.findByHousehold.mockResolvedValue(result);
|
|
|
|
|
|
|
|
|
|
const response = await service.list('hh1', { limit: 20 });
|
|
|
|
|
|
|
|
|
|
expect(response).toEqual(result);
|
|
|
|
|
expect(mockRepo.findByHousehold).toHaveBeenCalledWith('hh1', { limit: 20 });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('getById', () => {
|
|
|
|
|
it('returns list when found', async () => {
|
|
|
|
|
const list = { _id: 'rl-1', name: 'My List' };
|
|
|
|
|
mockRepo.findById.mockResolvedValue(list);
|
|
|
|
|
|
|
|
|
|
expect(await service.getById('rl-1', 'hh1')).toEqual(list);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('throws NotFoundError when not found', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue(null);
|
|
|
|
|
|
|
|
|
|
await expect(service.getById('missing', 'hh1')).rejects.toThrow('Refill list not found');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('updateList', () => {
|
|
|
|
|
it('updates and returns list', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({ _id: 'rl-1' });
|
|
|
|
|
const updated = { _id: 'rl-1', name: 'Updated' };
|
|
|
|
|
mockRepo.update.mockResolvedValue(updated);
|
|
|
|
|
|
|
|
|
|
const result = await service.updateList('rl-1', 'hh1', { name: 'Updated' });
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual(updated);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('throws NotFoundError when list not found', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue(null);
|
|
|
|
|
|
2026-04-26 18:44:59 +09:00
|
|
|
await expect(service.updateList('missing', 'hh1', {})).rejects.toThrow(
|
|
|
|
|
'Refill list not found',
|
|
|
|
|
);
|
2026-04-18 12:36:29 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('throws NotFoundError when update returns null', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({ _id: 'rl-1' });
|
|
|
|
|
mockRepo.update.mockResolvedValue(null);
|
|
|
|
|
|
|
|
|
|
await expect(service.updateList('rl-1', 'hh1', {})).rejects.toThrow('Refill list not found');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('updateItem', () => {
|
|
|
|
|
it('updates item and returns list', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({ _id: 'rl-1' });
|
|
|
|
|
const updated = { _id: 'rl-1', items: [{ _id: 'item-1', checked: true }] };
|
|
|
|
|
mockRepo.updateItem.mockResolvedValue(updated);
|
|
|
|
|
|
|
|
|
|
const result = await service.updateItem('rl-1', 'hh1', 'item-1', { checked: true });
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual(updated);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets checkedAt when checked is true', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({ _id: 'rl-1' });
|
|
|
|
|
mockRepo.updateItem.mockResolvedValue({ _id: 'rl-1', items: [] });
|
|
|
|
|
|
|
|
|
|
await service.updateItem('rl-1', 'hh1', 'item-1', { checked: true });
|
|
|
|
|
|
|
|
|
|
expect(mockRepo.updateItem).toHaveBeenCalledWith(
|
|
|
|
|
'rl-1',
|
|
|
|
|
'hh1',
|
|
|
|
|
'item-1',
|
|
|
|
|
expect.objectContaining({ checkedAt: expect.any(Date) }),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('throws NotFoundError when list not found', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue(null);
|
|
|
|
|
|
2026-04-26 18:44:59 +09:00
|
|
|
await expect(service.updateItem('missing', 'hh1', 'item-1', {})).rejects.toThrow(
|
|
|
|
|
'Refill list not found',
|
|
|
|
|
);
|
2026-04-18 12:36:29 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('throws NotFoundError when item not found', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({ _id: 'rl-1' });
|
|
|
|
|
mockRepo.updateItem.mockResolvedValue(null);
|
|
|
|
|
|
|
|
|
|
await expect(service.updateItem('rl-1', 'hh1', 'bad-item', {})).rejects.toThrow(
|
|
|
|
|
'Refill list or item not found',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('addToCabinet', () => {
|
|
|
|
|
it('adds checked items to cabinet', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({
|
|
|
|
|
_id: 'rl-1',
|
|
|
|
|
items: [
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
_id: { toString: () => 'item-1' },
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
quantity: 30,
|
|
|
|
|
unit: 'tablet',
|
|
|
|
|
checked: true,
|
|
|
|
|
addedToCabinet: false,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
mockCabinetService.addItem.mockResolvedValue({ _id: 'ci-1' });
|
|
|
|
|
mockRepo.markItemsAddedToCabinet.mockResolvedValue({ _id: 'rl-1' });
|
|
|
|
|
|
|
|
|
|
const result = await service.addToCabinet('rl-1', 'hh1', 'user-1');
|
|
|
|
|
|
|
|
|
|
expect(result.addedCount).toBe(1);
|
|
|
|
|
expect(mockCabinetService.addItem).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('computes unitPrice when actualPrice is set', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({
|
|
|
|
|
_id: 'rl-1',
|
|
|
|
|
items: [
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
_id: { toString: () => 'item-1' },
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
quantity: 30,
|
|
|
|
|
unit: 'tablet',
|
|
|
|
|
actualPrice: 9,
|
|
|
|
|
checked: true,
|
|
|
|
|
addedToCabinet: false,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
mockCabinetService.addItem.mockResolvedValue({ _id: 'ci-1' });
|
|
|
|
|
mockRepo.markItemsAddedToCabinet.mockResolvedValue({ _id: 'rl-1' });
|
|
|
|
|
|
|
|
|
|
await service.addToCabinet('rl-1', 'hh1', 'user-1');
|
|
|
|
|
|
|
|
|
|
expect(mockCabinetService.addItem).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ unitPrice: 0.3, totalPrice: 9 }),
|
|
|
|
|
'hh1',
|
|
|
|
|
'user-1',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns zero count when no checked items', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({
|
|
|
|
|
_id: 'rl-1',
|
|
|
|
|
items: [
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
_id: 'item-1',
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
quantity: 30,
|
|
|
|
|
unit: 'tablet',
|
|
|
|
|
checked: false,
|
|
|
|
|
addedToCabinet: false,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = await service.addToCabinet('rl-1', 'hh1', 'user-1');
|
|
|
|
|
|
|
|
|
|
expect(result.addedCount).toBe(0);
|
|
|
|
|
expect(mockCabinetService.addItem).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('skips already-added items', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({
|
|
|
|
|
_id: 'rl-1',
|
|
|
|
|
items: [
|
2026-04-26 18:44:59 +09:00
|
|
|
{
|
|
|
|
|
_id: 'item-1',
|
|
|
|
|
medicineId: 'med-1',
|
|
|
|
|
medicineName: 'Aspirin',
|
|
|
|
|
quantity: 30,
|
|
|
|
|
unit: 'tablet',
|
|
|
|
|
checked: true,
|
|
|
|
|
addedToCabinet: true,
|
|
|
|
|
},
|
2026-04-18 12:36:29 +09:00
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = await service.addToCabinet('rl-1', 'hh1', 'user-1');
|
|
|
|
|
|
|
|
|
|
expect(result.addedCount).toBe(0);
|
|
|
|
|
expect(mockCabinetService.addItem).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('getStoreComparison', () => {
|
|
|
|
|
it('returns store comparisons for list items', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({
|
|
|
|
|
_id: 'rl-1',
|
|
|
|
|
items: [{ medicineId: 'med-1' }, { medicineId: 'med-2' }],
|
|
|
|
|
});
|
|
|
|
|
mockPricesRepo.compareStores
|
|
|
|
|
.mockResolvedValueOnce([{ storeId: 'st-1', storeName: 'CVS' }])
|
|
|
|
|
.mockResolvedValueOnce([]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getStoreComparison('rl-1', 'hh1');
|
|
|
|
|
|
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
|
expect(result[0].medicineId).toBe('med-1');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('deduplicates medicine ids', async () => {
|
|
|
|
|
mockRepo.findById.mockResolvedValue({
|
|
|
|
|
_id: 'rl-1',
|
|
|
|
|
items: [{ medicineId: 'med-1' }, { medicineId: 'med-1' }],
|
|
|
|
|
});
|
|
|
|
|
mockPricesRepo.compareStores.mockResolvedValue([{ storeId: 'st-1', storeName: 'CVS' }]);
|
|
|
|
|
|
|
|
|
|
const result = await service.getStoreComparison('rl-1', 'hh1');
|
|
|
|
|
|
|
|
|
|
expect(mockPricesRepo.compareStores).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|