agenthub/tests/push-channel.test.ts
chahinebrini df698c9dff feat(agenthub): Rollenwechsel, agent-setup + Push-Channel
roleService haelt roles.*.preferredAgent (Routing-Autoritaet fuer asks,
Watchdog, start) und agents.*.role (Health/Board/MCP-Discovery) konsistent —
bisher konnte nur eine Seite gesetzt werden und der Roster lief auseinander.
Dazu agent-setup-Kommando, Push-Channel im MCP und Selbstheilung im
remoteClient.

Build clean, 304 Tests in 55 Dateien gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113SC5FkaMxcRzXHDBsHmdJ
2026-08-02 21:38:47 +02:00

78 lines
2.5 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { bindPushChannel } from '../src/mcp/pushChannel.js';
function sseFrame(seq: number): Uint8Array {
return new TextEncoder().encode(
`id: ${seq}\ndata: ${JSON.stringify({
type: 'task',
action: 'updated',
id: 'TSK-0042',
status: 'open',
assignedTo: 'codex',
})}\n\n`,
);
}
describe('MCP push channel replay cursor', () => {
const realFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = realFetch;
vi.restoreAllMocks();
});
it('reconnects with Last-Event-ID after receiving a durable event', async () => {
const eventHeaders: Headers[] = [];
let eventRequests = 0;
let resolveSecondRequest!: () => void;
const secondRequest = new Promise<void>((resolve) => {
resolveSecondRequest = resolve;
});
globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
if (url.endsWith('/agents/codex/identity')) {
return new Response(JSON.stringify({ canonical: 'codex', names: ['codex'] }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
eventHeaders.push(new Headers(init?.headers));
eventRequests += 1;
if (eventRequests === 1) {
return new Response(new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(sseFrame(42));
controller.close();
},
}), { status: 200, headers: { 'Content-Type': 'text/event-stream' } });
}
resolveSecondRequest();
return new Response(new ReadableStream<Uint8Array>({
start() {
// Keep the retry connected; the test only needs its request headers.
},
}), { status: 200, headers: { 'Content-Type': 'text/event-stream' } });
}) as typeof fetch;
const sendLoggingMessage = vi.fn(async () => undefined);
const fakeServer = {
server: {
getClientCapabilities: () => null,
sendLoggingMessage,
},
} as unknown as McpServer;
bindPushChannel(fakeServer, 'http://agenthub.test', 'codex');
await secondRequest;
expect(sendLoggingMessage).toHaveBeenCalledTimes(1);
expect(eventHeaders).toHaveLength(2);
expect(eventHeaders[0].get('Last-Event-ID')).toBeNull();
expect(eventHeaders[1].get('Last-Event-ID')).toBe('42');
}, 5000);
});