@nestjsvn/swagger-sse
Version:
OpenAPI documentation and interactive Swagger UI for NestJS Server-Sent Events endpoints
265 lines (264 loc) • 9.82 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const plugin_1 = require("../../ui/plugin");
// Mock EventSource
class MockEventSource {
constructor(url) {
this.url = url;
this.onopen = null;
this.onerror = null;
this.onmessage = null;
this.readyState = 0;
this.listeners = new Map();
setTimeout(() => {
this.readyState = 1;
if (this.onopen) {
this.onopen(new Event('open'));
}
}, 10);
}
addEventListener(type, listener) {
if (!this.listeners.has(type)) {
this.listeners.set(type, []);
}
this.listeners.get(type).push(listener);
}
removeEventListener(type, listener) {
const listeners = this.listeners.get(type);
if (listeners) {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
close() {
this.readyState = 2;
}
// Helper method to simulate events
simulateEvent(type, data) {
const listeners = this.listeners.get(type);
if (listeners) {
listeners.forEach(listener => {
listener({ type, data: JSON.stringify(data) });
});
}
}
simulateMessage(data) {
if (this.onmessage) {
this.onmessage(new MessageEvent('message', { data: JSON.stringify(data) }));
}
}
}
// Mock global EventSource
global.EventSource = MockEventSource;
describe('SwaggerSsePlugin', () => {
let mockSystem;
beforeEach(() => {
mockSystem = {
register: jest.fn(),
getState: jest.fn(() => ({
getIn: jest.fn(() => 'http://localhost:3000'),
})),
};
});
describe('Plugin Structure', () => {
it('should return a valid plugin object', () => {
const plugin = (0, plugin_1.SwaggerSsePlugin)();
expect(plugin).toHaveProperty('name', 'swagger-sse-plugin');
expect(plugin).toHaveProperty('init');
expect(plugin).toHaveProperty('statePlugins');
});
it('should register components and actions on init', () => {
const plugin = (0, plugin_1.SwaggerSsePlugin)();
plugin.init(mockSystem);
expect(mockSystem.register).toHaveBeenCalledWith({
components: expect.objectContaining({
SseExecuteButton: expect.any(Function),
SseEventLog: expect.any(Function),
SseConnectionStatus: expect.any(Function),
}),
actions: expect.objectContaining({
startSseConnection: expect.any(Function),
stopSseConnection: expect.any(Function),
addSseEvent: expect.any(Function),
}),
});
});
});
describe('Request Handling', () => {
it('should handle SSE requests correctly', () => {
const plugin = (0, plugin_1.SwaggerSsePlugin)();
const originalExecute = jest.fn();
const wrappedExecute = plugin.statePlugins.spec.wrapActions.execute(originalExecute, mockSystem);
const sseRequest = {
pathName: '/events/stream',
method: 'get',
spec: {
paths: {
'/events/stream': {
get: {
'x-sse-endpoint': true,
},
},
},
},
};
const result = wrappedExecute(sseRequest);
expect(result).toHaveProperty('type', 'sse-stream');
expect(result).toHaveProperty('connection');
expect(originalExecute).not.toHaveBeenCalled();
});
it('should pass through non-SSE requests', () => {
const plugin = (0, plugin_1.SwaggerSsePlugin)();
const originalExecute = jest.fn(() => ({ original: true }));
const wrappedExecute = plugin.statePlugins.spec.wrapActions.execute(originalExecute, mockSystem);
const regularRequest = {
pathName: '/api/users',
method: 'get',
spec: {
paths: {
'/api/users': {
get: {},
},
},
},
};
const result = wrappedExecute(regularRequest);
expect(result).toEqual({ original: true });
expect(originalExecute).toHaveBeenCalledWith(regularRequest);
});
});
});
describe('createSseConnection', () => {
it('should create a connection with initial state', () => {
const connection = (0, plugin_1.createSseConnection)('http://localhost:3000/events', {
url: 'http://localhost:3000/events',
eventTypes: ['user-created', 'message-received'],
});
expect(connection).toEqual({
eventSource: null,
status: 'disconnected',
events: [],
});
});
});
describe('createEventSourceConnection', () => {
beforeEach(() => {
// Mock DOM methods
const mockButton = {
className: '',
textContent: '',
innerHTML: '',
disabled: false,
addEventListener: jest.fn(),
click: jest.fn(),
};
const mockElement = {
className: '',
textContent: '',
innerHTML: '',
scrollTop: 0,
scrollHeight: 100,
querySelector: jest.fn((selector) => {
if (selector.includes('btn') || selector.includes('button')) {
return mockButton;
}
return null;
}),
appendChild: jest.fn(),
remove: jest.fn(),
addEventListener: jest.fn(),
};
global.document = {
querySelector: jest.fn(() => mockElement),
createElement: jest.fn(() => mockElement),
};
// Mock URL and Blob for export functionality
global.URL = {
createObjectURL: jest.fn(() => 'mock-url'),
revokeObjectURL: jest.fn(),
};
global.Blob = jest.fn();
});
it('should create EventSource connection', () => {
const connection = (0, plugin_1.createEventSourceConnection)('http://localhost:3000/events', [
'user-created',
]);
expect(connection.status).toBe('connecting');
expect(connection.eventSource).toBeInstanceOf(MockEventSource);
expect(connection.events).toEqual([]);
});
it('should handle connection open event', done => {
const connection = (0, plugin_1.createEventSourceConnection)('http://localhost:3000/events', []);
setTimeout(() => {
expect(connection.status).toBe('connected');
done();
}, 20);
});
it('should handle message events', done => {
const connection = (0, plugin_1.createEventSourceConnection)('http://localhost:3000/events', []);
const mockEventSource = connection.eventSource;
setTimeout(() => {
mockEventSource.simulateMessage({
userId: '123',
username: 'test',
});
setTimeout(() => {
expect(connection.events).toHaveLength(1);
expect(connection.events[0]).toMatchObject({
type: 'message',
data: { userId: '123', username: 'test' },
});
done();
}, 10);
}, 20);
});
it('should handle custom event types', done => {
const connection = (0, plugin_1.createEventSourceConnection)('http://localhost:3000/events', [
'user-created',
]);
const mockEventSource = connection.eventSource;
setTimeout(() => {
mockEventSource.simulateEvent('user-created', {
userId: '123',
username: 'test',
});
setTimeout(() => {
expect(connection.events).toHaveLength(1);
expect(connection.events[0]).toMatchObject({
type: 'user-created',
data: { userId: '123', username: 'test' },
});
done();
}, 10);
}, 20);
});
it('should handle connection errors', done => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const connection = (0, plugin_1.createEventSourceConnection)('http://localhost:3000/events', []);
const mockEventSource = connection.eventSource;
setTimeout(() => {
if (mockEventSource.onerror) {
mockEventSource.onerror(new Event('error'));
}
setTimeout(() => {
expect(connection.status).toBe('error');
expect(consoleSpy).toHaveBeenCalledWith('SSE Error:', expect.any(Event));
consoleSpy.mockRestore();
done();
}, 10);
}, 20);
});
});
describe('Utility Functions', () => {
describe('tryParseJson', () => {
// We need to access the internal function for testing
// In a real implementation, we might export it or test it indirectly
it('should parse valid JSON', () => {
const _validJson = '{"test": "value"}';
// This would test the internal tryParseJson function
// For now, we'll test the behavior through the main functions
});
});
});