Skip to main content
Glama

Nexus MCP Server

logging.test.ts13.1 kB
import { describe, it, expect, vi } from 'vitest'; import type { Logger } from 'winston'; import { maskSensitiveValue, sanitizeObject, sanitizeString, sanitizeError, SecureLogger, secureLogger, createSecureLogger, type SanitizeOptions, } from '../../../src/config/logging'; describe('Secure Logging', () => { describe('maskSensitiveValue', () => { it('should mask long values', () => { const result = maskSensitiveValue('sk-1234567890abcdef1234567890'); expect(result).not.toBe('sk-1234567890abcdef1234567890'); expect(result).toContain('*'); }); it('should mask short values completely', () => { const result = maskSensitiveValue('short'); expect(result).toBe('*****'); }); it('should respect custom masking options', () => { const options: SanitizeOptions = { maskChar: '#', showStart: 2, showEnd: 2, minLength: 6, }; const result = maskSensitiveValue('1234567890', options); expect(result).not.toBe('1234567890'); expect(result).toContain('#'); }); it('should handle empty strings', () => { const result = maskSensitiveValue(''); expect(result).toBe(''); }); it('should handle strings at minimum length threshold', () => { const result = maskSensitiveValue('12345678'); // exactly 8 chars, should be fully masked expect(result).toBe('********'); }); }); describe('sanitizeObject', () => { it('should sanitize sensitive field names', () => { const obj = { apiKey: 'sk-1234567890abcdef1234567890', username: 'john_doe', password: 'secret123', email: 'john@example.com', }; const result = sanitizeObject(obj) as Record<string, unknown>; expect(result.apiKey).not.toBe('sk-1234567890abcdef1234567890'); expect(result.username).toBe('john_doe'); // Not sensitive expect(result.password).toBe('[REDACTED]'); // Short sensitive strings expect(result.email).toBe('john@example.com'); // Not sensitive }); it('should handle nested objects', () => { const obj = { config: { openRouterApiKey: 'sk-1234567890abcdef1234567890', timeout: 30000, }, data: { name: 'test', secret: 'hidden', }, }; const result = sanitizeObject(obj) as Record<string, unknown>; const config = result.config as Record<string, unknown>; const data = result.data as Record<string, unknown>; expect(config.openRouterApiKey).not.toBe('sk-1234567890abcdef1234567890'); expect(config.timeout).toBe(30000); expect(data.name).toBe('test'); expect(data.secret).toBe('[REDACTED]'); }); it('should handle arrays', () => { const obj = { items: [{ apiKey: 'sk-1234567890abcdef1234567890' }, { name: 'test' }], }; const result = sanitizeObject(obj) as Record<string, unknown>; const items = result.items as Array<Record<string, unknown>>; expect(items[0].apiKey).not.toBe('sk-1234567890abcdef1234567890'); expect(items[1].name).toBe('test'); }); it('should handle primitive values', () => { expect(sanitizeObject('sk-1234567890abcdef1234567890')).not.toBe( 'sk-1234567890abcdef1234567890' ); expect(sanitizeObject(42)).toBe(42); expect(sanitizeObject(true)).toBe(true); expect(sanitizeObject(null)).toBe(null); expect(sanitizeObject(undefined)).toBe(undefined); }); it('should use additional sensitive fields', () => { const obj = { customSecret: 'secret123', normalField: 'normal', }; const options: SanitizeOptions = { additionalSensitiveFields: ['customSecret'], }; const result = sanitizeObject(obj, options) as Record<string, unknown>; expect(result.customSecret).toBe('[REDACTED]'); expect(result.normalField).toBe('normal'); }); it('should handle case-insensitive field matching', () => { const obj = { API_KEY: 'sk-1234567890abcdef1234567890', 'api-key': 'sk-0987654321fedcba0987654321', APIKEY: 'sk-abcdef1234567890abcdef1234', }; const result = sanitizeObject(obj) as Record<string, unknown>; expect(result.API_KEY).not.toBe('sk-1234567890abcdef1234567890'); expect(result['api-key']).not.toBe('sk-0987654321fedcba0987654321'); expect(result.APIKEY).not.toBe('sk-abcdef1234567890abcdef1234'); }); }); describe('sanitizeString', () => { it('should sanitize API keys in strings', () => { const text = 'Using API key sk-1234567890abcdef1234567890 for authentication'; const result = sanitizeString(text); expect(result).not.toBe(text); // Should be different from original expect(result).not.toContain('1234567890abcdef'); // Sensitive part should be gone expect(result).toContain('authentication'); // Non-sensitive part should remain }); it('should sanitize Bearer tokens', () => { const text = 'Authorization: Bearer sk-1234567890abcdef1234567890'; const result = sanitizeString(text); expect(result).not.toBe(text); // Should be different from original expect(result).not.toContain('1234567890abcdef'); // Sensitive part should be gone expect(result).toContain('Authorization'); // Non-sensitive part should remain }); it('should sanitize URLs with credentials', () => { const text = 'Connecting to https://user:pass@example.com/api'; const result = sanitizeString(text); expect(result).not.toBe(text); // Should be different from original expect(result).not.toContain('user:pass'); // Credentials should be gone expect(result).toContain('Connecting'); // Non-sensitive part should remain }); it('should handle multiple sensitive patterns', () => { const text = 'API key sk-1234567890abcdef1234567890 and token Bearer abcdef1234567890abcdef1234567890'; const result = sanitizeString(text); expect(result).not.toBe(text); // Should be different from original expect(result).not.toContain('1234567890abcdef'); // First sensitive part should be gone expect(result).not.toContain('abcdef1234567890abcdef'); // Second sensitive part should be gone expect(result).toContain('API key'); // Non-sensitive parts should remain expect(result).toContain('and token'); // Non-sensitive parts should remain }); it('should not modify safe strings', () => { const text = 'This is a safe string with no sensitive data'; const result = sanitizeString(text); expect(result).toBe(text); }); }); describe('sanitizeError', () => { it('should sanitize error messages', () => { const error = new Error( 'Authentication failed with API key sk-1234567890abcdef1234567890' ); const result = sanitizeError(error); expect(result.name).toBe('Error'); expect(result.message).not.toContain('sk-1234567890abcdef1234567890'); expect(result.message).toContain('Authentication failed'); }); it('should sanitize stack traces', () => { const error = new Error('Test error'); error.stack = 'Error: Test error with sk-1234567890abcdef1234567890\\n at test'; const result = sanitizeError(error); expect(result.stack).not.toContain('sk-1234567890abcdef1234567890'); expect(result.stack).toContain('Test error'); }); it('should handle custom error properties', () => { class CustomError extends Error { public apiKey = 'sk-1234567890abcdef1234567890'; public statusCode = 401; } const error = new CustomError('Custom error'); const result = sanitizeError(error); expect(result.apiKey).not.toBe('sk-1234567890abcdef1234567890'); expect(result.statusCode).toBe(401); }); it('should handle errors without stack traces', () => { const error = new Error('Test error'); delete error.stack; const result = sanitizeError(error); expect(result.name).toBe('Error'); expect(result.message).toBe('Test error'); expect(result.stack).toBeUndefined(); }); }); describe('SecureLogger', () => { it('should sanitize arguments before logging', () => { // Create a mock Winston logger const mockLogger = { info: vi.fn(), } as unknown as Logger; const logger = new SecureLogger({}, mockLogger); const obj = { apiKey: 'sk-1234567890abcdef1234567890' }; logger.info('User data:', obj); expect(mockLogger.info).toHaveBeenCalledWith( 'User data:', expect.objectContaining({ apiKey: expect.not.stringMatching('sk-1234567890abcdef1234567890'), }) ); }); it('should handle error objects', () => { const mockError = vi.fn(); const mockLogger = { error: mockError, } as unknown as Logger; const logger = new SecureLogger({}, mockLogger); const error = new Error( 'API key sk-1234567890abcdef1234567890 is invalid' ); logger.error('Authentication failed:', error); expect(mockError).toHaveBeenCalledWith( 'Authentication failed:', expect.objectContaining({ name: 'Error', message: expect.stringContaining('is invalid'), }) ); const call = mockError.mock.calls[0]; expect(call[1].message).not.toContain('sk-1234567890abcdef1234567890'); }); it('should support all log levels', () => { const mockLogger = { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn(), verbose: vi.fn(), } as unknown as Logger; const logger = new SecureLogger({}, mockLogger); const sensitiveData = { token: 'secret123' }; logger.error('Error:', sensitiveData); logger.warn('Warning:', sensitiveData); logger.info('Info:', sensitiveData); logger.debug('Debug:', sensitiveData); logger.verbose('Verbose:', sensitiveData); // Check that sensitive data is not logged as-is expect(mockLogger.error).toHaveBeenCalledWith( 'Error:', expect.objectContaining({ token: '[REDACTED]' }) ); expect(mockLogger.warn).toHaveBeenCalledWith( 'Warning:', expect.objectContaining({ token: '[REDACTED]' }) ); expect(mockLogger.info).toHaveBeenCalledWith( 'Info:', expect.objectContaining({ token: '[REDACTED]' }) ); expect(mockLogger.debug).toHaveBeenCalledWith( 'Debug:', expect.objectContaining({ token: '[REDACTED]' }) ); expect(mockLogger.verbose).toHaveBeenCalledWith( 'Verbose:', expect.objectContaining({ token: '[REDACTED]' }) ); }); it('should use custom options', () => { const mockLogger = { info: vi.fn(), } as unknown as Logger; const logger = new SecureLogger( { maskChar: '#', additionalSensitiveFields: ['customField'], }, mockLogger ); const obj = { apiKey: 'sk-1234567890abcdef1234567890', customField: 'sensitive', }; logger.info('Data:', obj); expect(mockLogger.info).toHaveBeenCalledWith( 'Data:', expect.objectContaining({ apiKey: expect.stringContaining('#'), customField: '[REDACTED]', }) ); }); }); describe('Factory functions', () => { it('should provide a default secure logger', () => { expect(secureLogger).toBeInstanceOf(SecureLogger); }); it('should create custom secure loggers', () => { const options: SanitizeOptions = { maskChar: '@', showStart: 3, showEnd: 3, }; const logger = createSecureLogger(options); expect(logger).toBeInstanceOf(SecureLogger); expect(logger).not.toBe(secureLogger); }); }); describe('Edge cases and performance', () => { it('should handle very large objects', () => { const largeObj: Record<string, unknown> = {}; for (let i = 0; i < 1000; i++) { largeObj[`field${i}`] = i % 10 === 0 ? 'sk-1234567890abcdef1234567890' : `value${i}`; } const result = sanitizeObject(largeObj); expect(result).toBeDefined(); expect(typeof result).toBe('object'); }); it('should handle circular references gracefully', () => { const obj: Record<string, unknown> = { name: 'test' }; obj.self = obj; // Create circular reference // This should not throw an error expect(() => sanitizeObject(obj)).not.toThrow(); }); it('should handle null and undefined in nested structures', () => { const obj = { nullValue: null, undefinedValue: undefined, nestedNull: { value: null, }, array: [null, undefined, 'value'], }; const result = sanitizeObject(obj); expect(result).toEqual(obj); }); }); });

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/adawalli/nexus'

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