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

scripts/conformance/grasp.test.ts raw
1// GRASP-01 conformance uses the stock Git client against the public Smart HTTP door.
2import { execFile } from "node:child_process";
3import { mkdtemp, writeFile, readFile, rm } from "node:fs/promises";
4import { tmpdir } from "node:os";
5import { promisify } from "node:util";
6import { describe, expect, it } from "vitest";
7import { finalizeEvent, getPublicKey, generateSecretKey } from "nostr-tools/pure";
8import { Client, HTTP_URL, RELAY_URL, sleep, now } from "./helpers.ts";
9import { npubEncode } from "nostr-tools/nip19";
10
11const exec = promisify(execFile);
12const KIND_REPO = 30617;
13const KIND_REPO_STATE = 30618;
14
15async function git(cwd: string, ...args: string[]): Promise<string> {
16 const result = await exec("git", args, { cwd, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
17 return result.stdout.trim();
18}
19
20function repositoryPath(npub: string, identifier: string): string {
21 return `/${npub}/${encodeURIComponent(identifier)}.git`;
22}
23
24async function waitForRefs(url: string): Promise<void> {
25 for (let attempt = 0; attempt < 60; attempt++) {
26 const response = await fetch(`${url}/info/refs?service=git-upload-pack`);
27 const text = await response.text();
28 if (response.status === 200 && text.includes("# service=git-upload-pack")) return;
29 await sleep(250);
30 }
31 throw new Error("GRASP repository did not become available");
32}
33
34async function publish(c: Client, sk: Uint8Array, kind: number, tags: string[][], createdAt: number): Promise<void> {
35 const event = finalizeEvent({ kind, content: "", tags, created_at: createdAt }, sk);
36 await c.publish(event);
37}
38
39describe("GRASP-01 stock Git", () => {
40 it("clones, pushes an accepted state, fetches incrementally, and supports partial clones", async ({ skip }) => {
41 const infoResponse = await fetch(HTTP_URL, { headers: { accept: "application/nostr+json" } });
42 const info: any = await infoResponse.json();
43 if (!info.supported_grasps?.includes("GRASP-01")) skip();
44
45 const root = await mkdtemp(`${tmpdir()}/tiny-grasp-`);
46 const clone = await mkdtemp(`${tmpdir()}/tiny-grasp-clone-`);
47 const partial = await mkdtemp(`${tmpdir()}/tiny-grasp-partial-`);
48 const sk = generateSecretKey();
49 const npub = npubEncode(getPublicKey(sk));
50 const identifier = `stock-${Math.random().toString(36).slice(2, 10)}`;
51 const path = repositoryPath(npub, identifier);
52 const repoURL = `${HTTP_URL}${path}`;
53 const relayURL = RELAY_URL;
54 const createdAt = now();
55 const c = await Client.connect(RELAY_URL);
56 try {
57 await git(root, "init", "--initial-branch=main");
58 await git(root, "config", "user.email", "grasp-conformance@example.com");
59 await git(root, "config", "user.name", "GRASP conformance");
60 await writeFile(`${root}/README.md`, "initial GRASP repository\n");
61 await git(root, "add", "README.md");
62 await git(root, "commit", "-m", "initial");
63 const first = await git(root, "rev-parse", "HEAD");
64 await git(root, "tag", "-a", "v1", "-m", "first release");
65 const tag = await git(root, "rev-parse", "refs/tags/v1^{tag}");
66
67 await publish(c, sk, KIND_REPO, [
68 ["d", identifier],
69 ["clone", repoURL],
70 ["relays", relayURL],
71 ["maintainers", getPublicKey(sk)],
72 ], createdAt);
73 await publish(c, sk, KIND_REPO_STATE, [
74 ["d", identifier],
75 ["HEAD", "ref: refs/heads/main"],
76 ["refs/heads/main", first],
77 ["refs/tags/v1", tag],
78 ], createdAt + 1);
79
80 await waitForRefs(repoURL);
81 await git(root, "push", repoURL, "HEAD:refs/heads/main", "refs/tags/v1:refs/tags/v1");
82 await git(clone, "clone", repoURL, ".");
83 expect(await git(clone, "rev-parse", "HEAD")).toBe(first);
84 expect(await git(clone, "describe", "--tags")).toBe("v1");
85 expect((await git(clone, "fsck", "--full"))).not.toContain("dangling");
86
87 await writeFile(`${root}/README.md`, "incremental GRASP repository\n");
88 await git(root, "add", "README.md");
89 await git(root, "commit", "-m", "incremental");
90 const second = await git(root, "rev-parse", "HEAD");
91 await publish(c, sk, KIND_REPO_STATE, [
92 ["d", identifier],
93 ["HEAD", "ref: refs/heads/main"],
94 ["refs/heads/main", second],
95 ["refs/tags/v1", tag],
96 ], createdAt + 2);
97 await git(root, "push", repoURL, "HEAD:refs/heads/main");
98 await git(clone, "fetch", repoURL, "refs/heads/main:refs/remotes/origin/main");
99 expect(await git(clone, "rev-parse", "refs/remotes/origin/main")).toBe(second);
100
101 await git(partial, "clone", "--filter=blob:none", "--no-checkout", repoURL, ".");
102 await git(partial, "fsck", "--full");
103 await git(partial, "checkout", "main");
104 expect(await readFile(`${partial}/README.md`, "utf8")).toBe("incremental GRASP repository\n");
105 } finally {
106 await publish(c, sk, 5, [["a", `${KIND_REPO}:${getPublicKey(sk)}:${identifier}`], ["a", `${KIND_REPO_STATE}:${getPublicKey(sk)}:${identifier}`]], createdAt + 3).catch(() => {});
107 c.close();
108 await Promise.all([rm(root, { recursive: true, force: true }), rm(clone, { recursive: true, force: true }), rm(partial, { recursive: true, force: true })]);
109 }
110 }, 120_000);
111});

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs