Skip to main content
Glama

firewalla-mcp-server

data-validator.test.ts12.5 kB
/** * Unit tests for data-validator utility functions */ import { validateResponseStructure, checkFieldTypes, normalizeTimestamps, createValidationSchema, type ValidationResult, type TypeValidationResult, type TimestampNormalizationResult, type ResponseSchema, } from '../../src/utils/data-validator.js'; describe('Data Validator', () => { describe('validateResponseStructure', () => { it('should validate valid response structure', () => { const data = { count: 10, results: [{ id: 1 }, { id: 2 }], timestamp: '2024-01-01T00:00:00Z', }; const schema: ResponseSchema = { required: { count: 'number', results: 'array', timestamp: 'string', }, }; const result = validateResponseStructure(data, schema); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); expect(result.metadata?.fieldsValidated).toBe(3); }); it('should detect missing required fields', () => { const data = { count: 10, // missing 'results' field }; const schema: ResponseSchema = { required: { count: 'number', results: 'array', }, }; const result = validateResponseStructure(data, schema); expect(result.isValid).toBe(false); expect(result.errors).toContain("Required field 'results' is missing"); expect(result.metadata?.missingFields).toBe(1); }); it('should detect type mismatches', () => { const data = { count: 'ten', // Should be number results: 'not-array', // Should be array }; const schema: ResponseSchema = { required: { count: 'number', results: 'array', }, }; const result = validateResponseStructure(data, schema); expect(result.isValid).toBe(false); expect(result.errors).toHaveLength(2); expect(result.metadata?.typeMismatches).toBe(2); }); it('should handle null or undefined data', () => { const schema: ResponseSchema = { required: { field: 'string' }, }; const nullResult = validateResponseStructure(null, schema); const undefinedResult = validateResponseStructure(undefined, schema); expect(nullResult.isValid).toBe(false); expect(undefinedResult.isValid).toBe(false); expect(nullResult.errors).toContain('Response data is null or undefined'); }); it('should validate optional fields when present', () => { const data = { count: 10, results: [], cursor: 'abc123', // Optional field }; const schema: ResponseSchema = { required: { count: 'number', results: 'array', }, optional: { cursor: 'string', }, }; const result = validateResponseStructure(data, schema); expect(result.isValid).toBe(true); expect(result.metadata?.fieldsValidated).toBe(3); }); it('should run custom validators', () => { const data = { count: -5, // Should fail custom validation results: [], }; const schema: ResponseSchema = { required: { count: 'number', results: 'array', }, customValidators: { count: (value) => ({ isValid: value >= 0, error: 'Count must be non-negative', }), }, }; const result = validateResponseStructure(data, schema); expect(result.isValid).toBe(false); expect(result.errors).toContain( "Custom validation failed for 'count': Count must be non-negative" ); }); it('should warn about unexpected fields when not allowed', () => { const data = { count: 10, results: [], unexpected: 'field', }; const schema: ResponseSchema = { required: { count: 'number', results: 'array', }, allowAdditionalFields: false, }; const result = validateResponseStructure(data, schema); expect(result.isValid).toBe(true); // Still valid, just warnings expect(result.warnings).toContain('Unexpected fields found: unexpected'); }); }); describe('checkFieldTypes', () => { it('should validate correct field types', () => { const data = { name: 'test', count: 42, active: true, items: [1, 2, 3], metadata: { key: 'value' }, }; const typeMap = { name: 'string', count: 'number', active: 'boolean', items: 'array', metadata: 'object', }; const result = checkFieldTypes(data, typeMap); expect(result.isValid).toBe(true); expect(result.metadata.summary.validFields).toBe(5); expect(result.metadata.summary.invalidFields).toBe(0); }); it('should detect type mismatches', () => { const data = { count: '123', // Should be number active: 'true', // Should be boolean }; const typeMap = { count: 'number', active: 'boolean', }; const result = checkFieldTypes(data, typeMap); expect(result.isValid).toBe(false); expect(result.invalidFields).toHaveLength(2); expect(result.metadata.summary.invalidFields).toBe(2); }); it('should suggest type conversions', () => { const data = { count: '123', // Convertible to number active: 'yes', // Convertible to boolean }; const typeMap = { count: 'number', active: 'boolean', }; const result = checkFieldTypes(data, typeMap); expect(result.metadata.summary.convertibleFields).toBe(2); expect(result.invalidFields[0].suggestion).toContain('Convert string'); }); it('should handle null and undefined values', () => { const data = { nullField: null, undefinedField: undefined, }; const typeMap = { nullField: 'string', undefinedField: 'number', }; const result = checkFieldTypes(data, typeMap); expect(result.isValid).toBe(false); expect(result.invalidFields).toHaveLength(2); }); it('should handle non-object input', () => { const result = checkFieldTypes('not-an-object', { field: 'string' }); expect(result.isValid).toBe(false); expect(result.invalidFields[0].field).toBe('<root>'); }); }); describe('normalizeTimestamps', () => { it('should normalize Unix timestamps', () => { const data = { created_at: 1640995200, // Unix timestamp in seconds updated_at: 1640995200000, // Unix timestamp in milliseconds }; const result = normalizeTimestamps(data); expect(result.success).toBe(true); expect(result.data.created_at).toBe('2022-01-01T00:00:00.000Z'); expect(result.data.updated_at).toBe('2022-01-01T00:00:00.000Z'); expect(result.modifications).toHaveLength(2); }); it('should normalize ISO date strings', () => { const data = { timestamp: '2022-01-01T00:00:00Z', date: '2022-01-01', }; const result = normalizeTimestamps(data); expect(result.success).toBe(true); expect(result.data.timestamp).toBe('2022-01-01T00:00:00.000Z'); expect(result.data.date).toBe('2022-01-01T00:00:00.000Z'); }); it('should normalize Date objects', () => { const date = new Date('2022-01-01T00:00:00Z'); const data = { timestamp: date, }; const result = normalizeTimestamps(data); expect(result.success).toBe(true); expect(result.data.timestamp).toBe('2022-01-01T00:00:00.000Z'); expect(result.modifications[0].modificationType).toBe('formatted'); }); it('should handle invalid timestamps', () => { const data = { timestamp: 'not-a-date', invalid_number: NaN, }; const result = normalizeTimestamps(data); expect(result.success).toBe(false); expect(result.warnings).toHaveLength(2); expect(result.warnings[0]).toContain('Failed to normalize timestamp'); }); it('should handle nested objects', () => { const data = { metadata: { created_at: 1640995200, nested: { timestamp: '2022-01-01T00:00:00Z', }, }, }; const result = normalizeTimestamps(data); expect(result.success).toBe(true); expect(result.modifications).toHaveLength(2); expect(result.modifications[0].field).toBe('metadata.created_at'); expect(result.modifications[1].field).toBe('metadata.nested.timestamp'); }); it('should handle non-object input', () => { const result = normalizeTimestamps(null); expect(result.success).toBe(false); expect(result.warnings).toContain('Input data is not an object'); }); it('should detect timestamp fields by naming patterns', () => { const data = { lastSeen: 1640995200, createdAt: '2022-01-01T00:00:00Z', expire_time: new Date('2022-01-01'), regular_field: 'not-a-timestamp', }; const result = normalizeTimestamps(data); expect(result.success).toBe(true); expect(result.modifications).toHaveLength(3); // Only timestamp fields modified expect(result.data.regular_field).toBe('not-a-timestamp'); // Unchanged }); }); describe('createValidationSchema', () => { it('should create appropriate schema for alarms', () => { const schema = createValidationSchema('alarms'); expect(schema.required).toHaveProperty('count', 'number'); expect(schema.required).toHaveProperty('results', 'array'); expect(schema.customValidators).toHaveProperty('count'); }); it('should create appropriate schema for flows', () => { const schema = createValidationSchema('flows'); expect(schema.required).toHaveProperty('count', 'number'); expect(schema.required).toHaveProperty('results', 'array'); expect(schema.optional).toHaveProperty('query_executed', 'string'); expect(schema.optional).toHaveProperty('aggregations', 'object'); }); it('should create appropriate schema for devices', () => { const schema = createValidationSchema('devices'); expect(schema.required).toHaveProperty('count', 'number'); expect(schema.required).toHaveProperty('results', 'array'); expect(schema.optional).toHaveProperty('total_count', 'number'); }); it('should create base schema for unknown types', () => { const schema = createValidationSchema('unknown'); expect(schema.required).toHaveProperty('count', 'number'); expect(schema.required).toHaveProperty('results', 'array'); expect(schema.allowAdditionalFields).toBe(true); }); }); describe('Edge cases and performance', () => { it('should handle very large datasets efficiently', () => { const largeData = { count: 10000, results: Array.from({ length: 1000 }, (_, i) => ({ id: i })), }; const schema = createValidationSchema('test'); const startTime = Date.now(); const result = validateResponseStructure(largeData, schema); const endTime = Date.now(); expect(result.isValid).toBe(true); expect(endTime - startTime).toBeLessThan(100); // Should be fast }); it('should handle deeply nested structures', () => { const deepData: any = { level0: {} }; let current = deepData.level0; for (let i = 1; i < 10; i++) { current[`level${i}`] = {}; current = current[`level${i}`]; } current.timestamp = 1640995200; const result = normalizeTimestamps(deepData); expect(result.success).toBe(true); expect(result.modifications).toHaveLength(1); }); it('should handle special number values', () => { const data = { infinity: Infinity, negativeInfinity: -Infinity, notANumber: NaN, zero: 0, negative: -42, }; const typeMap = { infinity: 'number', negativeInfinity: 'number', notANumber: 'number', zero: 'number', negative: 'number', }; const result = checkFieldTypes(data, typeMap); // All are technically numbers in JavaScript expect(result.isValid).toBe(true); }); }); });

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/amittell/firewalla-mcp-server'

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