// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // Memory app + injected service port. Proves app membership/roles, never external partition isolation. import { type Actor, buildRegistry, Container, createCaller } from "@conusai-kit/core" import { createMemoryDataSource, DATA_SOURCE, entitiesOf, SYSTEM_DATA } from "@conusai-kit/data" import { createTenancyHooks, TENANCY, tenancyModule, users } from "@conusai-kit/tenancy" import { describe, expect, it, vi } from "vitest" import { simulatedBusinessAdmission } from "../../../test/helpers/business-admission.js" import { INVOICING_SERVICE, type InvoicingService } from "../core/ports.js" import { invoicingModule } from "../module.js" import { id, invoiceId, row, terms } from "./fixture.js" import { invoicingIsolationFixtures } from "./isolation.js" async function fixture() { const registry = buildRegistry([tenancyModule, invoicingModule]) const data = createMemoryDataSource({ entities: entitiesOf(registry, "postgres") }) const root = new Container() root.value(DATA_SOURCE, data) root.value(SYSTEM_DATA, data) const port: InvoicingService = { list: vi.fn(async () => ({ rows: [row] })), get: vi.fn(async () => row), series: vi.fn(async () => ({ rows: [] })), ubl: vi.fn(async () => ({ filename: "fixture.xml", xml: "" })), draft: vi.fn(async () => row), update: vi.fn(async () => row), transition: vi.fn(async () => row), createSeries: vi.fn(async () => { throw new Error("not used") }), } root.value(INVOICING_SERVICE, port) const caller = (actor: Actor) => { const tenancy = createTenancyHooks({ data, registry }) const container = root.child() container.value(TENANCY, tenancy) return createCaller({ registry, container, tenancy, serviceReadiness: simulatedBusinessAdmission }, { actor }) } const user = async (name: string) => { const row = await data.global(users).insert({ issuer: "https://id.example.test", subject: name, email: `${name}@example.test`, displayName: name, locale: "en", }) return { userId: row.id } } const owner = caller(await user("owner")) await owner.callByName("tenancy.org.create", { slug: "alpha", name: "Synthetic Alpha" }) const foreign = caller(await user("foreign")) await foreign.callByName("tenancy.org.create", { slug: "beta", name: "Synthetic Beta" }) const invite = (await owner.callByName("tenancy.member.invite", { org: "alpha", email: "viewer@example.test", role: "viewer", })) as { token: string } const viewer = caller(await user("viewer")) await viewer.callByName("tenancy.invitation.accept", { org: "alpha", token: invite.token }) return { owner, foreign, viewer, port } } describe("Invoicing app dispatch (simulated service)", () => { it("denies every foreign-organization operation before reaching a service port", async () => { const { owner, port } = await fixture() for (const [name, fixture] of Object.entries(invoicingIsolationFixtures)) { const input = fixture.input({ orgSlug: "beta" } as never) await expect(owner.callByName(name, input)).rejects.toMatchObject({ code: "not_found" }) } for (const method of Object.values(port)) expect(method).not.toHaveBeenCalled() }) it("allows viewer reads but refuses fiscal writes and series administration", async () => { const { viewer, port } = await fixture() expect(await viewer.callByName("invoicing.invoice.list", { org: "alpha" })).toMatchObject({ rows: [{ invoiceId }] }) for (const name of [ "invoicing.invoice.draft", "invoicing.invoice.update", "invoicing.invoice.issue", "invoicing.invoice.credit", "invoicing.invoice.void", "invoicing.series.create", ]) { const fixture = invoicingIsolationFixtures[name] if (!fixture) throw new Error("fixture missing") await expect(viewer.callByName(name, fixture.input({ orgSlug: "alpha" } as never))).rejects.toMatchObject({ code: "not_found", }) } expect(port.draft).not.toHaveBeenCalled() expect(port.transition).not.toHaveBeenCalled() expect(port.createSeries).not.toHaveBeenCalled() }) it("routes an authorized draft with exact strings and a stable operation key", async () => { const { owner, port } = await fixture() const result = await owner.callByName("invoicing.invoice.draft", { org: "alpha", data: terms, requestId: id }) expect(result).toMatchObject({ invoiceId, revision: row.revision }) expect(port.draft).toHaveBeenCalledWith(terms, `invoicing:draft:${id}`) }) })