import assert from "node:assert/strict"; import test from "node:test"; import { readFileSync } from "node:fs"; import { signWecomBindState, verifyWecomBindState, signWecomIdentityTicket, WECOM_BIND_STATE_TTL_MS, } from "../lib/wecom-oauth-service.ts"; const SECRET = "internal-token-test"; const bindRoute = readFileSync( new URL("../app/api/wecom-bind/route.ts", import.meta.url), "utf8", ); const adminApp = readFileSync( new URL("../app/admin-app.tsx", import.meta.url), "utf8", ); test("bind state sign → verify roundtrip", async () => { const state = await signWecomBindState("user-abc", { secret: SECRET }); const verified = await verifyWecomBindState(state, { secret: SECRET }); assert.deepEqual(verified, { userId: "user-abc" }); }); test("bind state rejects expiry, tampering, wrong secret", async () => { const state = await signWecomBindState("user-abc", { secret: SECRET, nowMs: Date.now() - WECOM_BIND_STATE_TTL_MS - 120_000, }); assert.equal(await verifyWecomBindState(state, { secret: SECRET }), null); const fresh = await signWecomBindState("user-abc", { secret: SECRET }); const [payload, signature] = fresh.split("."); assert.ok(payload.length > 8); const flip = payload[5] === "a" ? "b" : "a"; const tampered = `${payload.slice(0, 5)}${flip}${payload.slice(6)}.${signature}`; assert.notEqual(tampered, fresh); assert.equal(await verifyWecomBindState(tampered, { secret: SECRET }), null); assert.equal( await verifyWecomBindState(fresh, { secret: "other-secret" }), null, ); assert.equal(await verifyWecomBindState("garbage", { secret: SECRET }), null); assert.equal(await verifyWecomBindState("", { secret: SECRET }), null); }); test("identity ticket cannot be replayed as bind state", async () => { const { ticket } = await signWecomIdentityTicket("wmExt123", "张三", { secret: SECRET, }); assert.equal(await verifyWecomBindState(ticket, { secret: SECRET }), null); }); test("bind state requires a user id", async () => { await assert.rejects( () => signWecomBindState("", { secret: SECRET }), /后台账号 ID/, ); }); test("bind route: start redirects logged-in users to wecom oauth", () => { assert.ok(bindRoute.includes("buildWecomOAuthUrl")); assert.ok(bindRoute.includes("signWecomBindState")); assert.ok(bindRoute.includes("redirectUri")); assert.ok(/APP_ORIGIN/.test(bindRoute)); assert.ok(/principal\.kind !== "user"/.test(bindRoute)); }); test("bind route: callback verifies state, session and conflicts", () => { assert.ok(bindRoute.includes("verifyWecomBindState")); assert.ok(/principal\.user\.id !== verified\.userId/.test(bindRoute)); assert.ok(bindRoute.includes("exchangeWecomOAuthCode")); assert.ok(bindRoute.includes("!identity.userId")); assert.ok( /SELECT id, username FROM users WHERE wecom_userid = \? AND id <> \?/.test( bindRoute, ), ); assert.ok( /UPDATE users SET wecom_userid = \?, updated_at = CURRENT_TIMESTAMP WHERE id = \?/.test( bindRoute, ), ); }); test("admin banner exposes self-service bind entry", () => { assert.ok(adminApp.includes('href="/api/wecom-bind"')); assert.ok(adminApp.includes("owner-bind-button")); });