import { syntheticNativeReadiness } from "../../../test/support/native-readiness.js" // A refusal of the platform's knowledge service reaches a person as one typed code: the procedures end to // end, on the real module, with the service's client refusing underneath; and each code a form looks up. import { AppError, buildRegistry, Container, createCaller, type TransportCaller } from "@conusai-kit/core" import { createMemoryDataSource, DATA_SOURCE, entitiesOf, OBJECT_STORE, SYSTEM_DATA } from "@conusai-kit/data" import { FILES_REGISTRY, filesModule } from "@conusai-kit/files" import { createI18n } from "@conusai-kit/i18n" import { KnowledgeError, type KnowledgeReasonCode } from "@conusai-kit/platform" import { createTenancyHooks, TENANCY, tenancyModule, users } from "@conusai-kit/tenancy" import { fakeObjectStore } from "@conusai-kit/testing" import { describe, expect, it } from "vitest" import { knowledgeRefusal } from "../adapters/refusals.js" import { KNOWLEDGE_REFUSALS, type KnowledgeRefusal } from "../contract/index.js" import { KNOWLEDGE_ENABLEMENT, KNOWLEDGE_FLAGS, KNOWLEDGE_READINESS, KNOWLEDGE_SEARCH, type KnowledgeSearchService, } from "../core/ports.js" import { knowledgeModule } from "../index.js" import { refusalLabels, refusalOf } from "../ui/labels.js" async function app( service: KnowledgeSearchService, options: { flags?: boolean } = {}, ): Promise<{ call: TransportCaller; slugs: Record }> { const registry = buildRegistry([tenancyModule, filesModule, knowledgeModule]) const data = createMemoryDataSource({ entities: entitiesOf(registry, "postgres") }) const root = new Container() root.value(DATA_SOURCE, data) root.value(SYSTEM_DATA, data) root.value(OBJECT_STORE, fakeObjectStore()) root.value(FILES_REGISTRY, registry) root.value(KNOWLEDGE_SEARCH, service) root.value(KNOWLEDGE_READINESS, service) root.value(KNOWLEDGE_ENABLEMENT, { checkEligibility: async () => ({ ok: true }), indexingQuota: async () => 1000 }) if (options.flags !== false) root.value(KNOWLEDGE_FLAGS, { enabled: () => true }) const user = await data.global(users).insert({ issuer: "https://id.example.test", subject: "owner", email: "owner@example.test", displayName: "Owner", locale: "en", }) const container = root.child() const tenancy = createTenancyHooks({ data, registry }) container.value(TENANCY, tenancy) const call = createCaller( { registry, container, tenancy, serviceReadiness: syntheticNativeReadiness }, { actor: { userId: user.id } }, ) await call.callByName("tenancy.org.create", { slug: "acme", name: "Acme" }) await call.callByName("tenancy.workspace.create", { org: "acme", slug: "ws", name: "WS", visibility: "org" }) await call.callByName("tenancy.project.create", { org: "acme", workspace: "ws", slug: "p", name: "P", moduleKey: "knowledge", }) return { call, slugs: { org: "acme", workspace: "ws", project: "p" } } } const refusing = (reason: KnowledgeReasonCode): KnowledgeSearchService => ({ readiness: async () => ({ state: "disabled", reasons: [], refusal: "plan_required" }), index: async () => undefined, search: async () => { throw new KnowledgeError(reason, "the service's own words") }, remove: async () => undefined, }) describe("a refused search", () => { it.each([ ["plan_required", "plan_required", 403], ["feature_disabled", "feature_disabled", 403], ["quota_exhausted", "quota_exhausted", 429], ["budget_exhausted", "budget_exhausted", 429], ["forbidden", "scope_missing", 503], ["indexing", "indexing", 503], ["approval_required", "approval_required", 403], ["runtime_unavailable", "runtime_unavailable", 503], ["unauthenticated", "runtime_unavailable", 503], ] as const)("by %s answers %s", async (reason, code, status) => { const { call, slugs } = await app(refusing(reason)) await call.callByName("knowledge.collection.create", { ...slugs, name: "contracts" }) const refused = await call .callByName("knowledge.search", { ...slugs, collectionName: "contracts", query: "anything" }) .catch((error: unknown) => error) expect(refused).toBeInstanceOf(AppError) expect((refused as AppError).problem).toMatchObject({ code, status }) }) it("leaves a resource the service does not hold as not found, and a programming error as it is", () => { const missing = knowledgeRefusal(new KnowledgeError("unavailable_resource", "no such collection")) expect((missing as AppError).problem.status).toBe(404) const bug = new TypeError("not a refusal") expect(knowledgeRefusal(bug)).toBe(bug) }) }) describe("the status a page reads", () => { it("carries the service's refusal through the read procedure", async () => { const { call, slugs } = await app(refusing("plan_required")) expect(await call.callByName("knowledge.status", slugs)).toMatchObject({ enabled: true, service: { state: "disabled", reasons: [], refusal: "plan_required" }, }) }) }) describe("enabling document AI", () => { const ready: KnowledgeSearchService = { readiness: async () => ({ state: "ready", reasons: [] }), index: async () => undefined, search: async () => [], remove: async () => undefined, } it("turns knowledge on for the organization's projects, and disabling turns it off again", async () => { const { call, slugs } = await app(ready, { flags: false }) const enabled = async () => ((await call.callByName("knowledge.status", slugs)) as { enabled: boolean }).enabled expect(await enabled()).toBe(false) await call.callByName("knowledge.settings.enableDocumentAi", { org: slugs.org }) expect(await enabled()).toBe(true) await call.callByName("knowledge.settings.disableDocumentAi", { org: slugs.org }) expect(await enabled()).toBe(false) }) }) describe("a form's refusal", () => { const t = (key: string) => key it("names every typed refusal, and leaves any other error to the form's own text", () => { const labels = refusalLabels(t) expect(Object.keys(labels).sort()).toEqual([...KNOWLEDGE_REFUSALS].sort()) const refused = (code: KnowledgeRefusal) => ({ problem: { code } }) expect(refusalOf(labels, refused("scope_missing"))).toEqual({ code: "scope_missing", text: "refusals.scope_missing", }) expect(refusalOf(labels, { problem: { code: "not_found" } })).toBeUndefined() expect(refusalOf(labels, new Error("boom"))).toBeUndefined() }) it("has a message in both languages, the Lithuanian one its own", () => { const catalogues = buildRegistry([tenancyModule, filesModule, knowledgeModule]).catalogues const en = createI18n("en", { en: { knowledge: catalogues.en?.knowledge ?? {} } }) const lt = createI18n("lt", { lt: { knowledge: catalogues.lt?.knowledge ?? {} } }) for (const code of KNOWLEDGE_REFUSALS) { const english = en.message(`knowledge.refusals.${code}`) expect(english, code).not.toContain("refusals.") expect(lt.message(`knowledge.refusals.${code}`), code).not.toBe(english) } }) })