Skip to main content
Glama

Nexus MCP Server

json-validator.test.ts11.8 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { JSONValidator, safeStringify, validateJSON, } from '../../../src/utils/json-validator.js'; // Mock logger to avoid noise in test output vi.mock('../../../src/utils/logger.js', () => ({ logger: { warn: vi.fn(), error: vi.fn(), debug: vi.fn(), info: vi.fn(), jsonSerialization: vi.fn(), responseValidation: vi.fn(), jsonRpc: vi.fn(), }, })); describe('JSONValidator', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('safeStringify', () => { it('should stringify simple objects correctly', () => { const obj = { name: 'test', value: 42 }; const result = JSONValidator.safeStringify(obj); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(JSON.parse(result.data!)).toEqual(obj); }); it('should handle circular references with fallback', () => { const obj: Record<string, unknown> = { name: 'test' }; obj.self = obj; // Create circular reference const result = JSONValidator.safeStringify(obj, { fallback: true }); expect(result.success).toBe(true); // Should use fallback serialization which shows depth exceeded expect(result.data).toContain('[Max Depth Exceeded]'); }); it('should handle circular references without fallback', () => { const obj: Record<string, unknown> = { name: 'test' }; obj.self = obj; // Create circular reference const result = JSONValidator.safeStringify(obj, { fallback: false }); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('should handle undefined values', () => { const obj = { name: 'test', value: undefined }; const result = JSONValidator.safeStringify(obj); expect(result.success).toBe(true); expect(result.data).toBeDefined(); // undefined values are omitted in JSON expect(JSON.parse(result.data!)).toEqual({ name: 'test' }); }); it('should handle functions in objects', () => { const obj = { name: 'test', func: () => 'hello', method: function () { return 'world'; }, }; const result = JSONValidator.safeStringify(obj, { fallback: true }); expect(result.success).toBe(true); expect(result.data).toContain('[Function]'); }); it('should handle Date objects', () => { const date = new Date('2023-01-01T00:00:00.000Z'); const obj = { timestamp: date }; const result = JSONValidator.safeStringify(obj, { fallback: true }); expect(result.success).toBe(true); expect(result.data).toContain('2023-01-01T00:00:00.000Z'); }); it('should handle Error objects', () => { const error = new Error('Test error'); error.stack = 'Error: Test error\n at test'; const obj = { error }; const result = JSONValidator.safeStringify(obj, { fallback: true }); expect(result.success).toBe(true); expect(result.data).toContain('Test error'); expect(result.data).toContain('Error: Test error'); }); it('should handle symbols', () => { const sym = Symbol('test'); const obj = { symbol: sym }; const result = JSONValidator.safeStringify(obj, { fallback: true }); expect(result.success).toBe(true); expect(result.data).toContain('Symbol(test)'); }); it('should handle BigInt values', () => { const obj = { bigNum: BigInt(123456789012345678901234567890n) }; const result = JSONValidator.safeStringify(obj, { fallback: true }); expect(result.success).toBe(true); expect(result.data).toContain('123456789012345678901234567890'); }); it('should respect max depth limit', () => { // Create deeply nested object const deep: Record<string, unknown> = {}; let current = deep; for (let i = 0; i < 25; i++) { current.next = {}; current = current.next as Record<string, unknown>; } current.value = 'deep'; const result = JSONValidator.safeStringify(deep, { fallback: true }); expect(result.success).toBe(true); expect(result.data).toContain('[Max Depth Exceeded]'); }); it('should sanitize unsafe characters', () => { const obj = { message: 'test\u0000\u001F\u007F\u009Ftest' }; const result = JSONValidator.safeStringify(obj, { sanitize: true }); expect(result.success).toBe(true); // eslint-disable-next-line no-control-regex expect(result.data).not.toMatch(/[\u0000-\u001F\u007F-\u009F]/); }); it('should handle custom replacer function', () => { const obj = { secret: 'password', public: 'data' }; const replacer = (key: string, value: unknown) => key === 'secret' ? '[REDACTED]' : value; const result = JSONValidator.safeStringify(obj, { replacer }); expect(result.success).toBe(true); expect(result.data).toContain('[REDACTED]'); expect(result.data).not.toContain('password'); }); }); describe('validateJSON', () => { it('should validate correct JSON', () => { const validJson = '{"name": "test", "value": 42}'; const result = validateJSON(validJson); expect(result.success).toBe(true); expect(result.data).toEqual({ name: 'test', value: 42 }); }); it('should reject invalid JSON', () => { const invalidJson = '{"name": "test", "value": }'; const result = validateJSON(invalidJson); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('should reject non-string input', () => { const result = validateJSON(null as unknown as string); expect(result.success).toBe(false); expect(result.error).toContain('not a string'); }); it('should reject empty string', () => { const result = validateJSON(''); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); }); describe('wrapMCPResponse', () => { it('should wrap valid MCP responses', () => { const response = { jsonrpc: '2.0', id: 1, result: { content: [{ type: 'text', text: 'Hello' }], }, }; const result = JSONValidator.wrapMCPResponse(response); expect(result).toEqual(response); }); it('should handle responses with circular references', () => { const response: Record<string, unknown> = { jsonrpc: '2.0', id: 1, result: { content: [{ type: 'text', text: 'Hello' }], }, }; response.self = response; // Create circular reference const result = JSONValidator.wrapMCPResponse(response); // Should return error response due to serialization failure expect(result).toHaveProperty('error'); if ( 'error' in result && result.error && typeof result.error === 'object' ) { expect(result.error).toHaveProperty('code'); expect((result.error as any).code).toBe(-32603); } }); it('should handle responses that fail serialization', () => { // Create an object that will cause serialization to fail with fallback: false const obj = {}; Object.defineProperty(obj, 'badProperty', { get() { throw new Error('Serialization should fail'); }, enumerable: true, }); const response = { jsonrpc: '2.0', id: 1, result: { content: [obj], }, }; const result = JSONValidator.wrapMCPResponse(response); // Should return error response due to serialization failure expect(result).toHaveProperty('error'); if ( 'error' in result && result.error && typeof result.error === 'object' ) { expect(result.error).toHaveProperty('code'); } }); }); describe('safeStringify utility function', () => { it('should return string for valid objects', () => { const obj = { name: 'test' }; const result = safeStringify(obj); expect(typeof result).toBe('string'); expect(JSON.parse(result)).toEqual(obj); }); it('should return error JSON for problematic objects', () => { const obj: Record<string, unknown> = {}; obj.self = obj; // Circular reference const result = safeStringify(obj, { fallback: false }); expect(typeof result).toBe('string'); expect(result).toContain('Serialization failed'); }); }); describe('edge cases and error handling', () => { it('should handle null values', () => { const result = JSONValidator.safeStringify(null); expect(result.success).toBe(true); expect(result.data).toBe('null'); }); it('should handle primitive values', () => { const stringResult = JSONValidator.safeStringify('test'); const numberResult = JSONValidator.safeStringify(42); const booleanResult = JSONValidator.safeStringify(true); expect(stringResult.success).toBe(true); expect(numberResult.success).toBe(true); expect(booleanResult.success).toBe(true); expect(stringResult.data).toBe('"test"'); expect(numberResult.data).toBe('42'); expect(booleanResult.data).toBe('true'); }); it('should handle arrays with mixed types', () => { const arr = [1, 'test', null, undefined, { key: 'value' }]; const result = JSONValidator.safeStringify(arr); expect(result.success).toBe(true); expect(JSON.parse(result.data!)).toEqual([ 1, 'test', null, null, { key: 'value' }, ]); }); it('should handle deeply nested arrays', () => { const nested = [[[[[['deep']]]]]]; const result = JSONValidator.safeStringify(nested); expect(result.success).toBe(true); expect(JSON.parse(result.data!)).toEqual(nested); }); it('should sanitize trailing commas', () => { // This test simulates what happens when malformed JSON is processed const malformedJson = '{"test": "value",}'; const cleanJson = malformedJson.replace(/,(\s*[}\]])/g, '$1'); expect(cleanJson).toBe('{"test": "value"}'); const result = validateJSON(cleanJson); expect(result.success).toBe(true); }); it('should handle Unicode characters properly', () => { const obj = { emoji: '🚀', chinese: '你好', arabic: 'مرحبا', special: 'test\u2028\u2029', }; const result = JSONValidator.safeStringify(obj, { sanitize: true }); expect(result.success).toBe(true); expect(result.data).toContain('🚀'); expect(result.data).toContain('你好'); expect(result.data).toContain('مرحبا'); }); }); describe('performance and stress testing', () => { it('should handle large objects efficiently', () => { const largeObj: Record<string, string> = {}; for (let i = 0; i < 1000; i++) { largeObj[`key${i}`] = `value${i}`; } const startTime = Date.now(); const result = JSONValidator.safeStringify(largeObj); const endTime = Date.now(); expect(result.success).toBe(true); expect(endTime - startTime).toBeLessThan(1000); // Should complete within 1 second }); it('should handle large arrays efficiently', () => { const largeArray = Array.from({ length: 10000 }, (_, i) => ({ id: i, value: `item${i}`, })); const startTime = Date.now(); const result = JSONValidator.safeStringify(largeArray); const endTime = Date.now(); expect(result.success).toBe(true); expect(endTime - startTime).toBeLessThan(2000); // Should complete within 2 seconds }); }); });

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