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 / object / nip11.test.ts
nip11.test.ts 7.7K
1// NIP-11 extras: the policy links and lists the owner can set, the terms of2// service page, and what gets into the information document.3import { SELF } from "cloudflare:test";4import { describe, it, expect } from "vitest";5import { generateSecretKey } from "nostr-tools/pure";6import { rpc, info } from "../helpers/relay.ts";78describe("NIP-11 extras", () => {9 it("describes Git admission from current write rules without hiding guest or kind restrictions", async () => {10 const host = "git-policy-info.bind.ws";11 const owner = generateSecretKey();12 await rpc(host, owner, "claim");13 for (const [writes, eligibility] of [14 ["open", "Anyone may announce a repository."],15 ["allowlist", "Allowlist hosting: only the relay owner and members may announce repositories."],16 ["owner", "Curated hosting: only the relay owner may announce repositories."],17 ["wot", "Curated hosting: the relay owner, members and people they follow may announce repositories."],18 ]) {19 expect((await rpc(host, owner, "setpolicy", { features: { grasp: true }, writes })).status).toBe(200);20 const doc = await info(host);21 expect(doc.repo_acceptance_criteria.startsWith(eligibility)).toBe(true);22 expect(doc.repo_acceptance_criteria).toContain(`write rule (${writes}) and kind rules`);23 expect(doc.supported_grasps).toEqual(["GRASP-01"]);24 }25 await rpc(host, owner, "setpolicy", { writes: "allowlist", openKinds: [30617] });26 let doc = await info(host);27 expect(doc.repo_acceptance_criteria).toContain("Anyone may announce a repository through the guest exception for kind 30617.");28 expect(doc.repo_acceptance_criteria).toContain("Guest write exceptions apply to kinds 30617.");29 expect(doc.repo_acceptance_criteria).toContain("write rule (allowlist)");30 await rpc(host, owner, "disallowkind", 30617);31 doc = await info(host);32 expect(doc.repo_acceptance_criteria).toContain("only the relay owner's repository announcements pass the kind rule");33 expect(doc.repo_acceptance_criteria).not.toContain("Anyone");34 await rpc(host, owner, "unrulekind", 30617);35 await rpc(host, owner, "setpolicy", { writes: "open", openKinds: [] });36 expect((await info(host)).repo_acceptance_criteria).toMatch(/^Anyone may announce/);37 await rpc(host, owner, "setpolicy", { reads: "members" });38 expect((await info(host)).repo_acceptance_criteria).toBeUndefined();39 await rpc(host, owner, "setpolicy", { reads: "open", features: { grasp: false } });40 expect((await info(host)).repo_acceptance_criteria).toBeUndefined();41 });4243 it("publishes the policy links and lists once set, validated", async () => {44 const host = "eleven.bind.ws";45 const owner = generateSecretKey();46 await rpc(host, owner, "claim");47 let doc = await info(host);48 for (const k of ["banner", "terms_of_service", "posting_policy", "privacy_policy", "tags", "language_tags", "relay_countries"]) expect(doc[k], k).toBeUndefined();4950 const r = await rpc(host, owner, "setpolicy", {51 banner: "https://img.example/banner.png",52 postingPolicy: "https://example.com/posting",53 privacyPolicy: "http://example.com/privacy", // not https: dropped54 tags: ["Bitcoin", "cats", "cats", "Not Valid!", "x".repeat(40)],55 languageTags: ["en", "pt-BR", "english", "zh-Hant-TW"],56 relayCountries: ["us", "DE", "USA", "fr"],57 });58 expect(r.status).toBe(200);59 expect(r.result.privacyPolicy).toBe("");60 doc = await info(host);61 expect(doc.banner).toBe("https://img.example/banner.png");62 expect(doc.posting_policy).toBe("https://example.com/posting");63 expect(doc.privacy_policy).toBeUndefined();64 expect(doc.tags).toEqual(["bitcoin", "cats"]);65 expect(doc.language_tags).toEqual(["en", "pt-BR", "zh-Hant-TW"]);66 expect(doc.relay_countries).toEqual(["US", "DE", "FR"]);6768 // Clearing a link removes it; a list capped at twenty.69 await rpc(host, owner, "setpolicy", { postingPolicy: "", tags: Array.from({ length: 30 }, (_, i) => "t" + i) });70 doc = await info(host);71 expect(doc.posting_policy).toBeUndefined();72 expect(doc.tags.length).toBe(20);73 });7475 it("serves the join terms as the terms of service page", async () => {76 const host = "terms.bind.ws";77 const owner = generateSecretKey();78 await rpc(host, owner, "claim");79 expect((await SELF.fetch(`http://${host}/terms`)).status).toBe(404);80 expect((await info(host)).terms_of_service).toBeUndefined();81 await rpc(host, owner, "setpolicy", { joinTerms: "Be kind.\nNo spam <ever>." });82 expect((await info(host)).terms_of_service).toBe(`https://${host}/terms`);83 const page = await SELF.fetch(`http://${host}/terms`);84 expect(page.status).toBe(200);85 const html = await page.text();86 expect(html).toContain("Be kind.");87 expect(html).toContain("No spam &lt;ever&gt;.");88 });8990 it("carries the extras through export and import", async () => {91 const host = "carry.bind.ws";92 const owner = generateSecretKey();93 await rpc(host, owner, "claim");94 await rpc(host, owner, "setpolicy", { privacyPolicy: "https://example.com/privacy", tags: ["art"], languageTags: ["fr"], relayCountries: ["FR"] });95 const cfg = (await rpc(host, owner, "exportconfig")).result;96 expect(cfg.policy.tags).toEqual(["art"]);97 const other = "carried.bind.ws";98 await rpc(other, owner, "claim");99 expect((await rpc(other, owner, "importconfig", cfg)).status).toBe(200);100 const doc = await info(other);101 expect(doc.privacy_policy).toBe("https://example.com/privacy");102 expect(doc.tags).toEqual(["art"]);103 expect(doc.language_tags).toEqual(["fr"]);104 expect(doc.relay_countries).toEqual(["FR"]);105 });106});107108describe("lettered capability identifiers", () => {109 it("preserves numeric support by default and advertises enabled lettered capabilities together", async () => {110 const host = "lettered-nips.bind.ws";111 const owner = generateSecretKey();112 await rpc(host, owner, "claim");113 const baseline = (await info(host)).supported_nips;114 expect(baseline.every((n: unknown) => typeof n === "number")).toBe(true);115 await rpc(host, owner, "setpolicy", { letteredNips: true });116 let nips = (await info(host)).supported_nips;117 expect(nips.filter((n: unknown) => typeof n === "number")).toEqual(baseline);118 expect(nips).toEqual(expect.arrayContaining(["AD", "5A"]));119 expect(nips).not.toContain("9a");120 expect(nips).not.toContain(90);121 await rpc(host, owner, "setpolicy", { letteredNips: false, features: { push: true, sites: false } });122 nips = (await info(host)).supported_nips;123 expect(nips).toEqual(expect.arrayContaining([1, 11, 86, "AD", "9a"]));124 expect(nips).not.toContain("5A");125 await rpc(host, owner, "setpolicy", { features: { push: false } });126 expect((await info(host)).supported_nips).toEqual(baseline);127 });128129 it("exports the representation and callback policy and validates entire origin lists", async () => {130 const host = "push-policy-config.bind.ws";131 const owner = generateSecretKey();132 await rpc(host, owner, "claim");133 await rpc(host, owner, "setpolicy", { letteredNips: true, pushCallbacks: ["https://push.example.com/"] });134 const config = (await rpc(host, owner, "exportconfig")).result;135 expect(config.policy).toMatchObject({ letteredNips: true, pushCallbacks: ["https://push.example.com"] });136 const invalid = await rpc(host, owner, "setpolicy", { pushCallbacks: ["https://127.0.0.1", "https://push.example.com"] });137 expect(invalid.result.pushCallbacks).toEqual(["https://push.example.com"]);138 const other = "push-policy-import.bind.ws";139 await rpc(other, owner, "claim");140 await rpc(other, owner, "importconfig", config);141 expect((await rpc(other, owner, "getpolicy")).result).toMatchObject({ letteredNips: true, pushCallbacks: ["https://push.example.com"] });142 });143});