152 lines
6.2 KiB
TypeScript
152 lines
6.2 KiB
TypeScript
import type { IsolationTenant } from "@conusai-kit/testing"
|
|
import { describe, expect, it, vi } from "vitest"
|
|
import { simulatedBusinessAdmission } from "../../../test/helpers/business-admission.js"
|
|
import type { LedgerService } from "../core/ports.js"
|
|
import { ledgerApp } from "./app.js"
|
|
import { ledgerIsolationFixtures } from "./isolation.js"
|
|
|
|
const fixtureInput = (name: string, tenant: IsolationTenant) => {
|
|
const fixture = ledgerIsolationFixtures[name]
|
|
if (!fixture) throw new Error("missing isolation fixture")
|
|
return fixture.input(tenant)
|
|
}
|
|
const resultAccount = {
|
|
accountId: "acc_00000000-0000-4000-8000-000000000001",
|
|
code: "SYNTHETIC",
|
|
name: "Operational",
|
|
currency: "EUR",
|
|
type: "asset",
|
|
normalBalance: "debit",
|
|
allowNegative: false,
|
|
status: "active",
|
|
revision: "1",
|
|
} as const
|
|
const resultTransaction = {
|
|
transactionId: "txn_00000000-0000-4000-8000-000000000001",
|
|
revision: "1",
|
|
effectiveAt: "2026-11-03T00:00:00Z",
|
|
description: "Synthetic",
|
|
reference: null,
|
|
reverses: null,
|
|
status: "pending",
|
|
entries: [],
|
|
} as const
|
|
const fake = () =>
|
|
({
|
|
accounts: vi.fn(async () => ({ rows: [] })),
|
|
verify: vi.fn(async () => ({ checkedAccounts: 0, drift: [] })),
|
|
createAccount: vi.fn(async () => resultAccount),
|
|
updateAccount: vi.fn(async () => resultAccount),
|
|
record: vi.fn(async () => ({ ...resultTransaction, entries: [] })),
|
|
transition: vi.fn(async () => ({ ...resultTransaction, entries: [] })),
|
|
}) as unknown as LedgerService
|
|
|
|
describe("Ledger actual caller with synthetic admission/port", () => {
|
|
it("refuses all 13 procedures with wrong-tenant slugs before service access", async () => {
|
|
const s = fake()
|
|
const app = ledgerApp(s)
|
|
const a = await app.tenant("alpha")
|
|
const b = await app.tenant("beta")
|
|
const tenant = { orgSlug: b.orgSlug } as IsolationTenant
|
|
expect(Object.keys(ledgerIsolationFixtures)).toHaveLength(13)
|
|
for (const [name, fixture] of Object.entries(ledgerIsolationFixtures)) {
|
|
await expect(a.call.callByName(name, fixture.input(tenant))).rejects.toHaveProperty("problem.status", 404)
|
|
}
|
|
expect(s.accounts).not.toHaveBeenCalled()
|
|
})
|
|
it("allows viewer reads, refuses viewer writes and separates member writer from account/reversal admin", async () => {
|
|
const app = ledgerApp(fake())
|
|
const t = await app.tenant("alpha")
|
|
const viewer = await app.member(t, "viewer@alpha.test", "viewer")
|
|
const member = await app.member(t, "writer@alpha.test", "member")
|
|
expect(await viewer.callByName("ledger.accounts.list", { org: t.orgSlug })).toEqual({ rows: [] })
|
|
const tenant = { orgSlug: t.orgSlug } as IsolationTenant
|
|
for (const name of [
|
|
"ledger.accounts.create",
|
|
"ledger.accounts.update",
|
|
"ledger.transactions.record",
|
|
"ledger.transactions.post",
|
|
"ledger.transactions.void",
|
|
"ledger.transactions.reverse",
|
|
]) {
|
|
await expect(viewer.callByName(name, fixtureInput(name, tenant))).rejects.toHaveProperty("problem.status", 404)
|
|
}
|
|
for (const name of ["ledger.accounts.create", "ledger.accounts.update", "ledger.transactions.reverse"])
|
|
await expect(member.callByName(name, fixtureInput(name, tenant))).rejects.toHaveProperty("problem.status", 404)
|
|
})
|
|
it("refuses unregistered default-off module and missing readiness/binding", async () => {
|
|
for (const [enabled, ready] of [
|
|
[false, true],
|
|
[true, false],
|
|
]) {
|
|
const s = fake()
|
|
const app = ledgerApp(s, enabled, ready)
|
|
const t = await app.tenant("alpha")
|
|
await expect(t.call.callByName("ledger.accounts.list", { org: t.orgSlug })).rejects.toThrow()
|
|
expect(s.accounts).not.toHaveBeenCalled()
|
|
}
|
|
})
|
|
it("refuses missing approved service scopes and Convex before any adapter call", async () => {
|
|
for (const readiness of [
|
|
{
|
|
...simulatedBusinessAdmission,
|
|
observe: async () => ({
|
|
...(await simulatedBusinessAdmission.observe({} as never, {} as never)),
|
|
authorized: false,
|
|
}),
|
|
},
|
|
{ ...simulatedBusinessAdmission, profile: "convex" as const },
|
|
]) {
|
|
const s = fake()
|
|
const app = ledgerApp(s, true, readiness)
|
|
const t = await app.tenant("alpha")
|
|
await expect(t.call.callByName("ledger.accounts.list", { org: t.orgSlug })).rejects.toHaveProperty(
|
|
"problem.code",
|
|
readiness.profile === "convex" ? "service_profile_unsupported" : "service_permission_missing",
|
|
)
|
|
expect(s.accounts).not.toHaveBeenCalled()
|
|
}
|
|
})
|
|
it("allows member financial writes and owner account/reversal administration through the real caller", async () => {
|
|
const service = fake()
|
|
const app = ledgerApp(service)
|
|
const t = await app.tenant("alpha")
|
|
const member = await app.member(t, "writer@alpha.test", "member")
|
|
const tenant = { orgSlug: t.orgSlug } as IsolationTenant
|
|
for (const name of ["ledger.transactions.record", "ledger.transactions.post", "ledger.transactions.void"]) {
|
|
expect(await member.callByName(name, fixtureInput(name, tenant))).toHaveProperty(
|
|
"transactionId",
|
|
resultTransaction.transactionId,
|
|
)
|
|
}
|
|
for (const name of ["ledger.accounts.create", "ledger.accounts.update"]) {
|
|
expect(await t.call.callByName(name, fixtureInput(name, tenant))).toHaveProperty(
|
|
"accountId",
|
|
resultAccount.accountId,
|
|
)
|
|
}
|
|
expect(
|
|
await t.call.callByName("ledger.transactions.reverse", fixtureInput("ledger.transactions.reverse", tenant)),
|
|
).toHaveProperty("transactionId", resultTransaction.transactionId)
|
|
})
|
|
it("requires explicit financial confirmation and refuses extra body-selected authority", async () => {
|
|
const service = fake()
|
|
const app = ledgerApp(service)
|
|
const t = await app.tenant("alpha")
|
|
const input = fixtureInput("ledger.transactions.record", { orgSlug: t.orgSlug } as IsolationTenant) as Record<
|
|
string,
|
|
unknown
|
|
>
|
|
for (const invalid of [
|
|
{ ...input, confirmed: false },
|
|
{ ...input, appTenantId: "forged", platformOrg: "beta" },
|
|
]) {
|
|
await expect(t.call.callByName("ledger.transactions.record", invalid)).rejects.toHaveProperty(
|
|
"problem.status",
|
|
422,
|
|
)
|
|
}
|
|
expect(service.record).not.toHaveBeenCalled()
|
|
})
|
|
})
|