UNPKG

npmplus-mcp-server

Version:

Production-ready MCP server for intelligent JavaScript package management. Works with Claude, Windsurf, Cursor, VS Code, and any MCP-compatible AI editor.

181 lines (150 loc) 5.45 kB
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { AnalyticsService } from '../../services/AnalyticsService.js'; describe('AnalyticsService', () => { let analyticsService: AnalyticsService; let consoleSpy: jest.MockedFunction<typeof console.log>; beforeEach(() => { // Reset environment delete process.env.ENABLE_ANALYTICS; delete process.env.ANALYTICS_SALT; // Create fresh console spy for each test consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}) as jest.MockedFunction<typeof console.log>; analyticsService = new AnalyticsService(); }); afterEach(() => { consoleSpy.mockRestore(); }); describe('constructor', () => { it('should initialize with analytics disabled by default', () => { expect(analyticsService.isAnalyticsEnabled()).toBe(false); }); it('should enable analytics when ENABLE_ANALYTICS is set', () => { process.env.ENABLE_ANALYTICS = 'true'; const service = new AnalyticsService(); expect(service.isAnalyticsEnabled()).toBe(true); }); it('should use default salt when ANALYTICS_SALT is not set', () => { // This is tested indirectly through the trackToolUsage method expect(() => new AnalyticsService()).not.toThrow(); }); }); describe('trackToolUsage', () => { let enabledService: AnalyticsService; beforeEach(() => { process.env.ENABLE_ANALYTICS = 'true'; enabledService = new AnalyticsService(); }); it('should log analytics data when enabled', async () => { await enabledService.trackToolUsage( 'search_packages', true, 245, '192.168.1.1', 'Mozilla/5.0 (claude-desktop)' ); expect(consoleSpy).toHaveBeenCalledWith( '[ANALYTICS]', expect.stringContaining('"tool":"search_packages"') ); }); it('should include all relevant data fields', async () => { const error = new Error('Test error'); await enabledService.trackToolUsage( 'install_packages', false, 1200, '10.0.0.1', 'Mozilla/5.0 (windsurf)', error, 'express' ); const logCall = consoleSpy.mock.calls[0]; const loggedData = JSON.parse(logCall[1] as string); expect(loggedData).toMatchObject({ tool: 'install_packages', success: false, responseTime: 1200, editor: 'windsurf', package: 'express', error: 'Error' }); expect(loggedData.timestamp).toBeDefined(); }); it('should detect editor correctly', async () => { const testCases = [ { userAgent: 'Mozilla/5.0 (claude-desktop)', expected: 'claude' }, { userAgent: 'Mozilla/5.0 (windsurf)', expected: 'windsurf' }, { userAgent: 'Mozilla/5.0 (cursor)', expected: 'cursor' }, { userAgent: 'Mozilla/5.0 (vscode)', expected: 'vscode' }, { userAgent: 'Mozilla/5.0 (unknown-browser)', expected: 'unknown' }, ]; for (const testCase of testCases) { consoleSpy.mockClear(); await enabledService.trackToolUsage( 'test_tool', true, 100, '127.0.0.1', testCase.userAgent ); const logCall = consoleSpy.mock.calls[0]; const loggedData = JSON.parse(logCall[1] as string); expect(loggedData.editor).toBe(testCase.expected); } }); it('should not log when analytics disabled', async () => { process.env.ENABLE_ANALYTICS = ''; const disabledService = new AnalyticsService(); await disabledService.trackToolUsage( 'search_packages', true, 245 ); expect(consoleSpy).not.toHaveBeenCalled(); }); it('should handle errors gracefully', async () => { // Mock console.log to throw an error consoleSpy.mockImplementationOnce(() => { throw new Error('Console error'); }); // Should not throw even if logging fails await expect( enabledService.trackToolUsage('test_tool', true, 100) ).resolves.not.toThrow(); }); }); describe('getAnalyticsSummary', () => { it('should return empty summary with message', async () => { const summary = await analyticsService.getAnalyticsSummary(7); expect(summary).toMatchObject({ period: '7 days', total_calls: 0, avg_daily_calls: 0, success_rate: 100, avg_response_time: 0, top_tools: {}, editors: {}, daily_stats: {}, message: 'Analytics data available in admin dashboard only' }); }); it('should accept custom days parameter', async () => { const summary = await analyticsService.getAnalyticsSummary(30); expect(summary.period).toBe('30 days'); }); }); describe('getPopularPackages', () => { it('should return empty object', async () => { const packages = await analyticsService.getPopularPackages(10); expect(packages).toEqual({}); }); }); describe('isAnalyticsEnabled', () => { it('should return correct analytics status', () => { expect(analyticsService.isAnalyticsEnabled()).toBe(false); process.env.ENABLE_ANALYTICS = 'true'; const enabledService = new AnalyticsService(); expect(enabledService.isAnalyticsEnabled()).toBe(true); }); }); });