import { afterEach, describe, expect, it } from "vitest"; import { requireExternalApiKey } from "@/lib/external-api-auth"; // N10 (PLAN-API.md D1) — auth ของ /api/note/external/*: fail closed เมื่อ env ว่าง + // ข้อความ 401 อันเดียวกันทั้งไม่ใส่ key และ key ผิด (กัน enumerate) — ไฟล์นี้ // ตั้งใจไม่แตะ module ใดที่ลาก @/db มาด้วย (db/index.ts throw ตอน import เมื่อ // ไม่มี DATABASE_URL — vitest ไม่โหลด .env) const HAD_KEY = Object.prototype.hasOwnProperty.call(process.env, "EXTERNAL_API_KEY"); const ORIG_KEY = process.env.EXTERNAL_API_KEY; function restoreKey() { if (HAD_KEY) process.env.EXTERNAL_API_KEY = ORIG_KEY; else delete process.env.EXTERNAL_API_KEY; } function reqWithKey(key?: string): Request { const headers = new Headers(); if (key !== undefined) headers.set("x-api-key", key); return new Request("http://localhost/docedit/api/note/external/documents", { headers }); } async function bodyOf(res: Response): Promise<{ error: string }> { expect(res.headers.get("content-type")).toBe("application/json; charset=utf-8"); return (await res.json()) as { error: string }; } describe("requireExternalApiKey", () => { afterEach(restoreKey); it("env ไม่ตั้ง / ว่าง / whitespace ล้วน = 401 fail closed (แม้จะส่ง key มาถูก)", async () => { for (const empty of [undefined, "", " "]) { if (empty === undefined) delete process.env.EXTERNAL_API_KEY; else process.env.EXTERNAL_API_KEY = empty; const res = requireExternalApiKey(reqWithKey("correct-key")); expect(res).not.toBeNull(); expect(res!.status).toBe(401); expect(await bodyOf(res!)).toEqual({ error: "API ยังไม่ได้ตั้งค่าคีย์" }); } }); it("ไม่ใส่ header กับ key ผิด = 401 ข้อความ/รูปร่างเดียวกัน (ไม่บอกว่าขาดอะไร)", async () => { process.env.EXTERNAL_API_KEY = "correct-key"; const missing = requireExternalApiKey(reqWithKey()); const wrong = requireExternalApiKey(reqWithKey("wrong-key")); expect(missing!.status).toBe(401); expect(wrong!.status).toBe(401); const missingBody = await bodyOf(missing!); const wrongBody = await bodyOf(wrong!); expect(missingBody).toEqual(wrongBody); expect(Object.keys(missingBody)).toEqual(["error"]); }); it("key ถูก = ผ่าน (คืน null)", () => { process.env.EXTERNAL_API_KEY = "correct-key"; expect(requireExternalApiKey(reqWithKey("correct-key"))).toBeNull(); }); it("env มี whitespace ครอบ (trailing newline ของ K8s secret/env_file) = เทียบด้วยค่า trim แล้ว", () => { process.env.EXTERNAL_API_KEY = " correct-key\n"; expect(requireExternalApiKey(reqWithKey("correct-key"))).toBeNull(); expect(requireExternalApiKey(reqWithKey("wrong-key"))!.status).toBe(401); }); it("key ยาว (300+ ตัวอักษร) เทียบตรง ๆ ได้ — ต่างแม้ตัวเดียว = 401", () => { // header เป็น ByteString จึงทดสอบด้วย latin1 เท่านั้น (ไทย/unicode ส่งผ่าน // header ไม่ได้ตั้งแต่ใน Headers.set แล้ว) const key = "k".repeat(300) + "-ok"; process.env.EXTERNAL_API_KEY = key; expect(requireExternalApiKey(reqWithKey(key))).toBeNull(); expect(requireExternalApiKey(reqWithKey(key.slice(0, -1)))!.status).toBe(401); }); });