char-slop/stars.hackery.site

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

Charlotte Somsyntactic pass: no casts, assertions, or void92004f9

main
5.4 KiB179 linesraw
1import { assert, assertEquals, assertRejects } from "@std/assert";
2import { DatabaseSync } from "node:sqlite";
3import { Auth, type Session } from "./auth.ts";
4import { Hydrant } from "./hydrant.ts";
5import { handler } from "./main.ts";
6import { Index } from "./sync.ts";
7import {
8  parseTitle,
9  publicAddress,
10  TitleLookup,
11  TitleRateLimit,
12} from "./title.ts";
13
14Deno.test("title previews prefer decoded Open Graph titles, then HTML titles", () => {
15  assertEquals(
16    parseTitle(
17      '<html><head><title>HTML title</title><meta content="  Cerulea &amp; friends  " property="og:title"></head></html>',
18    ),
19    { title: "Cerulea & friends", source: "og" },
20  );
21  assertEquals(parseTitle("<title>A &amp; B</title>"), {
22    title: "A & B",
23    source: "html",
24  });
25  assertEquals(parseTitle("<title> </title>"), undefined);
26});
27
28Deno.test("title lookup rejects private and non-unicast destinations", () => {
29  for (
30    const ip of [
31      "127.0.0.1",
32      "10.0.0.1",
33      "169.254.169.254",
34      "192.168.0.1",
35      "100.64.0.1",
36      "::1",
37      "fd00::1",
38      "::ffff:127.0.0.1",
39      "2002:c0a8:0101::",
40    ]
41  ) assert(!publicAddress(ip), ip);
42  assert(publicAddress("8.8.8.8"));
43  assert(publicAddress("2606:4700:4700::1111"));
44});
45
46Deno.test("title lookup is limited per signed-in account", async () => {
47  const db = new DatabaseSync(":memory:");
48  try {
49    let calls = 0;
50    const lookup = new TitleLookup(db, (url) => {
51      calls++;
52      return Promise.resolve({ title: url, source: "url" });
53    });
54    for (let i = 0; i < 10; i++) {
55      await lookup.lookup("did:plc:alice", "https://example.org/");
56    }
57    await assertRejects(
58      () => lookup.lookup("did:plc:alice", "https://example.org/"),
59      TitleRateLimit,
60    );
61    await lookup.lookup("did:plc:bob", "https://example.org/");
62    assertEquals(calls, 11);
63  } finally {
64    db.close();
65  }
66});
67
68Deno.test("share form previews a title and publishes the user's revision", async () => {
69  const db = new DatabaseSync(":memory:");
70  try {
71    const origin = "http://127.0.0.1:8000";
72    const auth = new Auth(db, new URL(origin));
73    const did = "did:plc:abcdefghij";
74    let published: unknown;
75    auth.session = () =>
76      Promise.resolve({
77        did,
78        csrf: "test-csrf",
79        id: "test-session",
80        oauth: {
81          did,
82          handle: (_path: string, init: RequestInit) => {
83            published = JSON.parse(init.body as string);
84            return Promise.resolve(Response.json({
85              uri: `at://${did}/site.hackery.stars.star/3mgabcdefghij`,
86            }));
87          },
88        },
89      } as unknown as Session);
90    const hydrant = new Hydrant(new URL("http://127.0.0.1:3000"));
91    const titles = new TitleLookup(
92      db,
93      () => Promise.resolve({ title: "Fetched title", source: "og" }),
94    );
95    const index = new Index(db, hydrant);
96    hydrant.track = () => Promise.resolve();
97    const app = { auth, hydrant, titles, index };
98    const request = (path: string, fields: Record<string, string>) =>
99      new Request(origin + path, {
100        method: "POST",
101        headers: {
102          origin,
103          "content-type": "application/x-www-form-urlencoded",
104        },
105        body: new URLSearchParams({ csrf: "test-csrf", ...fields }),
106      });
107    const preview = await handler(
108      request("/share/title", {
109        url: "https://example.org/project",
110        comment: "Worth reading",
111      }),
112      app,
113    );
114    assertEquals(preview.status, 200);
115    const html = await preview.text();
116    assert(
117      html.includes('name="title" maxlength="2048" value="Fetched title"'),
118    );
119    assert(html.includes("Worth reading"));
120    const script = /<script>([^]*?)<\/script>/.exec(html)![1];
121    const hash = btoa(String.fromCharCode(
122      ...new Uint8Array(
123        await crypto.subtle.digest("SHA-256", new TextEncoder().encode(script)),
124      ),
125    ));
126    assert(
127      preview.headers.get("content-security-policy")!.includes(
128        `'sha256-${hash}'`,
129      ),
130    );
131    const rejected = await handler(
132      new Request(origin + "/xrpc/site.hackery.stars.getTitle", {
133        method: "POST",
134        headers: { origin, "content-type": "application/json" },
135        body: JSON.stringify({ url: "https://example.org/project" }),
136      }),
137      app,
138    );
139    assertEquals(rejected.status, 403);
140    const xrpc = await handler(
141      new Request(origin + "/xrpc/site.hackery.stars.getTitle", {
142        method: "POST",
143        headers: {
144          origin,
145          "content-type": "application/json",
146          "x-csrf-token": "test-csrf",
147        },
148        body: JSON.stringify({ url: "https://example.org/project" }),
149      }),
150      app,
151    );
152    assertEquals(xrpc.status, 200);
153    assertEquals((await xrpc.json()).title, "Fetched title");
154    const share = await handler(
155      request("/share", {
156        url: "https://example.org/project",
157        title: "My revised title",
158        comment: "Worth reading",
159      }),
160      app,
161    );
162    assertEquals(share.status, 303);
163    assertEquals(
164      (published as { record: { title: string } }).record.title,
165      "My revised title",
166    );
167    const next = await handler(
168      new Request(new URL(share.headers.get("location")!, origin)),
169      app,
170    );
171    const form = await next.text();
172    assert(form.includes("Starred!"));
173    assert(
174      form.includes('name="url" type="url" maxlength="2048" required value=""'),
175    );
176  } finally {
177    db.close();
178  }
179});