Skip to main content
Glama
sleep.test.ts7.26 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { registerSleepTool } from './sleep.js'; import * as utils from './utils.js'; import { CommonSchemas } from './utils.js'; import { FITBIT_API_VERSIONS, VALIDATION_MESSAGES } from './config.js'; // Mock the utils module vi.mock('./utils.js', async () => { const actualUtils = await vi.importActual('./utils.js'); return { ...actualUtils, // Import and retain actual CommonSchemas registerTool: vi.fn(), handleFitbitApiCall: vi.fn() }; }); // Mock the config module to control FITBIT_API_VERSIONS if needed for tests // For now, we assume sleep.ts correctly imports and uses it. vi.mock('./config.js', async () => { const actualConfig = await vi.importActual('./config.js'); return { ...actualConfig, // If specific versions need to be mocked for testing, do it here. }; }); describe('Sleep Tool', () => { let mockServer: McpServer; let mockGetAccessToken: ReturnType<typeof vi.fn>; let mockRegisterTool: ReturnType<typeof vi.fn>; let mockHandleFitbitApiCall: ReturnType<typeof vi.fn>; beforeEach(() => { mockServer = {} as McpServer; mockGetAccessToken = vi.fn().mockResolvedValue('test_access_token'); mockRegisterTool = vi.mocked(utils.registerTool); mockHandleFitbitApiCall = vi.mocked(utils.handleFitbitApiCall); vi.clearAllMocks(); }); afterEach(() => { vi.resetAllMocks(); }); describe('registerSleepTool', () => { it('should register the get_sleep_by_date_range tool with correct configuration', () => { registerSleepTool(mockServer, mockGetAccessToken); expect(mockRegisterTool).toHaveBeenCalledOnce(); expect(mockRegisterTool).toHaveBeenCalledWith(mockServer, { name: 'get_sleep_by_date_range', description: `Get the raw JSON response for sleep logs from Fitbit for a specific date range. Requires 'startDate' and 'endDate' parameters in 'YYYY-MM-DD' format. ${VALIDATION_MESSAGES.MAX_RANGE_100_DAYS}.`, parametersSchema: { startDate: CommonSchemas.startDate, endDate: CommonSchemas.endDate, }, handler: expect.any(Function) }); }); it('should call handler with correct endpoint, parameters, and options', async () => { const mockSleepData = { sleep: [{ logId: 123, dateOfSleep: '2025-05-25' }] }; const testParams = { startDate: '2025-05-01', endDate: '2025-05-25' }; mockHandleFitbitApiCall.mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify(mockSleepData.sleep) }] }); registerSleepTool(mockServer, mockGetAccessToken); const registeredToolConfig = mockRegisterTool.mock.calls[0][1]; const handler = registeredToolConfig.handler; const result = await handler(testParams); expect(mockHandleFitbitApiCall).toHaveBeenCalledWith( `/sleep/date/${testParams.startDate}/${testParams.endDate}.json`, testParams, mockGetAccessToken, { apiBase: FITBIT_API_VERSIONS.V1_2, successDataExtractor: expect.any(Function), noDataMessage: `the date range '${testParams.startDate}' to '${testParams.endDate}'`, errorContext: `date range '${testParams.startDate}' to '${testParams.endDate}'` } ); expect(result).toEqual({ content: [{ type: 'text', text: JSON.stringify(mockSleepData.sleep) }] }); }); it('should handle API errors gracefully', async () => { const errorMessage = 'Fitbit API error'; const testParams = { startDate: '2025-05-01', endDate: '2025-05-25' }; mockHandleFitbitApiCall.mockRejectedValue(new Error(errorMessage)); registerSleepTool(mockServer, mockGetAccessToken); const registeredToolConfig = mockRegisterTool.mock.calls[0][1]; const handler = registeredToolConfig.handler; await expect(handler(testParams)).rejects.toThrow(errorMessage); expect(mockHandleFitbitApiCall).toHaveBeenCalledWith( `/sleep/date/${testParams.startDate}/${testParams.endDate}.json`, testParams, mockGetAccessToken, expect.objectContaining({ apiBase: FITBIT_API_VERSIONS.V1_2, }) ); }); it('should handle null access token', async () => { const testParams = { startDate: '2025-05-01', endDate: '2025-05-25' }; mockGetAccessToken.mockResolvedValue(null); // Simulate the error that would be thrown by handleFitbitApiCall or its internals mockHandleFitbitApiCall.mockRejectedValue(new Error('No access token available')); registerSleepTool(mockServer, mockGetAccessToken); const registeredToolConfig = mockRegisterTool.mock.calls[0][1]; const handler = registeredToolConfig.handler; await expect(handler(testParams)).rejects.toThrow('No access token available'); }); it('should use correct endpoint URL structure', async () => { const testParams = { startDate: '2025-01-01', endDate: '2025-01-31' }; registerSleepTool(mockServer, mockGetAccessToken); const registeredToolConfig = mockRegisterTool.mock.calls[0][1]; const handler = registeredToolConfig.handler; // Catch expected error if API call fails, we only care about the endpoint arg await handler(testParams).catch(() => {}); expect(mockHandleFitbitApiCall).toHaveBeenCalledWith( `/sleep/date/${testParams.startDate}/${testParams.endDate}.json`, testParams, mockGetAccessToken, expect.any(Object) // Options object ); }); it('should have correct parameters schema', () => { registerSleepTool(mockServer, mockGetAccessToken); const registeredToolConfig = mockRegisterTool.mock.calls[0][1]; expect(registeredToolConfig.parametersSchema).toEqual({ startDate: CommonSchemas.startDate, endDate: CommonSchemas.endDate, }); }); it('should correctly extract data with successDataExtractor', async () => { const rawApiResponse = { sleep: [{ logId: 789, dateOfSleep: '2025-05-20' }], summary: {} }; const testParams = { startDate: '2025-05-20', endDate: '2025-05-20' }; let successDataExtractor: (data: any) => any = (data) => data; // Default mockHandleFitbitApiCall.mockImplementation(async (endpoint, params, tokenFn, opts) => { if (opts && typeof opts.successDataExtractor === 'function') { successDataExtractor = opts.successDataExtractor; } return { content: [{ type: 'text', text: JSON.stringify(successDataExtractor(rawApiResponse)) }] }; }); registerSleepTool(mockServer, mockGetAccessToken); const registeredToolConfig = mockRegisterTool.mock.calls[0][1]; const handler = registeredToolConfig.handler; await handler(testParams); // Call handler to ensure opts are passed const extractedData = successDataExtractor(rawApiResponse); expect(extractedData).toEqual(rawApiResponse.sleep); const extractedDataEmpty = successDataExtractor({ 'otherKey': [] }); // No 'sleep' key expect(extractedDataEmpty).toEqual([]); }); }); });

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/TheDigitalNinja/mcp-fitbit'

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