UNPKG

@sailboat-computer/event-bus

Version:

Standardized event bus for sailboat computer v3 with resilience features and offline capabilities

354 lines (302 loc) 9.84 kB
/** * Validation tests */ import { SchemaRegistry, createSchemaRegistry, ValidationResult } from '../../src/validation'; import { EventEnvelope, EventPriority, EventCategory } from '../../src/types'; describe('SchemaRegistry', () => { let registry: SchemaRegistry; beforeEach(() => { registry = createSchemaRegistry(); }); describe('registerSchema', () => { it('should register a schema', () => { const schema = { type: 'object', required: ['id', 'name'], properties: { id: { type: 'string' }, name: { type: 'string' }, age: { type: 'number' } } }; registry.registerSchema('test.event.v1', schema); expect(registry.hasSchema('test.event.v1')).toBe(true); expect(registry.getSchema('test.event.v1')).toEqual(schema); }); it('should overwrite an existing schema', () => { const schema1 = { type: 'object', required: ['id', 'name'], properties: { id: { type: 'string' }, name: { type: 'string' } } }; const schema2 = { type: 'object', required: ['id', 'name', 'age'], properties: { id: { type: 'string' }, name: { type: 'string' }, age: { type: 'number' } } }; registry.registerSchema('test.event.v1', schema1); registry.registerSchema('test.event.v1', schema2); expect(registry.getSchema('test.event.v1')).toEqual(schema2); }); }); describe('hasSchema', () => { it('should return true if schema exists', () => { const schema = { type: 'object', required: ['id'], properties: { id: { type: 'string' } } }; registry.registerSchema('test.event.v1', schema); expect(registry.hasSchema('test.event.v1')).toBe(true); }); it('should return false if schema does not exist', () => { expect(registry.hasSchema('test.event.v1')).toBe(false); }); }); describe('getSchema', () => { it('should return the schema if it exists', () => { const schema = { type: 'object', required: ['id'], properties: { id: { type: 'string' } } }; registry.registerSchema('test.event.v1', schema); expect(registry.getSchema('test.event.v1')).toEqual(schema); }); it('should return null if schema does not exist', () => { expect(registry.getSchema('test.event.v1')).toBeNull(); }); }); describe('validate', () => { beforeEach(() => { const userSchema = { type: 'object', required: ['id', 'username', 'email'], properties: { id: { type: 'string' }, username: { type: 'string', minLength: 3 }, email: { type: 'string', format: 'email' }, age: { type: 'number', minimum: 0 } }, additionalProperties: false }; registry.registerSchema('user.created.v1', userSchema); }); it('should validate valid data', () => { const event: EventEnvelope = { id: 'event-123', type: 'user.created.v1', timestamp: new Date(), source: 'test-service', version: '1.0', data: { id: 'user-123', username: 'sailor', email: 'sailor@example.com', age: 30 }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }; const result = registry.validate(event); expect(result.valid).toBe(true); expect(result.errors).toBeNull(); }); it('should validate valid data without optional fields', () => { const event: EventEnvelope = { id: 'event-123', type: 'user.created.v1', timestamp: new Date(), source: 'test-service', version: '1.0', data: { id: 'user-123', username: 'sailor', email: 'sailor@example.com' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }; const result = registry.validate(event); expect(result.valid).toBe(true); expect(result.errors).toBeNull(); }); it('should invalidate data with missing required fields', () => { const event: EventEnvelope = { id: 'event-123', type: 'user.created.v1', timestamp: new Date(), source: 'test-service', version: '1.0', data: { id: 'user-123', username: 'sailor' // Missing email }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }; const result = registry.validate(event); expect(result.valid).toBe(false); expect(result.errors).not.toBeNull(); expect(result.errors!.length).toBeGreaterThan(0); expect(result.errors![0].message).toContain('email'); }); it('should invalidate data with invalid field types', () => { const event: EventEnvelope = { id: 'event-123', type: 'user.created.v1', timestamp: new Date(), source: 'test-service', version: '1.0', data: { id: 'user-123', username: 'sailor', email: 'sailor@example.com', age: 'thirty' as any // Should be a number }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }; const result = registry.validate(event); expect(result.valid).toBe(false); expect(result.errors).not.toBeNull(); expect(result.errors!.length).toBeGreaterThan(0); expect(result.errors![0].message).toContain('number'); }); it('should invalidate data with additional properties', () => { const event: EventEnvelope = { id: 'event-123', type: 'user.created.v1', timestamp: new Date(), source: 'test-service', version: '1.0', data: { id: 'user-123', username: 'sailor', email: 'sailor@example.com', role: 'admin' // Not in schema } as any, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }; const result = registry.validate(event); expect(result.valid).toBe(false); expect(result.errors).not.toBeNull(); expect(result.errors!.length).toBeGreaterThan(0); expect(result.errors![0].message).toContain('additional properties'); }); it('should invalidate data with invalid format', () => { const event: EventEnvelope = { id: 'event-123', type: 'user.created.v1', timestamp: new Date(), source: 'test-service', version: '1.0', data: { id: 'user-123', username: 'sailor', email: 'not-an-email' // Invalid email format }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }; const result = registry.validate(event); expect(result.valid).toBe(false); expect(result.errors).not.toBeNull(); expect(result.errors!.length).toBeGreaterThan(0); expect(result.errors![0].message).toContain('email'); }); it('should invalidate data with value outside constraints', () => { const event: EventEnvelope = { id: 'event-123', type: 'user.created.v1', timestamp: new Date(), source: 'test-service', version: '1.0', data: { id: 'user-123', username: 'sa', // Too short email: 'sailor@example.com' }, metadata: { priority: EventPriority.NORMAL, category: EventCategory.DATA } }; const result = registry.validate(event); expect(result.valid).toBe(false); expect(result.errors).not.toBeNull(); expect(result.errors!.length).toBeGreaterThan(0); expect(result.errors![0].message).toContain('shorter than 3'); }); }); describe('removeSchema', () => { it('should remove a schema', () => { const schema = { type: 'object', required: ['id'], properties: { id: { type: 'string' } } }; registry.registerSchema('test.event.v1', schema); expect(registry.hasSchema('test.event.v1')).toBe(true); registry.removeSchema('test.event.v1'); expect(registry.hasSchema('test.event.v1')).toBe(false); }); it('should do nothing if schema does not exist', () => { expect(() => { registry.removeSchema('non.existent.schema'); }).not.toThrow(); }); }); describe('clearSchemas', () => { it('should clear all schemas', () => { const schema1 = { type: 'object', required: ['id'], properties: { id: { type: 'string' } } }; const schema2 = { type: 'object', required: ['name'], properties: { name: { type: 'string' } } }; registry.registerSchema('test.event.v1', schema1); registry.registerSchema('test.event.v2', schema2); expect(registry.hasSchema('test.event.v1')).toBe(true); expect(registry.hasSchema('test.event.v2')).toBe(true); registry.clearSchemas(); expect(registry.hasSchema('test.event.v1')).toBe(false); expect(registry.hasSchema('test.event.v2')).toBe(false); }); }); });