char-slop/stars.hackery.site

git clone https://git.t4t.associates/char-slop/stars.hackery.site

Charlotte Sommake the sync loop read as its intent2550ded

main
5.5 KiB160 linesraw
1import { assert, assertEquals } from "@std/assert";
2import { DatabaseSync } from "node:sqlite";
3import { Auth } from "./auth.ts";
4import { Hydrant } from "./hydrant.ts";
5import { handler } from "./main.ts";
6import { Follow, Star, starUrl } from "./records.ts";
7import { Index } from "./sync.ts";
8import { TitleLookup } from "./title.ts";
9
10const did = "did:plc:abcdefghij";
11const other = "did:plc:zyxwvutsrq";
12
13Deno.test("record schemas and link rules", () => {
14  const star = Star.build({
15    url: "https://example.org/post",
16    comment: "hello",
17    createdAt: new Date().toISOString(),
18  });
19  assert(Star.validate(star).success);
20  assert(!Star.validate({ ...star, comment: "🧡".repeat(32769) }).success);
21  assertEquals(starUrl("javascript:alert(1)"), undefined);
22  assertEquals(starUrl("https://username@example.org"), undefined);
23  assert(
24    Follow.validate(
25      Follow.build({ subject: other, createdAt: new Date().toISOString() }),
26    ).success,
27  );
28});
29
30Deno.test("index feeds follow subjects, handles duplicate follows and deletions", () => {
31  const db = new DatabaseSync(":memory:");
32  try {
33    new Auth(db, new URL("http://127.0.0.1:8000"));
34    const index = new Index(db, new Hydrant(new URL("http://127.0.0.1:3000")));
35    const s = (author: string, tid: string) =>
36      `at://${author}/${Star.id}/${tid}`;
37    const follow = (tid: string) => `at://${did}/${Follow.id}/${tid}`;
38    const record = Star.build({
39      url: "https://example.org/",
40      createdAt: new Date().toISOString(),
41    });
42    index.star(s(other, "3mgabcdefghij"), record);
43    index.star(s(did, "3mgabcdefghik"), record);
44    assertEquals(index.page(did).stars.length, 1);
45    const f = Follow.build({
46      subject: other,
47      createdAt: new Date().toISOString(),
48    });
49    index.follow(follow("3mgabcdefghij"), f);
50    index.follow(follow("3mgabcdefghik"), f);
51    assertEquals(index.page(did).stars.length, 2);
52    index.removeFollow(follow("3mgabcdefghij"));
53    assertEquals(index.page(did).stars.length, 2);
54    index.removeFollow(follow("3mgabcdefghik"));
55    assertEquals(index.page(did).stars.length, 1);
56    index.removeStar(s(did, "3mgabcdefghik"));
57    assertEquals(index.page(did).stars.length, 0);
58  } finally {
59    db.close();
60  }
61});
62
63Deno.test("an empty follow page completes reconciliation", async () => {
64  const db = new DatabaseSync(":memory:");
65  try {
66    new Auth(db, new URL("http://127.0.0.1:8000"));
67    const hydrant = new Hydrant(new URL("http://127.0.0.1:3000"));
68    hydrant.records = () => Promise.resolve({ entries: [] });
69    const index = new Index(db, hydrant);
70    await index.reconcileFollows(did);
71    assertEquals(index.following(did, other), []);
72  } finally {
73    db.close();
74  }
75});
76
77Deno.test("anonymous pages are rendered without client scripts", async () => {
78  const db = new DatabaseSync(":memory:");
79  try {
80    const auth = new Auth(db, new URL("http://127.0.0.1:8000"));
81    const hydrant = new Hydrant(new URL("http://127.0.0.1:3000"));
82    const index = new Index(db, hydrant);
83    const titles = new TitleLookup(db);
84    const res = await handler(new Request("http://127.0.0.1:8000/"), {
85      auth,
86      hydrant,
87      index,
88      titles,
89    });
90    assertEquals(res.status, 200);
91    const body = await res.text();
92    assert(body.includes('href="/login">Sign in</a>'));
93    assert(!body.includes('action="/login"'));
94    assert(!body.includes("<script"));
95    const signIn = await handler(
96      new Request("http://127.0.0.1:8000/login"),
97      { auth, hydrant, index, titles },
98    );
99    assertEquals(signIn.status, 200);
100    assert((await signIn.text()).includes('action="/login"'));
101    const share = await handler(
102      new Request("http://127.0.0.1:8000/share"),
103      { auth, hydrant, index, titles },
104    );
105    assertEquals(share.headers.get("location"), "/login");
106    const metadata = await handler(
107      new Request("http://127.0.0.1:8000/oauth-client-metadata.json"),
108      { auth, hydrant, index, titles },
109    );
110    assertEquals(metadata.status, 200);
111    assert((await metadata.json()).scope.includes(Star.id));
112  } finally {
113    db.close();
114  }
115});
116
117Deno.test("stream events are mirrored once and resumed from the cursor", async () => {
118  const db = new DatabaseSync(":memory:");
119  const requested: string[] = [];
120  const server = Deno.serve(
121    { hostname: "127.0.0.1", port: 0, onListen() {} },
122    (req) => {
123      requested.push(new URL(req.url).searchParams.get("cursor")!);
124      const { socket, response } = Deno.upgradeWebSocket(req);
125      socket.onopen = () => {
126        const record = { did, collection: Star.id, rkey: "3mgabcdefghij" };
127        socket.send(JSON.stringify({ id: 7, type: "record", record }));
128        socket.send(JSON.stringify({ id: 8, type: "identity" }));
129        socket.close();
130      };
131      return response;
132    },
133  );
134  try {
135    new Auth(db, new URL("http://127.0.0.1:8000"));
136    const hydrant = new Hydrant(
137      new URL(`http://127.0.0.1:${server.addr.port}`),
138    );
139    hydrant.repos = async function* () {};
140    hydrant.record = (uri) =>
141      Promise.resolve({
142        uri,
143        value: Star.build({
144          url: "https://example.org/",
145          createdAt: new Date().toISOString(),
146        }),
147      });
148    const index = new Index(db, hydrant);
149    const stop = new AbortController();
150    const run = index.run(stop.signal);
151    while (requested.length < 2) await new Promise((r) => setTimeout(r, 20));
152    stop.abort();
153    await run;
154    assertEquals(requested, ["0", "9"]);
155    assertEquals(index.page(did).stars.length, 1);
156  } finally {
157    await server.shutdown();
158    db.close();
159  }
160});