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 / conformance / nip01.test.ts
nip01.test.ts 3.9K
1import { describe, it, expect } from "vitest";2import { newEvent, newKey, pub, now, rand, sockets } from "./helpers.ts";34const connect = sockets();56describe("NIP-01 core", () => {7 it("publishes, pushes live, dedups, rejects tampering, queries, replaces, limits, closes", async () => {8 const sk = newKey();9 const alice = await connect();10 const bob = await connect();11 const tag = rand();12 const t0 = now() - 1000;1314 // Bob subscribes before anything exists: immediate EOSE.15 bob.send("REQ", "notes", { kinds: [1], "#t": [tag] });16 await bob.expect("EOSE");1718 const note = newEvent(sk, 1, "hello <world> & friends", [["t", tag]], t0);19 await alice.publish(note);20 const live = await bob.expect("EVENT");21 expect(live[1]).toBe("notes");22 expect(live[2].id).toBe(note.id);23 expect(live[2].content).toBe(note.content);2425 // Duplicate acknowledged, not re-broadcast.26 alice.send("EVENT", note);27 expect(await alice.expectOK(note.id, true)).toMatch(/^duplicate:/);28 await bob.expectNothing();2930 // Tampered content is refused with an invalid: prefix.31 alice.send("EVENT", { ...note, content: "tampered" });32 expect(await alice.expectOK(note.id, false)).toMatch(/^invalid:/);3334 // Stored query by tag and author.35 const carol = await connect();36 let r = await carol.req({ "#t": [tag], authors: [pub(sk)] });37 expect(r.events.map((e) => e.id)).toEqual([note.id]);3839 // Replaceable: newest wins, stale refused.40 const meta1 = newEvent(sk, 0, '{"name":"v1"}', [], t0 + 1);41 const meta2 = newEvent(sk, 0, '{"name":"v2"}', [], t0 + 2);42 await alice.publish(meta2);43 alice.send("EVENT", meta1);44 expect(await alice.expectOK(meta1.id, false)).toMatch(/newer/);45 r = await carol.req({ kinds: [0], authors: [pub(sk)] });46 expect(r.events.map((e) => e.id)).toEqual([meta2.id]);4748 // Addressable: same d replaces, different d coexists.49 const a1 = newEvent(sk, 30023, "draft", [["d", "post"]], t0 + 3);50 const a2 = newEvent(sk, 30023, "final", [["d", "post"]], t0 + 4);51 const a3 = newEvent(sk, 30023, "other", [["d", "other"]], t0 + 4);52 for (const e of [a1, a2, a3]) await alice.publish(e);53 r = await carol.req({ kinds: [30023], authors: [pub(sk)] });54 expect(r.events.map((e) => e.id).sort()).toEqual([a2.id, a3.id].sort());5556 // Limit, newest-first ordering, and NIP-67 hints.57 for (let i = 0; i < 5; i++) await alice.publish(newEvent(sk, 1, "n" + i, [["t", tag]], t0 + 10 + i));58 r = await carol.req({ kinds: [1], "#t": [tag], limit: 2 });59 expect(r.events.length).toBe(2);60 expect(r.events[0].created_at).toBe(t0 + 14);61 expect(r.hints).toContain("more");62 r = await carol.req({ kinds: [1], "#t": [tag] });63 expect(r.events.length).toBe(6);64 expect(r.hints).toContain("finish");6566 // Bob's live sub saw the five notes; CLOSE stops delivery.67 for (let i = 0; i < 5; i++) await bob.expect("EVENT");68 bob.send("CLOSE", "notes");69 await alice.publish(newEvent(sk, 1, "after close", [["t", tag]], t0 + 20));70 await bob.expectNothing();71 });7273 it("does not store ephemeral events but does broadcast them", async () => {74 const a = await connect();75 const b = await connect();76 const sk = newKey();77 b.send("REQ", "eph", { kinds: [20001], authors: [pub(sk)] });78 await b.expect("EOSE");79 await a.publish(newEvent(sk, 20001, "poof"));80 await b.expect("EVENT");81 const c = await connect();82 const r = await c.req({ kinds: [20001], authors: [pub(sk)] });83 expect(r.events).toEqual([]);84 });8586 it("answers bad input with NOTICE or CLOSED and keeps the connection", async () => {87 const c = await connect();88 c.send("BOGUS", 1);89 expect((await c.expect("NOTICE"))[1]).toMatch(/^error:/);90 c.send("REQ", "x", { kinds: "not-a-list" });91 await c.expectClosed("invalid:");92 c.send("REQ", "still-alive", { kinds: [1], limit: 0 });93 await c.expect("EOSE");94 });95});