UNPKG

guardz-event

Version:

Type-safe event handling with runtime validation using guardz for unsafe data from 3rd parties

315 lines 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const safe_event_never_throws_1 = require("../utils/safe-event-never-throws"); // Mock type guards for testing const isUserMessage = (data) => { return (typeof data === 'object' && data !== null && typeof data.id === 'number' && typeof data.name === 'string' && typeof data.email === 'string' && typeof data.isActive === 'boolean'); }; const isStockUpdate = (data) => { return (typeof data === 'object' && data !== null && typeof data.symbol === 'string' && typeof data.price === 'number' && typeof data.change === 'number'); }; const isClickData = (data) => { return (typeof data === 'object' && data !== null && typeof data.clientX === 'number' && typeof data.clientY === 'number' && typeof data.button === 'number'); }; describe('Ergonomic API', () => { describe('safePostMessageListener', () => { it('should call onSuccess with validated data for valid message', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onSecurityViolation = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, { allowedOrigins: ['https://trusted-domain.com'], onSuccess, onTypeMismatch, onSecurityViolation, onError, }); const validEvent = new MessageEvent('message', { origin: 'https://trusted-domain.com', data: { id: 1, name: 'John', email: 'john@example.com', isActive: true, }, }); handler(validEvent); expect(onSuccess).toHaveBeenCalledWith({ id: 1, name: 'John', email: 'john@example.com', isActive: true, }); expect(onTypeMismatch).not.toHaveBeenCalled(); expect(onSecurityViolation).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); }); it('should call onSecurityViolation for untrusted origin', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onSecurityViolation = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, { allowedOrigins: ['https://trusted-domain.com'], onSuccess, onTypeMismatch, onSecurityViolation, onError, }); const maliciousEvent = new MessageEvent('message', { origin: 'https://malicious-site.com', data: { id: 1, name: 'Hacker', email: 'hack@evil.com', isActive: true }, }); handler(maliciousEvent); expect(onSuccess).not.toHaveBeenCalled(); expect(onTypeMismatch).not.toHaveBeenCalled(); expect(onSecurityViolation).toHaveBeenCalledWith('https://malicious-site.com', expect.stringContaining('not allowed')); expect(onError).not.toHaveBeenCalled(); }); it('should call onTypeMismatch for invalid data', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onSecurityViolation = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, { allowedOrigins: ['https://trusted-domain.com'], onSuccess, onTypeMismatch, onSecurityViolation, onError, }); const invalidEvent = new MessageEvent('message', { origin: 'https://trusted-domain.com', data: { id: 'not-a-number', name: 'John', email: 'john@example.com', isActive: true, }, }); handler(invalidEvent); expect(onSuccess).not.toHaveBeenCalled(); expect(onTypeMismatch).toHaveBeenCalledWith(expect.stringContaining('Validation failed')); expect(onSecurityViolation).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); }); }); describe('safeWebSocketListener', () => { it('should call onSuccess with validated data for valid WebSocket message', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safeWebSocketListener)(isStockUpdate, { onSuccess, onTypeMismatch, onError, }); const validEvent = new MessageEvent('message', { data: { symbol: 'AAPL', price: 150.25, change: 2.5 }, }); handler(validEvent); expect(onSuccess).toHaveBeenCalledWith({ symbol: 'AAPL', price: 150.25, change: 2.5, }); expect(onTypeMismatch).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); }); it('should call onTypeMismatch for invalid WebSocket data', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safeWebSocketListener)(isStockUpdate, { onSuccess, onTypeMismatch, onError, }); const invalidEvent = new MessageEvent('message', { data: { symbol: 'AAPL', price: 'not-a-number', change: 2.5 }, }); handler(invalidEvent); expect(onSuccess).not.toHaveBeenCalled(); expect(onTypeMismatch).toHaveBeenCalledWith(expect.stringContaining('Validation failed')); expect(onError).not.toHaveBeenCalled(); }); }); describe('safeDOMEventListener', () => { it('should call onSuccess with validated data for valid DOM event', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safeDOMEventListener)(isClickData, { onSuccess, onTypeMismatch, onError, }); const validEvent = new MouseEvent('click', { clientX: 100, clientY: 200, button: 0, }); handler(validEvent); expect(onSuccess).toHaveBeenCalledWith({ clientX: 100, clientY: 200, button: 0, }); expect(onTypeMismatch).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); }); it('should call onTypeMismatch for invalid DOM event data', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safeDOMEventListener)(isClickData, { onSuccess, onTypeMismatch, onError, }); // Create an event that doesn't match the expected structure const invalidEvent = new Event('click'); handler(invalidEvent); expect(onSuccess).not.toHaveBeenCalled(); expect(onTypeMismatch).toHaveBeenCalledWith(expect.stringContaining('Validation failed')); expect(onError).not.toHaveBeenCalled(); }); }); describe('safeEventSourceListener', () => { it('should call onSuccess with validated data for valid EventSource message', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safeEventSourceListener)(isStockUpdate, { onSuccess, onTypeMismatch, onError, }); const validEvent = new MessageEvent('message', { data: { symbol: 'AAPL', price: 150.25, change: 2.5 }, }); handler(validEvent); expect(onSuccess).toHaveBeenCalledWith({ symbol: 'AAPL', price: 150.25, change: 2.5, }); expect(onTypeMismatch).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); }); }); describe('safeCustomEventListener', () => { it('should call onSuccess with validated data for valid custom event', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safeCustomEventListener)(isStockUpdate, { onSuccess, onTypeMismatch, onError, }); const validEvent = new CustomEvent('stock-update', { detail: { symbol: 'AAPL', price: 150.25, change: 2.5 }, }); handler(validEvent); expect(onSuccess).toHaveBeenCalledWith({ symbol: 'AAPL', price: 150.25, change: 2.5, }); expect(onTypeMismatch).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); }); }); describe('safeStorageEventListener', () => { it('should call onSuccess with validated data for valid storage event', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safeStorageEventListener)((data) => { return (typeof data === 'object' && data !== null && typeof data.key === 'string' && typeof data.url === 'string'); }, { onSuccess, onTypeMismatch, onError, }); const validEvent = new StorageEvent('storage', { key: 'user_preferences', oldValue: 'old_value', newValue: 'new_value', url: 'https://example.com', }); handler(validEvent); expect(onSuccess).toHaveBeenCalledWith({ key: 'user_preferences', oldValue: 'old_value', newValue: 'new_value', url: 'https://example.com', }); expect(onTypeMismatch).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); }); }); describe('Tolerance Mode', () => { it('should handle tolerance mode correctly', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safeWebSocketListener)(isStockUpdate, { tolerance: true, onSuccess, onTypeMismatch, onError, }); // Test with partially valid data const partialEvent = new MessageEvent('message', { data: { symbol: 'AAPL', price: 150.25, change: 'not-a-number' }, }); handler(partialEvent); // In tolerance mode, it should still call onSuccess but with warnings expect(onSuccess).toHaveBeenCalled(); expect(onTypeMismatch).toHaveBeenCalledWith(expect.stringContaining('Validation failed')); expect(onError).not.toHaveBeenCalled(); }); }); describe('Error Handling', () => { it('should call onError for unexpected errors', () => { const onSuccess = jest.fn(); const onTypeMismatch = jest.fn(); const onSecurityViolation = jest.fn(); const onError = jest.fn(); const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, { allowedOrigins: ['https://trusted-domain.com'], onSuccess, onTypeMismatch, onSecurityViolation, onError, }); // Test with null event to trigger error const nullEvent = null; handler(nullEvent); expect(onSuccess).not.toHaveBeenCalled(); expect(onTypeMismatch).not.toHaveBeenCalled(); expect(onSecurityViolation).not.toHaveBeenCalled(); expect(onError).toHaveBeenCalled(); }); }); }); //# sourceMappingURL=ergonomic-api.test.js.map