import assert from "node:assert/strict"; import crypto from "node:crypto"; import { readFileSync } from "node:fs"; import test from "node:test"; import { decryptEpayKey, encryptEpayKey, EpayEncryptionError } from "../src/lib/epay/encryption-core"; import { resolveEpayConfig } from "../src/lib/epay/config-core"; import { assertPublicEpayGateway, isPublicEpayAddress } from "../src/lib/epay/gateway-policy"; const root = new URL("../", import.meta.url); const route = readFileSync(new URL("src/app/api/admin/epay-settings/route.ts", root), "utf8"); const management = readFileSync(new URL("src/components/admin/payment-management.tsx", root), "utf8"); const createRoute = readFileSync(new URL("src/app/api/payment/epay/create/route.ts", root), "utf8"); const notifyRoute = readFileSync(new URL("src/app/api/payment/epay/notify/route.ts", root), "utf8"); const configRoute = readFileSync(new URL("src/lib/epay/config.ts", root), "utf8"); const availability = readFileSync(new URL("src/lib/epay/availability.ts", root), "utf8"); const packagesRoute = readFileSync(new URL("src/app/api/payment/packages/route.ts", root), "utf8"); const testRoute = readFileSync(new URL("src/app/api/admin/epay-settings/test/route.ts", root), "utf8"); const page = readFileSync(new URL("src/app/page.tsx", root), "utf8"); const migration = readFileSync(new URL("supabase/migrations/20260729010000_epay_settings.sql", root), "utf8"); const key = crypto.randomBytes(32).toString("base64"); test("易支付密钥 AES-256-GCM 往返、篡改与错误主密钥", () => { const encrypted = encryptEpayKey("merchant-secret", key); assert.match(encrypted, /^v1\.[^.]+\.[^.]+\.[^.]+$/); assert.equal(decryptEpayKey(encrypted, key), "merchant-secret"); const tampered = `${encrypted.slice(0, -1)}${encrypted.endsWith("A") ? "B" : "A"}`; assert.throws(() => decryptEpayKey(tampered, key), EpayEncryptionError); assert.throws(() => decryptEpayKey(encrypted, crypto.randomBytes(32).toString("base64")), EpayEncryptionError); assert.throws(() => encryptEpayKey("merchant-secret", "not-base64"), EpayEncryptionError); }); test("配置解析数据库优先且无行时回退环境变量", async () => { const encrypted = encryptEpayKey("database-secret", key); const database = await resolveEpayConfig(async () => ({ gateway_url: "https://database-pay.example.com/", pid: "database-pid", encrypted_key: encrypted, notify_url: "https://staging.example.com/api/payment/epay/notify", return_url: "https://staging.example.com/", site_name: "Staging", chat_enabled: false, }), { NODE_ENV: "test", EPAY_CONFIG_ENCRYPTION_KEY: key, EPAY_GATEWAY_URL: "https://environment-pay.example.com", EPAY_PID: "environment-pid", EPAY_KEY: "environment-secret", }); assert.equal(database.gatewayUrl.toString(), "https://database-pay.example.com/"); assert.equal(database.pid, "database-pid"); assert.equal(database.key, "database-secret"); assert.equal(database.chatEnabled, false); const environment = await resolveEpayConfig(async () => null, { NODE_ENV: "test", SITE_ADDRESS: "https://staging.example.com", EPAY_GATEWAY_URL: "https://environment-pay.example.com", EPAY_PID: "environment-pid", EPAY_KEY: "environment-secret", EPAY_CHAT_ENABLED: "1", }); assert.equal(environment.chatEnabled, true); assert.equal(environment.notifyUrl, "https://staging.example.com/api/payment/epay/notify"); assert.equal(environment.returnUrl, "https://staging.example.com/"); }); test("管理员 API 不回显任何密钥并强制首次显式录入", () => { assert.match(route, /requireAdminSession\("read"\)/); assert.match(route, /requireAdminSession\("write"\)/); assert.match(route, /\.strict\(\)/); assert.match(route, /crypto\.randomUUID\(\)/); assert.match(route, /首次保存数据库配置时必须输入新的商户密钥/); assert.match(route, /chatEnabled: z\.boolean\(\)/); assert.match(route, /p_chat_enabled: parsed\.data\.chatEnabled/); assert.match(route, /keyConfigured/); assert.doesNotMatch(route, /NextResponse\.json\([^\n]*(?:encrypted_key|newKey|encryptedKey|maskedKey|keyMask)/); assert.doesNotMatch(route, /BETTER_AUTH_SECRET/); }); test("迁移前仅在配置表不存在时继续使用环境变量", () => { assert.match(configRoute, /error\?\.code === "42P01"/); assert.match(route, /error\?\.code === "42P01"/); assert.match(configRoute, /if \(error\) throw new Error\(\)/); }); test("支付调用点等待异步数据库配置", () => { assert.match(createRoute, /await readEpayConfig\(\)/); assert.match(notifyRoute, /await readEpayConfig\(\)/); assert.match(notifyRoute, /export async function POST/); assert.match(notifyRoute, /export async function GET/); }); test("统一支付页面含系统配置 Card 与永不预填的 Password", () => { assert.match(management, /title="易支付系统配置"/); assert.match(management, /\/api\/admin\/epay-settings/); assert.match(management, / { assert.match(migration, /chat_enabled boolean not null default false/); assert.match(migration, /p_chat_enabled boolean/); assert.match(migration, /'chatEnabled'/); assert.match(availability, /EPAY_CHAT_ENABLED/); assert.doesNotMatch(availability, /EPAY_CONFIG_ENCRYPTION_KEY|decryptEpayKey/); assert.match(packagesRoute, /enabled: false, packages: \[\]/); assert.match(packagesRoute, /enabled: true/); assert.match(createRoute, /在线支付暂未开放/); assert.match(createRoute, /EPAY_DISABLED/); assert.match(createRoute, /await readEpayAvailability\(\)/); assert.ok(createRoute.indexOf("availability.enabled") < createRoute.indexOf("await readEpayConfig()")); assert.ok(createRoute.indexOf("availability.enabled") < createRoute.indexOf("payment_packages")); assert.match(management, /在对话页开放支付/); assert.match(page, /paymentEnabled &&