Merge pull request #228 from amal66/olp-pr/test-harness

[Testing 05] test: minimal vitest harness for backend and frontend
This commit is contained in:
cosimoastrada 2026-07-21 16:21:11 +08:00 committed by GitHub
commit 8a34a6457f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 2683 additions and 31 deletions

1230
backend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,8 @@
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
"start": "node dist/index.js",
"test": "vitest run"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.90.0",
@ -38,7 +39,8 @@
"@types/node": "^22.14.1",
"prettier": "^3.8.1",
"tsx": "^4.19.3",
"typescript": "^5.8.3"
"typescript": "^5.8.3",
"vitest": "^4.1.9"
},
"license": "AGPL-3.0-only"
}

View file

@ -0,0 +1,109 @@
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { signDownload, verifyDownload, buildDownloadUrl } from "../downloadTokens";
const SECRET = "test-secret-32-bytes-long-enough!!";
beforeAll(() => {
process.env.DOWNLOAD_SIGNING_SECRET = SECRET;
});
afterAll(() => {
delete process.env.DOWNLOAD_SIGNING_SECRET;
});
describe("signDownload", () => {
it("returns a two-part dot-separated token", () => {
const token = signDownload("documents/user/doc.pdf", "contract.pdf");
const parts = token.split(".");
expect(parts).toHaveLength(2);
expect(parts[0].length).toBeGreaterThan(0);
expect(parts[1].length).toBeGreaterThan(0);
});
it("produces different tokens for different paths", () => {
const t1 = signDownload("documents/a/file.pdf", "a.pdf");
const t2 = signDownload("documents/b/file.pdf", "b.pdf");
expect(t1).not.toBe(t2);
});
it("uses base64url characters only (no +, /, =)", () => {
const token = signDownload("documents/user/file.pdf", "file.pdf");
expect(token).not.toMatch(/[+/=]/);
});
});
describe("verifyDownload", () => {
it("round-trips a valid token", () => {
const path = "documents/user123/doc456/source.pdf";
const filename = "Contract Final v2.pdf";
const token = signDownload(path, filename);
const result = verifyDownload(token);
expect(result).not.toBeNull();
expect(result!.path).toBe(path);
expect(result!.filename).toBe(filename);
});
it("returns null for a tampered payload", () => {
const token = signDownload("documents/user/file.pdf", "file.pdf");
const [, sig] = token.split(".");
const fakePayload = Buffer.from(
JSON.stringify({ p: "documents/attacker/file.pdf", f: "file.pdf" }),
)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
expect(verifyDownload(`${fakePayload}.${sig}`)).toBeNull();
});
it("returns null for a tampered signature", () => {
const token = signDownload("documents/user/file.pdf", "file.pdf");
const [enc] = token.split(".");
const fakeSig = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
expect(verifyDownload(`${enc}.${fakeSig}`)).toBeNull();
});
it("returns null for a token with too many parts", () => {
expect(verifyDownload("a.b.c")).toBeNull();
});
it("returns null for a token with too few parts", () => {
expect(verifyDownload("onlyonepart")).toBeNull();
});
it("returns null when payload JSON is missing required fields", () => {
const bad = Buffer.from(JSON.stringify({ x: 1 }))
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
const sig = Buffer.alloc(32).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
expect(verifyDownload(`${bad}.${sig}`)).toBeNull();
});
it("returns null when signed with a different secret", () => {
const token = signDownload("documents/user/file.pdf", "file.pdf");
process.env.DOWNLOAD_SIGNING_SECRET = "different-secret-value-!!";
const result = verifyDownload(token);
process.env.DOWNLOAD_SIGNING_SECRET = SECRET;
expect(result).toBeNull();
});
});
describe("buildDownloadUrl", () => {
it("returns a path starting with /download/", () => {
const url = buildDownloadUrl("documents/user/file.pdf", "file.pdf");
expect(url).toMatch(/^\/download\//);
});
it("embeds a verifiable token in the URL", () => {
const path = "documents/user/file.pdf";
const filename = "file.pdf";
const url = buildDownloadUrl(path, filename);
const token = url.replace("/download/", "");
const result = verifyDownload(token);
expect(result).not.toBeNull();
expect(result!.path).toBe(path);
expect(result!.filename).toBe(filename);
});
});

View file

@ -16,5 +16,5 @@
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules", "dist", "src/**/*.test.ts", "src/**/__tests__/**"]
}

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@
"build": "next build",
"start": "next start",
"lint": "eslint",
"test": "vitest run",
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"upload": "opennextjs-cloudflare build && opennextjs-cloudflare upload",
@ -73,6 +74,7 @@
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5",
"vitest": "^4.1.9",
"wrangler": "^4.90.0"
},
"license": "AGPL-3.0-only"

View file

@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";
import { cn, diceCoefficient, isFuzzyMatch } from "./utils";
describe("cn", () => {
it("joins truthy class names and drops falsy ones", () => {
expect(cn("a", false && "b", undefined, "c")).toBe("a c");
});
it("merges conflicting tailwind classes, keeping the last", () => {
expect(cn("px-2", "px-4")).toBe("px-4");
});
});
describe("diceCoefficient", () => {
it("returns 1 for identical strings (ignoring case and punctuation)", () => {
expect(diceCoefficient("Hello, World!", "hello world")).toBe(1);
});
it("returns 0 when either input is empty", () => {
expect(diceCoefficient("", "anything")).toBe(0);
expect(diceCoefficient("anything", "")).toBe(0);
});
it("returns a partial score for partially overlapping strings", () => {
const score = diceCoefficient("night", "nacht");
expect(score).toBeGreaterThan(0);
expect(score).toBeLessThan(1);
});
});
describe("isFuzzyMatch", () => {
it("matches near-identical strings above the default threshold", () => {
expect(isFuzzyMatch("organization", "organisation")).toBe(true);
});
it("rejects clearly different strings", () => {
expect(isFuzzyMatch("apple", "zebra")).toBe(false);
});
it("respects a custom threshold", () => {
// "night" vs "nacht" scores ~0.25 — passes a low bar, fails a high one.
expect(isFuzzyMatch("night", "nacht", 0.1)).toBe(true);
expect(isFuzzyMatch("night", "nacht", 0.9)).toBe(false);
});
});