79 lines
3.6 KiB
TypeScript
79 lines
3.6 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { resolveAssetPath } from "../src/server/assets-path";
|
|
import { ConfigError, clientIp, readConfig } from "../src/server/config";
|
|
import { RateLimiter } from "../src/server/rate-limit";
|
|
|
|
test("RateLimiter: N per window per key", () => {
|
|
let now = 0;
|
|
const limiter = new RateLimiter(2, 60_000, () => now);
|
|
expect([limiter.take("a"), limiter.take("a"), limiter.take("a"), limiter.take("b")]).toEqual([true, true, false, true]);
|
|
now = 60_000;
|
|
expect(limiter.take("a")).toBe(true);
|
|
});
|
|
|
|
describe("readConfig", () => {
|
|
test("defaults", () => {
|
|
expect(readConfig({})).toEqual({
|
|
port: 8787, hostname: "127.0.0.1", publicUrl: null, storageDir: null,
|
|
storageMaxBytes: 1073741824, trustProxy: false, assetsDir: "dist/web",
|
|
});
|
|
});
|
|
test("everything set", () => {
|
|
expect(readConfig({
|
|
MBSEED_PORT: "9000", MBSEED_HOST: "0.0.0.0", MBSEED_PUBLIC_URL: "https://mbseed.example.org/",
|
|
MBSEED_STORAGE_DIR: "/srv/mbseed/covers", MBSEED_STORAGE_MAX_BYTES: "5000000", MBSEED_TRUST_PROXY: "1", MBSEED_ASSETS_DIR: "/srv/mbseed/web",
|
|
})).toEqual({
|
|
port: 9000, hostname: "0.0.0.0", publicUrl: "https://mbseed.example.org", storageDir: "/srv/mbseed/covers",
|
|
storageMaxBytes: 5000000, trustProxy: true, assetsDir: "/srv/mbseed/web",
|
|
});
|
|
});
|
|
test.each([
|
|
[{ MBSEED_STORAGE_DIR: "/x" }, /MBSEED_PUBLIC_URL/],
|
|
[{ MBSEED_PUBLIC_URL: "mbseed.example.org" }, /not a URL/],
|
|
[{ MBSEED_PUBLIC_URL: "https://example.org/sub/path" }, /origin/],
|
|
[{ MBSEED_PORT: "eighty" }, /whole number/],
|
|
[{ MBSEED_STORAGE_MAX_BYTES: "1GB" }, /whole number/],
|
|
])("%o is refused", (env, message) => {
|
|
expect(() => readConfig(env)).toThrow(ConfigError);
|
|
expect(() => readConfig(env)).toThrow(message);
|
|
});
|
|
});
|
|
|
|
describe("clientIp", () => {
|
|
const req = (xff?: string) => new Request("http://x/", { headers: xff ? { "x-forwarded-for": xff } : {} });
|
|
test("ignores X-Forwarded-For unless the proxy is trusted", () => {
|
|
expect(clientIp(req("1.2.3.4"), false, "10.0.0.1")).toBe("10.0.0.1");
|
|
});
|
|
test("trusts only the last entry, the one our proxy appended", () => {
|
|
// Review Focus 4: the client sent "1.2.3.4" itself; the proxy appended the real address.
|
|
expect(clientIp(req("1.2.3.4, 203.0.113.9"), true, "127.0.0.1")).toBe("203.0.113.9");
|
|
expect(clientIp(req(), true, "127.0.0.1")).toBe("127.0.0.1");
|
|
expect(clientIp(req(), false)).toBe("unknown");
|
|
});
|
|
});
|
|
|
|
describe("resolveAssetPath", () => {
|
|
test.each([
|
|
["/", "index.html"],
|
|
["/index.html", "index.html"],
|
|
["/main-ab12cd.js", "main-ab12cd.js"],
|
|
["/mbseed.user.js", "mbseed.user.js"],
|
|
["/fonts/x.woff2", "fonts/x.woff2"],
|
|
])("%p → %p", (path, file) => {
|
|
expect(resolveAssetPath(path)).toBe(file);
|
|
});
|
|
// Review Focus 5
|
|
test.each(["/..%2f.env", "/%2e%2e/package.json", "/a/../../b", "/.env", "/%00x", "/a\\..\\b", "/%E0%A4%A"])("%p is refused", (path) => {
|
|
expect(resolveAssetPath(path)).toBeNull();
|
|
});
|
|
});
|
|
|
|
test("the server keeps a connection open longer than its own slowest upstream work", async () => {
|
|
// Bun.serve's default idleTimeout (10 s) cut slow Wayback lookups off mid-request ("Failed to fetch").
|
|
const { SERVER_IDLE_TIMEOUT_S } = await import("../src/server/config");
|
|
const { UPSTREAM_TIMEOUT_MS } = await import("../src/server/app");
|
|
const { UPLOAD_TIMEOUT_MS } = await import("../src/core/hosting/host");
|
|
expect(SERVER_IDLE_TIMEOUT_S * 1000).toBeGreaterThan(UPSTREAM_TIMEOUT_MS + UPLOAD_TIMEOUT_MS);
|
|
expect(SERVER_IDLE_TIMEOUT_S).toBeLessThanOrEqual(255); // Bun's maximum
|
|
});
|