import { describe, it, expect, beforeEach, vi } from 'vitest'; import '../helpers/mock-api'; import { api } from '@/services/request'; import { inputVitalSign, getTodaySummary, getTrend, listDailyMonitoring, createDailyMonitoring, findThreshold, DEFAULT_THRESHOLDS, } from '@/services/health'; describe('health service', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('getTodaySummary', () => { it('calls api.get with correct path', async () => { const mock = { blood_pressure: { systolic: 120, diastolic: 80, status: 'normal' } }; vi.mocked(api.get).mockResolvedValueOnce(mock); const result = await getTodaySummary(); expect(api.get).toHaveBeenCalledWith('/health/vital-signs/today', {}); expect(result).toEqual(mock); }); it('passes patient_id when provided', async () => { vi.mocked(api.get).mockResolvedValueOnce({}); await getTodaySummary('p-123'); expect(api.get).toHaveBeenCalledWith('/health/vital-signs/today', { patient_id: 'p-123' }); }); }); describe('inputVitalSign', () => { it('maps blood_pressure to morning fields', async () => { vi.mocked(api.post).mockResolvedValueOnce({}); await inputVitalSign('p-1', { indicator_type: 'blood_pressure', value: 0, extra: { systolic: 130, diastolic: 85 }, }); expect(api.post).toHaveBeenCalledWith( '/health/patients/p-1/vital-signs', expect.objectContaining({ systolic_bp_morning: 130, diastolic_bp_morning: 85, }), ); }); it('maps blood_pressure_evening to evening fields', async () => { vi.mocked(api.post).mockResolvedValueOnce({}); await inputVitalSign('p-1', { indicator_type: 'blood_pressure_evening', value: 0, extra: { systolic: 125, diastolic: 80 }, }); expect(api.post).toHaveBeenCalledWith( '/health/patients/p-1/vital-signs', expect.objectContaining({ systolic_bp_evening: 125, diastolic_bp_evening: 80, }), ); }); it('maps heart_rate and rounds value', async () => { vi.mocked(api.post).mockResolvedValueOnce({}); await inputVitalSign('p-1', { indicator_type: 'heart_rate', value: 72.5 }); expect(api.post).toHaveBeenCalledWith( '/health/patients/p-1/vital-signs', expect.objectContaining({ heart_rate: 73 }), ); }); it('maps spo2 and rounds value', async () => { vi.mocked(api.post).mockResolvedValueOnce({}); await inputVitalSign('p-1', { indicator_type: 'spo2', value: 98.7 }); expect(api.post).toHaveBeenCalledWith( '/health/patients/p-1/vital-signs', expect.objectContaining({ spo2: 99 }), ); }); it('includes notes when provided', async () => { vi.mocked(api.post).mockResolvedValueOnce({}); await inputVitalSign('p-1', { indicator_type: 'weight', value: 65, note: '饭后' }); expect(api.post).toHaveBeenCalledWith( '/health/patients/p-1/vital-signs', expect.objectContaining({ weight: 65, notes: '饭后' }), ); }); it('includes record_date as today', async () => { vi.mocked(api.post).mockResolvedValueOnce({}); await inputVitalSign('p-1', { indicator_type: 'weight', value: 65 }); const callData = vi.mocked(api.post).mock.calls[0][1] as Record; expect(callData.record_date).toMatch(/^\d{4}-\d{2}-\d{2}$/); }); }); describe('getTrend', () => { it('calls api.get with indicator and range', async () => { vi.mocked(api.get).mockResolvedValueOnce({ indicator: 'systolic_bp', data_points: [] }); await getTrend('systolic_bp', '7d'); expect(api.get).toHaveBeenCalledWith('/health/vital-signs/trend', { indicator: 'systolic_bp', range: '7d', }); }); }); describe('listDailyMonitoring', () => { it('calls api.get with patient id and pagination', async () => { vi.mocked(api.get).mockResolvedValueOnce({ data: [], total: 0 }); await listDailyMonitoring('p-1', { page: 2, page_size: 10 }); expect(api.get).toHaveBeenCalledWith( '/health/patients/p-1/daily-monitoring', { page: 2, page_size: 10 }, ); }); }); describe('createDailyMonitoring', () => { it('calls api.post with monitoring data', async () => { const input = { patient_id: 'p-1', record_date: '2026-05-13', morning_bp_systolic: 130, morning_bp_diastolic: 85, }; vi.mocked(api.post).mockResolvedValueOnce({ id: 'dm-1', ...input }); await createDailyMonitoring(input); expect(api.post).toHaveBeenCalledWith('/health/daily-monitoring', input); }); }); describe('findThreshold', () => { it('finds matching active threshold', () => { const result = findThreshold(DEFAULT_THRESHOLDS, 'systolic_bp', 'high', 'warning'); expect(result).toBeDefined(); expect(result!.threshold_value).toBe(140); }); it('returns undefined for inactive threshold', () => { const thresholds = [{ ...DEFAULT_THRESHOLDS[0], is_active: false }]; const result = findThreshold(thresholds, 'systolic_bp', 'high', 'warning'); expect(result).toBeUndefined(); }); it('returns undefined when no match', () => { const result = findThreshold(DEFAULT_THRESHOLDS, 'unknown_indicator', 'high'); expect(result).toBeUndefined(); }); }); });