175 lines
6.0 KiB
TypeScript
175 lines
6.0 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import { GunDBServer } from "../src/server/server.js";
|
|
import { FakeGun, makeFakeGunFactory } from "./setup/fake-gun.js";
|
|
|
|
const VALID_ANNOUNCE = {
|
|
kind: "peer-announce" as const,
|
|
peerId: "client-1",
|
|
addresses: ["ws://1.2.3.4:80"],
|
|
metadata: { tag: "alpha" },
|
|
capabilities: ["chat"],
|
|
lastSeen: 1_700_000_000_000,
|
|
signaturePublicKey: "c".repeat(64),
|
|
signature: "d".repeat(128),
|
|
};
|
|
|
|
async function fetchJson(
|
|
url: string
|
|
): Promise<{ readonly status: number; readonly body: unknown }> {
|
|
const res = await fetch(url);
|
|
const body = await res.json();
|
|
return { status: res.status, body };
|
|
}
|
|
|
|
describe("integration: real HTTP boot + health probes", () => {
|
|
it("health/ready/live respond with 200 while running", {
|
|
timeout: 10_000,
|
|
}, async () => {
|
|
const { factory } = makeFakeGunFactory();
|
|
const server = new GunDBServer({
|
|
port: 0,
|
|
host: "127.0.0.1",
|
|
gunFactory: factory,
|
|
});
|
|
const { address, port } = await server.start();
|
|
const base = `http://${address === "::" || address === "0.0.0.0" ? "127.0.0.1" : address}:${port}`;
|
|
try {
|
|
const health = await fetchJson(`${base}/health`);
|
|
expect(health.status).toBe(200);
|
|
expect((health.body as { status: string }).status).toBe("ok");
|
|
|
|
const ready = await fetchJson(`${base}/ready`);
|
|
expect(ready.status).toBe(200);
|
|
|
|
const live = await fetchJson(`${base}/live`);
|
|
expect(live.status).toBe(200);
|
|
|
|
// Non-health route falls through to the relay 200/OK marker.
|
|
const fall = await fetch(`${base}/gun`);
|
|
expect(fall.status).toBe(200);
|
|
await fall.text();
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
});
|
|
|
|
it("two browser-tier clients relay a signed announce via shared graph", {
|
|
timeout: 10_000,
|
|
}, async () => {
|
|
// The injected FakeGun shares storage across calls; both
|
|
// "clients" inject the same announce-room namespace and the
|
|
// server observes the inbound frame deterministically. This
|
|
// demonstrates the wire path without pulling the real `gun`
|
|
// runtime into the test env.
|
|
const sharedStore = new Map<string, unknown>();
|
|
const sharedListeners = new Map<
|
|
string,
|
|
Array<(data: unknown, key: string) => void>
|
|
>();
|
|
const serverFactory = (cfg: {
|
|
readonly peers: readonly string[];
|
|
}): FakeGun =>
|
|
new FakeGun(
|
|
{ web: undefined, peers: cfg.peers },
|
|
sharedStore,
|
|
sharedListeners
|
|
);
|
|
const server = new GunDBServer({
|
|
port: 0,
|
|
host: "127.0.0.1",
|
|
gunFactory: serverFactory,
|
|
});
|
|
await server.start();
|
|
try {
|
|
// Two distinct "clients" inject into the same shared graph
|
|
// — the server observes each one as a separate peer.
|
|
const clientA = new FakeGun(
|
|
{ web: undefined, peers: [] },
|
|
sharedStore,
|
|
sharedListeners
|
|
);
|
|
const clientB = new FakeGun(
|
|
{ web: undefined, peers: [] },
|
|
sharedStore,
|
|
sharedListeners
|
|
);
|
|
clientA.inject("suaveplan-discovery", "client-1", {
|
|
...VALID_ANNOUNCE,
|
|
peerId: "client-1",
|
|
});
|
|
clientB.inject("suaveplan-discovery", "client-2", {
|
|
...VALID_ANNOUNCE,
|
|
peerId: "client-2",
|
|
});
|
|
expect(server.activeConnections).toBe(2);
|
|
const peerIds = server
|
|
.peers()
|
|
.map((p) => p.id)
|
|
.sort();
|
|
expect(peerIds).toEqual(["client-1", "client-2"]);
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
});
|
|
|
|
it("malformed payload from a misbehaving client is dropped (counter only)", {
|
|
timeout: 10_000,
|
|
}, async () => {
|
|
const sharedStore = new Map<string, unknown>();
|
|
const sharedListeners = new Map<
|
|
string,
|
|
Array<(data: unknown, key: string) => void>
|
|
>();
|
|
const serverFactory = (cfg: {
|
|
readonly peers: readonly string[];
|
|
}): FakeGun =>
|
|
new FakeGun(
|
|
{ web: undefined, peers: cfg.peers },
|
|
sharedStore,
|
|
sharedListeners
|
|
);
|
|
const server = new GunDBServer({
|
|
port: 0,
|
|
host: "127.0.0.1",
|
|
gunFactory: serverFactory,
|
|
});
|
|
await server.start();
|
|
try {
|
|
const badClient = new FakeGun(
|
|
{ web: undefined, peers: [] },
|
|
sharedStore,
|
|
sharedListeners
|
|
);
|
|
badClient.inject("suaveplan-discovery", "evil-1", {
|
|
kind: "wrong",
|
|
junk: true,
|
|
});
|
|
badClient.inject("suaveplan-discovery", "evil-2", null);
|
|
badClient.inject("suaveplan-discovery", "evil-3", {
|
|
...VALID_ANNOUNCE,
|
|
signature: undefined,
|
|
});
|
|
expect(server.activeConnections).toBe(0);
|
|
expect(server.peers()).toHaveLength(0);
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
});
|
|
|
|
it("graceful stop closes the HTTP listener", {
|
|
timeout: 10_000,
|
|
}, async () => {
|
|
const { factory } = makeFakeGunFactory();
|
|
const server = new GunDBServer({
|
|
port: 0,
|
|
host: "127.0.0.1",
|
|
gunFactory: factory,
|
|
});
|
|
const { address, port } = await server.start();
|
|
const base = `http://${address === "::" || address === "0.0.0.0" ? "127.0.0.1" : address}:${port}`;
|
|
await server.stop();
|
|
// After stop, the socket is closed — connection refused.
|
|
await expect(fetch(`${base}/health`)).rejects.toBeDefined();
|
|
});
|
|
});
|