Skip to main content
Glama

Google Calendar and Meet MCP Server

by INSIDE-HAIR
monitoring.test.ts•13.4 kB
/** * Tests for the monitoring system * Tests health checks, metrics collection, and API monitoring */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { HealthChecker } from "../src/monitoring/healthCheck.js"; import { MetricsCollector } from "../src/monitoring/metrics.js"; import { ApiMonitor } from "../src/monitoring/apiMonitor.js"; import { MonitoringEndpoints } from "../src/endpoints/monitoring.js"; // Mock global fetch global.fetch = vi.fn(); describe("Monitoring System", () => { let mockOAuth2Client: any; let healthChecker: HealthChecker; let metricsCollector: MetricsCollector; let apiMonitor: ApiMonitor; beforeEach(() => { vi.clearAllMocks(); // Mock OAuth2 client mockOAuth2Client = { getAccessToken: vi.fn().mockResolvedValue({ token: "mock-token" }), credentials: { expiry_date: Date.now() + 3600000, // 1 hour from now scope: "https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/meetings.space.created" } }; // Initialize monitoring components healthChecker = new HealthChecker(mockOAuth2Client); metricsCollector = new MetricsCollector(); apiMonitor = new ApiMonitor(metricsCollector); }); afterEach(() => { vi.restoreAllMocks(); }); describe("HealthChecker", () => { it("should perform basic health check", async () => { // Mock successful API responses (global.fetch as any).mockResolvedValue({ ok: true, headers: { get: vi.fn().mockReturnValue("application/json") }, json: async () => ({ items: [] }) }); const health = await healthChecker.getHealthStatus(); expect(health).toBeDefined(); expect(health.status).toBeOneOf(['healthy', 'degraded', 'unhealthy']); expect(health.timestamp).toBeDefined(); expect(health.version).toBeDefined(); expect(health.uptime).toBeGreaterThan(0); expect(health.memory).toBeDefined(); expect(health.auth).toBeDefined(); expect(health.apis).toBeDefined(); expect(health.dependencies).toBeInstanceOf(Array); }); it("should detect healthy auth status", async () => { const health = await healthChecker.getHealthStatus(); expect(health.auth.status).toBe('healthy'); expect(health.auth.token_valid).toBe(true); expect(health.auth.expires_in).toBeGreaterThan(0); expect(health.auth.scopes_granted).toBeInstanceOf(Array); }); it("should detect unhealthy auth when token is invalid", async () => { mockOAuth2Client.getAccessToken.mockRejectedValue(new Error("Token expired")); const health = await healthChecker.getHealthStatus(); expect(health.auth.status).toBe('unhealthy'); expect(health.auth.token_valid).toBe(false); expect(health.auth.error).toContain("Token expired"); }); it("should check API health", async () => { // Mock successful API responses (global.fetch as any).mockResolvedValue({ ok: true, headers: { get: vi.fn().mockReturnValue("application/json") } }); const health = await healthChecker.getHealthStatus(); expect(health.apis.calendar_api.status).toBe('healthy'); expect(health.apis.meet_api.status).toBe('healthy'); expect(health.apis.overall_status).toBe('healthy'); }); it("should detect API failures", async () => { // Mock failed API responses (global.fetch as any).mockResolvedValue({ ok: false, status: 500, statusText: "Internal Server Error", headers: { get: vi.fn().mockReturnValue("application/json") } }); const health = await healthChecker.getHealthStatus(); expect(health.apis.calendar_api.status).toBeOneOf(['degraded', 'unhealthy']); expect(health.apis.meet_api.status).toBeOneOf(['degraded', 'unhealthy']); }); it("should provide simple health check", async () => { // Mock successful responses (global.fetch as any).mockResolvedValue({ ok: true, headers: { get: vi.fn().mockReturnValue("application/json") } }); const isHealthy = await healthChecker.isHealthy(); expect(typeof isHealthy).toBe('boolean'); }); }); describe("MetricsCollector", () => { it("should initialize with empty metrics", () => { const metrics = metricsCollector.getMetrics(); expect(metrics.requests_total).toBe(0); expect(metrics.errors_total).toBe(0); expect(metrics.error_rate).toBe(0); expect(typeof metrics.tool_usage).toBe('object'); expect(typeof metrics.api_performance).toBe('object'); expect(typeof metrics.system_metrics).toBe('object'); }); it("should record tool calls", () => { metricsCollector.recordToolCall("calendar_v3_list_events", 150, true); metricsCollector.recordToolCall("meet_v2_create_space", 300, false, new Error("Test error")); const metrics = metricsCollector.getMetrics(); expect(metrics.requests_total).toBe(2); expect(metrics.errors_total).toBe(1); expect(metrics.error_rate).toBe(50); const calendarTool = metrics.tool_usage["calendar_v3_list_events"]; expect(calendarTool.total_calls).toBe(1); expect(calendarTool.successful_calls).toBe(1); expect(calendarTool.failed_calls).toBe(0); expect(calendarTool.avg_duration_ms).toBe(150); const meetTool = metrics.tool_usage["meet_v2_create_space"]; expect(meetTool.total_calls).toBe(1); expect(meetTool.successful_calls).toBe(0); expect(meetTool.failed_calls).toBe(1); expect(meetTool.avg_duration_ms).toBe(300); }); it("should record API calls", () => { metricsCollector.recordApiCall("calendar", 200, true, 200); metricsCollector.recordApiCall("meet", 500, false, 429, true); const metrics = metricsCollector.getMetrics(); const calendarApi = metrics.api_performance["calendar"]; expect(calendarApi.total_calls).toBe(1); expect(calendarApi.successful_calls).toBe(1); expect(calendarApi.failed_calls).toBe(0); expect(calendarApi.avg_response_time).toBe(200); const meetApi = metrics.api_performance["meet"]; expect(meetApi.total_calls).toBe(1); expect(meetApi.successful_calls).toBe(0); expect(meetApi.failed_calls).toBe(1); expect(meetApi.rate_limit_hits).toBe(1); }); it("should record errors", () => { metricsCollector.recordError("validation_error", { field: "test" }); const metrics = metricsCollector.getMetrics(); expect(metrics.errors_total).toBe(1); }); it("should generate Prometheus metrics", () => { metricsCollector.recordToolCall("calendar_v3_list_events", 150, true); metricsCollector.recordApiCall("calendar", 200, true, 200); const prometheusMetrics = metricsCollector.getPrometheusMetrics(); expect(prometheusMetrics).toContain("gmcp_requests_total"); expect(prometheusMetrics).toContain("gmcp_tool_calls_total"); expect(prometheusMetrics).toContain("gmcp_api_calls_total"); expect(prometheusMetrics).toContain("# HELP"); expect(prometheusMetrics).toContain("# TYPE"); }); it("should calculate requests per minute", () => { // Record several calls for (let i = 0; i < 5; i++) { metricsCollector.recordToolCall("test_tool", 100, true); } const metrics = metricsCollector.getMetrics(); expect(metrics.requests_per_minute).toBeGreaterThan(0); }); it("should reset metrics", () => { metricsCollector.recordToolCall("test_tool", 100, true); metricsCollector.recordError("test_error"); metricsCollector.reset(); const metrics = metricsCollector.getMetrics(); expect(metrics.requests_total).toBe(0); expect(metrics.errors_total).toBe(0); }); }); describe("ApiMonitor", () => { it("should track API calls", () => { const callId = "test-call-1"; apiMonitor.startApiCall(callId, "calendar", "GET", "https://api.example.com"); apiMonitor.endApiCall(callId, 200, true); const activeCalls = apiMonitor.getActiveCalls(); expect(activeCalls).toHaveLength(0); // Should be removed after ending }); it("should detect rate limiting", () => { const callId = "test-call-2"; const mockHeaders = new Headers(); mockHeaders.set('X-RateLimit-Remaining', '0'); mockHeaders.set('Retry-After', '60'); apiMonitor.startApiCall(callId, "calendar", "GET", "https://api.example.com"); apiMonitor.endApiCall(callId, 429, false, undefined, mockHeaders); const rateLimitInfo = apiMonitor.getRateLimitInfo("calendar"); expect(rateLimitInfo?.isLimited).toBe(true); expect(rateLimitInfo?.retryAfter).toBe(60); }); it("should wrap API calls", async () => { let callExecuted = false; const mockApiCall = async () => { callExecuted = true; return "success"; }; const result = await apiMonitor.wrapApiCall( "calendar", "GET", "https://api.example.com", mockApiCall ); expect(callExecuted).toBe(true); expect(result).toBe("success"); }); it("should handle API call failures", async () => { const mockApiCall = async () => { throw new Error("API Error"); }; await expect( apiMonitor.wrapApiCall("calendar", "GET", "https://api.example.com", mockApiCall) ).rejects.toThrow("API Error"); }); it("should wrap fetch calls", async () => { (global.fetch as any).mockResolvedValue({ ok: true, status: 200, headers: new Headers() }); const response = await apiMonitor.wrapFetch( "calendar", "https://api.example.com" ); expect(response.ok).toBe(true); expect(global.fetch).toHaveBeenCalledWith("https://api.example.com", {}); }); it("should get performance summary", () => { // Create some activity const callId = "test-call-3"; apiMonitor.startApiCall(callId, "calendar", "GET", "https://api.example.com"); apiMonitor.endApiCall(callId, 200, true); const summary = apiMonitor.getApiPerformanceSummary(); expect(typeof summary).toBe('object'); }); it("should recommend delays for rate limited APIs", () => { // Set up rate limit const callId = "test-call-4"; const mockHeaders = new Headers(); mockHeaders.set('Retry-After', '30'); apiMonitor.startApiCall(callId, "calendar", "GET", "https://api.example.com"); apiMonitor.endApiCall(callId, 429, false, undefined, mockHeaders); const delay = apiMonitor.getRecommendedDelay("calendar"); expect(delay).toBe(30000); // 30 seconds in milliseconds }); }); describe("MonitoringEndpoints", () => { let monitoringEndpoints: MonitoringEndpoints; let mockServer: any; beforeEach(() => { // Mock HTTP server mockServer = { listen: vi.fn((port: number, host: string, callback: () => void) => { callback(); }), close: vi.fn((callback: () => void) => { callback(); }), on: vi.fn(), listening: true }; // Mock http.createServer vi.doMock('http', () => ({ createServer: vi.fn(() => mockServer) })); }); it("should create monitoring endpoints", () => { const config = { port: 3001, host: '0.0.0.0', enableCors: true }; monitoringEndpoints = new MonitoringEndpoints( healthChecker, metricsCollector, apiMonitor, config ); expect(monitoringEndpoints).toBeDefined(); }); it("should check if server is running", () => { const config = { port: 3001 }; monitoringEndpoints = new MonitoringEndpoints( healthChecker, metricsCollector, apiMonitor, config ); // Before starting expect(monitoringEndpoints.isRunning()).toBe(false); }); }); describe("System Integration", () => { it("should work together for comprehensive monitoring", async () => { // Simulate tool execution metricsCollector.recordToolCall("calendar_v3_list_events", 150, true); metricsCollector.recordApiCall("calendar", 120, true, 200); // Check health (global.fetch as any).mockResolvedValue({ ok: true, headers: { get: vi.fn().mockReturnValue("application/json") } }); const health = await healthChecker.getHealthStatus(); const metrics = metricsCollector.getMetrics(); expect(health.status).toBe('healthy'); expect(metrics.requests_total).toBe(1); expect(metrics.google_api_calls).toBeGreaterThanOrEqual(0); }); it("should handle monitoring initialization failures gracefully", () => { // Test that monitoring failures don't crash the main system const invalidOAuth = null; expect(() => { new HealthChecker(invalidOAuth as any); }).not.toThrow(); }); }); });

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/INSIDE-HAIR/mcp-google-calendar-and-meet'

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