Skip to main content
Glama
middleware.test.ts7.58 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import type { Request, Response, NextFunction } from 'express'; import { validateToken, validateEnvironment, getConfig, } from '../../src/auth/middleware.js'; describe('auth/middleware', () => { const originalEnv = process.env; beforeEach(() => { vi.resetModules(); process.env = { ...originalEnv }; }); afterEach(() => { process.env = originalEnv; }); describe('validateToken', () => { let mockReq: Partial<Request>; let mockRes: Partial<Response>; let mockNext: NextFunction; let jsonMock: ReturnType<typeof vi.fn>; let statusMock: ReturnType<typeof vi.fn>; beforeEach(() => { jsonMock = vi.fn(); statusMock = vi.fn().mockReturnValue({ json: jsonMock }); mockReq = { headers: {}, query: {}, }; mockRes = { status: statusMock, json: jsonMock, }; mockNext = vi.fn(); }); it('should call next() with valid token', () => { process.env.MCP_AUTH_TOKEN = 'valid-secret-token'; mockReq.query = { token: 'valid-secret-token' }; validateToken(mockReq as Request, mockRes as Response, mockNext); expect(mockNext).toHaveBeenCalled(); expect(statusMock).not.toHaveBeenCalled(); }); it('should return 401 when token is missing', () => { process.env.MCP_AUTH_TOKEN = 'valid-secret-token'; mockReq.query = {}; validateToken(mockReq as Request, mockRes as Response, mockNext); expect(statusMock).toHaveBeenCalledWith(401); expect(jsonMock).toHaveBeenCalledWith({ error: 'Authentication token required' }); expect(mockNext).not.toHaveBeenCalled(); }); it('should return 403 when token is invalid', () => { process.env.MCP_AUTH_TOKEN = 'valid-secret-token'; mockReq.query = { token: 'wrong-token' }; validateToken(mockReq as Request, mockRes as Response, mockNext); expect(statusMock).toHaveBeenCalledWith(403); expect(jsonMock).toHaveBeenCalledWith({ error: 'Invalid authentication token' }); expect(mockNext).not.toHaveBeenCalled(); }); it('should return 500 when MCP_AUTH_TOKEN is not set', () => { delete process.env.MCP_AUTH_TOKEN; mockReq.query = { token: 'some-token' }; validateToken(mockReq as Request, mockRes as Response, mockNext); expect(statusMock).toHaveBeenCalledWith(500); expect(jsonMock).toHaveBeenCalledWith({ error: 'Server configuration error' }); expect(mockNext).not.toHaveBeenCalled(); }); it('should use constant-time comparison for tokens', () => { process.env.MCP_AUTH_TOKEN = 'secret'; // Test with same length but different content mockReq.query = { token: 'secreX' }; validateToken(mockReq as Request, mockRes as Response, mockNext); expect(statusMock).toHaveBeenCalledWith(403); // Reset mocks statusMock.mockClear(); jsonMock.mockClear(); // Test with different length mockReq.query = { token: 'sec' }; validateToken(mockReq as Request, mockRes as Response, mockNext); expect(statusMock).toHaveBeenCalledWith(403); }); }); describe('validateEnvironment', () => { it('should not throw when all required variables are set', () => { process.env.MCP_AUTH_TOKEN = 'token'; process.env.INTERVALS_API_KEY = 'key'; process.env.INTERVALS_ATHLETE_ID = 'athlete'; expect(() => validateEnvironment()).not.toThrow(); }); it('should throw when MCP_AUTH_TOKEN is missing', () => { delete process.env.MCP_AUTH_TOKEN; process.env.INTERVALS_API_KEY = 'key'; process.env.INTERVALS_ATHLETE_ID = 'athlete'; expect(() => validateEnvironment()).toThrow('Missing required environment variables: MCP_AUTH_TOKEN'); }); it('should throw when INTERVALS_API_KEY is missing', () => { process.env.MCP_AUTH_TOKEN = 'token'; delete process.env.INTERVALS_API_KEY; process.env.INTERVALS_ATHLETE_ID = 'athlete'; expect(() => validateEnvironment()).toThrow('Missing required environment variables: INTERVALS_API_KEY'); }); it('should throw when INTERVALS_ATHLETE_ID is missing', () => { process.env.MCP_AUTH_TOKEN = 'token'; process.env.INTERVALS_API_KEY = 'key'; delete process.env.INTERVALS_ATHLETE_ID; expect(() => validateEnvironment()).toThrow('Missing required environment variables: INTERVALS_ATHLETE_ID'); }); it('should list all missing variables', () => { delete process.env.MCP_AUTH_TOKEN; delete process.env.INTERVALS_API_KEY; delete process.env.INTERVALS_ATHLETE_ID; expect(() => validateEnvironment()).toThrow( 'Missing required environment variables: MCP_AUTH_TOKEN, INTERVALS_API_KEY, INTERVALS_ATHLETE_ID' ); }); it('should require Whoop refresh token when access token is set', () => { process.env.MCP_AUTH_TOKEN = 'token'; process.env.INTERVALS_API_KEY = 'key'; process.env.INTERVALS_ATHLETE_ID = 'athlete'; process.env.WHOOP_CLIENT_ID = 'client-id'; delete process.env.WHOOP_CLIENT_SECRET; delete process.env.REDIS_URL; expect(() => validateEnvironment()).toThrow( 'Missing required environment variables: WHOOP_CLIENT_SECRET, REDIS_URL' ); }); it('should not require Whoop vars when client ID is not set', () => { process.env.MCP_AUTH_TOKEN = 'token'; process.env.INTERVALS_API_KEY = 'key'; process.env.INTERVALS_ATHLETE_ID = 'athlete'; delete process.env.WHOOP_CLIENT_ID; expect(() => validateEnvironment()).not.toThrow(); }); }); describe('getConfig', () => { beforeEach(() => { process.env.MCP_AUTH_TOKEN = 'auth-token'; process.env.INTERVALS_API_KEY = 'intervals-key'; process.env.INTERVALS_ATHLETE_ID = 'i12345'; }); it('should return basic config', () => { const config = getConfig(); expect(config.port).toBe(3000); expect(config.mcpAuthToken).toBe('auth-token'); expect(config.intervals.apiKey).toBe('intervals-key'); expect(config.intervals.athleteId).toBe('i12345'); }); it('should return custom port when set', () => { process.env.PORT = '8080'; const config = getConfig(); expect(config.port).toBe(8080); }); it('should return Whoop config when client ID is set', () => { process.env.WHOOP_CLIENT_ID = 'whoop-client'; process.env.WHOOP_CLIENT_SECRET = 'whoop-secret'; const config = getConfig(); // accessToken and refreshToken are empty - loaded from Redis at runtime expect(config.whoop).toEqual({ accessToken: '', refreshToken: '', clientId: 'whoop-client', clientSecret: 'whoop-secret', }); }); it('should return null for Whoop when client ID is not set', () => { delete process.env.WHOOP_CLIENT_ID; const config = getConfig(); expect(config.whoop).toBeNull(); }); it('should return TrainerRoad config when set', () => { process.env.TRAINERROAD_CALENDAR_URL = 'https://trainerroad.com/calendar/xyz'; const config = getConfig(); expect(config.trainerRoad).toEqual({ calendarUrl: 'https://trainerroad.com/calendar/xyz', }); }); it('should return null for TrainerRoad when not set', () => { delete process.env.TRAINERROAD_CALENDAR_URL; const config = getConfig(); expect(config.trainerRoad).toBeNull(); }); }); });

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