@@ -0,0 +1,144 @@
|
||||
// Every knowledge procedure a page calls, run as the kernel runs it: `createCaller` hands a handler the
|
||||
// container of its kind, and that container refuses a port above the kind. `knowledge.status` backs the
|
||||
// knowledge page and `knowledge.settings.get` the organization's settings page, so a handler that reaches
|
||||
// for an effect port from a read is a 500 `kind_violation` on those pages on every real stack. The ports
|
||||
// stay effects: what changes is what each handler asks for.
|
||||
|
||||
import { Container, type ProcedureKind, type TenantScope } from "@conusai-kit/core"
|
||||
import { createMemoryDataSource, DATA_SOURCE, OBJECT_STORE } from "@conusai-kit/data"
|
||||
import { files } from "@conusai-kit/files"
|
||||
import { fakeObjectStore } from "@conusai-kit/testing"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import {
|
||||
knowledgeStatusImpl,
|
||||
settingsDisableDocumentAiImpl,
|
||||
settingsEnableDocumentAiImpl,
|
||||
settingsGetImpl,
|
||||
} from "../adapters/procedures.js"
|
||||
import { platformKnowledgeReadiness } from "../adapters/readiness.js"
|
||||
import { KNOWLEDGE_ENTITIES } from "../contract/index.js"
|
||||
import {
|
||||
type DocumentAiPlatform,
|
||||
KNOWLEDGE_ENABLEMENT,
|
||||
KNOWLEDGE_READINESS,
|
||||
KNOWLEDGE_SEARCH,
|
||||
type KnowledgeScope,
|
||||
} from "../core/ports.js"
|
||||
|
||||
const scope: TenantScope & KnowledgeScope = {
|
||||
level: "project",
|
||||
actor: { userId: "01900000-0000-7000-0000-000000000009" },
|
||||
orgId: "01900000-0000-7000-0000-000000000001",
|
||||
workspaceId: "01900000-0000-7000-0000-000000000002",
|
||||
projectId: "01900000-0000-7000-0000-000000000003",
|
||||
}
|
||||
const projectInput = { org: "acme", workspace: "general", project: "demo" }
|
||||
const orgInput = { org: "acme" }
|
||||
|
||||
const processContainer = () => {
|
||||
const container = new Container()
|
||||
container.value(DATA_SOURCE, createMemoryDataSource({ entities: [...KNOWLEDGE_ENTITIES, files] }))
|
||||
container.value(OBJECT_STORE, fakeObjectStore())
|
||||
return container
|
||||
}
|
||||
|
||||
/** A handler as the kernel calls it (`createCaller`): with the container of its procedure's kind. */
|
||||
const run = (
|
||||
impl: { readonly kind: ProcedureKind; readonly handler: (call: never) => unknown },
|
||||
input: unknown,
|
||||
container: Container,
|
||||
) => impl.handler({ input, ctx: { container: container.restrict(impl.kind), scope } } as never)
|
||||
|
||||
const neverBuilt = () =>
|
||||
vi.fn((): never => {
|
||||
throw new Error("a port above this procedure's kind was built")
|
||||
})
|
||||
|
||||
const platform: DocumentAiPlatform = {
|
||||
checkEligibility: async () => ({ ok: true }),
|
||||
indexingQuota: async () => 1000,
|
||||
}
|
||||
|
||||
describe("the ports that leave the process", () => {
|
||||
it("stay refused to a read and a write", () => {
|
||||
const container = processContainer()
|
||||
container.value(KNOWLEDGE_ENABLEMENT, platform)
|
||||
container.register(KNOWLEDGE_SEARCH, neverBuilt())
|
||||
for (const kind of ["read", "write"] as const) {
|
||||
expect(() => container.restrict(kind).resolve(KNOWLEDGE_SEARCH)).toThrow(/cannot resolve knowledge.search/)
|
||||
expect(() => container.restrict(kind).resolve(KNOWLEDGE_ENABLEMENT)).toThrow(
|
||||
/cannot resolve knowledge.enablement/,
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("the knowledge page's knowledge.status", () => {
|
||||
it("answers through a read handler's container, and never builds the search service", async () => {
|
||||
const container = processContainer()
|
||||
const search = neverBuilt()
|
||||
container.register(KNOWLEDGE_SEARCH, search)
|
||||
await expect(run(knowledgeStatusImpl, projectInput, container)).resolves.toMatchObject({
|
||||
documents: { queued: 0, indexing: 0, indexed: 0, failed: 0, cancelled: 0 },
|
||||
})
|
||||
expect(search).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("the settings page's knowledge.settings", () => {
|
||||
it("get answers through a read handler's container, without the enablement platform", async () => {
|
||||
const container = processContainer()
|
||||
const enablement = neverBuilt()
|
||||
container.register(KNOWLEDGE_ENABLEMENT, enablement)
|
||||
await expect(run(settingsGetImpl, orgInput, container)).resolves.toEqual({
|
||||
enabled: false,
|
||||
backend: null,
|
||||
})
|
||||
expect(enablement).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("enable reaches the platform and stores the configuration", async () => {
|
||||
const container = processContainer()
|
||||
container.value(KNOWLEDGE_ENABLEMENT, platform)
|
||||
await expect(run(settingsEnableDocumentAiImpl, orgInput, container)).resolves.toEqual({
|
||||
enabled: true,
|
||||
backend: "platform",
|
||||
})
|
||||
await expect(run(settingsGetImpl, orgInput, container)).resolves.toEqual({ enabled: true, backend: "platform" })
|
||||
})
|
||||
|
||||
it("disable only flips the stored flag, without the enablement platform", async () => {
|
||||
const container = processContainer()
|
||||
const enablement = neverBuilt()
|
||||
container.register(KNOWLEDGE_ENABLEMENT, enablement)
|
||||
await expect(run(settingsDisableDocumentAiImpl, orgInput, container)).resolves.toEqual({ enabled: false })
|
||||
expect(enablement).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("what knowledge.status says of the backend", () => {
|
||||
it("says what the bound readiness says, and asks it with the request's scope", async () => {
|
||||
const container = processContainer()
|
||||
const readiness = vi.fn(async () => ({ state: "degraded" as const, reasons: ["the index is behind"] }))
|
||||
container.value(KNOWLEDGE_READINESS, { readiness })
|
||||
await expect(run(knowledgeStatusImpl, projectInput, container)).resolves.toMatchObject({
|
||||
service: { state: "degraded", reasons: ["the index is behind"] },
|
||||
})
|
||||
expect(readiness).toHaveBeenCalledWith(scope)
|
||||
})
|
||||
|
||||
it("says the backend is not bound in a process that has none", async () => {
|
||||
await expect(run(knowledgeStatusImpl, projectInput, processContainer())).resolves.toMatchObject({
|
||||
service: { state: "unavailable", reasons: ["the knowledge search service is not bound in this process"] },
|
||||
})
|
||||
})
|
||||
|
||||
it("reports the managed service failing to answer as unavailable, not as an error page", async () => {
|
||||
const container = processContainer()
|
||||
const client = { readiness: vi.fn(async () => Promise.reject(new Error("no answer\nstack"))) }
|
||||
container.value(KNOWLEDGE_READINESS, platformKnowledgeReadiness(client as never))
|
||||
await expect(run(knowledgeStatusImpl, projectInput, container)).resolves.toMatchObject({
|
||||
service: { state: "unavailable", reasons: [], refusal: "runtime_unavailable" },
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user