UNPKG

@nestjsvn/swagger-sse

Version:

OpenAPI documentation and interactive Swagger UI for NestJS Server-Sent Events endpoints

265 lines (264 loc) 9.82 kB
"use strict"; 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 }); }); });