/** * `HttpsTransport.send` — covers happy path, 4xx, 5xx, malformed * response bodies, ok:false rejections, fetch-throws, and lifecycle * preconditions. */ import { describe, expect, it } from "@suaveplan/testing/runner"; import { HTTPS_TRANSPORT_CODES, HttpsTransport, HttpsTransportError, } from "../src/index.js"; import { type FakeFetchCall, jsonResponse, makeEnvelope, makeFakeFetch, TEST_PEER, } from "./_helpers.js"; const RELAY = "https://relay.test"; describe("HttpsTransport.send — wire flow", () => { it("posts to /send with bearer auth and the envelope body", { timeout: 5000, }, async () => { const history: FakeFetchCall[] = []; const fetchFn = makeFakeFetch({ responder: (call) => call.url.endsWith("/send") ? jsonResponse({ ok: true }) : jsonResponse({ envelopes: [], nextCursor: "0" }), history, }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", authToken: "secret", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "ignored"); await t.send(TEST_PEER, makeEnvelope()); const sendCall = history.find((c) => c.url === `${RELAY}/send`); expect(sendCall).toBeDefined(); expect(sendCall?.method).toBe("POST"); expect(sendCall?.headers.authorization).toBe("Bearer secret"); const parsed = JSON.parse(sendCall?.body ?? "{}"); expect(parsed.to).toBe(TEST_PEER); expect(parsed.envelope.id).toBe("msg-1"); await t.stop(); }); it("strips trailing slashes from relayUrl", { timeout: 5000 }, async () => { const history: FakeFetchCall[] = []; const fetchFn = makeFakeFetch({ responder: () => jsonResponse({ ok: true }), history, }); const t = new HttpsTransport({ relayUrl: `${RELAY}//`, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "ignored"); await t.send(TEST_PEER, makeEnvelope()); const sendCall = history.find((c) => c.url.endsWith("/send")); expect(sendCall?.url).toBe(`${RELAY}/send`); await t.stop(); }); it("omits the Authorization header when no token is set", { timeout: 5000, }, async () => { const history: FakeFetchCall[] = []; const fetchFn = makeFakeFetch({ responder: () => jsonResponse({ ok: true }), history, }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "ignored"); await t.send(TEST_PEER, makeEnvelope()); const sendCall = history.find((c) => c.url.endsWith("/send")); expect(sendCall?.headers.authorization).toBeUndefined(); await t.stop(); }); it("maps 401 to AUTH_FAILED", { timeout: 5000 }, async () => { const fetchFn = makeFakeFetch({ responder: (call) => call.url.endsWith("/send") ? new Response("nope", { status: 401 }) : jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); await expect(t.send(TEST_PEER, makeEnvelope())).rejects.toMatchObject({ code: HTTPS_TRANSPORT_CODES.AUTH_FAILED, }); await t.stop(); }); it("maps 503 to RELAY_UNAVAILABLE", { timeout: 5000 }, async () => { const fetchFn = makeFakeFetch({ responder: (call) => call.url.endsWith("/send") ? new Response("down", { status: 503 }) : jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); await expect(t.send(TEST_PEER, makeEnvelope())).rejects.toMatchObject({ code: HTTPS_TRANSPORT_CODES.RELAY_UNAVAILABLE, }); await t.stop(); }); it("maps 400 (other 4xx) to SEND_FAILED", { timeout: 5000 }, async () => { const fetchFn = makeFakeFetch({ responder: (call) => call.url.endsWith("/send") ? new Response("bad", { status: 400 }) : jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); await expect(t.send(TEST_PEER, makeEnvelope())).rejects.toMatchObject({ code: HTTPS_TRANSPORT_CODES.SEND_FAILED, }); await t.stop(); }); it("treats fetch-throws as SEND_FAILED with cause", { timeout: 5000, }, async () => { const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn: async (url) => { if (String(url).endsWith("/send")) { throw new Error("ECONNRESET"); } return jsonResponse({ envelopes: [], nextCursor: "0" }); }, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); try { await t.send(TEST_PEER, makeEnvelope()); throw new Error("expected SEND_FAILED"); } catch (err) { expect(err).toBeInstanceOf(HttpsTransportError); const e = err as HttpsTransportError; expect(e.code).toBe(HTTPS_TRANSPORT_CODES.SEND_FAILED); expect(e.message).toContain("ECONNRESET"); expect(e.cause).toBeInstanceOf(Error); } await t.stop(); }); it("treats fetch-throws of non-Error as SEND_FAILED with stringified cause", { timeout: 5000, }, async () => { const fetchSubstitute = async ( url: string | URL ): Promise => { if (String(url).endsWith("/send")) { // Throw a non-Error value to exercise the toError(cause) // branch that wraps strings. const literal: unknown = "boom-string"; throw literal; } return jsonResponse({ envelopes: [], nextCursor: "0" }); }; const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn: fetchSubstitute, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); try { await t.send(TEST_PEER, makeEnvelope()); throw new Error("expected SEND_FAILED"); } catch (err) { const e = err as HttpsTransportError; expect(e.code).toBe(HTTPS_TRANSPORT_CODES.SEND_FAILED); expect(e.message).toContain("boom-string"); } await t.stop(); }); it("MALFORMED_RESPONSE when /send returns non-JSON 200", { timeout: 5000, }, async () => { const fetchFn = makeFakeFetch({ responder: (call) => call.url.endsWith("/send") ? new Response("not-json{{", { status: 200, headers: { "content-type": "application/json" }, }) : jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); await expect(t.send(TEST_PEER, makeEnvelope())).rejects.toMatchObject({ code: HTTPS_TRANSPORT_CODES.MALFORMED_RESPONSE, }); await t.stop(); }); it("MALFORMED_RESPONSE when /send body is unexpected JSON shape", { timeout: 5000, }, async () => { const fetchFn = makeFakeFetch({ responder: (call) => call.url.endsWith("/send") ? jsonResponse({ unexpected: true }) : jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); await expect(t.send(TEST_PEER, makeEnvelope())).rejects.toMatchObject({ code: HTTPS_TRANSPORT_CODES.MALFORMED_RESPONSE, }); await t.stop(); }); it("SEND_FAILED when relay returns { ok: false, code: 'POLICY' }", { timeout: 5000, }, async () => { const fetchFn = makeFakeFetch({ responder: (call) => call.url.endsWith("/send") ? jsonResponse({ ok: false, code: "POLICY" }) : jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); try { await t.send(TEST_PEER, makeEnvelope()); throw new Error("expected"); } catch (err) { const e = err as HttpsTransportError; expect(e.code).toBe(HTTPS_TRANSPORT_CODES.SEND_FAILED); expect(e.message).toContain("POLICY"); } await t.stop(); }); it("SEND_FAILED when relay returns { ok: false } without a code", { timeout: 5000, }, async () => { const fetchFn = makeFakeFetch({ responder: (call) => call.url.endsWith("/send") ? jsonResponse({ ok: false }) : jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await t.connect(TEST_PEER, "x"); try { await t.send(TEST_PEER, makeEnvelope()); throw new Error("expected"); } catch (err) { const e = err as HttpsTransportError; expect(e.code).toBe(HTTPS_TRANSPORT_CODES.SEND_FAILED); expect(e.message).toContain("unknown"); } await t.stop(); }); it("PEER_UNKNOWN when sending to a peer that has not connected", { timeout: 5000, }, async () => { const fetchFn = makeFakeFetch({ responder: () => jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, longPollTimeoutMs: 100, }); t.start(); await expect( t.send("not-connected", makeEnvelope()) ).rejects.toMatchObject({ code: HTTPS_TRANSPORT_CODES.PEER_UNKNOWN, }); await t.stop(); }); it("NOT_CONNECTED when send() is called before start()", { timeout: 5000, }, async () => { const fetchFn = makeFakeFetch({ responder: () => jsonResponse({ ok: true }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, }); await expect(t.send(TEST_PEER, makeEnvelope())).rejects.toMatchObject({ code: HTTPS_TRANSPORT_CODES.NOT_CONNECTED, }); }); it("CLOSED when send() is called after stop()", { timeout: 5000, }, async () => { const fetchFn = makeFakeFetch({ responder: () => jsonResponse({ envelopes: [], nextCursor: "0" }), }); const t = new HttpsTransport({ relayUrl: RELAY, ourPeerId: "peer-a", fetchFn, }); t.start(); await t.stop(); await expect(t.send(TEST_PEER, makeEnvelope())).rejects.toMatchObject({ code: HTTPS_TRANSPORT_CODES.CLOSED, }); }); });