Skip to main content
Glama
2389-research

MCP Agent Social Media Server

json.test.ts•11.7 kB
// ABOUTME: Tests for safe JSON utilities in src/utils/json.ts // ABOUTME: Validates safe stringify, parse, and validation functions with edge cases import { jest } from '@jest/globals'; import { isValidJson, safeJsonParse, safeJsonStringify } from '../../src/utils/json.js'; describe('JSON Utilities', () => { describe('safeJsonStringify', () => { test('should stringify normal objects correctly', () => { const obj = { name: 'test', value: 42, active: true }; const result = safeJsonStringify(obj); expect(result).toBe('{"name":"test","value":42,"active":true}'); }); test('should stringify arrays correctly', () => { const arr = [1, 'two', { three: 3 }, null]; const result = safeJsonStringify(arr); expect(result).toBe('[1,"two",{"three":3},null]'); }); test('should stringify primitives correctly', () => { expect(safeJsonStringify(42)).toBe('42'); expect(safeJsonStringify('hello')).toBe('"hello"'); expect(safeJsonStringify(true)).toBe('true'); expect(safeJsonStringify(null)).toBe('null'); }); test('should handle undefined values', () => { expect(safeJsonStringify(undefined)).toBe(undefined); }); test('should handle circular references gracefully', () => { const obj: Record<string, unknown> = { name: 'test' }; obj.self = obj; // Create circular reference const result = safeJsonStringify(obj); const parsed = JSON.parse(result); expect(parsed._error).toBe('JSON_SERIALIZATION_ERROR'); expect(parsed._originalError).toContain('circular'); expect(parsed._fallback).toBe('[object Object]'); }); test('should use custom replacer function when provided', () => { const obj = { password: 'secret', username: 'admin' }; const replacer = (key: string, value: unknown) => { if (key === 'password') return '[REDACTED]'; return value; }; const result = safeJsonStringify(obj, replacer); expect(result).toBe('{"password":"[REDACTED]","username":"admin"}'); }); test('should handle BigInt values that cannot be serialized', () => { const objWithBigInt = { id: BigInt(9007199254740991), name: 'test', }; const result = safeJsonStringify(objWithBigInt); const parsed = JSON.parse(result); expect(parsed._error).toBe('JSON_SERIALIZATION_ERROR'); expect(parsed._originalError).toContain('BigInt'); }); test('should handle functions that cannot be serialized', () => { const objWithFunction = { name: 'test', handler: () => 'test', }; // Functions are normally ignored by JSON.stringify, but we test custom cases const result = safeJsonStringify(objWithFunction); expect(result).toBe('{"name":"test"}'); // Function is ignored }); test('should handle date objects correctly', () => { const date = new Date('2023-01-01T00:00:00.000Z'); const obj = { timestamp: date, name: 'test' }; const result = safeJsonStringify(obj); expect(result).toBe('{"timestamp":"2023-01-01T00:00:00.000Z","name":"test"}'); }); test('should handle nested objects with potential issues', () => { const complexObj = { level1: { level2: { level3: { data: 'deep', }, }, }, }; const result = safeJsonStringify(complexObj); expect(JSON.parse(result)).toEqual(complexObj); }); test('should provide fallback for extreme edge cases', () => { // Mock JSON.stringify to always fail const originalStringify = JSON.stringify; JSON.stringify = jest.fn(() => { throw new Error('Mock error'); }); try { const result = safeJsonStringify({ test: 'value' }); // When both JSON.stringify calls fail, it goes to last resort fallback const parsed = JSON.parse(result); expect(parsed._error).toBe('JSON_SERIALIZATION_FAILED'); expect(parsed._type).toBe('object'); } finally { // Restore original JSON.stringify JSON.stringify = originalStringify; } }); test('should provide last resort fallback when everything fails', () => { // Mock JSON.stringify to always fail for both attempts const originalStringify = JSON.stringify; let callCount = 0; JSON.stringify = jest.fn(() => { callCount++; throw new Error('Mock error'); }); try { const testObj = { test: 'value' }; const result = safeJsonStringify(testObj); // Should use the last resort fallback expect(result).toBe('{"_error":"JSON_SERIALIZATION_FAILED","_type":"object"}'); expect(callCount).toBe(2); // Called twice - once for main, once for fallback } finally { // Restore original JSON.stringify JSON.stringify = originalStringify; } }); }); describe('safeJsonParse', () => { test('should parse valid JSON strings correctly', () => { const jsonString = '{"name":"test","value":42}'; const defaultValue = { error: 'default' }; const result = safeJsonParse(jsonString, defaultValue); expect(result).toEqual({ name: 'test', value: 42 }); }); test('should parse JSON arrays correctly', () => { const jsonString = '[1, 2, 3, "test"]'; const defaultValue: unknown[] = []; const result = safeJsonParse(jsonString, defaultValue); expect(result).toEqual([1, 2, 3, 'test']); }); test('should parse primitive JSON values', () => { expect(safeJsonParse('42', 0)).toBe(42); expect(safeJsonParse('"hello"', '')).toBe('hello'); expect(safeJsonParse('true', false)).toBe(true); expect(safeJsonParse('null', 'default')).toBe(null); }); test('should return default value for invalid JSON', () => { const invalidJson = '{"invalid": json}'; const defaultValue = { error: 'parsing failed' }; const result = safeJsonParse(invalidJson, defaultValue); expect(result).toEqual(defaultValue); }); test('should return default value for malformed JSON', () => { const malformedJson = '{"name": "test", "value":}'; const defaultValue = null; const result = safeJsonParse(malformedJson, defaultValue); expect(result).toBe(null); }); test('should return default value for empty string', () => { const defaultValue = { empty: true }; const result = safeJsonParse('', defaultValue); expect(result).toEqual(defaultValue); }); test('should return default value for non-JSON strings', () => { const defaultValue = 'fallback'; expect(safeJsonParse('not json at all', defaultValue)).toBe(defaultValue); expect(safeJsonParse('123abc', defaultValue)).toBe(defaultValue); expect(safeJsonParse('undefined', defaultValue)).toBe(defaultValue); }); test('should handle different default value types', () => { // String default expect(safeJsonParse('invalid', 'default')).toBe('default'); // Number default expect(safeJsonParse('invalid', 0)).toBe(0); // Boolean default expect(safeJsonParse('invalid', true)).toBe(true); // Array default expect(safeJsonParse('invalid', [])).toEqual([]); // Object default expect(safeJsonParse('invalid', { key: 'value' })).toEqual({ key: 'value' }); // Null default expect(safeJsonParse('invalid', null)).toBe(null); // Undefined default expect(safeJsonParse('invalid', undefined)).toBe(undefined); }); }); describe('isValidJson', () => { test('should return true for valid JSON strings', () => { expect(isValidJson('{"name":"test","value":42}')).toBe(true); expect(isValidJson('[1,2,3]')).toBe(true); expect(isValidJson('"hello world"')).toBe(true); expect(isValidJson('42')).toBe(true); expect(isValidJson('true')).toBe(true); expect(isValidJson('false')).toBe(true); expect(isValidJson('null')).toBe(true); }); test('should return true for nested valid JSON', () => { const nestedJson = JSON.stringify({ level1: { level2: { array: [1, 2, { nested: true }], date: new Date().toISOString(), }, }, }); expect(isValidJson(nestedJson)).toBe(true); }); test('should return false for invalid JSON strings', () => { expect(isValidJson('{"invalid": json}')).toBe(false); expect(isValidJson('{"name": "test", "value":}')).toBe(false); expect(isValidJson('[1, 2, 3')).toBe(false); expect(isValidJson('{"unclosed": "object"')).toBe(false); }); test('should return false for non-JSON strings', () => { expect(isValidJson('hello world')).toBe(false); expect(isValidJson('123abc')).toBe(false); expect(isValidJson('undefined')).toBe(false); expect(isValidJson('function() {}')).toBe(false); }); test('should return false for empty string', () => { expect(isValidJson('')).toBe(false); }); test('should return false for whitespace only', () => { expect(isValidJson(' ')).toBe(false); expect(isValidJson('\n\t')).toBe(false); }); test('should return true for JSON with valid whitespace', () => { expect(isValidJson(' {"valid": true} ')).toBe(true); expect(isValidJson('{\n "multiline": true\n}')).toBe(true); }); test('should handle special characters in JSON', () => { const jsonWithSpecial = JSON.stringify({ emoji: '🚀', unicode: 'café', quotes: 'He said "hello"', backslash: 'path\\to\\file', }); expect(isValidJson(jsonWithSpecial)).toBe(true); }); test('should return false for JSON-like but invalid syntax', () => { expect(isValidJson('{name: "test"}')).toBe(false); // Unquoted property name expect(isValidJson("{'name': 'test'}")).toBe(false); // Single quotes expect(isValidJson('{"name": "test",}')).toBe(false); // Trailing comma }); }); describe('Integration Tests', () => { test('should work together for round-trip with valid data', () => { const originalData = { string: 'test', number: 42, boolean: true, array: [1, 2, 3], nested: { key: 'value' }, nullValue: null, }; const jsonString = safeJsonStringify(originalData); expect(isValidJson(jsonString)).toBe(true); const parsedData = safeJsonParse(jsonString, {}); expect(parsedData).toEqual(originalData); }); test('should handle problematic data gracefully', () => { const problematicData: Record<string, unknown> = { name: 'test' }; problematicData.circular = problematicData; const jsonString = safeJsonStringify(problematicData); expect(isValidJson(jsonString)).toBe(true); const parsedData = safeJsonParse(jsonString, {}); expect(parsedData._error).toBe('JSON_SERIALIZATION_ERROR'); }); test('should provide consistent behavior across functions', () => { const testCases = [ '{"valid": "json"}', 'invalid json', '[1,2,3]', '{"incomplete":', 'null', '42', ]; for (const testCase of testCases) { const isValid = isValidJson(testCase); const parsed = safeJsonParse(testCase, { error: 'default' }); if (isValid) { expect(parsed).not.toEqual({ error: 'default' }); } else { expect(parsed).toEqual({ error: 'default' }); } } }); }); });

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/2389-research/mcp-socialmedia'

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