Files
WRNexusJS/packages/payment/test/database-plugin.test.ts
T
Clintchiz 98e0813061
Quality / quality (ubuntu-latest) (push) Failing after 9m55s
Quality / quality (windows-latest) (push) Canceled after 0s
feat: add gateway-neutral payment package
2026-08-23 21:05:27 +05:30

108 lines
3.6 KiB
TypeScript

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<string, unknown>();
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"]);
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<string, unknown>();
const runner = createPluginRunner(paymentPlugin(), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
});
await runner.configure({ payment: { default: "sandbox" } } as any);
const source = (await runner.contributions()).migrations[0].source!;
const up = 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);
}
});