Skip to main content
Glama
activity-goals.test.ts5.77 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { registerActivityGoalsTool } from './activity-goals.js'; import * as utils from './utils.js'; import { z } from 'zod'; // Mock the utils module vi.mock('./utils.js', () => ({ registerTool: vi.fn(), handleFitbitApiCall: vi.fn() })); describe('Activity Goals 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('registerActivityGoalsTool', () => { it('should register the get_activity_goals tool with correct configuration', () => { registerActivityGoalsTool(mockServer, mockGetAccessToken); expect(mockRegisterTool).toHaveBeenCalledOnce(); const registeredToolConfig = mockRegisterTool.mock.calls[0][1]; expect(registeredToolConfig.name).toBe('get_activity_goals'); expect(registeredToolConfig.description).toBe("Get the raw JSON response for user\'s activity goals from Fitbit. Supports \'daily\' and \'weekly\' periods. Returns goal values for steps, distance, calories, floors, active minutes, and active zone minutes."); // Compare schema properties instead of the schema object itself expect(registeredToolConfig.parametersSchema.period).toBeInstanceOf(z.ZodEnum); expect(registeredToolConfig.parametersSchema.period._def.values).toEqual(['daily', 'weekly']); expect(registeredToolConfig.parametersSchema.period._def.description).toBe("Goal period - either \'daily\' or \'weekly\'"); expect(registeredToolConfig.handler).toEqual(expect.any(Function)); }); it('should call handler with correct endpoint and parameters for "daily" period', async () => { const mockGoalsData = { goals: { steps: 10000 } }; mockHandleFitbitApiCall.mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify(mockGoalsData) }] }); registerActivityGoalsTool(mockServer, mockGetAccessToken); const handler = mockRegisterTool.mock.calls[0][1].handler; const result = await handler({ period: 'daily' }); expect(mockHandleFitbitApiCall).toHaveBeenCalledWith( 'activities/goals/daily.json', { period: 'daily' }, mockGetAccessToken, { errorContext: "period 'daily'" } ); expect(result).toEqual({ content: [{ type: 'text', text: JSON.stringify(mockGoalsData) }] }); }); it('should call handler with correct endpoint and parameters for "weekly" period', async () => { const mockGoalsData = { goals: { activeMinutes: 60 } }; mockHandleFitbitApiCall.mockResolvedValue({ content: [{ type: 'text', text: JSON.stringify(mockGoalsData) }] }); registerActivityGoalsTool(mockServer, mockGetAccessToken); const handler = mockRegisterTool.mock.calls[0][1].handler; const result = await handler({ period: 'weekly' }); expect(mockHandleFitbitApiCall).toHaveBeenCalledWith( 'activities/goals/weekly.json', { period: 'weekly' }, mockGetAccessToken, { errorContext: "period 'weekly'" } ); expect(result).toEqual({ content: [{ type: 'text', text: JSON.stringify(mockGoalsData) }] }); }); it('should handle API errors gracefully', async () => { const errorMessage = 'API error'; mockHandleFitbitApiCall.mockRejectedValue(new Error(errorMessage)); registerActivityGoalsTool(mockServer, mockGetAccessToken); const handler = mockRegisterTool.mock.calls[0][1].handler; await expect(handler({ period: 'daily' })).rejects.toThrow(errorMessage); expect(mockHandleFitbitApiCall).toHaveBeenCalledWith( 'activities/goals/daily.json', { period: 'daily' }, mockGetAccessToken, { errorContext: "period 'daily'" } ); }); it('should handle null access token', async () => { mockGetAccessToken.mockResolvedValue(null); // Simulate the behavior of handleFitbitApiCall when token is null, // which might be to throw or return a specific error structure. // For this test, let's assume it throws, consistent with profile.test.ts mockHandleFitbitApiCall.mockRejectedValue(new Error('No access token available')); registerActivityGoalsTool(mockServer, mockGetAccessToken); const handler = mockRegisterTool.mock.calls[0][1].handler; await expect(handler({ period: 'weekly' })).rejects.toThrow('No access token available'); }); it('should have correct parameter schema for period', () => { registerActivityGoalsTool(mockServer, mockGetAccessToken); const registeredToolConfig = mockRegisterTool.mock.calls[0][1]; const schema = z.object(registeredToolConfig.parametersSchema); // Test valid inputs expect(schema.safeParse({ period: 'daily' }).success).toBe(true); expect(schema.safeParse({ period: 'weekly' }).success).toBe(true); // Test invalid input const invalidResult = schema.safeParse({ period: 'monthly' }); expect(invalidResult.success).toBe(false); if (!invalidResult.success) { expect(invalidResult.error.errors[0].message).toBe("Invalid enum value. Expected 'daily' | 'weekly', received 'monthly'"); } }); }); });

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