clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

dependabot/github_actions/actions-9b61906d8b
bindws / test / unit / network-client.test.ts
network-client.test.ts 3.9K
1import { createServer } from "node:http";2import { describe, expect, it } from "vitest";3import { WebSocketServer } from "ws";4import { finalizeEvent, generateSecretKey } from "nostr-tools/pure";5// @ts-expect-error the production-only helper is JavaScript by design.6import { request, sample } from "../../scripts/ops/network-client.mjs";78const key = generateSecretKey();910describe("network test client", () => {11 it("does not retry an ambiguous mutation response", async () => {12 let calls = 0;13 const server = createServer((_req, res) => {14 calls++;15 res.writeHead(503, { "content-type": "application/json" });16 res.end(JSON.stringify({ error: "error: upstream unavailable" }));17 });18 await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve()));19 const address = server.address();20 const url = `http://127.0.0.1:${typeof address === "object" && address ? address.port : 0}/`;21 await expect(request({ url }, key, { mutation: true })).rejects.toMatchObject({ status: 503, message: "error: upstream unavailable" });22 expect(calls).toBe(1);23 await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));24 });2526 it("retries an explicit operation-in-progress refusal and returns the response", async () => {27 let calls = 0;28 const server = createServer((_req, res) => {29 calls++;30 res.writeHead(calls === 1 ? 429 : 200, { "content-type": "application/json" });31 res.end(JSON.stringify(calls === 1 ? { error: "restricted: relay operation in progress; retry" } : { result: { ok: true } }));32 });33 await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve()));34 const address = server.address();35 const url = `http://127.0.0.1:${typeof address === "object" && address ? address.port : 0}/`;36 await expect(request({ url }, key, { method: "stats", params: [] }, true)).resolves.toEqual({ result: { ok: true } });37 expect(calls).toBe(2);38 await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));39 });4041 it("samples signed events after an unsolicited auth challenge without publishing", async () => {42 const server = new WebSocketServer({ port: 0 });43 let clientMessages: string[] = [];44 const event = finalizeEvent({ kind: 0, content: JSON.stringify({ name: "sample" }), tags: [], created_at: Math.floor(Date.now() / 1000) }, key);45 server.on("connection", (socket) => {46 socket.on("message", (data) => {47 clientMessages.push(data.toString());48 socket.send(JSON.stringify(["AUTH", "challenge"]));49 socket.send(JSON.stringify(["EVENT", "network-sample", { id: "bad" }]));50 socket.send(JSON.stringify(["EVENT", "network-sample", event]));51 socket.send(JSON.stringify(["EOSE", "network-sample"]));52 });53 });54 await new Promise<void>((resolve) => server.once("listening", () => resolve()));55 const address = server.address();56 const result = await sample(`ws://127.0.0.1:${typeof address === "object" && address ? address.port : 0}`, { kinds: [0] });57 expect(result.status).toBe("complete");58 expect(result.events.map((x: { id: string }) => x.id)).toEqual([event.id]);59 expect(clientMessages).toHaveLength(1);60 expect(clientMessages[0]).not.toContain("AUTH");61 expect(clientMessages[0]).not.toContain("EVENT");62 server.close();63 });6465 it("reports a source that closes before completing its sample", async () => {66 const server = new WebSocketServer({ port: 0 });67 server.on("connection", (socket) => socket.close());68 await new Promise<void>((resolve) => server.once("listening", () => resolve()));69 const address = server.address();70 const result = await sample(`ws://127.0.0.1:${typeof address === "object" && address ? address.port : 0}`);71 expect(result.status).toBe("failed");72 expect(result.error).toContain("closed");73 server.close();74 });75});