Files
gerbeur/api/test/middleware/og.test.ts

175 lines
5.7 KiB
TypeScript

import {
BASE_URL,
createUser,
db,
insertDump,
insertPlaylist,
test,
} from "../helpers.ts";
import { assertEquals, assertFalse, assertStringIncludes } from "@std/assert";
import { Application } from "@oak/oak";
import { ogMiddleware } from "../../middleware/og.ts";
async function handle(
app: Application,
path: string,
headers: HeadersInit = {},
) {
const res = await app.handle(new Request(`${BASE_URL}${path}`, { headers }));
if (!res) throw new Error("no response");
return { res, text: await res.text() };
}
function ogApp() {
const app = new Application();
app.use(ogMiddleware);
app.use((ctx) => {
ctx.response.body = "fallthrough";
});
return app;
}
test("ogMiddleware: dump pages get escaped OG tags", async () => {
const user = await createUser();
const dump = insertDump({
userId: user.id,
title: `Tom & "Jerry" <script>`,
comment: "A comment",
richContent: {
type: "generic",
url: "x",
thumbnailUrl: "https://img.test/a.jpg",
},
});
const { res, text } = await handle(ogApp(), dump.path);
assertEquals(res.headers.get("content-type"), "text/html; charset=utf-8");
assertStringIncludes(
text,
`<meta property="og:title" content="Tom &amp; &quot;Jerry&quot; &lt;script&gt;" />`,
);
assertStringIncludes(
text,
`<title>Tom &amp; &quot;Jerry&quot; &lt;script&gt;</title>`,
);
assertStringIncludes(
text,
`<meta property="og:description" content="A comment" />`,
);
assertStringIncludes(
text,
`<meta property="og:image" content="https://img.test/a.jpg" />`,
);
assertStringIncludes(text, `content="summary_large_image"`);
assertStringIncludes(
text,
`<meta property="og:url" content="${BASE_URL}${dump.path}" />`,
);
// The index's own title is replaced, not duplicated.
assertEquals(text.match(/<title>/g)?.length, 1);
assertFalse(text.includes("__SITE_NAME__"));
});
test("ogMiddleware: no artwork falls back to the site icon as a small card", async () => {
const user = await createUser();
const dump = insertDump({ userId: user.id, title: "Plain" });
const { text } = await handle(ogApp(), dump.path);
assertStringIncludes(text, `content="${BASE_URL}/apple-touch-icon.png"`);
assertStringIncludes(text, `<meta name="twitter:card" content="summary" />`);
const file = insertDump({
userId: user.id,
fileName: "a.png",
fileMime: "image/png",
});
assertStringIncludes(
(await handle(ogApp(), file.path)).text,
`content="${BASE_URL}/api/dumps/${file.id}/file"`,
);
});
test("ogMiddleware: users and public playlists; private things fall through", async () => {
const user = await createUser();
const app = ogApp();
assertStringIncludes(
(await handle(app, `/@${user.username}/upvoted`)).text,
`<meta property="og:title" content="${user.username}" />`,
);
assertEquals(
(await handle(app, `/@${user.username}/nope`)).text,
"fallthrough",
);
const pub = insertPlaylist({ userId: user.id, title: "Open list" });
assertStringIncludes(
(await handle(app, pub.path)).text,
"Open list",
);
const priv = insertPlaylist({
userId: user.id,
title: "Closed",
isPublic: false,
});
assertEquals(
(await handle(app, priv.path)).text,
"fallthrough",
);
const secret = insertDump({
userId: user.id,
title: "Secret",
isPrivate: true,
});
assertEquals((await handle(app, secret.path)).text, "fallthrough");
assertEquals(
(await handle(app, `/@${user.username}/dumps/nope`)).text,
"fallthrough",
);
// v3 paths aren't redirected.
assertEquals((await handle(app, `/dumps/${secret.id}`)).text, "fallthrough");
assertEquals(
(await handle(app, `/api/dumps/${secret.id}`)).text,
"fallthrough",
);
assertEquals((await handle(app, `/favicon.ico`)).text, "fallthrough");
});
test("ogMiddleware: a former slug, a former name or another case gets a 301", async () => {
const user = await createUser();
const dump = insertDump({ userId: user.id, title: "Now" });
db.prepare(
`INSERT INTO dump_slug_aliases (user_id, slug, dump_id, created_at) VALUES (?, 'before', ?, ?);`,
).run(user.id, dump.id, new Date().toISOString());
db.prepare(
`INSERT INTO user_aliases (username, user_id, created_at) VALUES ('old_name', ?, ?);`,
).run(user.id, new Date().toISOString());
for (
const [from, to] of [
[`/@${user.username}/dumps/before?x=1`, `${dump.path}?x=1`],
[`/@OLD_NAME/dumps/now`, dump.path],
[`/@old_name/follow/playlists`, `/@${user.username}/follow/playlists`],
[`/@${user.username.toUpperCase()}`, `/@${user.username}`],
[`/@${user.username}@gerbeur.test/upvoted`, `/@${user.username}/upvoted`],
]
) {
const { res } = await handle(ogApp(), from);
assertEquals(res.status, 301, from);
assertEquals(res.headers.get("location"), `${BASE_URL}${to}`, from);
}
// A remote user's path has dots in it, as a file's does.
const remote = await createUser({ username: "bob@music.example" });
const remoteDump = insertDump({ userId: remote.id, title: "Far" });
assertStringIncludes(
(await handle(ogApp(), remoteDump.path)).text,
`<meta property="og:title" content="Far" />`,
);
const { res } = await handle(ogApp(), "/@BOB@music.example/dumps/far");
assertEquals(res.status, 301);
assertEquals(res.headers.get("location"), `${BASE_URL}${remoteDump.path}`);
// A private dump's former slug tells nothing.
const secret = insertDump({ userId: user.id, title: "S", isPrivate: true });
db.prepare(
`INSERT INTO dump_slug_aliases (user_id, slug, dump_id, created_at) VALUES (?, 'hush', ?, ?);`,
).run(user.id, secret.id, new Date().toISOString());
assertEquals(
(await handle(ogApp(), `/@${user.username}/dumps/hush`)).text,
"fallthrough",
);
});