fix(ssr): resolve EventSource SSR violations and implement best practices

- Fix EventSource is not defined error in queue dashboard
- Add browser guards for all EventSource usage
- Replace static constants (EventSource.OPEN/CLOSED) with numeric values
- Fix setInterval SSR violation in LLM health indicator
- Replace $effect anti-pattern with onMount in share page
- Add comprehensive SvelteKit SSR best practices documentation
- Add SSR audit and testing verification

All changes follow SvelteKit best practices and are verified against
official documentation. Production build succeeds with no SSR errors.

Closes: FixEventSourceSSR
See: docs/outcomes/FixEventSourceSSR.md
This commit is contained in:
Giancarmine Salucci
2025-12-22 03:00:29 +01:00
parent 35d6f6e40a
commit 8545744bb1
47 changed files with 12827 additions and 363 deletions

141
src/tests/queue-sse.spec.ts Normal file
View File

@@ -0,0 +1,141 @@
/**
* Integration tests for Queue SSE Stream endpoint
*
* Tests the Server-Sent Events stream for real-time queue updates.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { queueManager } from '$lib/server/queue/QueueManager';
import { GET as streamGET } from '../routes/api/queue/stream/+server.js';
describe('Queue SSE Stream Endpoint', () => {
beforeEach(() => {
// Clear queue between tests
queueManager.getAll().forEach(item => queueManager.remove(item.id));
});
afterEach(() => {
// Clean up after tests
queueManager.getAll().forEach(item => queueManager.remove(item.id));
});
describe('GET /api/queue/stream', () => {
it('should return SSE response with correct headers', async () => {
const url = new URL('http://localhost/api/queue/stream');
const request = new Request(url);
const response = await streamGET({
url,
request: {
...request,
signal: new AbortController().signal
}
} as any);
expect(response.status).toBe(200);
expect(response.headers.get('Content-Type')).toBe('text/event-stream');
expect(response.headers.get('Cache-Control')).toBe('no-cache');
expect(response.headers.get('Connection')).toBe('keep-alive');
});
it('should reject invalid status filter', async () => {
const url = new URL('http://localhost/api/queue/stream?status=invalid');
const request = new Request(url);
const response = await streamGET({
url,
request: {
...request,
signal: new AbortController().signal
}
} as any);
expect(response.status).toBe(400);
const text = await response.text();
expect(text).toContain('Invalid status filter');
});
it('should reject invalid item ID format', async () => {
const url = new URL('http://localhost/api/queue/stream?id=invalid-id');
const request = new Request(url);
const response = await streamGET({
url,
request: {
...request,
signal: new AbortController().signal
}
} as any);
expect(response.status).toBe(400);
const text = await response.text();
expect(text).toBe('Invalid queue item ID format');
});
it('should accept valid status filter', async () => {
const url = new URL('http://localhost/api/queue/stream?status=pending');
const request = new Request(url);
const response = await streamGET({
url,
request: {
...request,
signal: new AbortController().signal
}
} as any);
expect(response.status).toBe(200);
expect(response.headers.get('Content-Type')).toBe('text/event-stream');
});
it('should accept valid item ID filter', async () => {
// Add a test item first
const item = queueManager.enqueue('https://instagram.com/p/TEST123');
const url = new URL(`http://localhost/api/queue/stream?id=${item.id}`);
const request = new Request(url);
const response = await streamGET({
url,
request: {
...request,
signal: new AbortController().signal
}
} as any);
expect(response.status).toBe(200);
expect(response.headers.get('Content-Type')).toBe('text/event-stream');
});
it('should handle stream initialization without errors', async () => {
// Add some test items
queueManager.enqueue('https://instagram.com/p/TEST1');
queueManager.enqueue('https://instagram.com/p/TEST2');
const url = new URL('http://localhost/api/queue/stream');
const abortController = new AbortController();
const request = new Request(url, {
signal: abortController.signal
});
const response = await streamGET({
url,
request
} as any);
expect(response.status).toBe(200);
expect(response.body).toBeInstanceOf(ReadableStream);
// Abort the request to clean up
abortController.abort();
});
});
// Note: Full SSE stream testing would require more complex setup with
// ReadableStream readers and async iteration, which is beyond the scope
// of these basic endpoint validation tests. The above tests verify that:
// 1. The endpoint responds correctly
// 2. Headers are set properly for SSE
// 3. Parameter validation works
// 4. Stream initialization succeeds
});