MeshiTrack/packages/api/tests/modules/nutrition-targets/nutrition-target.service.test.ts

121 lines
4.1 KiB
TypeScript
Raw Permalink Normal View History

2026-05-14 18:38:50 +09:00
import { describe, it, expect, vi, beforeEach } from 'vitest';
2026-05-19 11:06:03 +09:00
import { NutritionTargetService } from '../../../src/modules/nutrition-targets/nutrition-target.service.js';
import type { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js';
2026-05-14 18:38:50 +09:00
describe(NutritionTargetService.name, () => {
let service: NutritionTargetService;
let mockRepo: { [K in keyof NutritionTargetRepository]: ReturnType<typeof vi.fn> };
beforeEach(() => {
mockRepo = {
findByUser: vi.fn(),
findAllByUser: vi.fn(),
create: vi.fn(),
deactivateAllForUser: vi.fn(),
update: vi.fn(),
} as never;
service = new NutritionTargetService({
nutritionTargetRepository: mockRepo as unknown as NutritionTargetRepository,
});
});
describe('getActiveByUser', () => {
it('delegates to repository', async () => {
const mockTarget = { dailyCalories: 2000, isActive: true };
mockRepo.findByUser.mockResolvedValue(mockTarget);
const result = await service.getActiveByUser('u1', 'h1');
expect(mockRepo.findByUser).toHaveBeenCalledWith('u1', 'h1');
expect(result).toEqual(mockTarget);
});
});
describe('getAllByUser', () => {
it('delegates to repository', async () => {
const mockTargets = [{ dailyCalories: 2000 }, { dailyCalories: 1800 }];
mockRepo.findAllByUser.mockResolvedValue(mockTargets);
const result = await service.getAllByUser('u1', 'h1');
expect(mockRepo.findAllByUser).toHaveBeenCalledWith('u1', 'h1');
expect(result).toEqual(mockTargets);
});
});
describe('setTarget', () => {
it('deactivates existing targets before creating an active one', async () => {
const mockInput = { dailyCalories: 2000, proteinG: 100, carbsG: 200, fatG: 50, isActive: true };
const createdTarget = { ...mockInput, _id: 'new-id', userId: 'u1', householdId: 'h1' };
mockRepo.create.mockResolvedValue(createdTarget);
const result = await service.setTarget('u1', 'h1', mockInput);
expect(mockRepo.deactivateAllForUser).toHaveBeenCalledWith('u1', 'h1');
expect(mockRepo.create).toHaveBeenCalledWith({
...mockInput,
userId: 'u1',
householdId: 'h1',
});
expect(result).toEqual(createdTarget);
});
it('does NOT deactivate others if isActive is explicitly false', async () => {
const mockInput = { dailyCalories: 2000, proteinG: 100, carbsG: 200, fatG: 50, isActive: false };
await service.setTarget('u1', 'h1', mockInput);
expect(mockRepo.deactivateAllForUser).not.toHaveBeenCalled();
expect(mockRepo.create).toHaveBeenCalled();
});
});
describe('calculatePreset', () => {
it('calculates macros correctly for maintenance (30p / 40c / 30f)', () => {
const result = service.calculatePreset(2000, 'maintenance');
// Math:
// Protein: (2000 * 0.3) / 4 = 600 / 4 = 150
// Carbs: (2000 * 0.4) / 4 = 800 / 4 = 200
// Fat: (2000 * 0.3) / 9 = 600 / 9 = 66.66 => 67
expect(result).toEqual({
dailyCalories: 2000,
proteinG: 150,
carbsG: 200,
fatG: 67,
isActive: true,
});
});
it('calculates macros correctly for loss (40p / 30c / 30f)', () => {
const result = service.calculatePreset(2000, 'loss');
// Math:
// Protein: (2000 * 0.4) / 4 = 800 / 4 = 200
// Carbs: (2000 * 0.3) / 4 = 600 / 4 = 150
// Fat: (2000 * 0.3) / 9 = 600 / 9 = 66.66 => 67
expect(result).toEqual({
dailyCalories: 2000,
proteinG: 200,
carbsG: 150,
fatG: 67,
isActive: true,
});
});
it('calculates macros correctly for gain (25p / 50c / 25f)', () => {
const result = service.calculatePreset(2000, 'gain');
// Math:
// Protein: (2000 * 0.25) / 4 = 500 / 4 = 125
// Carbs: (2000 * 0.5) / 4 = 1000 / 4 = 250
// Fat: (2000 * 0.25) / 9 = 500 / 9 = 55.55 => 56
expect(result).toEqual({
dailyCalories: 2000,
proteinG: 125,
carbsG: 250,
fatG: 56,
isActive: true,
});
});
});
});