clone

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

dependabot/npm_and_yarn/npm-f01a459b0f
bindws / test / object / grasp-coordination.test.ts
grasp-coordination.test.ts 5.9K
1// Real HTTP and alarm reads pause at SQLite to expose the live instance's2// admission fences. These tests establish no crash-safe collection lease.3import { env, runInDurableObject } from "cloudflare:test";4import { describe, expect, it } from "vitest";5import { generateSecretKey } from "nostr-tools/pure";6import { npubEncode } from "nostr-tools/nip19";7import { KIND_REPO, KIND_REPO_STATE } from "../../src/kinds.ts";8import { gitRepositoryPath } from "../../src/grasp-policy.ts";9import { ev, nip98, pk, rpc } from "../helpers/relay.ts";10import { WS } from "../helpers/ws.ts";11import { GitSqliteRepository } from "../../src/git-sqlite.ts";1213describe("GRASP coordination", () => {14 it("keeps admission free while a management body is incomplete", async () => {15 const host = "grasp-management-body.bind.ws";16 const owner = generateSecretKey();17 await rpc(host, owner, "claim");18 await rpc(host, owner, "applypreset", "grasp");19 const path = gitRepositoryPath(npubEncode(pk(owner)), "body");20 const connection = await WS.connect(host);21 expect((await connection.ok(ev(owner, KIND_REPO, "", [["d", "body"], ["clone", `https://${host}${path}`], ["relays", `wss://${host}`]]))).ok).toBe(true);22 connection.ws.close();23 await runInDurableObject(env.RELAY.getByName("grasp-management-body"), async (relay) => {24 const url = `http://${host}/`;25 const payload = { method: "setpolicy", params: [{ name: "body completed" }] };26 const authorization = await nip98(owner, url, "POST", payload);27 let controller!: ReadableStreamDefaultController<Uint8Array>;28 const body = new ReadableStream<Uint8Array>({ start(value) { controller = value; } });29 const active = relay.fetch(new Request(url, { method: "POST", headers: { authorization, "content-type": "application/nostr+json+rpc" }, body }));30 let closed = false;31 try {32 await Promise.resolve();33 expect(relay.repositoryAccess.busy).toBe(false);34 const git = await relay.fetch(new Request(`http://${host}${path}`));35 expect(git.status).toBe(200);36 await git.arrayBuffer();37 const admitted = await relay.acceptAny(ev(owner, 1, "while the body waits"), relay.virtualConn(host, pk(owner)));38 expect(admitted.ok).toBe(true);39 expect(relay.settings.policy.name).not.toBe("body completed");40 controller.enqueue(new TextEncoder().encode(JSON.stringify(payload)));41 controller.close();42 closed = true;43 const response = await active;44 expect(response.status).toBe(200);45 await response.arrayBuffer();46 expect(relay.settings.policy.name).toBe("body completed");47 expect(relay.repositoryAccess.busy).toBe(false);48 } finally {49 if (!closed) { controller.enqueue(new TextEncoder().encode(JSON.stringify(payload))); controller.close(); }50 await active;51 }52 });53 });5455 it.each([56 { mode: "http", outcome: "success" }, { mode: "http", outcome: "failure" },57 { mode: "alarm", outcome: "success" }, { mode: "alarm", outcome: "failure" },58 ])("a paused $mode read holds admission until $outcome", async ({ mode, outcome }) => {59 const host = `grasp-coordination-${mode}-${outcome}.bind.ws`;60 const owner = generateSecretKey();61 await rpc(host, owner, "claim");62 await rpc(host, owner, "applypreset", "grasp");63 const path = gitRepositoryPath(npubEncode(pk(owner)), "coordination");64 const announcement = ev(owner, KIND_REPO, "", [65 ["d", "coordination"], ["clone", `https://${host}${path}`],66 ["relays", `wss://${host}`], ["maintainers", pk(owner)],67 ]);68 const connection = await WS.connect(host);69 expect((await connection.ok(announcement)).ok).toBe(true);70 connection.ws.close();71 const state = ev(owner, KIND_REPO_STATE, "", [["d", "coordination"], ["refs/heads/main", "a".repeat(40)]]);72 await runInDurableObject(env.RELAY.getByName(host.split(".")[0]), async (relay) => {73 let entered!: () => void;74 let release!: () => void;75 const atRead = new Promise<void>((resolve) => { entered = resolve; });76 const gate = new Promise<void>((resolve) => { release = resolve; });77 let held = false;78 const originalInfo = GitSqliteRepository.prototype.loadRefs;79 GitSqliteRepository.prototype.loadRefs = async function () {80 if (!held) {81 held = true;82 entered();83 await gate;84 if (outcome === "failure") throw new Error("injected SQLite read failure");85 }86 return originalInfo.call(this);87 };88 const request = () => new Request(`http://${host}${path}/info/refs?service=git-upload-pack`);89 const active = mode === "http" ? relay.fetch(request()) : relay.alarm();90 try {91 await atRead;92 expect(relay.repositoryAccess.busy).toBe(true);93 expect(relay.repositoryAccess.kind).toBe(mode === "http" ? "git" : "alarm");94 const refused = await relay.fetch(request());95 expect(refused.status).toBe(429);96 await refused.arrayBuffer();97 const admission = await relay.acceptAny(state, relay.virtualConn(host, pk(owner)));98 expect(admission.ok).toBe(false);99 expect(admission.msg).toContain("relay operation in progress");100 const management = await relay.fetch(new Request(`http://${host}/`, { method: "POST", body: "{}" }));101 expect(management.status).toBe(429);102 await management.arrayBuffer();103 if (mode === "http") await relay.alarm();104 expect(relay.repositoryAccess.busy).toBe(true);105 expect(relay.repositoryAccess.kind).toBe(mode === "http" ? "git" : "alarm");106 release();107 const response = await active;108 expect(relay.repositoryAccess.busy).toBe(false);109 if (response) {110 expect(response.status).toBe(outcome === "failure" ? 503 : 200);111 await response.arrayBuffer();112 }113 } finally {114 release();115 await active;116 GitSqliteRepository.prototype.loadRefs = originalInfo;117 }118 });119 });120});