first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
// @ts-nocheck
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { createCompatApiErrorResponse, createSseResponse } from '../src/server.js';
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe('createSseResponse', () => {
|
||||
it('sets SSE headers and sends JSON app events', () => {
|
||||
const res = new FakeResponse();
|
||||
const sse = createSseResponse(res, { keepAliveIntervalMs: 0 });
|
||||
|
||||
expect(res.headers).toEqual({
|
||||
'Cache-Control': 'no-cache, no-transform',
|
||||
Connection: 'keep-alive',
|
||||
'Content-Type': 'text/event-stream',
|
||||
'X-Accel-Buffering': 'no',
|
||||
});
|
||||
expect(res.flushed).toBe(true);
|
||||
|
||||
expect(sse.send('start', { ok: true })).toBe(true);
|
||||
expect(res.writes.join('')).toBe('event: start\ndata: {"ok":true}\n\n');
|
||||
});
|
||||
|
||||
it('can attach SSE event ids for resumable streams', () => {
|
||||
const res = new FakeResponse();
|
||||
const sse = createSseResponse(res, { keepAliveIntervalMs: 0 });
|
||||
|
||||
expect(sse.send('stdout', { chunk: 'hello' }, 12)).toBe(true);
|
||||
|
||||
expect(res.writes.join('')).toBe('id: 12\nevent: stdout\ndata: {"chunk":"hello"}\n\n');
|
||||
});
|
||||
|
||||
it('emits heartbeat comments before real events', () => {
|
||||
const res = new FakeResponse();
|
||||
const sse = createSseResponse(res, { keepAliveIntervalMs: 0 });
|
||||
|
||||
expect(sse.writeKeepAlive()).toBe(true);
|
||||
expect(sse.send('end', {})).toBe(true);
|
||||
expect(res.writes.join('')).toBe(': keepalive\n\nevent: end\ndata: {}\n\n');
|
||||
});
|
||||
|
||||
it('clears interval heartbeat on close', () => {
|
||||
vi.useFakeTimers();
|
||||
const res = new FakeResponse();
|
||||
createSseResponse(res, { keepAliveIntervalMs: 10 });
|
||||
|
||||
vi.advanceTimersByTime(10);
|
||||
expect(res.writes).toEqual([': keepalive\n\n']);
|
||||
|
||||
res.emit('close');
|
||||
vi.advanceTimersByTime(30);
|
||||
expect(res.writes).toEqual([': keepalive\n\n']);
|
||||
});
|
||||
|
||||
it('skips writes after the response ends', () => {
|
||||
const res = new FakeResponse();
|
||||
const sse = createSseResponse(res, { keepAliveIntervalMs: 0 });
|
||||
|
||||
sse.end();
|
||||
|
||||
expect(res.ended).toBe(true);
|
||||
expect(sse.writeKeepAlive()).toBe(false);
|
||||
expect(sse.send('end', {})).toBe(false);
|
||||
expect(res.writes).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createCompatApiErrorResponse', () => {
|
||||
it('wraps legacy string errors in the shared ApiError response shape', () => {
|
||||
expect(createCompatApiErrorResponse('BAD_REQUEST', 'message required')).toEqual({
|
||||
error: {
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'message required',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves shared ApiError metadata fields', () => {
|
||||
expect(
|
||||
createCompatApiErrorResponse('AGENT_UNAVAILABLE', 'missing agent', {
|
||||
retryable: true,
|
||||
details: { legacyCode: 'ENOENT' },
|
||||
}),
|
||||
).toEqual({
|
||||
error: {
|
||||
code: 'AGENT_UNAVAILABLE',
|
||||
message: 'missing agent',
|
||||
retryable: true,
|
||||
details: { legacyCode: 'ENOENT' },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FakeResponse extends EventEmitter {
|
||||
headers = {};
|
||||
writes = [];
|
||||
destroyed = false;
|
||||
writableEnded = false;
|
||||
flushed = false;
|
||||
ended = false;
|
||||
|
||||
setHeader(name, value) {
|
||||
this.headers[name] = value;
|
||||
}
|
||||
|
||||
flushHeaders() {
|
||||
this.flushed = true;
|
||||
}
|
||||
|
||||
write(chunk) {
|
||||
this.writes.push(chunk);
|
||||
return true;
|
||||
}
|
||||
|
||||
end() {
|
||||
this.ended = true;
|
||||
this.writableEnded = true;
|
||||
this.emit('finish');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user