guardz-event
Version:
Type-safe event handling with runtime validation using guardz for unsafe data from 3rd parties
355 lines • 17.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
/// <reference types="jest" />
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 isMouseEventData = (data) => {
return (typeof data === 'object' &&
data !== null &&
typeof data.clientX === 'number' &&
typeof data.clientY === 'number' &&
typeof data.button === 'number');
};
describe('Fluent API Builder', () => {
describe('safe() function', () => {
it('should create a new SafeEventBuilder instance', () => {
const builder = (0, safe_event_never_throws_1.safe)();
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('SafeEventBuilder', () => {
describe('postMessage()', () => {
it('should set event type to postMessage', () => {
const builder = (0, safe_event_never_throws_1.safe)().postMessage();
expect(builder.eventType).toBe('postmessage');
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().postMessage();
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('webSocket()', () => {
it('should set event type to webSocket', () => {
const builder = (0, safe_event_never_throws_1.safe)().webSocket();
expect(builder.eventType).toBe('websocket');
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().webSocket();
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('domEvent()', () => {
it('should set event type to domEvent with custom type', () => {
const builder = (0, safe_event_never_throws_1.safe)().domEvent('click');
expect(builder.eventType).toBe('click');
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().domEvent('click');
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('eventSource()', () => {
it('should set event type to eventSource', () => {
const builder = (0, safe_event_never_throws_1.safe)().eventSource();
expect(builder.eventType).toBe('eventsource');
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().eventSource();
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('customEvent()', () => {
it('should set event type to customEvent with custom type', () => {
const builder = (0, safe_event_never_throws_1.safe)().customEvent('stock-update');
expect(builder.eventType).toBe('stock-update');
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().customEvent('stock-update');
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('storageEvent()', () => {
it('should set event type to storageEvent', () => {
const builder = (0, safe_event_never_throws_1.safe)().storageEvent();
expect(builder.eventType).toBe('storage');
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().storageEvent();
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('guard()', () => {
it('should set the guard function', () => {
const builder = (0, safe_event_never_throws_1.safe)().guard(isUserMessage);
expect(builder.config.guard).toBe(isUserMessage);
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().guard(isUserMessage);
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('tolerance()', () => {
it('should enable tolerance mode', () => {
const builder = (0, safe_event_never_throws_1.safe)().tolerance(true);
expect(builder.config.tolerance).toBe(true);
});
it('should enable tolerance mode by default', () => {
const builder = (0, safe_event_never_throws_1.safe)().tolerance();
expect(builder.config.tolerance).toBe(true);
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().tolerance(true);
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('identifier()', () => {
it('should set the identifier', () => {
const builder = (0, safe_event_never_throws_1.safe)().identifier('test-id');
expect(builder.config.identifier).toBe('test-id');
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().identifier('test-id');
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('timeout()', () => {
it('should set the timeout', () => {
const builder = (0, safe_event_never_throws_1.safe)().timeout(5000);
expect(builder.config.timeout).toBe(5000);
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().timeout(5000);
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('allowedOrigins()', () => {
it('should set allowed origins', () => {
const origins = ['https://trusted-domain.com'];
const builder = (0, safe_event_never_throws_1.safe)().allowedOrigins(origins);
expect(builder.config.allowedOrigins).toBe(origins);
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().allowedOrigins(['https://trusted-domain.com']);
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('allowedSources()', () => {
it('should set allowed sources', () => {
const sources = ['trusted-source'];
const builder = (0, safe_event_never_throws_1.safe)().allowedSources(sources);
expect(builder.config.allowedSources).toBe(sources);
});
it('should return the builder for chaining', () => {
const builder = (0, safe_event_never_throws_1.safe)().allowedSources(['trusted-source']);
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('onError()', () => {
it('should set the error callback', () => {
const onError = jest.fn();
const builder = (0, safe_event_never_throws_1.safe)().onError(onError);
expect(builder.config.onError).toBe(onError);
});
it('should return the builder for chaining', () => {
const onError = jest.fn();
const builder = (0, safe_event_never_throws_1.safe)().onError(onError);
expect(builder).toBeInstanceOf(safe_event_never_throws_1.SafeEventBuilder);
});
});
describe('createHandler()', () => {
it('should create a PostMessage handler', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.postMessage()
.guard(isUserMessage)
.allowedOrigins(['https://trusted-domain.com'])
.createHandler();
expect(typeof handler).toBe('function');
const validEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
});
it('should create a WebSocket handler', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.webSocket()
.guard(isStockUpdate)
.createHandler();
expect(typeof handler).toBe('function');
const validEvent = new MessageEvent('message', {
data: { symbol: 'AAPL', price: 150.25, change: 2.5 },
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
});
it('should create a DOM event handler', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.domEvent('click')
.guard(isMouseEventData)
.createHandler();
expect(typeof handler).toBe('function');
const validEvent = new MouseEvent('click', {
clientX: 100,
clientY: 200,
button: 0,
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
});
it('should create an EventSource handler', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.eventSource()
.guard(isStockUpdate)
.createHandler();
expect(typeof handler).toBe('function');
const validEvent = new MessageEvent('message', {
data: { symbol: 'AAPL', price: 150.25, change: 2.5 },
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
});
it('should create a CustomEvent handler', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.customEvent('stock-update')
.guard(isStockUpdate)
.createHandler();
expect(typeof handler).toBe('function');
const validEvent = new CustomEvent('stock-update', {
detail: { symbol: 'AAPL', price: 150.25, change: 2.5 },
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
});
it('should create a StorageEvent handler', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.storageEvent()
.guard((data) => {
return (typeof data === 'object' &&
data !== null &&
typeof data.key === 'string' &&
typeof data.url === 'string');
})
.createHandler();
expect(typeof handler).toBe('function');
const validEvent = new StorageEvent('storage', {
key: 'user_preferences',
oldValue: 'old_value',
newValue: 'new_value',
url: 'https://example.com',
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
});
it('should handle security violations in PostMessage', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.postMessage()
.guard(isUserMessage)
.allowedOrigins(['https://trusted-domain.com'])
.createHandler();
const maliciousEvent = new MessageEvent('message', {
origin: 'https://malicious-site.com',
data: {
id: 1,
name: 'Hacker',
email: 'hack@evil.com',
isActive: true,
},
});
const result = handler(maliciousEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(403);
}
});
it('should handle validation errors', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.postMessage()
.guard(isUserMessage)
.allowedOrigins(['https://trusted-domain.com'])
.createHandler();
const invalidEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 'not-a-number',
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
const result = handler(invalidEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(500);
}
});
});
});
describe('Complex chaining', () => {
it('should support full configuration chain', () => {
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safe)()
.postMessage()
.guard(isUserMessage)
.tolerance(true)
.identifier('user-message-handler')
.timeout(3000)
.allowedOrigins(['https://trusted-domain.com'])
.allowedSources(['trusted-source'])
.onError(onError)
.createHandler();
expect(typeof handler).toBe('function');
const validEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {
id: 1,
name: 'John',
email: 'john@example.com',
isActive: true,
},
});
// Manually set the source property since MessageEvent constructor doesn't support it
Object.defineProperty(validEvent, 'source', {
value: { 'trusted-source': true },
writable: false,
configurable: false,
});
const result = handler(validEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
});
it('should handle null events gracefully', () => {
const handler = (0, safe_event_never_throws_1.safe)()
.postMessage()
.guard(isUserMessage)
.createHandler();
const nullEvent = null;
const result = handler(nullEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
if (result.status === safe_event_never_throws_1.Status.ERROR) {
expect(result.code).toBe(500);
expect(result.message).toContain('Invalid event');
}
});
});
});
//# sourceMappingURL=builder-api.test.js.map