Initial Commit
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { MCP_CODES } from "../../error-codes/error-codes.js";
|
||||
import type { JsonRpcMessage } from "../../schemas/schemas.js";
|
||||
import type {
|
||||
EventSourceCtor,
|
||||
EventSourceLike,
|
||||
FetchLike,
|
||||
} from "../../types/types.js";
|
||||
import { createHttpSseTransport } from "./http-sse.js";
|
||||
|
||||
class FakeEventSource implements EventSourceLike {
|
||||
public static instances: FakeEventSource[] = [];
|
||||
private listeners: ((ev: { data: string }) => void)[] = [];
|
||||
private _closed = false;
|
||||
public constructor(public readonly url: string) {
|
||||
FakeEventSource.instances.push(this);
|
||||
}
|
||||
public addEventListener(
|
||||
_type: "message",
|
||||
handler: (ev: { data: string }) => void
|
||||
): void {
|
||||
this.listeners.push(handler);
|
||||
}
|
||||
public close(): void {
|
||||
this._closed = true;
|
||||
}
|
||||
public get closed(): boolean {
|
||||
return this._closed;
|
||||
}
|
||||
public emit(data: string): void {
|
||||
for (const l of this.listeners) l({ data });
|
||||
}
|
||||
}
|
||||
|
||||
const FakeEventSourceCtor = FakeEventSource as unknown as EventSourceCtor;
|
||||
|
||||
describe("HttpSseTransport", () => {
|
||||
it("posts requests via fetchImpl", { timeout: 5000 }, async () => {
|
||||
const calls: Array<{ url: string; body: string }> = [];
|
||||
const fetchImpl: FetchLike = async (url, init) => {
|
||||
calls.push({ url, body: init.body });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => "",
|
||||
};
|
||||
};
|
||||
FakeEventSource.instances = [];
|
||||
const t = createHttpSseTransport({
|
||||
endpoint: "http://x",
|
||||
fetchImpl,
|
||||
eventSourceImpl: FakeEventSourceCtor,
|
||||
});
|
||||
await t.send({ jsonrpc: "2.0", id: 1, method: "ping" });
|
||||
expect(calls.length).toBe(1);
|
||||
expect(calls[0]?.url).toBe("http://x");
|
||||
await t.close();
|
||||
});
|
||||
|
||||
it("dispatches messages from eventSourceImpl", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
FakeEventSource.instances = [];
|
||||
const t = createHttpSseTransport({
|
||||
endpoint: "http://x",
|
||||
fetchImpl: async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => "",
|
||||
}),
|
||||
eventSourceImpl: FakeEventSourceCtor,
|
||||
});
|
||||
const received: JsonRpcMessage[] = [];
|
||||
t.onMessage((m) => received.push(m));
|
||||
const es = FakeEventSource.instances[0];
|
||||
if (es === undefined) throw new Error("no es");
|
||||
es.emit(
|
||||
JSON.stringify({ jsonrpc: "2.0", id: 1, result: { ok: true } })
|
||||
);
|
||||
es.emit("garbage-not-json");
|
||||
// JSON-valid but schema-invalid — exercises validated.success false.
|
||||
es.emit(JSON.stringify({ not: "a jsonrpc message" }));
|
||||
await Promise.resolve();
|
||||
expect(received.length).toBe(1);
|
||||
await t.close();
|
||||
});
|
||||
|
||||
it("maps non-2xx responses to TRANSPORT_ERROR", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
FakeEventSource.instances = [];
|
||||
const t = createHttpSseTransport({
|
||||
endpoint: "http://x",
|
||||
fetchImpl: async () => ({
|
||||
ok: false,
|
||||
status: 503,
|
||||
text: async () => "boom",
|
||||
}),
|
||||
eventSourceImpl: FakeEventSourceCtor,
|
||||
});
|
||||
await expect(
|
||||
t.send({ jsonrpc: "2.0", id: 1, method: "ping" })
|
||||
).rejects.toMatchObject({ code: MCP_CODES.TRANSPORT_ERROR });
|
||||
await t.close();
|
||||
});
|
||||
|
||||
it("raises TRANSPORT_CLOSED after close", { timeout: 5000 }, async () => {
|
||||
FakeEventSource.instances = [];
|
||||
const t = createHttpSseTransport({
|
||||
endpoint: "http://x",
|
||||
fetchImpl: async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => "",
|
||||
}),
|
||||
eventSourceImpl: FakeEventSourceCtor,
|
||||
});
|
||||
await t.close();
|
||||
await expect(
|
||||
t.send({ jsonrpc: "2.0", id: 1, method: "ping" })
|
||||
).rejects.toMatchObject({ code: MCP_CODES.TRANSPORT_CLOSED });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user