110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
// Synthetic injected port; canonical caller/tenancy checks, not live gateway or persistence.
|
|
import {
|
|
type Actor,
|
|
buildRegistry,
|
|
Container,
|
|
createCaller,
|
|
type ServiceReadiness,
|
|
type TransportCaller,
|
|
} 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 { simulatedBusinessAdmission } from "../../../test/helpers/business-admission.js"
|
|
import { LEDGER_SERVICE, type LedgerService } from "../core/ports.js"
|
|
import { ledgerModule } from "../module.js"
|
|
|
|
/** The platform project of the fake deployment. */
|
|
export const BINDING_ID = "01900000-0000-7000-8000-000000000001"
|
|
|
|
export interface Tenant {
|
|
readonly owner: Actor
|
|
readonly call: TransportCaller
|
|
readonly orgSlug: string
|
|
readonly orgId: string
|
|
readonly workspaceSlug: string
|
|
readonly workspaceId: string
|
|
readonly projectSlug: string
|
|
readonly projectId: string
|
|
readonly slugs: { org: string; workspace: string; project: string }
|
|
}
|
|
|
|
export function ledgerApp(service: LedgerService, enabled = true, ready: boolean | ServiceReadiness = true) {
|
|
const registry = buildRegistry([tenancyModule, ...(enabled ? [ledgerModule] : [])])
|
|
const entities = entitiesOf(registry, "postgres")
|
|
const data = createMemoryDataSource({ entities })
|
|
const root = new Container()
|
|
root.value(DATA_SOURCE, data)
|
|
root.value(SYSTEM_DATA, data)
|
|
root.value(LEDGER_SERVICE, service)
|
|
|
|
const caller = (actor: Actor): TransportCaller => {
|
|
const tenancy = createTenancyHooks({ data, registry })
|
|
const container = root.child()
|
|
container.value(TENANCY, tenancy)
|
|
return createCaller(
|
|
{
|
|
registry,
|
|
container,
|
|
tenancy,
|
|
...(ready ? { serviceReadiness: ready === true ? simulatedBusinessAdmission : ready } : {}),
|
|
},
|
|
{ actor },
|
|
)
|
|
}
|
|
|
|
const signUp = async (email: string): Promise<Actor> => {
|
|
const user = await data.global(users).insert({
|
|
issuer: "https://id.example.test",
|
|
subject: email,
|
|
email,
|
|
displayName: email.split("@")[0] ?? email,
|
|
locale: "en",
|
|
})
|
|
return { userId: user.id }
|
|
}
|
|
|
|
/** An organization with one workspace and one project, owned by a new user. */
|
|
const tenant = async (slug: string): Promise<Tenant> => {
|
|
const owner = await signUp(`owner@${slug}.test`)
|
|
const call = caller(owner)
|
|
const org = (await call.callByName("tenancy.org.create", { slug, name: slug })) as { orgId: string; slug: string }
|
|
const workspace = (await call.callByName("tenancy.workspace.create", {
|
|
org: slug,
|
|
slug: `ws-${slug}`,
|
|
name: `Workspace ${slug}`,
|
|
visibility: "org",
|
|
})) as { workspaceId: string; slug: string }
|
|
const project = (await call.callByName("tenancy.project.create", {
|
|
org: slug,
|
|
workspace: workspace.slug,
|
|
slug: `p-${slug}`,
|
|
name: `Project ${slug}`,
|
|
moduleKey: "ledger",
|
|
})) as { projectId: string; slug: string }
|
|
return {
|
|
owner,
|
|
call,
|
|
orgSlug: org.slug,
|
|
orgId: org.orgId,
|
|
workspaceSlug: workspace.slug,
|
|
workspaceId: workspace.workspaceId,
|
|
projectSlug: project.slug,
|
|
projectId: project.projectId,
|
|
slugs: { org: org.slug, workspace: workspace.slug, project: project.slug },
|
|
}
|
|
}
|
|
|
|
/** A second user who joins `t`'s organization with the org role `role`. */
|
|
const member = async (t: Tenant, email: string, role: "viewer" | "member"): Promise<TransportCaller> => {
|
|
const invite = (await t.call.callByName("tenancy.member.invite", { org: t.orgSlug, email, role })) as {
|
|
token: string
|
|
}
|
|
const actor = await signUp(email)
|
|
const call = caller(actor)
|
|
await call.callByName("tenancy.invitation.accept", { org: t.orgSlug, token: invite.token })
|
|
return call
|
|
}
|
|
|
|
return { registry, data, caller, signUp, tenant, member }
|
|
}
|