import { afterEach, expect, test } from "bun:test"; import { closeDatabases, setDb } from "@wrnexus/db"; import { connectFromConfig } from "@wrnexus/db/connect"; import { createPluginRunner } from "@wrnexus/plugin"; import { parse } from "@wrnexus/syntax"; import { readFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { databasePaymentStore, paymentPlugin } from "../src/index.ts"; afterEach(() => closeDatabases()); test("payment plugin contributes its schema only when configured", async () => { const metadata = new Map(); const context = { root: process.cwd(), mode: "development" as const, command: "dev" as const, metadata, warn() {}, }; const runner = createPluginRunner(paymentPlugin(), context); await runner.configure({ payment: { default: "sandbox", sandbox: true } } as any); const contributions = await runner.contributions(); expect(contributions.migrations.map((row) => row.id)).toEqual([ "wrnexus-payment-001", "wrnexus-payment-002-event-order", ]); expect(contributions.componentDirs).toHaveLength(1); expect(contributions.routes.map((row) => row.path)).toEqual(["/api/payments/webhook/:gateway"]); const off = createPluginRunner(paymentPlugin(), { ...context, metadata: new Map() }); await off.configure({} as any); expect((await off.contributions()).migrations).toHaveLength(0); expect((await off.contributions()).routes).toHaveLength(0); }); test("database store persists append-only events and atomically caps refunds", async () => { const db = connectFromConfig({ driver: "sqlite", url: ":memory:" }); setDb(db); const metadata = new Map(); const runner = createPluginRunner(paymentPlugin(), { root: process.cwd(), mode: "development", command: "dev", metadata, warn() {}, }); await runner.configure({ payment: { default: "sandbox" } } as any); for (const migration of (await runner.contributions()).migrations) { const up = migration.source!.split("-- +down")[0].replace("-- +up", ""); for (const statement of up .split(";") .map((x) => x.trim()) .filter(Boolean)) await db.exec(statement); } const store = databasePaymentStore(); const intent = { id: "pay-1", subject: "u", gateway: "sandbox", gatewayRef: "sb-1", amount: 100, currency: "USD", idempotencyKey: "once", createdAt: 1, client: {}, metadata: {}, }; await store.putIntent(intent); expect((await store.findIntentByKey("once"))?.id).toBe("pay-1"); expect( await store.appendEvent({ id: "e1", intentId: "pay-1", type: "captured", status: "captured", at: 1, }), ).toBe(true); expect( await store.appendEvent({ id: "e1", intentId: "pay-1", type: "captured", status: "captured", at: 1, }), ).toBe(false); const base = { intentId: "pay-1", gatewayRef: "", currency: "USD", reason: "test", status: "pending" as const, createdAt: 1, }; const results = await Promise.all([ store.reserveRefund({ id: "r1", amount: 60, idempotencyKey: "r1", ...base }, 100), store.reserveRefund({ id: "r2", amount: 60, idempotencyKey: "r2", ...base }, 100), ]); expect(results.filter(Boolean)).toHaveLength(1); }); test("all payment components parse and never collect raw card data", () => { for (const file of readdirSync(join(import.meta.dir, "../components")).filter((x) => x.endsWith(".wrn"), )) { const source = readFileSync(join(import.meta.dir, "../components", file), "utf8"); expect(() => parse(source)).not.toThrow(); expect(source).not.toMatch(/cardNumber|cvv|\bpan\b/i); } });