Initial Commit
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Lifecycle and option-validation tests for `HttpsTransport`.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
HTTPS_TRANSPORT_CODES,
|
||||
HttpsTransport,
|
||||
type HttpsTransportError,
|
||||
} from "../src/index.js";
|
||||
import { jsonResponse, makeFakeFetch } from "./_helpers.js";
|
||||
|
||||
const RELAY = "https://relay.test";
|
||||
|
||||
describe("HttpsTransport — lifecycle", () => {
|
||||
it("constructor rejects an invalid relayUrl", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
() =>
|
||||
new HttpsTransport({
|
||||
relayUrl: "not-a-url",
|
||||
ourPeerId: "peer-a",
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("constructor rejects empty ourPeerId", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
() =>
|
||||
new HttpsTransport({
|
||||
relayUrl: RELAY,
|
||||
ourPeerId: "",
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("constructor rejects backoffBaseMs > backoffMaxMs", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(
|
||||
() =>
|
||||
new HttpsTransport({
|
||||
relayUrl: RELAY,
|
||||
ourPeerId: "peer-a",
|
||||
backoffBaseMs: 1000,
|
||||
backoffMaxMs: 100,
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("start is idempotent while running", { timeout: 5000 }, async () => {
|
||||
const fetchFn = makeFakeFetch({
|
||||
responder: () => jsonResponse({ envelopes: [], nextCursor: "0" }),
|
||||
});
|
||||
const t = new HttpsTransport({
|
||||
relayUrl: RELAY,
|
||||
ourPeerId: "peer-a",
|
||||
fetchFn,
|
||||
longPollTimeoutMs: 50,
|
||||
pollIdleDelayMs: 5,
|
||||
});
|
||||
t.start();
|
||||
t.start();
|
||||
expect(t.state).toBe("running");
|
||||
await t.stop();
|
||||
});
|
||||
|
||||
it("start after stop throws CLOSED", { 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();
|
||||
try {
|
||||
t.start();
|
||||
throw new Error("expected CLOSED");
|
||||
} catch (err) {
|
||||
expect((err as HttpsTransportError).code).toBe(
|
||||
HTTPS_TRANSPORT_CODES.CLOSED
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("stop is idempotent", { 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 t.stop();
|
||||
expect(t.state).toBe("stopped");
|
||||
});
|
||||
|
||||
it("stop before start does not throw", { timeout: 5000 }, async () => {
|
||||
const fetchFn = makeFakeFetch({
|
||||
responder: () => jsonResponse({ envelopes: [], nextCursor: "0" }),
|
||||
});
|
||||
const t = new HttpsTransport({
|
||||
relayUrl: RELAY,
|
||||
ourPeerId: "peer-a",
|
||||
fetchFn,
|
||||
});
|
||||
await t.stop();
|
||||
expect(t.state).toBe("stopped");
|
||||
});
|
||||
|
||||
it("connect/disconnect track peers and getConnectedPeers reports them", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const fetchFn = makeFakeFetch({
|
||||
responder: () => jsonResponse({ envelopes: [], nextCursor: "0" }),
|
||||
});
|
||||
const t = new HttpsTransport({
|
||||
relayUrl: RELAY,
|
||||
ourPeerId: "peer-a",
|
||||
fetchFn,
|
||||
longPollTimeoutMs: 50,
|
||||
pollIdleDelayMs: 5,
|
||||
});
|
||||
t.start();
|
||||
await t.connect("peer-b", "ignored");
|
||||
await t.connect("peer-c", "ignored");
|
||||
expect(t.getConnectedPeers()).toEqual(["peer-b", "peer-c"]);
|
||||
await t.disconnect("peer-b");
|
||||
expect(t.getConnectedPeers()).toEqual(["peer-c"]);
|
||||
// disconnect on an unknown peer is a no-op
|
||||
await t.disconnect("peer-x");
|
||||
expect(t.getConnectedPeers()).toEqual(["peer-c"]);
|
||||
await t.stop();
|
||||
});
|
||||
|
||||
it("connect before start throws NOT_CONNECTED", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const fetchFn = makeFakeFetch({
|
||||
responder: () => jsonResponse({ ok: true }),
|
||||
});
|
||||
const t = new HttpsTransport({
|
||||
relayUrl: RELAY,
|
||||
ourPeerId: "peer-a",
|
||||
fetchFn,
|
||||
});
|
||||
await expect(t.connect("peer-b", "x")).rejects.toMatchObject({
|
||||
code: HTTPS_TRANSPORT_CODES.NOT_CONNECTED,
|
||||
});
|
||||
});
|
||||
|
||||
it("cursor starts empty and updates after a successful /recv", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
let recvCalls = 0;
|
||||
const fetchFn = makeFakeFetch({
|
||||
responder: (call) => {
|
||||
if (call.url.includes("/recv")) {
|
||||
recvCalls++;
|
||||
return jsonResponse({
|
||||
envelopes: [],
|
||||
nextCursor: "c-1",
|
||||
});
|
||||
}
|
||||
return jsonResponse({ ok: true });
|
||||
},
|
||||
});
|
||||
const t = new HttpsTransport({
|
||||
relayUrl: RELAY,
|
||||
ourPeerId: "peer-a",
|
||||
fetchFn,
|
||||
longPollTimeoutMs: 25,
|
||||
pollIdleDelayMs: 5,
|
||||
});
|
||||
expect(t.cursor).toBe("");
|
||||
expect(t.state).toBe("idle");
|
||||
t.start();
|
||||
// Wait for at least one successful recv cycle to complete.
|
||||
const start = Date.now();
|
||||
while (t.cursor === "" && Date.now() - start < 2000) {
|
||||
await new Promise<void>((r) => setTimeout(r, 5));
|
||||
}
|
||||
expect(t.cursor).toBe("c-1");
|
||||
expect(recvCalls).toBeGreaterThan(0);
|
||||
await t.stop();
|
||||
});
|
||||
|
||||
it("clears handlers and peers on stop", { timeout: 5000 }, async () => {
|
||||
const fetchFn = makeFakeFetch({
|
||||
responder: () => jsonResponse({ envelopes: [], nextCursor: "0" }),
|
||||
});
|
||||
const t = new HttpsTransport({
|
||||
relayUrl: RELAY,
|
||||
ourPeerId: "peer-a",
|
||||
fetchFn,
|
||||
longPollTimeoutMs: 25,
|
||||
});
|
||||
t.start();
|
||||
await t.connect("peer-b", "x");
|
||||
const off = t.onMessage(() => undefined);
|
||||
await t.stop();
|
||||
expect(t.getConnectedPeers()).toEqual([]);
|
||||
// Calling unsubscribe after stop is a no-op (set already cleared).
|
||||
off();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user