// A project's automatic PDF indexing (template lesson core-11): an administrator turns it on into an // existing collection, and is recorded as the one whose permission each indexing job re-checks; off // is no row. Another project's collection is not found, and a project with knowledge off refuses. import type { TenantScope } from "@conusai-kit/core" import { createMemoryDataSource } from "@conusai-kit/data" import { DEFAULT_FILES_POLICY, files } from "@conusai-kit/files" import { fakeObjectStore } from "@conusai-kit/testing" import { describe, expect, it } from "vitest" import { autoIndex, collections, KNOWLEDGE_ENTITIES } from "../contract/index.js" import { getAutoIndex, setAutoIndex } from "../core/auto-index.js" import type { KnowledgeDeps } from "../core/ports.js" const ADMIN = "01900000-0000-7000-0000-000000000009" const scope: TenantScope = { level: "project", actor: { userId: ADMIN }, orgId: "01900000-0000-7000-0000-000000000001", workspaceId: "01900000-0000-7000-0000-000000000002", projectId: "01900000-0000-7000-0000-000000000003", } const otherProject: TenantScope = { ...scope, projectId: "01900000-0000-7000-0000-000000000004" } function makeDeps(enabled = true) { const data = createMemoryDataSource({ entities: [...KNOWLEDGE_ENTITIES, files] }) const deps: KnowledgeDeps & { now: () => number } = { data, files: { data, store: fakeObjectStore(), policy: DEFAULT_FILES_POLICY }, flags: { enabled: async () => enabled }, now: () => Date.parse("2026-11-09T19:00:00.000Z"), } return { data, deps } } describe("automatic PDF indexing of a project", () => { it("is off until an administrator names a collection, and records who turned it on", async () => { const { data, deps } = makeDeps() await data.forScope(scope).of(collections).insert({ name: "contracts", createdBy: ADMIN }) expect(await getAutoIndex(deps, scope)).toEqual({ collectionName: null }) expect(await setAutoIndex(deps, scope, "contracts")).toEqual({ collectionName: "contracts" }) expect(await getAutoIndex(deps, scope)).toEqual({ collectionName: "contracts" }) const [row] = (await data.forScope(scope).of(autoIndex).list({ limit: 10 })).items expect(row).toMatchObject({ collectionName: "contracts", enabledBy: ADMIN, enabledAt: "2026-11-09T19:00:00.000Z" }) // Another project of the same organization is not opted in. expect(await getAutoIndex(deps, otherProject)).toEqual({ collectionName: null }) }) it("keeps one row per project: naming another collection moves it, and off removes it", async () => { const { data, deps } = makeDeps() for (const name of ["contracts", "invoices"]) await data.forScope(scope).of(collections).insert({ name, createdBy: ADMIN }) await setAutoIndex(deps, scope, "contracts") await setAutoIndex(deps, scope, "invoices") expect( (await data.forScope(scope).of(autoIndex).list({ limit: 10 })).items.map((row) => row.collectionName), ).toEqual(["invoices"]) expect(await setAutoIndex(deps, scope, null)).toEqual({ collectionName: null }) expect((await data.forScope(scope).of(autoIndex).list({ limit: 10 })).items).toEqual([]) // Off when already off writes nothing and answers the same. expect(await setAutoIndex(deps, scope, null)).toEqual({ collectionName: null }) }) it("answers another project's collection as not found, and refuses while knowledge is off", async () => { const { data, deps } = makeDeps() await data.forScope(otherProject).of(collections).insert({ name: "theirs", createdBy: ADMIN }) await expect(setAutoIndex(deps, scope, "theirs")).rejects.toMatchObject({ status: 404 }) const off = makeDeps(false) await off.data.forScope(scope).of(collections).insert({ name: "contracts", createdBy: ADMIN }) await expect(setAutoIndex(off.deps, scope, "contracts")).rejects.toMatchObject({ status: 403 }) expect((await off.data.forScope(scope).of(autoIndex).list({ limit: 10 })).items).toEqual([]) }) })