@nestjsvn/swagger-sse
Version:
OpenAPI documentation and interactive Swagger UI for NestJS Server-Sent Events endpoints
269 lines (268 loc) • 13.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const setup_1 = require("../e2e/setup");
const test_controllers_1 = require("../e2e/test-controllers");
class PerformanceMonitor {
constructor() {
this.startTime = 0;
this.eventCount = 0;
this.firstEventReceived = false;
this.firstEventTime = 0;
}
start() {
this.startTime = Date.now();
this.eventCount = 0;
this.firstEventReceived = false;
this.firstEventTime = 0;
}
recordEvent() {
this.eventCount++;
if (!this.firstEventReceived) {
this.firstEventReceived = true;
this.firstEventTime = Date.now() - this.startTime;
}
}
getMetrics(duration) {
return {
firstEventTime: this.firstEventTime,
eventsPerSecond: this.eventCount / (duration / 1000),
};
}
}
describe('Performance Benchmarks', () => {
let context;
let monitor;
beforeAll(async () => {
context = await setup_1.E2ETestSetup.createContext('performance-benchmarks', {
controllers: [test_controllers_1.TestEventsController],
});
monitor = new PerformanceMonitor();
});
afterAll(async () => {
await setup_1.E2ETestSetup.cleanup('performance-benchmarks');
});
describe('Connection Performance', () => {
it('should establish SSE connection within acceptable time', async () => {
await context.page.goto(`${context.baseUrl}/api-docs`);
await context.page.waitForSelector('.swagger-ui');
await context.page.click('[data-path="/test-events/basic"] .opblock-summary');
const startTime = Date.now();
await setup_1.SSETestHelpers.connectToSSE(context.page);
const connectionTime = Date.now() - startTime;
console.log(`Connection established in ${connectionTime}ms`);
// Connection should be established within 1 second
expect(connectionTime).toBeLessThan(1000);
});
it('should handle multiple concurrent connections', async () => {
const connectionPromises = [];
const startTime = Date.now();
// Create multiple browser contexts for concurrent connections
for (let i = 0; i < 5; i++) {
const testContext = await setup_1.E2ETestSetup.createContext(`concurrent-${i}`, {
controllers: [test_controllers_1.TestEventsController],
});
const promise = (async () => {
await testContext.page.goto(`${testContext.baseUrl}/api-docs`);
await testContext.page.waitForSelector('.swagger-ui');
await testContext.page.click('[data-path="/test-events/basic"] .opblock-summary');
await setup_1.SSETestHelpers.connectToSSE(testContext.page);
return testContext;
})();
connectionPromises.push(promise);
}
const contexts = await Promise.all(connectionPromises);
const totalTime = Date.now() - startTime;
console.log(`${contexts.length} concurrent connections established in ${totalTime}ms`);
// All connections should be established within 5 seconds
expect(totalTime).toBeLessThan(5000);
// Cleanup concurrent contexts
for (let i = 0; i < contexts.length; i++) {
await setup_1.E2ETestSetup.cleanup(`concurrent-${i}`);
}
});
});
describe('Event Processing Performance', () => {
it('should handle high-frequency events efficiently', async () => {
await context.page.goto(`${context.baseUrl}/api-docs`);
await context.page.waitForSelector('.swagger-ui');
await context.page.click('[data-path="/test-events/high-frequency"] .opblock-summary');
monitor.start();
// Set up event monitoring
await context.page.evaluate(() => {
let eventCount = 0;
// eslint-disable-next-line @typescript-eslint/unbound-method
const originalAddEventListener = EventSource.prototype.addEventListener;
EventSource.prototype.addEventListener = function (type, listener, options) {
if (type === 'performance-metric') {
const wrappedListener = (event) => {
eventCount++;
window.eventCount = eventCount;
if (listener)
listener(event);
};
return originalAddEventListener.call(this, type, wrappedListener, options);
}
return originalAddEventListener.call(this, type, listener, options);
};
});
await setup_1.SSETestHelpers.connectToSSE(context.page);
// Wait for high-frequency events (50 events at 100ms intervals = 5 seconds)
await context.page.waitForTimeout(6000);
const eventCount = await context.page.evaluate(() => window.eventCount || 0);
const _metrics = monitor.getMetrics(6000);
console.log(`Processed ${eventCount} events in 6 seconds`);
console.log(`Events per second: ${eventCount / 6}`);
// Should process at least 40 events (allowing for some timing variance)
expect(eventCount).toBeGreaterThan(40);
// Should handle at least 8 events per second
expect(eventCount / 6).toBeGreaterThan(8);
});
it('should maintain performance with large event payloads', async () => {
await context.page.goto(`${context.baseUrl}/api-docs`);
await context.page.waitForSelector('.swagger-ui');
await context.page.click('[data-path="/test-events/basic"] .opblock-summary');
// Monitor memory usage
const initialMemory = await context.page.evaluate(() => {
return performance.memory ? performance.memory.usedJSHeapSize : 0;
});
await setup_1.SSETestHelpers.connectToSSE(context.page);
await setup_1.SSETestHelpers.waitForSSEEvents(context.page, 5, 15000);
const finalMemory = await context.page.evaluate(() => {
return performance.memory ? performance.memory.usedJSHeapSize : 0;
});
const memoryIncrease = finalMemory - initialMemory;
console.log(`Memory increase: ${memoryIncrease} bytes`);
// Memory increase should be reasonable (less than 10MB)
expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024);
});
});
describe('UI Rendering Performance', () => {
it('should render events without blocking UI', async () => {
await context.page.goto(`${context.baseUrl}/api-docs`);
await context.page.waitForSelector('.swagger-ui');
await context.page.click('[data-path="/test-events/high-frequency"] .opblock-summary');
await setup_1.SSETestHelpers.connectToSSE(context.page);
// Measure UI responsiveness during high-frequency events
const _startTime = Date.now();
// Perform UI interactions while events are streaming
const uiResponsiveness = await context.page.evaluate(async () => {
const button = document.querySelector('[data-testid="sse-disconnect-btn"]');
if (!button)
return false;
const clickStart = performance.now();
button.click();
// Wait for UI to respond
await new Promise(resolve => setTimeout(resolve, 100));
const clickEnd = performance.now();
return clickEnd - clickStart < 200; // Should respond within 200ms
});
expect(uiResponsiveness).toBe(true);
});
it('should handle large event logs efficiently', async () => {
await context.page.goto(`${context.baseUrl}/api-docs`);
await context.page.waitForSelector('.swagger-ui');
await context.page.click('[data-path="/test-events/high-frequency"] .opblock-summary');
await setup_1.SSETestHelpers.connectToSSE(context.page);
// Wait for many events to accumulate
await context.page.waitForTimeout(8000);
// Measure scrolling performance in event log
const scrollPerformance = await context.page.evaluate(() => {
const eventLog = document.querySelector('[data-testid="sse-event-log"]');
if (!eventLog)
return false;
const scrollStart = performance.now();
eventLog.scrollTop = eventLog.scrollHeight;
const scrollEnd = performance.now();
return scrollEnd - scrollStart < 50; // Scrolling should be smooth
});
expect(scrollPerformance).toBe(true);
});
});
describe('Memory Management', () => {
it('should not leak memory during long-running connections', async () => {
await context.page.goto(`${context.baseUrl}/api-docs`);
await context.page.waitForSelector('.swagger-ui');
await context.page.click('[data-path="/test-events/long-running"] .opblock-summary');
// Take initial memory snapshot
const initialMemory = await context.page.evaluate(() => {
if (performance.memory) {
return {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
};
}
return { used: 0, total: 0 };
});
await setup_1.SSETestHelpers.connectToSSE(context.page);
// Let connection run for extended period
await context.page.waitForTimeout(15000);
// Force garbage collection if available
await context.page.evaluate(() => {
if (window.gc) {
window.gc();
}
});
const finalMemory = await context.page.evaluate(() => {
if (performance.memory) {
return {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
};
}
return { used: 0, total: 0 };
});
const memoryGrowth = finalMemory.used - initialMemory.used;
console.log(`Memory growth over 15 seconds: ${memoryGrowth} bytes`);
// Memory growth should be minimal (less than 5MB)
expect(memoryGrowth).toBeLessThan(5 * 1024 * 1024);
});
it('should clean up resources on disconnect', async () => {
await context.page.goto(`${context.baseUrl}/api-docs`);
await context.page.waitForSelector('.swagger-ui');
await context.page.click('[data-path="/test-events/basic"] .opblock-summary');
await setup_1.SSETestHelpers.connectToSSE(context.page);
await context.page.waitForTimeout(2000);
// Take memory snapshot before disconnect
const beforeDisconnect = await context.page.evaluate(() => {
return performance.memory ? performance.memory.usedJSHeapSize : 0;
});
await setup_1.SSETestHelpers.disconnectFromSSE(context.page);
// Force garbage collection
await context.page.evaluate(() => {
if (window.gc) {
window.gc();
}
});
await context.page.waitForTimeout(1000);
const afterDisconnect = await context.page.evaluate(() => {
return performance.memory ? performance.memory.usedJSHeapSize : 0;
});
const memoryFreed = beforeDisconnect - afterDisconnect;
console.log(`Memory freed on disconnect: ${memoryFreed} bytes`);
// Should free some memory (or at least not increase significantly)
expect(Number(afterDisconnect)).toBeLessThanOrEqual(Number(beforeDisconnect) + 1024 * 1024); // Allow 1MB variance
});
});
describe('Network Performance', () => {
it('should handle network interruptions gracefully', async () => {
await context.page.goto(`${context.baseUrl}/api-docs`);
await context.page.waitForSelector('.swagger-ui');
await context.page.click('[data-path="/test-events/basic"] .opblock-summary');
await setup_1.SSETestHelpers.connectToSSE(context.page);
await context.page.waitForTimeout(2000);
// Simulate network interruption by going offline
await context.page.context().setOffline(true);
await context.page.waitForTimeout(2000);
// Check error handling
const status = await context.page.getAttribute('[data-testid="sse-status"]', 'data-status');
expect(status).toBe('error');
// Restore network
await context.page.context().setOffline(false);
// Should be able to reconnect
await context.page.click('[data-testid="sse-connect-btn"]');
await setup_1.SSETestHelpers.waitForSSEConnection(context.page);
const reconnectedStatus = await context.page.getAttribute('[data-testid="sse-status"]', 'data-status');
expect(reconnectedStatus).toBe('connected');
});
});
});