Files
template-app/test/module-inclusion.test.ts
T
2026-11-14 09:56:45 +00:00

163 lines
7.4 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
import { readFileSync } from "node:fs"
import { buildRegistry, type ModuleManifest, type Token } from "@conusai-kit/core"
import { generateConvex } from "@conusai-kit/data-convex"
import { guidesModule } from "@conusai-kit/guides" // kit:guides
import { supportModule } from "@conusai-kit/support" // kit:support
import { accountDataOf } from "@conusai-kit/tenancy"
import { describe, expect, it } from "vitest"
import { composedClientModules } from "../apps/web/src/ui/client-module-list.js"
import { helpFor } from "../apps/web/src/ui/help.js"
import { convexModules } from "../convex/lib/convexModules.mjs"
import { agentModule, assistantAppModule } from "../modules/agent/index.js" // kit:agent
import { custodyModule } from "../modules/custody.js"
import { documentsModule } from "../modules/documents/index.js" // kit:documents
import { moduleInclusion, OPTIONAL_MODULE_IDS, validateInclusion } from "../modules/inclusion.js"
import { appModules } from "../modules/index.js"
import { knowledgeModule } from "../modules/knowledge/index.js" // kit:knowledge
import { modulePages } from "../modules/pages.js"
describe("fresh module admission", () => {
it.runIf(moduleInclusion.everAdopted.length === 0)(
"registers no optional procedure, job, permission, page or rights obligation",
() => {
expect(moduleInclusion).toEqual({ schemaVersion: 1, selected: [], everAdopted: [] })
const config = JSON.parse(readFileSync(new URL("../conusai.json", import.meta.url), "utf8"))
expect(
config.token.permissions.filter((scope: string) =>
/^(ocr:|ai:|ai_tools:|knowledge[.:]|guides:|support:)/.test(scope),
),
).toEqual([])
const registry = buildRegistry([...appModules])
expect(appModules.some((m) => OPTIONAL_MODULE_IDS.some((id) => id === m.id) || m.id === "assistant")).toBe(false)
expect(Object.keys(modulePages)).toEqual(["notes"])
expect(accountDataOf(registry).some((share) => ["guides", "support", "assistant"].includes(share.key))).toBe(
false,
)
},
)
it.runIf(moduleInclusion.everAdopted.length === 0)("mounts no optional help section/client", () => {
expect(
helpFor({ locale: "en" } as never, { title: "Help", sections: "Sections", guides: "Guides", support: "Support" })
.any,
).toBe(false)
expect(composedClientModules.modules).toEqual([])
expect(composedClientModules.providers).toEqual([])
expect(composedClientModules.slots).toEqual([])
})
it("refuses unbounded fields, IDs, versions and selected-without-custody", () => {
for (const value of [
{ schemaVersion: 2, selected: [], everAdopted: [] },
{ schemaVersion: 1, selected: ["agent"], everAdopted: [] },
{ schemaVersion: 1, selected: [], everAdopted: ["customer"] },
{ schemaVersion: 1, selected: [], everAdopted: [], subject: "synthetic" },
])
expect(() => validateInclusion(value)).toThrow()
})
})
describe("retained manifests", () => {
it.each<ModuleManifest>([
documentsModule, // kit:documents
knowledgeModule, // kit:knowledge
agentModule, // kit:agent
assistantAppModule, // kit:agent
guidesModule, // kit:guides
supportModule, // kit:support
])("retains $id schema under its original identity without interactive admission", (original) => {
const retained = custodyModule(original)
expect(retained.id).toBe(original.id)
expect(retained.repositories).toBe(original.repositories)
expect(retained.migrations).toBe(original.migrations)
for (const key of [
"procedures",
"permissions",
"collections",
"ui",
"serviceRequirements",
"extensionPoints",
] as const)
expect(retained[key]).toBeUndefined()
expect((retained.extends ?? []).every((entry) => entry.point.id === "tenancy.account")).toBe(true)
})
it("retains exactly the original rights hooks and retention/removal jobs", () => {
const withRights: ModuleManifest[] = [
guidesModule, // kit:guides
supportModule, // kit:support
assistantAppModule, // kit:agent
]
for (const manifest of withRights)
expect(custodyModule(manifest).extends).toEqual(
manifest.extends?.filter((entry) => entry.point.id === "tenancy.account"),
)
// kit:agent:begin
expect(Object.keys(custodyModule(agentModule).jobs ?? {})).toEqual(["agent.runs.expire"])
expect(Object.keys(custodyModule(assistantAppModule).jobs ?? {})).toEqual(["assistant.conversations.expire"])
const schedules = custodyModule(agentModule).schedules
expect(typeof schedules === "function" ? schedules({}) : []).toHaveLength(1)
// kit:agent:end
// kit:knowledge:begin
const retained = custodyModule(knowledgeModule)
expect(Object.keys(retained.jobs ?? {})).toEqual(["knowledge.remove"])
expect(retained.events?.subscribes?.map((entry) => entry.event.name)).toEqual([
"files.FileDeleted",
"knowledge.documentRemoveRequested",
])
// kit:knowledge:end
})
})
it.runIf(moduleInclusion.everAdopted.length === 0)(
"does not construct optional provider ports on a fresh web/worker boot",
async () => {
const { Container } = await import("@conusai-kit/core")
const { bindWebPorts } = await import("../apps/web/src/server/ports.js")
const { bindModulePorts } = await import("../apps/worker/src/ports.js")
const optionalPorts: Token<unknown>[] = [
(await import("../modules/documents/index.js")).DOCUMENT_OCR, // kit:documents
(await import("@conusai-kit/guides")).GUIDES_PLATFORM, // kit:guides
(await import("@conusai-kit/support")).SUPPORT_PLATFORM, // kit:support
(await import("../modules/knowledge/index.js")).KNOWLEDGE_SEARCH, // kit:knowledge
]
const platform = {
mode: "fake",
deployment: { CONUSAI_API_URL: "http://127.0.0.1:1/api", CONUSAI_API_TOKEN: "synthetic" },
objectStore: {},
audit: {},
usage: {},
}
for (const key of ["models", "documents", "model"])
Object.defineProperty(platform, key, {
get: () => {
throw new Error("optional provider constructed at boot")
},
})
const registry = buildRegistry([...appModules])
for (const bind of [bindWebPorts, bindModulePorts]) {
const container = new Container()
bind(container, platform as never, registry)
for (const token of optionalPorts) expect(container.has(token)).toBe(false)
}
},
)
describe("committed Convex projection", () => {
const root = new URL("..", import.meta.url)
it("commits generated wrappers that forward explicit idempotency keys to the kernel", () => {
// The same profile filter as below: the Postgres-only modules are not Convex-deployed yet.
const convexBound = appModules.filter(
(module) => module.profiles === undefined || module.profiles.includes("convex"),
)
const registry = buildRegistry([...convexModules(convexBound)])
const generated = generateConvex(registry, {
header: "// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0\n",
})
for (const file of generated) {
expect(readFileSync(new URL(`convex/${file.path}`, root), "utf8"), file.path).toBe(file.content)
}
const proposal = generated.find(({ path }) => path === "generated/proposals.ts")
expect(proposal?.content).toContain(
'ctx.run("proposals.proposal.create", input, idempotencyKey === undefined ? undefined : { idempotencyKey })',
)
})
})