Skip to main content
Glama
historical.test.ts7.79 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { HistoricalTools } from '../../src/tools/historical.js'; import { IntervalsClient } from '../../src/clients/intervals.js'; import { WhoopClient } from '../../src/clients/whoop.js'; import type { NormalizedWorkout, RecoveryData, FitnessMetrics } from '../../src/types/index.js'; vi.mock('../../src/clients/intervals.js'); vi.mock('../../src/clients/whoop.js'); describe('HistoricalTools', () => { let tools: HistoricalTools; let mockIntervalsClient: IntervalsClient; let mockWhoopClient: WhoopClient; beforeEach(() => { vi.clearAllMocks(); vi.useFakeTimers(); vi.setSystemTime(new Date('2024-12-15T12:00:00Z')); mockIntervalsClient = new IntervalsClient({ apiKey: 'test', athleteId: 'test' }); mockWhoopClient = new WhoopClient({ accessToken: 'test', refreshToken: 'test', clientId: 'test', clientSecret: 'test', }); tools = new HistoricalTools(mockIntervalsClient, mockWhoopClient); }); afterEach(() => { vi.useRealTimers(); }); describe('getWorkoutHistory', () => { const mockWorkouts: NormalizedWorkout[] = [ { id: '1', date: '2024-12-10T10:00:00Z', activity_type: 'Cycling', duration_seconds: 3600, tss: 85, source: 'intervals.icu', }, { id: '2', date: '2024-12-12T08:00:00Z', activity_type: 'Running', duration_seconds: 2400, tss: 45, source: 'intervals.icu', }, ]; it('should fetch workouts for ISO date range', async () => { vi.mocked(mockIntervalsClient.getActivities).mockResolvedValue(mockWorkouts); const result = await tools.getWorkoutHistory({ start_date: '2024-12-01', end_date: '2024-12-15', }); expect(result).toEqual(mockWorkouts); expect(mockIntervalsClient.getActivities).toHaveBeenCalledWith( '2024-12-01', '2024-12-15', undefined ); }); it('should parse natural language start date', async () => { vi.mocked(mockIntervalsClient.getActivities).mockResolvedValue(mockWorkouts); await tools.getWorkoutHistory({ start_date: '30 days ago', }); expect(mockIntervalsClient.getActivities).toHaveBeenCalledWith( '2024-11-15', '2024-12-15', undefined ); }); it('should default end_date to today', async () => { vi.mocked(mockIntervalsClient.getActivities).mockResolvedValue(mockWorkouts); await tools.getWorkoutHistory({ start_date: '2024-12-01', }); expect(mockIntervalsClient.getActivities).toHaveBeenCalledWith( '2024-12-01', '2024-12-15', undefined ); }); it('should pass sport filter', async () => { vi.mocked(mockIntervalsClient.getActivities).mockResolvedValue([mockWorkouts[0]]); await tools.getWorkoutHistory({ start_date: '2024-12-01', sport: 'cycling', }); expect(mockIntervalsClient.getActivities).toHaveBeenCalledWith( '2024-12-01', '2024-12-15', 'cycling' ); }); }); describe('getRecoveryTrends', () => { const mockRecoveries: RecoveryData[] = [ { date: '2024-12-13', recovery_score: 80, hrv_rmssd: 60, resting_heart_rate: 52, sleep_performance_percentage: 85, sleep_duration_hours: 7.5, }, { date: '2024-12-14', recovery_score: 70, hrv_rmssd: 55, resting_heart_rate: 54, sleep_performance_percentage: 75, sleep_duration_hours: 6.5, }, { date: '2024-12-15', recovery_score: 90, hrv_rmssd: 70, resting_heart_rate: 50, sleep_performance_percentage: 95, sleep_duration_hours: 8.0, }, ]; it('should return recovery data with summary', async () => { vi.mocked(mockWhoopClient.getRecoveries).mockResolvedValue(mockRecoveries); const result = await tools.getRecoveryTrends({ start_date: '2024-12-13', end_date: '2024-12-15', }); expect(result.data).toEqual(mockRecoveries); expect(result.summary.avg_recovery).toBe(80); // (80 + 70 + 90) / 3 expect(result.summary.avg_hrv).toBeCloseTo(61.7, 1); // (60 + 55 + 70) / 3 expect(result.summary.avg_sleep_hours).toBeCloseTo(7.3, 1); // (7.5 + 6.5 + 8.0) / 3 expect(result.summary.min_recovery).toBe(70); expect(result.summary.max_recovery).toBe(90); }); it('should return empty summary when no Whoop client', async () => { const toolsWithoutWhoop = new HistoricalTools(mockIntervalsClient, null); const result = await toolsWithoutWhoop.getRecoveryTrends({ start_date: '2024-12-13', }); expect(result.data).toEqual([]); expect(result.summary.avg_recovery).toBe(0); }); it('should handle empty recovery data', async () => { vi.mocked(mockWhoopClient.getRecoveries).mockResolvedValue([]); const result = await tools.getRecoveryTrends({ start_date: '2024-12-13', }); expect(result.data).toEqual([]); expect(result.summary.avg_recovery).toBe(0); expect(result.summary.min_recovery).toBe(0); expect(result.summary.max_recovery).toBe(0); }); }); describe('getFitnessProgression', () => { const mockMetrics: FitnessMetrics[] = [ { date: '2024-12-01', ctl: 50, atl: 45, tsb: 5 }, { date: '2024-12-08', ctl: 55, atl: 60, tsb: -5 }, { date: '2024-12-15', ctl: 60, atl: 55, tsb: 5 }, ]; it('should return fitness data with summary', async () => { vi.mocked(mockIntervalsClient.getFitnessMetrics).mockResolvedValue(mockMetrics); const result = await tools.getFitnessProgression({ start_date: '2024-12-01', end_date: '2024-12-15', }); expect(result.data).toEqual(mockMetrics); expect(result.summary.start_ctl).toBe(50); expect(result.summary.end_ctl).toBe(60); expect(result.summary.ctl_change).toBe(10); expect(result.summary.peak_ctl).toBe(60); expect(result.summary.peak_ctl_date).toBe('2024-12-15'); expect(result.summary.avg_tsb).toBeCloseTo(1.7, 1); // (5 + -5 + 5) / 3 }); it('should handle unsorted data', async () => { const unsortedMetrics = [ mockMetrics[2], // 2024-12-15 mockMetrics[0], // 2024-12-01 mockMetrics[1], // 2024-12-08 ]; vi.mocked(mockIntervalsClient.getFitnessMetrics).mockResolvedValue(unsortedMetrics); const result = await tools.getFitnessProgression({ start_date: '2024-12-01', }); // Should still calculate correctly based on dates expect(result.summary.start_ctl).toBe(50); expect(result.summary.end_ctl).toBe(60); }); it('should handle empty data', async () => { vi.mocked(mockIntervalsClient.getFitnessMetrics).mockResolvedValue([]); const result = await tools.getFitnessProgression({ start_date: '2024-12-01', }); expect(result.data).toEqual([]); expect(result.summary.start_ctl).toBe(0); expect(result.summary.end_ctl).toBe(0); expect(result.summary.ctl_change).toBe(0); expect(result.summary.peak_ctl).toBe(0); expect(result.summary.peak_ctl_date).toBe(''); }); it('should parse natural language dates', async () => { vi.mocked(mockIntervalsClient.getFitnessMetrics).mockResolvedValue(mockMetrics); await tools.getFitnessProgression({ start_date: '30 days ago', }); expect(mockIntervalsClient.getFitnessMetrics).toHaveBeenCalledWith( '2024-11-15', '2024-12-15' ); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gesteves/domestique'

If you have feedback or need assistance with the MCP directory API, please join our Discord server