guardz-event
Version:
Type-safe event handling with runtime validation using guardz for unsafe data from 3rd parties
363 lines • 17 kB
JavaScript
"use strict";
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 isPartialUserMessage = (data) => {
return typeof data === 'object' && data !== null;
};
describe('Tolerance Mode', () => {
describe('Legacy API - Tolerance Mode', () => {
describe('safePostMessage with tolerance', () => {
it('should return success for partial data in tolerance mode', () => {
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isPartialUserMessage,
tolerance: true,
onError,
});
const partialEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: { id: 1, name: 'John' }, // Missing email and isActive
});
const result = handler(partialEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
if (result.status === safe_event_never_throws_1.Status.SUCCESS) {
expect(result.data).toEqual({ id: 1, name: 'John' });
}
expect(onError).not.toHaveBeenCalled();
});
it('should call onError for invalid data in tolerance mode', () => {
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessage)({
guard: isPartialUserMessage,
tolerance: true,
onError,
});
const invalidEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: 'not-an-object',
});
const result = handler(invalidEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.ERROR);
expect(onError).toHaveBeenCalledWith(expect.stringContaining('Validation failed'), expect.objectContaining({
identifier: 'postMessage',
originalError: expect.any(Error),
}));
});
});
describe('safeWebSocket with tolerance', () => {
it('should return success for partial data in tolerance mode', () => {
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safeWebSocket)({
guard: isPartialUserMessage,
tolerance: true,
onError,
});
const partialEvent = new MessageEvent('message', {
data: { id: 1, name: 'John' }, // Missing email and isActive
});
const result = handler(partialEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
if (result.status === safe_event_never_throws_1.Status.SUCCESS) {
expect(result.data).toEqual({ id: 1, name: 'John' });
}
expect(onError).not.toHaveBeenCalled();
});
});
describe('safeDOMEvent with tolerance', () => {
it('should return success for partial data in tolerance mode', () => {
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safeDOMEvent)({
guard: isPartialUserMessage,
tolerance: true,
onError,
});
const partialEvent = new MouseEvent('click', {
clientX: 100,
clientY: 200,
});
const result = handler(partialEvent);
expect(result.status).toBe(safe_event_never_throws_1.Status.SUCCESS);
if (result.status === safe_event_never_throws_1.Status.SUCCESS) {
expect(result.data).toEqual({
clientX: 100,
clientY: 200,
button: 0,
});
}
expect(onError).not.toHaveBeenCalled();
});
});
});
describe('Ergonomic API - Tolerance Mode', () => {
describe('safePostMessageListener with tolerance', () => {
it('should call onSuccess for partial data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isPartialUserMessage, {
tolerance: true,
allowedOrigins: ['https://trusted-domain.com'],
onSuccess,
onTypeMismatch,
onError,
});
const partialEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: { id: 1, name: 'John' }, // Missing email and isActive
});
handler(partialEvent);
expect(onSuccess).toHaveBeenCalledWith({ id: 1, name: 'John' });
expect(onTypeMismatch).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
it('should call onTypeMismatch for invalid data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isPartialUserMessage, {
tolerance: true,
allowedOrigins: ['https://trusted-domain.com'],
onSuccess,
onTypeMismatch,
onError,
});
const invalidEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: 'not-an-object',
});
handler(invalidEvent);
expect(onSuccess).not.toHaveBeenCalled();
expect(onTypeMismatch).toHaveBeenCalledWith(expect.stringContaining('Validation failed'));
expect(onError).not.toHaveBeenCalled();
});
});
describe('safeWebSocketListener with tolerance', () => {
it('should call onSuccess for partial data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safeWebSocketListener)(isPartialUserMessage, {
tolerance: true,
onSuccess,
onTypeMismatch,
onError,
});
const partialEvent = new MessageEvent('message', {
data: { id: 1, name: 'John' }, // Missing email and isActive
});
handler(partialEvent);
expect(onSuccess).toHaveBeenCalledWith({ id: 1, name: 'John' });
expect(onTypeMismatch).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
});
describe('safeDOMEventListener with tolerance', () => {
it('should call onSuccess for partial data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safeDOMEventListener)(isPartialUserMessage, {
tolerance: true,
onSuccess,
onTypeMismatch,
onError,
});
const partialEvent = new MouseEvent('click', {
clientX: 100,
clientY: 200,
});
handler(partialEvent);
expect(onSuccess).toHaveBeenCalledWith({
clientX: 100,
clientY: 200,
button: 0,
});
expect(onTypeMismatch).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
});
describe('safeEventSourceListener with tolerance', () => {
it('should call onSuccess for partial data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safeEventSourceListener)(isPartialUserMessage, {
tolerance: true,
onSuccess,
onTypeMismatch,
onError,
});
const partialEvent = new MessageEvent('message', {
data: { id: 1, name: 'John' }, // Missing email and isActive
});
handler(partialEvent);
expect(onSuccess).toHaveBeenCalledWith({ id: 1, name: 'John' });
expect(onTypeMismatch).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
});
describe('safeCustomEventListener with tolerance', () => {
it('should call onSuccess for partial data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safeCustomEventListener)(isPartialUserMessage, {
tolerance: true,
onSuccess,
onTypeMismatch,
onError,
});
const partialEvent = new CustomEvent('user-update', {
detail: { id: 1, name: 'John' }, // Missing email and isActive
});
handler(partialEvent);
expect(onSuccess).toHaveBeenCalledWith({ id: 1, name: 'John' });
expect(onTypeMismatch).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
});
describe('safeStorageEventListener with tolerance', () => {
it('should call onSuccess for partial data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safeStorageEventListener)(isPartialUserMessage, {
tolerance: true,
onSuccess,
onTypeMismatch,
onError,
});
const partialEvent = new StorageEvent('storage', {
key: 'user_preferences',
oldValue: 'old_value',
newValue: 'new_value',
url: 'https://example.com',
});
handler(partialEvent);
expect(onSuccess).toHaveBeenCalledWith({
key: 'user_preferences',
oldValue: 'old_value',
newValue: 'new_value',
url: 'https://example.com',
storageArea: null,
});
expect(onTypeMismatch).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
});
});
describe('Tolerance Mode Edge Cases', () => {
it('should handle null data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isPartialUserMessage, {
tolerance: true,
allowedOrigins: ['https://trusted-domain.com'],
onSuccess,
onTypeMismatch,
onError,
});
const nullEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: null,
});
handler(nullEvent);
expect(onSuccess).not.toHaveBeenCalled();
expect(onTypeMismatch).toHaveBeenCalledWith(expect.stringContaining('Validation failed'));
expect(onError).not.toHaveBeenCalled();
});
it('should handle undefined data in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isPartialUserMessage, {
tolerance: true,
allowedOrigins: ['https://trusted-domain.com'],
onSuccess,
onTypeMismatch,
onError,
});
const undefinedEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: undefined,
});
handler(undefinedEvent);
expect(onSuccess).not.toHaveBeenCalled();
expect(onTypeMismatch).toHaveBeenCalledWith(expect.stringContaining('Validation failed'));
expect(onError).not.toHaveBeenCalled();
});
it('should handle empty object in tolerance mode', () => {
const onSuccess = jest.fn();
const onTypeMismatch = jest.fn();
const onError = jest.fn();
const handler = (0, safe_event_never_throws_1.safePostMessageListener)(isPartialUserMessage, {
tolerance: true,
allowedOrigins: ['https://trusted-domain.com'],
onSuccess,
onTypeMismatch,
onError,
});
const emptyEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: {},
});
handler(emptyEvent);
expect(onSuccess).toHaveBeenCalledWith({});
expect(onTypeMismatch).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
});
describe('Tolerance Mode vs Strict Mode Comparison', () => {
it('should behave differently in tolerance vs strict mode', () => {
const partialData = { id: 1, name: 'John' }; // Missing required fields
// Strict mode (tolerance: false)
const strictOnSuccess = jest.fn();
const strictOnTypeMismatch = jest.fn();
const strictHandler = (0, safe_event_never_throws_1.safePostMessageListener)(isUserMessage, // Requires all fields
{
tolerance: false,
allowedOrigins: ['https://trusted-domain.com'],
onSuccess: strictOnSuccess,
onTypeMismatch: strictOnTypeMismatch,
onError: jest.fn(),
});
const strictEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: partialData,
});
strictHandler(strictEvent);
expect(strictOnSuccess).not.toHaveBeenCalled();
expect(strictOnTypeMismatch).toHaveBeenCalled();
// Tolerance mode (tolerance: true)
const toleranceOnSuccess = jest.fn();
const toleranceOnTypeMismatch = jest.fn();
const toleranceHandler = (0, safe_event_never_throws_1.safePostMessageListener)(isPartialUserMessage, // Allows partial data
{
tolerance: true,
allowedOrigins: ['https://trusted-domain.com'],
onSuccess: toleranceOnSuccess,
onTypeMismatch: toleranceOnTypeMismatch,
onError: jest.fn(),
});
const toleranceEvent = new MessageEvent('message', {
origin: 'https://trusted-domain.com',
data: partialData,
});
toleranceHandler(toleranceEvent);
expect(toleranceOnSuccess).toHaveBeenCalledWith(partialData);
expect(toleranceOnTypeMismatch).not.toHaveBeenCalled();
});
});
});
//# sourceMappingURL=tolerance-mode.test.js.map