This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { createMemoryDataSource } from "@conusai-kit/data"
|
||||
import { DEFAULT_FILES_POLICY, type FilesDeps, files } from "@conusai-kit/files"
|
||||
import type { Knowledge, KnowledgeSearchHit } from "@conusai-kit/platform"
|
||||
import { fakeObjectStore } from "@conusai-kit/testing"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { platformKnowledgeSearch } from "../adapters/platform-knowledge.js"
|
||||
import type { KnowledgeScope } from "../core/ports.js"
|
||||
|
||||
const scope: 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 otherScope: KnowledgeScope = {
|
||||
...scope,
|
||||
projectId: "01900000-0000-7000-0000-000000000004",
|
||||
}
|
||||
const storageKey = `files/${scope.workspaceId}/${scope.projectId}/01900000-0000-7000-0000-000000000100`
|
||||
const otherStorageKey = `files/${otherScope.workspaceId}/${otherScope.projectId}/01900000-0000-7000-0000-000000000101`
|
||||
const pathname = `org/${scope.orgId}/${storageKey}`
|
||||
|
||||
interface ClientStub {
|
||||
indexed: string[]
|
||||
removed: string[]
|
||||
searches: Array<Parameters<Knowledge["search"]>[0]>
|
||||
results: readonly KnowledgeSearchHit[]
|
||||
}
|
||||
|
||||
function knowledgeClient(stub: ClientStub): Knowledge {
|
||||
return {
|
||||
readiness: async () => ({ state: "ready", reasons: [], checkedAt: new Date().toISOString() }),
|
||||
createCollection: async (input) => input,
|
||||
search: async (input) => {
|
||||
stub.searches.push(input)
|
||||
return stub.results
|
||||
},
|
||||
index: async (input) => {
|
||||
stub.indexed.push(input.documentRef)
|
||||
return { jobId: "job-1" }
|
||||
},
|
||||
job: async (jobId) => ({ jobId, status: "succeeded", progress: 1, generation: 1, error: null }),
|
||||
cancel: async () => undefined,
|
||||
remove: async (documentRef) => {
|
||||
stub.removed.push(documentRef)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function stub(results: readonly KnowledgeSearchHit[] = []): ClientStub {
|
||||
return { indexed: [], removed: [], searches: [], results }
|
||||
}
|
||||
|
||||
function makeFiles() {
|
||||
const data = createMemoryDataSource({ entities: [files] })
|
||||
const deps: FilesDeps = { data, store: fakeObjectStore(), policy: DEFAULT_FILES_POLICY }
|
||||
return { data, deps }
|
||||
}
|
||||
|
||||
async function seedFile(
|
||||
data: ReturnType<typeof createMemoryDataSource>,
|
||||
atScope: KnowledgeScope,
|
||||
key: string,
|
||||
filename = "contract.txt",
|
||||
): Promise<string> {
|
||||
const row = await data.forScope(atScope).of(files).insert({
|
||||
status: "stored",
|
||||
storageKey: key,
|
||||
filename,
|
||||
contentType: "text/plain",
|
||||
size: 17,
|
||||
sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
uploadedBy: atScope.actor.userId,
|
||||
storedAt: "2026-10-24T00:00:00.000Z",
|
||||
uploadId: null,
|
||||
declaredSize: null,
|
||||
partSize: null,
|
||||
deletingAt: null,
|
||||
})
|
||||
return row.id
|
||||
}
|
||||
|
||||
describe("the managed knowledge Blob reference", () => {
|
||||
it("indexes and removes by Blob pathname rather than opaque file id", async () => {
|
||||
const { data, deps } = makeFiles()
|
||||
const fileId = await seedFile(data, scope, storageKey)
|
||||
const client = stub()
|
||||
const service = platformKnowledgeSearch(knowledgeClient(client), deps)
|
||||
|
||||
await service.index(scope, {
|
||||
collectionName: "contracts",
|
||||
fileId,
|
||||
filename: "contract.txt",
|
||||
bytes: new TextEncoder().encode("managed knowledge"),
|
||||
})
|
||||
await service.remove(scope, { collectionName: "contracts", fileId })
|
||||
|
||||
expect(client.indexed).toEqual([pathname])
|
||||
expect(client.removed).toEqual([pathname])
|
||||
})
|
||||
|
||||
it("maps citations through scoped file rows and drops foreign or stale references", async () => {
|
||||
const { data, deps } = makeFiles()
|
||||
const fileId = await seedFile(data, scope, storageKey)
|
||||
await seedFile(data, otherScope, otherStorageKey, "other-project.txt")
|
||||
const client = stub([
|
||||
{ documentRef: pathname, snippet: "own snippet", score: 0.9, citations: [{ page: 1 }] },
|
||||
{
|
||||
documentRef: `org/${scope.orgId}/${otherStorageKey}`,
|
||||
snippet: "other project snippet",
|
||||
score: 0.8,
|
||||
citations: [],
|
||||
},
|
||||
{
|
||||
documentRef: `org/01900000-0000-7000-0000-000000000777/${storageKey}`,
|
||||
snippet: "other organization snippet",
|
||||
score: 0.7,
|
||||
citations: [],
|
||||
},
|
||||
{
|
||||
documentRef: `org/${scope.orgId}/files/${scope.workspaceId}/${scope.projectId}/01900000-0000-7000-0000-000000000200`,
|
||||
snippet: "stale snippet",
|
||||
score: 0.6,
|
||||
citations: [],
|
||||
},
|
||||
])
|
||||
const service = platformKnowledgeSearch(knowledgeClient(client), deps)
|
||||
|
||||
const hits = await service.search(scope, {
|
||||
collectionName: "contracts",
|
||||
query: "contract",
|
||||
limit: 10,
|
||||
actorId: scope.actor.userId,
|
||||
})
|
||||
|
||||
expect(hits).toEqual([
|
||||
{
|
||||
fileId,
|
||||
filename: "contract.txt",
|
||||
collectionName: "contracts",
|
||||
score: 0.9,
|
||||
chunk: 1,
|
||||
snippet: "own snippet",
|
||||
},
|
||||
])
|
||||
expect(client.searches).toHaveLength(1)
|
||||
expect(client.searches[0]).toMatchObject({ collection: "contracts", query: "contract", limit: 10 })
|
||||
expect(client.searches[0]).not.toHaveProperty("tenant")
|
||||
expect(client.searches[0]).not.toHaveProperty("ownerRef")
|
||||
})
|
||||
|
||||
it("refuses a foreign file id before any managed index call", async () => {
|
||||
const { data, deps } = makeFiles()
|
||||
const foreignFileId = await seedFile(data, otherScope, otherStorageKey)
|
||||
const client = stub()
|
||||
const service = platformKnowledgeSearch(knowledgeClient(client), deps)
|
||||
|
||||
await expect(
|
||||
service.index(scope, {
|
||||
collectionName: "contracts",
|
||||
fileId: foreignFileId,
|
||||
filename: "other-project.txt",
|
||||
bytes: new TextEncoder().encode("foreign content"),
|
||||
}),
|
||||
).rejects.toThrow()
|
||||
expect(client.indexed).toEqual([])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user