| Archivo | Statements | Branches | Functions | Lines | Uncovered |
|---|---|---|---|---|---|
| src/utils/nutrition-calculator.ts |
100%
|
97% | 100% | 100% | — |
| src/services/meal-plan-service.ts |
88%
|
82% | 90% | 88% | L78–82, L134 |
| src/api/patients.ts |
95%
|
91% | 100% | 95% | L203 |
| Total |
91%
|
88% | 96% | 91% |
1import { describe, it, expect } from 'vitest'; 2import { 3 calculateBMR, getTDEE, getMacros, 4 calculateBMI, formatNutritionLabel 5} from './nutrition-calculator'; 6 7describe('calculateBMR', () => { 8 it('hombre 30 años 80kg 175cm → 1883 kcal (Mifflin-St Jeor)', () => { 9 expect(calculateBMR({ gender: 'male', age: 30, weight: 80, height: 175 })) 10 .toBeCloseTo(1883, 0); 11 }); 12 13 it('mujer 25 años 60kg 165cm → 1449 kcal', () => { 14 expect(calculateBMR({ gender: 'female', age: 25, weight: 60, height: 165 })) 15 .toBeCloseTo(1449, 0); 16 }); 17 18 it('lanza ValueError si edad ≤ 0', () => { 19 expect(() => calculateBMR({ gender: 'male', age: 0, weight: 70, height: 170 })) 20 .toThrow('La edad debe ser mayor a 0'); 21 }); 22 23 it('lanza ValueError si peso es negativo', () => { 24 expect(() => calculateBMR({ gender: 'male', age: 30, weight: -5, height: 170 })) 25 .toThrow('El peso no puede ser negativo'); 26 }); 27}); 28 29describe('getTDEE', () => { 30 it('sedentario multiplica por 1.2', () => { 31 expect(getTDEE(1883, 'sedentary')).toBeCloseTo(2259.6, 0); 32 }); 33 34 it('muy activo multiplica por 1.725', () => { 35 expect(getTDEE(1883, 'very_active')).toBeCloseTo(3248.2, 0); 36 }); 37 38 it('factor de actividad desconocido lanza Error', () => { 39 expect(() => getTDEE(1883, 'superhero' as any)) 40 .toThrow('Nivel de actividad no reconocido'); 41 }); 42}); 43 44describe('getMacros', () => { 45 it('distribución pérdida peso (40C/30P/30F) suma 100%', () => { 46 const macros = getMacros(2000, 'weight_loss'); 47 expect(macros.carbsPct + macros.proteinPct + macros.fatPct).toBe(100); 48 expect(macros.carbsPct).toBe(40); 49 }); 50 51 it('distribución ganancia muscular (35C/40P/25F) suma 100%', () => { 52 const macros = getMacros(3200, 'muscle_gain'); 53 expect(macros.proteinPct).toBe(40); 54 expect(macros.carbsPct + macros.proteinPct + macros.fatPct).toBe(100); 55 }); 56}); 57 58describe('calculateBMI', () => { 59 it('70kg 1.75m → 22.86 (Normal)', () => { 60 const result = calculateBMI(70, 1.75); 61 expect(result.value).toBeCloseTo(22.86, 1); 62 expect(result.category).toBe('Normal'); 63 }); 64 65 it('100kg 1.70m → 34.6 (Obesidad I)', () => { 66 const result = calculateBMI(100, 1.70); 67 expect(result.category).toBe('Obesidad I'); 68 }); 69});
1import { describe, it, expect, vi, beforeEach } from 'vitest'; 2import { createMealPlan, getMealPlan, updateMealPlan } from './meal-plan-service'; 3import { db } from '../lib/database'; 4import { sendEmail } from '../lib/email'; 5 6vi.mock('../lib/database'); 7vi.mock('../lib/email'); 8 9const MOCK_PLAN = { 10 id: 'plan_abc123', 11 pacienteId: 'pac_001', 12 dietistaId: 'diet_007', 13 calories: 2200, 14 objetivo: 'muscle_gain', 15 createdAt: new Date('2026-06-01'), 16}; 17 18describe('createMealPlan', () => { 19 beforeEach(() => { 20 vi.clearAllMocks(); 21 vi.mocked(db.mealPlans.create).mockResolvedValue(MOCK_PLAN); 22 vi.mocked(sendEmail).mockResolvedValue({ delivered: true }); 23 }); 24 25 it('guarda el plan en DB y retorna id', async () => { 26 const result = await createMealPlan({ 27 pacienteId: 'pac_001', dietistaId: 'diet_007', 28 calories: 2200, objetivo: 'muscle_gain' 29 }); 30 expect(db.mealPlans.create).toHaveBeenCalledOnce(); 31 expect(result.id).toBe('plan_abc123'); 32 }); 33 34 it('envía email de confirmación al dietista', async () => { 35 await createMealPlan({ 36 pacienteId: 'pac_001', dietistaId: 'diet_007', 37 calories: 2200, objetivo: 'muscle_gain' 38 }); 39 expect(sendEmail).toHaveBeenCalledWith( 40 expect.objectContaining({ planId: 'plan_abc123', to: 'diet_007' }) 41 ); 42 }); 43 44 it('no envía email si flag skipEmail=true', async () => { 45 await createMealPlan({ 46 pacienteId: 'pac_001', dietistaId: 'diet_007', 47 calories: 2200, objetivo: 'muscle_gain', skipEmail: true 48 }); 49 expect(sendEmail).not.toHaveBeenCalled(); 50 }); 51 52 it('lanza PlanValidationError si calorías < 500', async () => { 53 await expect(createMealPlan({ 54 pacienteId: 'pac_001', dietistaId: 'diet_007', 55 calories: 300, objetivo: 'weight_loss' 56 })).rejects.toThrow('PlanValidationError'); 57 expect(db.mealPlans.create).not.toHaveBeenCalled(); 58 }); 59});
1import { test, expect, request } from '@playwright/test'; 2 3// API: POST /api/patients 4test('crea paciente y retorna 201 + id', async ({ request }) => { 5 const res = await request.post('/api/patients', { 6 headers: { Authorization: `Bearer ${process.env.TEST_TOKEN}` }, 7 data: { email: 'maria@test.com', nombre: 'María López', edad: 34 } 8 }); 9 expect(res.status()).toBe(201); 10 const body = await res.json(); 11 expect(body).toMatchObject({ email: 'maria@test.com' }); 12 expect(body.id).toMatch(/^pac_/); 13}); 14 15test('email duplicado → 409 Conflict', async ({ request }) => { 16 // Primer POST crea el usuario 17 await request.post('/api/patients', { 18 headers: { Authorization: `Bearer ${process.env.TEST_TOKEN}` }, 19 data: { email: 'duplicado@test.com', nombre: 'Test Dup' } 20 }); 21 // Segundo POST debe fallar con 409 22 const res2 = await request.post('/api/patients', { 23 headers: { Authorization: `Bearer ${process.env.TEST_TOKEN}` }, 24 data: { email: 'duplicado@test.com', nombre: 'Test Dup 2' } 25 }); 26 expect(res2.status()).toBe(409); 27}); 28 29test('UI: formulario → muestra errores de validación', async ({ page }) => { 30 await page.goto('/dashboard/pacientes/nuevo'); 31 await page.click('button[data-testid="submit-patient"]'); 32 await expect(page.locator('[data-testid="error-email"]')) 33 .toContainText('El email es obligatorio'); 34 await expect(page.locator('[data-testid="error-nombre"]')) 35 .toContainText('El nombre es obligatorio'); 36}); 37 38test('UI: registro exitoso → redirige a /dashboard/pacientes/:id', async ({ page }) => { 39 await page.goto('/dashboard/pacientes/nuevo'); 40 await page.fill('[data-testid="input-nombre"]', 'Carlos Ruiz'); 41 await page.fill('[data-testid="input-email"]', 'carlos@test.com'); 42 await page.fill('[data-testid="input-edad"]', '28'); 43 await page.click('button[data-testid="submit-patient"]'); 44 await expect(page).toHaveURL(/\/dashboard\/pacientes\/pac_/); 45});
generador-tests-automaticos