244 lines
7.1 KiB
TypeScript
244 lines
7.1 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
createAmberProxyHandler,
|
|
createAmberSyncHandler,
|
|
createCdnHandler,
|
|
createCountHandler,
|
|
createProxyHandler,
|
|
createSlipHandler,
|
|
createStatesHandler,
|
|
} from "./server-handlers";
|
|
|
|
const request = new Request("https://buzz.example/test");
|
|
const notFound = () => {
|
|
throw new Error("not found");
|
|
};
|
|
|
|
describe("artifact count handler", () => {
|
|
test("returns the submission count as JSON", async () => {
|
|
const response = await createCountHandler(async () => 12)();
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get("content-type")).toContain("application/json");
|
|
expect(await response.json()).toBe(12);
|
|
});
|
|
});
|
|
|
|
describe("rubgram count handler", () => {
|
|
test("returns the non-deleted submission count as JSON", async () => {
|
|
const response = await createCountHandler(async () => 7)();
|
|
|
|
expect(await response.json()).toBe(7);
|
|
});
|
|
});
|
|
|
|
describe("tier-list states handler", () => {
|
|
test("passes the route version to the query and returns its states", async () => {
|
|
let queriedVersion: string | undefined;
|
|
const states = [{ id: "state-1" }, { id: "state-2" }];
|
|
const handler = createStatesHandler(async (version) => {
|
|
queriedVersion = version;
|
|
return states;
|
|
});
|
|
|
|
const response = await handler(request, {
|
|
params: Promise.resolve({ ver: "3.4" }),
|
|
});
|
|
|
|
expect(queriedVersion).toBe("3.4");
|
|
expect(await response.json()).toEqual(states);
|
|
});
|
|
});
|
|
|
|
describe("slip handler", () => {
|
|
test("returns JPEG bytes for the requested slip", async () => {
|
|
let queriedId: string | undefined;
|
|
const handler = createSlipHandler(async (id) => {
|
|
queriedId = id;
|
|
return new Uint8Array([0xff, 0xd8, 0xff]);
|
|
}, notFound);
|
|
|
|
const response = await handler(request, {
|
|
params: Promise.resolve({ id: "slip-1" }),
|
|
});
|
|
|
|
expect(queriedId).toBe("slip-1");
|
|
expect(response.headers.get("content-type")).toBe("image/jpeg");
|
|
expect(new Uint8Array(await response.arrayBuffer())).toEqual(
|
|
new Uint8Array([0xff, 0xd8, 0xff]),
|
|
);
|
|
});
|
|
|
|
test("uses the not-found path when the slip is absent", async () => {
|
|
const handler = createSlipHandler(async () => null, notFound);
|
|
|
|
expect(
|
|
handler(request, { params: Promise.resolve({ id: "missing" }) }),
|
|
).rejects.toThrow("not found");
|
|
});
|
|
});
|
|
|
|
describe("CDN handler", () => {
|
|
test("returns immutable content with stored metadata", async () => {
|
|
let queriedId: string | undefined;
|
|
const handler = createCdnHandler(async (id) => {
|
|
queriedId = id;
|
|
return {
|
|
data: new Uint8Array([1, 2, 3, 4]),
|
|
type: "image/png",
|
|
size: "4",
|
|
};
|
|
}, notFound);
|
|
|
|
const response = await handler(request, {
|
|
params: Promise.resolve({ id: "asset-1" }),
|
|
});
|
|
|
|
expect(queriedId).toBe("asset-1");
|
|
expect(response.headers.get("content-type")).toBe("image/png");
|
|
expect(response.headers.get("content-length")).toBe("4");
|
|
expect(response.headers.get("cache-control")).toBe(
|
|
"public, max-age=31536000, immutable",
|
|
);
|
|
expect(new Uint8Array(await response.arrayBuffer())).toEqual(
|
|
new Uint8Array([1, 2, 3, 4]),
|
|
);
|
|
});
|
|
|
|
test("uses the not-found path when the asset is absent", async () => {
|
|
const handler = createCdnHandler(async () => null, notFound);
|
|
|
|
expect(
|
|
handler(request, { params: Promise.resolve({ id: "missing" }) }),
|
|
).rejects.toThrow("not found");
|
|
});
|
|
});
|
|
|
|
describe("Amber log handler", () => {
|
|
test("fetches the changelog with the current version hash", async () => {
|
|
let requestedUrl: string | undefined;
|
|
const handler = createAmberProxyHandler("static/changelog", {
|
|
getVersionHash: async () => "hash-log",
|
|
fetchText: async (url) => {
|
|
requestedUrl = url;
|
|
return '{"changes":[]}';
|
|
},
|
|
});
|
|
|
|
const response = await handler();
|
|
|
|
expect(requestedUrl).toBe(
|
|
"https://gi.yatta.moe/api/v2/static/changelog?vh=hash-log",
|
|
);
|
|
expect(response.headers.get("content-type")).toBe("application/json");
|
|
expect(await response.text()).toBe('{"changes":[]}');
|
|
});
|
|
});
|
|
|
|
describe("Amber character handler", () => {
|
|
test("fetches avatars with the current version hash", async () => {
|
|
let requestedUrl: string | undefined;
|
|
const handler = createAmberProxyHandler("en/avatar", {
|
|
getVersionHash: async () => "hash-char",
|
|
fetchText: async (url) => {
|
|
requestedUrl = url;
|
|
return '{"items":[]}';
|
|
},
|
|
});
|
|
|
|
const response = await handler();
|
|
|
|
expect(requestedUrl).toBe(
|
|
"https://gi.yatta.moe/api/v2/en/avatar?vh=hash-char",
|
|
);
|
|
expect(await response.text()).toBe('{"items":[]}');
|
|
});
|
|
});
|
|
|
|
describe("Amber sync handler", () => {
|
|
test("rejects non-admin requests without syncing or logging", async () => {
|
|
let synced = false;
|
|
let logged = false;
|
|
const handler = createAmberSyncHandler({
|
|
adminCheck: async () => null,
|
|
sync: async () => {
|
|
synced = true;
|
|
},
|
|
log: async () => {
|
|
logged = true;
|
|
},
|
|
});
|
|
|
|
const response = await handler();
|
|
|
|
expect(response.status).toBe(401);
|
|
expect(await response.text()).toBe("Unauthorized");
|
|
expect(synced).toBeFalse();
|
|
expect(logged).toBeFalse();
|
|
});
|
|
|
|
test("syncs before recording the admin action", async () => {
|
|
const calls: string[] = [];
|
|
const handler = createAmberSyncHandler({
|
|
adminCheck: async () => ({ role: "admin" }),
|
|
sync: async () => {
|
|
calls.push("sync");
|
|
},
|
|
log: async (message) => {
|
|
calls.push(message);
|
|
},
|
|
});
|
|
|
|
const response = await handler();
|
|
|
|
expect(calls).toEqual(["sync", "Triggered an Amber sync from API"]);
|
|
expect(response.status).toBe(200);
|
|
expect(await response.text()).toContain("OK(");
|
|
});
|
|
});
|
|
|
|
describe("application proxy", () => {
|
|
const proxyRequest = {
|
|
url: "https://buzz.example/admin/users?tab=banned",
|
|
nextUrl: { pathname: "/admin/users", search: "?tab=banned" },
|
|
};
|
|
|
|
test("redirects anonymous requests to login with the full next path", async () => {
|
|
let redirectUrl: URL | undefined;
|
|
const handler = createProxyHandler({
|
|
getSessionCookie: () => undefined,
|
|
redirect: (url) => {
|
|
redirectUrl = url;
|
|
return new Response(null, { status: 307 });
|
|
},
|
|
next: () => new Response("next"),
|
|
});
|
|
|
|
const response = await handler(proxyRequest);
|
|
|
|
expect(response.status).toBe(307);
|
|
expect(redirectUrl?.pathname).toBe("/login");
|
|
expect(redirectUrl?.searchParams.get("next")).toBe(
|
|
"/admin/users?tab=banned",
|
|
);
|
|
});
|
|
|
|
test("continues requests with a session cookie", async () => {
|
|
let redirected = false;
|
|
const handler = createProxyHandler({
|
|
getSessionCookie: () => "session-cookie",
|
|
redirect: () => {
|
|
redirected = true;
|
|
return new Response(null, { status: 307 });
|
|
},
|
|
next: () => new Response("next", { status: 200 }),
|
|
});
|
|
|
|
const response = await handler(proxyRequest);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(await response.text()).toBe("next");
|
|
expect(redirected).toBeFalse();
|
|
});
|
|
});
|