Merge branch 'forgejo-dev' into working
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 4s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m12s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 4s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s

Resolve CLAUDE.md: คำอธิบาย lib/document-versioning.ts เอาของ
forgejo-dev (field `fields` ถอดออก 2026-10-30) ส่วน lib/docver-token.ts
เอาของ working (purpose login / verifyDocverLoginToken — โค้ดฝั่ง
forgejo-dev มีครบแล้ว แค่คำอธิบายล้าหลัง)

Verify: npx tsc --noEmit ผ่าน · vitest 23 files / 338 tests ผ่าน

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-11-02 10:07:08 +07:00
commit a288e5643c
31 changed files with 1445 additions and 278 deletions

View file

@ -4,6 +4,7 @@ import {
isTicketExpired,
normalizeHexDigest,
parseDocverTicket,
parseTicketDataParam,
timingSafeEqualHex,
} from "@/utils/docver-ticket";
@ -123,3 +124,84 @@ describe("timingSafeEqualHex", () => {
expect(timingSafeEqualHex(a, "ab")).toBe(false);
});
});
// parseTicketDataParam — query param `ticket_data` ของ GET /api/documents/latest:
// ticket ก่อนเติม exp (server เติมตอน stamp) แต่บังคับ fid/uid/uname เพราะ
// parseDocverTicket บังคับทั้งสามตอน add-in redeem — ขาด = ไฟล์ที่คืนไปเปิดไม่ได้
describe("parseTicketDataParam", () => {
it("fid+uid+uname น้อยสุด = ผ่าน (trim ค่า) · +tid ก็ได้ · ไม่มี exp ก็ผ่าน", () => {
const minimal = parseTicketDataParam(
JSON.stringify({ fid: " F1 ", uid: " u1 ", uname: " ทดสอบ " }),
);
ok(minimal);
expect(minimal.ticket).toEqual({ fid: "F1", uid: "u1", uname: "ทดสอบ", tid: null });
const withTid = parseTicketDataParam(
JSON.stringify({ fid: "F1", tid: "T12", uid: "u1", uname: "ทดสอบ" }),
);
ok(withTid);
expect(withTid.ticket.tid).toBe("T12");
});
it("tid ส่งค่าว่าง = null · exp ถ้าส่งมาเป็นจำนวนเต็ม = รับ (เติมทับตอน stamp)", () => {
const blankTid = parseTicketDataParam(
JSON.stringify({ fid: "F1", tid: " ", uid: "u1", uname: "n" }),
);
ok(blankTid);
expect(blankTid.ticket.tid).toBeNull();
const withExp = parseTicketDataParam(
JSON.stringify({ exp: 1_793_067_433, fid: "F1", uid: "u1", uname: "n" }),
);
ok(withExp);
});
it("ไม่ส่ง / ว่าง / ยาวเกิน 2048 = ไม่ผ่าน", () => {
expect(parseTicketDataParam(null).ok).toBe(false);
expect(parseTicketDataParam(" ").ok).toBe(false);
expect(parseTicketDataParam("x".repeat(2049)).ok).toBe(false);
});
it("JSON ไม่ผ่าน / ไม่ใช่ object (array/string/null/ตัวเลข) = ไม่ผ่าน", () => {
for (const raw of ["not json", "[1,2]", '"text"', "null", "42"]) {
expect(parseTicketDataParam(raw).ok, raw).toBe(false);
}
});
it("field อื่นนอก allowlist = ไม่ผ่าน (error ระบุชื่อ field)", () => {
const result = parseTicketDataParam(
JSON.stringify({ fid: "f", uid: "u", uname: "n", oops: 1 }),
);
expect(result.ok).toBe(false);
if (!result.ok) expect(result.error).toContain("oops");
});
it("exp ส่งมาผิดชนิด (string/ทศนิยม) = ไม่ผ่าน แม้จะไม่ได้ใช้ค่าเดิม", () => {
for (const exp of ["1793067433", 1.5]) {
const result = parseTicketDataParam(
JSON.stringify({ exp, fid: "f", uid: "u", uname: "n" }),
);
expect(result.ok, String(exp)).toBe(false);
}
});
it("ขาด/ผิดชนิด fid·uid·uname รายตัว = ไม่ผ่าน — บังคับครบเหมือน parseDocverTicket", () => {
for (const ticket of [
{ uid: "u", uname: "n" }, // ไม่มี fid
{ fid: "f", uname: "n" }, // ไม่มี uid
{ fid: "f", uid: "u" }, // ไม่มี uname
{ fid: "", uid: "u", uname: "n" }, // fid ว่าง
{ fid: 3, uid: "u", uname: "n" }, // fid เป็น number
{ fid: "f", uid: "u", uname: "n", tid: 12 }, // tid ไม่ใช่ string
]) {
expect(parseTicketDataParam(JSON.stringify(ticket)).ok, JSON.stringify(ticket)).toBe(false);
}
});
it("ความยาวเกิน cap: uid > 100 / uname > 200 / fid > 200 / tid > 100 = ไม่ผ่าน", () => {
expect(parseTicketDataParam(JSON.stringify({ fid: "f", uid: "u".repeat(101), uname: "n" })).ok).toBe(false);
expect(parseTicketDataParam(JSON.stringify({ fid: "f", uid: "u", uname: "n".repeat(201) })).ok).toBe(false);
expect(parseTicketDataParam(JSON.stringify({ fid: "f".repeat(201), uid: "u", uname: "n" })).ok).toBe(false);
expect(parseTicketDataParam(JSON.stringify({ fid: "f", uid: "u", uname: "n", tid: "t".repeat(101) })).ok).toBe(false);
});
});