tinyrelayfile
tinyrelay/scripts/conformance/nip01.test.ts/

scripts/conformance/nip01.test.ts raw
1import { describe, it, expect } from "vitest";
2import { newEvent, newKey, pub, now, rand, sockets } from "./helpers.ts";
3
4const connect = sockets();
5
6describe("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;
13
14 // Bob subscribes before anything exists: immediate EOSE.
15 bob.send("REQ", "notes", { kinds: [1], "#t": [tag] });
16 await bob.expect("EOSE");
17
18 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);
24
25 // Duplicate acknowledged, not re-broadcast.
26 alice.send("EVENT", note);
27 expect(await alice.expectOK(note.id, true)).toMatch(/^duplicate:/);
28 await bob.expectNothing();
29
30 // 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:/);
33
34 // 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]);
38
39 // 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]);
47
48 // 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());
55
56 // 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");
65
66 // 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 });
72
73 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 });
85
86 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});

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs