Files
dify/knowledge-fs/apps/admin/lib/api-client.test.ts
Jyong 4ee43b8afc chore: migrate knowledge-fs source tree
Import the committed KnowledgeFS snapshot dc4072ee302317145612087ce7440851dc329fd0 under knowledge-fs/ without its Git history, local IDE settings, or build artifacts.
2026-07-20 04:54:20 -04:00

1770 lines
60 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
createAdminApiClient,
getAdminApiBase,
getAdminPublicApiBase,
parseSseEvents,
} from "./api-client";
describe("createAdminApiClient", () => {
it("calls Hono health and knowledge-space APIs through a bounded shared client", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
if (request.url === "http://api.test/health") {
return Response.json({
components: { database: true },
ok: true,
runtime: "node-docker",
});
}
if (request.url === "http://api.test/knowledge-spaces?limit=2") {
return Response.json({
items: [
{
createdAt: "2026-05-11T00:00:00.000Z",
id: "space-1",
name: "Workspace",
slug: "workspace",
tenantId: "tenant-1",
updatedAt: "2026-05-11T00:00:00.000Z",
},
],
});
}
throw new Error(`Unexpected request ${request.method} ${request.url}`);
},
});
await expect(client.health()).resolves.toMatchObject({
ok: true,
runtime: "node-docker",
});
await expect(client.listKnowledgeSpaces({ limit: 2, token: "secret-token" })).resolves.toEqual({
items: [
expect.objectContaining({
id: "space-1",
slug: "workspace",
}),
],
});
expect(requests.map((request) => [request.method, request.url])).toEqual([
["GET", "http://api.test/health"],
["GET", "http://api.test/knowledge-spaces?limit=2"],
]);
expect(requests[1]?.headers.get("authorization")).toBe("Bearer secret-token");
});
it("loads KnowledgeSpace manifest and status summaries", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
if (request.url === "http://api.test/knowledge-spaces/space-1/manifest") {
return Response.json({
consistencyPolicy: { defaultClass: "path-consistent" },
manifestVersion: 1,
objectKeyPrefix: "tenant-1/spaces/space-1",
parserPolicyVersion: "default-v1",
projectionSetVersion: "projection-v1",
quotaPolicy: { maxRawDocumentBytes: null },
storageProvider: "memory-dev",
});
}
if (request.url === "http://api.test/knowledge-spaces/space-1/status") {
return Response.json({
runtime: { activeSessionCount: 2 },
storage: { documentCount: 3, rawDocumentBytes: 42 },
});
}
if (request.url === "http://api.test/knowledge-spaces/space-1/staged-commits?limit=5") {
return Response.json({
items: [
{
errorCode: "parser_timeout",
id: "commit-1",
idempotencyKey: "upload:doc-1",
operationType: "document-upload",
status: "failed-retryable",
updatedAt: "2026-05-27T11:01:00.000Z",
},
],
});
}
if (request.url === "http://api.test/knowledge-spaces/space-1/leases/active?limit=5") {
return Response.json({
items: [
{
expiresAt: "2026-05-27T11:30:00.000Z",
heartbeatAt: "2026-05-27T11:05:00.000Z",
id: "lease-1",
leaseType: "publish",
status: "active",
targetId: "commit-1",
targetType: "staged-commit",
virtualPath: "/sources/staged/doc-1",
},
],
});
}
if (request.url === "http://api.test/knowledge-spaces/space-1/fsck?check=raw-objects") {
return Response.json({
issues: [
{
code: "missing_raw_object",
message: "Raw object is missing.",
repairability: "manual",
severity: "error",
target: {
objectKey: "tenant-1/spaces/space-1/documents/missing.md",
type: "raw-object",
},
type: "missing-raw-object",
},
],
knowledgeSpaceId: "space-1",
scannedAt: "2026-05-27T11:05:00.000Z",
summary: { critical: 0, error: 1, info: 0, repairable: 0, scanned: 1, warning: 0 },
tenantId: "tenant-1",
});
}
if (request.url === "http://api.test/knowledge-spaces/space-1/gc/staged-objects") {
return Response.json(adminGcDryRun());
}
if (request.url === "http://api.test/knowledge-spaces/space-1/gc/staged-objects/execute") {
await expect(request.json()).resolves.toEqual({
candidates: [adminGcCandidate()],
});
return Response.json({
deleted: 1,
items: [
{
idempotencyKey: "staged-object:doc-1",
objectKey: "tenant-1/spaces/space-1/staging/doc-1.md",
status: "deleted",
},
],
skipped: 0,
tenantId: "tenant-1",
});
}
throw new Error(`Unexpected request ${request.method} ${request.url}`);
},
});
await expect(
client.getKnowledgeSpaceManifest({ knowledgeSpaceId: "space-1", token: "secret-token" }),
).resolves.toMatchObject({
manifestVersion: 1,
projectionSetVersion: "projection-v1",
});
await expect(
client.getKnowledgeSpaceStatus({ knowledgeSpaceId: "space-1", token: "secret-token" }),
).resolves.toMatchObject({
runtime: { activeSessionCount: 2 },
storage: { rawDocumentBytes: 42 },
});
await expect(
client.listStagedCommits({ knowledgeSpaceId: "space-1", limit: 5, token: "secret-token" }),
).resolves.toMatchObject({
items: [{ errorCode: "parser_timeout", id: "commit-1" }],
});
await expect(
client.listActiveLeases({ knowledgeSpaceId: "space-1", limit: 5, token: "secret-token" }),
).resolves.toMatchObject({
items: [{ id: "lease-1", leaseType: "publish", targetType: "staged-commit" }],
});
await expect(
client.getKnowledgeFsck({
check: "raw-objects",
knowledgeSpaceId: "space-1",
token: "secret-token",
}),
).resolves.toMatchObject({
issues: [{ code: "missing_raw_object", severity: "error" }],
summary: { error: 1, scanned: 1 },
});
await expect(
client.getStagedObjectGcDryRun({
knowledgeSpaceId: "space-1",
token: "secret-token",
}),
).resolves.toMatchObject({
candidates: [{ idempotencyKey: "staged-object:doc-1" }],
dryRunId: "gc-dry-run-1",
});
await expect(
client.executeStagedObjectGc({
candidates: [adminGcCandidate()],
dryRunId: "gc-dry-run-1",
idempotencyKey: "staged-object:doc-1",
knowledgeSpaceId: "space-1",
token: "secret-token",
}),
).resolves.toMatchObject({
deleted: 1,
items: [{ status: "deleted" }],
});
expect(requests.map((request) => [request.method, request.url])).toEqual([
["GET", "http://api.test/knowledge-spaces/space-1/manifest"],
["GET", "http://api.test/knowledge-spaces/space-1/status"],
["GET", "http://api.test/knowledge-spaces/space-1/staged-commits?limit=5"],
["GET", "http://api.test/knowledge-spaces/space-1/leases/active?limit=5"],
["GET", "http://api.test/knowledge-spaces/space-1/fsck?check=raw-objects"],
["GET", "http://api.test/knowledge-spaces/space-1/gc/staged-objects"],
["POST", "http://api.test/knowledge-spaces/space-1/gc/staged-objects/execute"],
]);
});
it("loads document outlines for the Admin outline browser", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
expect(request.url).toBe(
"http://api.test/knowledge-spaces/space-1/documents/doc-1/outline",
);
expect(request.headers.get("authorization")).toBe("Bearer secret-token");
return Response.json({
artifactHash: "c".repeat(64),
createdAt: "2026-06-22T00:00:00.000Z",
documentAssetId: "doc-1",
id: "outline-1",
knowledgeSpaceId: "space-1",
metadata: { builder: "deterministic-parse-artifact" },
nodes: [
{
childNodeIds: ["outline-child"],
children: [
{
childNodeIds: [],
children: [],
id: "outline-child",
level: 2,
metadata: {},
sectionPath: ["Guide", "Refunds"],
sourceElementIds: ["element-2"],
sourceNodeIds: [],
startPage: 2,
summary: "Refund approval summary.",
title: "Refunds",
titleLocation: {
confidence: 1,
pageNumber: 2,
source: "parser-heading",
startOffset: 20,
},
tocSource: "parser-heading",
},
],
id: "outline-root",
level: 1,
metadata: {},
sectionPath: ["Guide"],
sourceElementIds: ["element-1"],
sourceNodeIds: [],
startPage: 1,
summary: "Guide summary.",
title: "Guide",
tocSource: "parser-heading",
},
],
outlineVersion: "document-outline-v1",
parseArtifactId: "artifact-1",
version: 1,
});
},
});
await expect(
client.getDocumentOutline({
documentId: "doc-1",
knowledgeSpaceId: "space-1",
token: "secret-token",
}),
).resolves.toMatchObject({
documentAssetId: "doc-1",
nodes: [
{
children: [
{
sectionPath: ["Guide", "Refunds"],
startPage: 2,
summary: "Refund approval summary.",
},
],
title: "Guide",
},
],
});
expect(requests).toHaveLength(1);
});
it("loads document multimodal manifests for the Admin multimodal browser", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
expect(request.url).toBe(
"http://api.test/knowledge-spaces/space-1/documents/doc-1/multimodal",
);
expect(request.headers.get("authorization")).toBe("Bearer secret-token");
return Response.json({
artifactHash: "c".repeat(64),
createdAt: "2026-06-22T00:00:00.000Z",
documentAssetId: "doc-1",
id: "manifest-1",
items: [
{
assetRef: {
contentType: "image/png",
objectKey: "tenant-1/spaces/space-1/documents/doc-1/assets/item-1.png",
sha256: "a".repeat(64),
variants: {
thumbnail: {
contentType: "image/png",
height: 90,
objectKey:
"tenant-1/spaces/space-1/documents/doc-1/assets/item-1-thumbnail.png",
sha256: "b".repeat(64),
width: 120,
},
},
},
boundingBox: { height: 120, width: 240, x: 10, y: 20 },
caption: "Renewal trend chart",
endOffset: 420,
enrichment: {
asset: "provided",
caption: "provided",
ocr: "provided",
tableStructure: "unsupported",
visualEmbedding: "missing",
},
id: "item-1",
modality: "image",
ocrText: "Q1 renewals increased 12%",
pageNumber: 3,
parseElementId: "element-image-1",
sectionPath: ["Metrics", "Charts"],
sourceMetadata: {
enrichment: {
error: "provider quota exceeded",
provider: "failing-understanding",
status: "failed",
},
},
startOffset: 120,
textPreview: "Q1 renewals increased 12%",
title: "Renewal trend chart",
},
],
knowledgeSpaceId: "space-1",
manifestVersion: "document-multimodal-manifest-v1",
metadata: { modalityCounts: { image: 1 } },
parseArtifactId: "artifact-1",
version: 1,
});
},
});
await expect(
client.getDocumentMultimodalManifest({
documentId: "doc-1",
knowledgeSpaceId: "space-1",
token: "secret-token",
}),
).resolves.toMatchObject({
documentAssetId: "doc-1",
items: [
{
assetRef: {
contentType: "image/png",
objectKey: "tenant-1/spaces/space-1/documents/doc-1/assets/item-1.png",
sha256: "a".repeat(64),
variants: {
thumbnail: {
height: 90,
objectKey: "tenant-1/spaces/space-1/documents/doc-1/assets/item-1-thumbnail.png",
width: 120,
},
},
},
boundingBox: { height: 120, width: 240, x: 10, y: 20 },
caption: "Renewal trend chart",
endOffset: 420,
modality: "image",
pageNumber: 3,
sectionPath: ["Metrics", "Charts"],
sourceMetadata: {
enrichment: {
error: "provider quota exceeded",
provider: "failing-understanding",
status: "failed",
},
},
startOffset: 120,
},
],
manifestVersion: "document-multimodal-manifest-v1",
});
expect(requests).toHaveLength(1);
});
it("loads document multimodal assets for Admin previews", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
expect(request.url).toBe(
requests.length === 1
? "http://api.test/knowledge-spaces/space-1/documents/doc-1/multimodal/artifact-1%3A0%3Afigure-1/asset"
: "http://api.test/knowledge-spaces/space-1/documents/doc-1/multimodal/artifact-1%3A0%3Afigure-1/asset?variant=thumbnail",
);
expect(request.headers.get("authorization")).toBe("Bearer secret-token");
return new Response(
requests.length === 1 ? new Uint8Array([1, 2, 3]) : new Uint8Array([4, 5, 6]),
{
headers: {
"content-length": "3",
"content-type": "image/png",
"x-document-multimodal-item-id": "artifact-1:0:figure-1",
...(requests.length === 1
? {}
: { "x-document-multimodal-asset-variant": "thumbnail" }),
},
},
);
},
maxAssetBytes: 16,
});
const asset = await client.getDocumentMultimodalAsset({
documentId: "doc-1",
itemId: "artifact-1:0:figure-1",
knowledgeSpaceId: "space-1",
token: "secret-token",
});
const thumbnail = await client.getDocumentMultimodalAsset({
documentId: "doc-1",
itemId: "artifact-1:0:figure-1",
knowledgeSpaceId: "space-1",
token: "secret-token",
variant: "thumbnail",
});
expect(asset.contentType).toBe("image/png");
expect(asset.itemId).toBe("artifact-1:0:figure-1");
expect([...new Uint8Array(asset.bytes)]).toEqual([1, 2, 3]);
expect(thumbnail.contentType).toBe("image/png");
expect(thumbnail.itemId).toBe("artifact-1:0:figure-1");
expect([...new Uint8Array(thumbnail.bytes)]).toEqual([4, 5, 6]);
expect(requests).toHaveLength(2);
});
it("rejects empty document multimodal asset variants", async () => {
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async () =>
new Response(new Uint8Array([1]), {
headers: {
"content-length": "1",
"content-type": "image/png",
"x-document-multimodal-item-id": "artifact-1:0:figure-1",
},
}),
maxAssetBytes: 16,
});
await expect(
client.getDocumentMultimodalAsset({
documentId: "doc-1",
itemId: "artifact-1:0:figure-1",
knowledgeSpaceId: "space-1",
token: "secret-token",
variant: "",
}),
).rejects.toThrow("Admin API document multimodal asset variant must not be empty");
});
it("rejects empty document multimodal asset item ids", async () => {
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async () =>
new Response(new Uint8Array([1]), {
headers: {
"content-length": "1",
"content-type": "image/png",
"x-document-multimodal-item-id": "artifact-1:0:figure-1",
},
}),
maxAssetBytes: 16,
});
await expect(
client.getDocumentMultimodalAsset({
documentId: "doc-1",
itemId: "",
knowledgeSpaceId: "space-1",
token: "secret-token",
}),
).rejects.toThrow("Admin API document multimodal itemId must not be empty");
});
it("streams query SSE events and rejects unbounded inputs", async () => {
const client = createAdminApiClient({
baseUrl: "http://api.test",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
expect(request.method).toBe("POST");
expect(request.url).toBe("http://api.test/queries");
expect(request.headers.get("authorization")).toBe("Bearer read-token");
await expect(request.json()).resolves.toEqual({
knowledgeSpaceId: "space-1",
mode: "fast",
query: "What changed?",
});
return new Response(
[
'event: answer.delta\ndata: {"delta":"Hello"}',
'event: answer.done\ndata: {"finishReason":"stop"}',
"",
].join("\n\n"),
{ headers: { "content-type": "text/event-stream" } },
);
},
});
await expect(
client.streamQuery({
knowledgeSpaceId: "space-1",
mode: "fast",
query: "What changed?",
token: "read-token",
}),
).resolves.toEqual([
{ data: { delta: "Hello" }, event: "answer.delta" },
{ data: { finishReason: "stop" }, event: "answer.done" },
]);
expect(() => createAdminApiClient({ baseUrl: " " })).toThrow("Admin API baseUrl is required");
await expect(client.listKnowledgeSpaces({ limit: 101, token: "secret-token" })).rejects.toThrow(
"Admin API listKnowledgeSpaces limit must be between 1 and 100",
);
await expect(
client.streamQuery({
knowledgeSpaceId: "space-1",
query: "x".repeat(4_001),
token: "read-token",
}),
).rejects.toThrow("Admin API query exceeds maxQueryBytes=4000");
});
it("loads answer traces and query virtual evidence trees", async () => {
const requests: Request[] = [];
const traceId = "018f0d60-7a49-7cc2-9c1b-5b36f18f8a01";
const client = createAdminApiClient({
baseUrl: "http://api.test",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
if (request.url === `http://api.test/queries/${traceId}`) {
expect(request.headers.get("authorization")).toBe("Bearer read-token");
return Response.json({
createdAt: "2026-05-13T00:00:00.000Z",
evidenceBundleId: "018f0d60-7a49-7cc2-9c1b-5b36f18f8b01",
id: traceId,
knowledgeSpaceId: "space-1",
mode: "research",
query: "What changed?",
steps: [
{
metadata: { count: 4 },
name: "recall.candidates",
startedAt: "2026-05-13T00:00:00.000Z",
status: "ok",
},
],
});
}
if (request.url === `http://api.test/queries/${traceId}/evidence?limit=2`) {
return Response.json({
items: [
{
kind: "resource",
metadata: { score: 0.9 },
name: "roadmap.md#L4",
path: `/queries/${traceId}/evidence/roadmap.md`,
resourceType: "evidence-bundle",
targetId: "evidence-1",
},
],
path: `/queries/${traceId}/evidence`,
truncated: false,
});
}
throw new Error(`Unexpected request ${request.method} ${request.url}`);
},
});
await expect(client.getAnswerTrace({ token: "read-token", traceId })).resolves.toMatchObject({
id: traceId,
mode: "research",
steps: [{ name: "recall.candidates", metadata: { count: 4 } }],
});
await expect(
client.listQueryEvidence({ limit: 2, token: "read-token", traceId }),
).resolves.toMatchObject({
items: [{ name: "roadmap.md#L4", resourceType: "evidence-bundle" }],
truncated: false,
});
expect(requests.map((request) => [request.method, request.url])).toEqual([
["GET", `http://api.test/queries/${traceId}`],
["GET", `http://api.test/queries/${traceId}/evidence?limit=2`],
]);
});
it("rejects oversized SSE responses while reading stream chunks", async () => {
const client = createAdminApiClient({
baseUrl: "http://api.test",
fetch: async () =>
new Response(
new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("event: answer.delta\n"));
controller.enqueue(
new TextEncoder().encode('data: {"delta":"this is too large"}\n\n'),
);
controller.close();
},
}),
{ headers: { "content-type": "text/event-stream" } },
),
maxSseBytes: 16,
});
await expect(
client.streamQuery({
knowledgeSpaceId: "space-1",
query: "What changed?",
token: "read-token",
}),
).rejects.toThrow("Admin API SSE response exceeds maxSseBytes=16");
});
it("rejects oversized JSON responses while reading stream chunks", async () => {
const client = createAdminApiClient({
baseUrl: "http://api.test",
fetch: async () =>
new Response(
new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('{"items":['));
controller.enqueue(new TextEncoder().encode('"too large"'));
controller.close();
},
}),
{ headers: { "content-type": "application/json" } },
),
maxJsonBytes: 12,
});
await expect(client.listKnowledgeSpaces({ limit: 1, token: "secret-token" })).rejects.toThrow(
"Admin API JSON response exceeds maxJsonBytes=12",
);
});
it("uploads a single document and reads document ingestion state", async () => {
const requests: Request[] = [];
const asset = {
createdAt: "2026-05-11T00:00:00.000Z",
filename: "roadmap.md",
id: "asset-1",
knowledgeSpaceId: "space-1",
metadata: { traceId: "trace-1" },
mimeType: "text/markdown",
objectKey: "tenant/spaces/space-1/documents/asset-1/roadmap.md",
parserStatus: "parsed",
sha256: "a".repeat(64),
sizeBytes: 12,
version: 1,
};
const artifact = {
artifactHash: "b".repeat(64),
contentType: "text",
createdAt: "2026-05-11T00:00:00.000Z",
documentAssetId: "asset-1",
elements: [
{ id: "el-1", metadata: {}, sectionPath: ["Intro"], text: "Hello", type: "paragraph" },
],
id: "artifact-1",
metadata: { traceId: "trace-1" },
parser: "native-markdown",
version: 1,
};
const client = createAdminApiClient({
baseUrl: "http://api.test",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
if (
request.method === "POST" &&
request.url === "http://api.test/knowledge-spaces/space-1/documents"
) {
const body = await request.formData();
expect(request.headers.get("authorization")).toBe("Bearer write-token");
expect(body.get("sourceId")).toBe("source-1");
expect(body.get("file")).toBeInstanceOf(File);
return Response.json(asset, { status: 201 });
}
if (
request.method === "GET" &&
request.url ===
"http://api.test/knowledge-spaces/space-1/documents?limit=5&cursor=asset-1"
) {
expect(request.headers.get("authorization")).toBe("Bearer read-token");
return Response.json({ items: [asset], nextCursor: "asset-2" });
}
if (request.url === "http://api.test/knowledge-spaces/space-1/documents/asset-1") {
return Response.json(asset);
}
if (
request.url ===
"http://api.test/knowledge-spaces/space-1/documents/asset-1/parse-artifacts/1"
) {
return Response.json(artifact);
}
throw new Error(`Unexpected request ${request.method} ${request.url}`);
},
maxUploadBytes: 16,
});
await expect(
client.uploadDocument({
file: new File(["hello upload"], "roadmap.md", { type: "text/markdown" }),
knowledgeSpaceId: "space-1",
sourceId: "source-1",
token: "write-token",
}),
).resolves.toMatchObject({ filename: "roadmap.md", parserStatus: "parsed" });
await expect(
client.getDocument({
documentId: "asset-1",
knowledgeSpaceId: "space-1",
token: "read-token",
}),
).resolves.toMatchObject({ id: "asset-1" });
await expect(
client.listDocuments({
cursor: "asset-1",
knowledgeSpaceId: "space-1",
limit: 5,
token: "read-token",
}),
).resolves.toMatchObject({ items: [{ id: "asset-1" }], nextCursor: "asset-2" });
await expect(
client.getParseArtifact({
documentId: "asset-1",
knowledgeSpaceId: "space-1",
token: "read-token",
version: 1,
}),
).resolves.toMatchObject({ documentAssetId: "asset-1", elements: artifact.elements });
await expect(
client.uploadDocument({
file: new File(["x".repeat(17)], "large.md", { type: "text/markdown" }),
knowledgeSpaceId: "space-1",
token: "write-token",
}),
).rejects.toThrow("Admin API upload exceeds maxUploadBytes=16");
expect(requests.map((request) => [request.method, request.url])).toEqual([
["POST", "http://api.test/knowledge-spaces/space-1/documents"],
["GET", "http://api.test/knowledge-spaces/space-1/documents/asset-1"],
["GET", "http://api.test/knowledge-spaces/space-1/documents?limit=5&cursor=asset-1"],
["GET", "http://api.test/knowledge-spaces/space-1/documents/asset-1/parse-artifacts/1"],
]);
});
it("manages golden questions with bounded CRUD requests", async () => {
const requests: Request[] = [];
const goldenQuestion = {
createdAt: "2026-05-12T00:00:00.000Z",
expectedEvidenceIds: ["018f0d60-7a49-7cc2-9c1b-5b36f18f4a01"],
id: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a01",
knowledgeSpaceId: "space-1",
metadata: { owner: "qa" },
question: "What does the roadmap require?",
tags: ["roadmap", "phase-6"],
updatedAt: "2026-05-12T00:00:00.000Z",
};
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
if (request.url === "http://api.test/knowledge-spaces/space-1/golden-questions?limit=2") {
expect(request.method).toBe("GET");
expect(request.headers.get("authorization")).toBe("Bearer read-token");
return Response.json({ items: [goldenQuestion], nextCursor: "cursor-1" });
}
if (request.url === "http://api.test/knowledge-spaces/space-1/golden-questions") {
expect(request.method).toBe("POST");
expect(request.headers.get("authorization")).toBe("Bearer write-token");
await expect(request.json()).resolves.toEqual({
expectedEvidenceIds: ["018f0d60-7a49-7cc2-9c1b-5b36f18f4a01"],
metadata: { owner: "qa" },
question: "What does the roadmap require?",
tags: ["roadmap", "phase-6"],
});
return Response.json(goldenQuestion, { status: 201 });
}
if (
request.url ===
"http://api.test/knowledge-spaces/space-1/golden-questions/018f0d60-7a49-7cc2-9c1b-5b36f18f3a01"
) {
if (request.method === "GET") {
expect(request.headers.get("authorization")).toBe("Bearer read-token");
return Response.json(goldenQuestion);
}
if (request.method === "PATCH") {
expect(request.headers.get("authorization")).toBe("Bearer write-token");
await expect(request.json()).resolves.toEqual({
expectedEvidenceIds: [],
question: "Updated roadmap question",
tags: ["updated"],
});
return Response.json({ ...goldenQuestion, question: "Updated roadmap question" });
}
if (request.method === "DELETE") {
expect(request.headers.get("authorization")).toBe("Bearer write-token");
return new Response(null, { status: 204 });
}
}
throw new Error(`Unexpected request ${request.method} ${request.url}`);
},
maxListLimit: 2,
});
await expect(
client.listGoldenQuestions({
knowledgeSpaceId: "space-1",
limit: 2,
token: "read-token",
}),
).resolves.toMatchObject({
items: [{ question: "What does the roadmap require?", tags: ["roadmap", "phase-6"] }],
nextCursor: "cursor-1",
});
await expect(
client.createGoldenQuestion({
expectedEvidenceIds: ["018f0d60-7a49-7cc2-9c1b-5b36f18f4a01"],
knowledgeSpaceId: "space-1",
metadata: { owner: "qa" },
question: "What does the roadmap require?",
tags: ["roadmap", "phase-6"],
token: "write-token",
}),
).resolves.toMatchObject({ id: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a01" });
await expect(
client.getGoldenQuestion({
knowledgeSpaceId: "space-1",
questionId: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a01",
token: "read-token",
}),
).resolves.toMatchObject({ expectedEvidenceIds: ["018f0d60-7a49-7cc2-9c1b-5b36f18f4a01"] });
await expect(
client.updateGoldenQuestion({
expectedEvidenceIds: [],
knowledgeSpaceId: "space-1",
question: "Updated roadmap question",
questionId: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a01",
tags: ["updated"],
token: "write-token",
}),
).resolves.toMatchObject({ question: "Updated roadmap question" });
await expect(
client.deleteGoldenQuestion({
knowledgeSpaceId: "space-1",
questionId: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a01",
token: "write-token",
}),
).resolves.toBe(true);
await expect(
client.listGoldenQuestions({
knowledgeSpaceId: "space-1",
limit: 3,
token: "read-token",
}),
).rejects.toThrow("Admin API golden question list limit must be between 1 and 2");
await expect(
client.createGoldenQuestion({
knowledgeSpaceId: "space-1",
question: " ",
token: "write-token",
}),
).rejects.toThrow("Admin API golden question is required");
await expect(
client.createGoldenQuestion({
expectedEvidenceIds: ["not-a-node-id"],
knowledgeSpaceId: "space-1",
question: "What does the roadmap require?",
token: "write-token",
}),
).rejects.toThrow("Admin API expectedEvidenceId must be a UUID");
expect(requests.map((request) => [request.method, request.url])).toEqual([
["GET", "http://api.test/knowledge-spaces/space-1/golden-questions?limit=2"],
["POST", "http://api.test/knowledge-spaces/space-1/golden-questions"],
[
"GET",
"http://api.test/knowledge-spaces/space-1/golden-questions/018f0d60-7a49-7cc2-9c1b-5b36f18f3a01",
],
[
"PATCH",
"http://api.test/knowledge-spaces/space-1/golden-questions/018f0d60-7a49-7cc2-9c1b-5b36f18f3a01",
],
[
"DELETE",
"http://api.test/knowledge-spaces/space-1/golden-questions/018f0d60-7a49-7cc2-9c1b-5b36f18f3a01",
],
]);
});
it("surfaces structured Admin API validation messages", async () => {
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async () =>
Response.json(
{
error: {
issues: [
{
message: "Invalid UUID",
path: ["expectedEvidenceIds", 0],
},
],
},
},
{ status: 400 },
),
});
await expect(
client.createGoldenQuestion({
expectedEvidenceIds: ["018f0d60-7a49-7cc2-9c1b-5b36f18f4a01"],
knowledgeSpaceId: "space-1",
question: "What does the roadmap require?",
token: "write-token",
}),
).rejects.toThrow(
"Admin API request failed with status 400: expectedEvidenceIds.0: Invalid UUID",
);
});
it("captures production bad cases into the evaluation queue", async () => {
const requests: Request[] = [];
const captured = {
createdAt: "2026-05-13T14:00:00.000Z",
expectedEvidenceIds: ["018f0d60-7a49-7cc2-9c1b-5b36f18f4a01"],
id: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a10",
knowledgeSpaceId: "space-1",
metadata: {
reason: "Missed rollback evidence",
source: "production-bad-case",
traceId: "018f0d60-7a49-7cc2-9c1b-5b36f18f8a01",
},
question: "What changed after the production incident?",
tags: ["production-bad-case", "needs-review", "incident-review"],
updatedAt: "2026-05-13T14:00:00.000Z",
};
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
expect(request.method).toBe("POST");
expect(request.url).toBe("http://api.test/knowledge-spaces/space-1/production-bad-cases");
expect(request.headers.get("authorization")).toBe("Bearer write-token");
await expect(request.json()).resolves.toEqual({
reason: "Missed rollback evidence",
tags: ["incident-review"],
traceId: "018f0d60-7a49-7cc2-9c1b-5b36f18f8a01",
});
return Response.json(captured, { status: 201 });
},
});
await expect(
client.captureProductionBadCase({
knowledgeSpaceId: "space-1",
reason: "Missed rollback evidence",
tags: ["incident-review"],
token: "write-token",
traceId: "018f0d60-7a49-7cc2-9c1b-5b36f18f8a01",
}),
).resolves.toMatchObject({
metadata: {
source: "production-bad-case",
traceId: "018f0d60-7a49-7cc2-9c1b-5b36f18f8a01",
},
question: "What changed after the production incident?",
tags: ["production-bad-case", "needs-review", "incident-review"],
});
await expect(
client.captureProductionBadCase({
knowledgeSpaceId: "space-1",
token: "write-token",
traceId: "not-a-uuid",
}),
).rejects.toThrow("Admin API traceId must be a UUID");
expect(requests.map((request) => [request.method, request.url])).toEqual([
["POST", "http://api.test/knowledge-spaces/space-1/production-bad-cases"],
]);
});
it("submits bounded human annotations for golden questions", async () => {
const requests: Request[] = [];
const annotated = {
createdAt: "2026-05-13T15:00:00.000Z",
expectedEvidenceIds: ["018f0d60-7a49-7cc2-9c1b-5b36f18f4a21"],
id: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a21",
knowledgeSpaceId: "space-1",
metadata: {
annotationSummary: {
latestAnswerCorrectness: "incorrect",
totalAnnotations: 1,
},
annotations: [
{
annotatedBy: "subject-1",
answerCorrectness: "incorrect",
evidenceRelevance: [
{ evidenceId: "018f0d60-7a49-7cc2-9c1b-5b36f18f4a21", relevant: true },
],
},
],
},
question: "Was the cited evidence relevant?",
tags: ["annotated"],
updatedAt: "2026-05-13T15:01:00.000Z",
};
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
expect(request.method).toBe("POST");
expect(request.url).toBe(
"http://api.test/knowledge-spaces/space-1/golden-questions/018f0d60-7a49-7cc2-9c1b-5b36f18f3a21/annotations",
);
expect(request.headers.get("authorization")).toBe("Bearer write-token");
await expect(request.json()).resolves.toEqual({
answerCorrectness: "incorrect",
evidenceRelevance: [
{
evidenceId: "018f0d60-7a49-7cc2-9c1b-5b36f18f4a21",
note: "Direct support",
relevant: true,
},
],
note: "Answer overclaimed.",
});
return Response.json(annotated);
},
});
await expect(
client.annotateGoldenQuestion({
answerCorrectness: "incorrect",
evidenceRelevance: [
{
evidenceId: "018f0d60-7a49-7cc2-9c1b-5b36f18f4a21",
note: "Direct support",
relevant: true,
},
],
knowledgeSpaceId: "space-1",
note: "Answer overclaimed.",
questionId: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a21",
token: "write-token",
}),
).resolves.toMatchObject({
metadata: {
annotationSummary: { latestAnswerCorrectness: "incorrect", totalAnnotations: 1 },
},
tags: ["annotated"],
});
await expect(
client.annotateGoldenQuestion({
answerCorrectness: "correct",
evidenceRelevance: Array.from({ length: 51 }, (_, index) => ({
evidenceId: `018f0d60-7a49-7cc2-9c1b-5b36f18f${String(index).padStart(4, "0")}`,
relevant: true,
})),
knowledgeSpaceId: "space-1",
questionId: "018f0d60-7a49-7cc2-9c1b-5b36f18f3a21",
token: "write-token",
}),
).rejects.toThrow("Admin API annotation evidenceRelevance cannot exceed 50 items");
expect(requests.map((request) => [request.method, request.url])).toEqual([
[
"POST",
"http://api.test/knowledge-spaces/space-1/golden-questions/018f0d60-7a49-7cc2-9c1b-5b36f18f3a21/annotations",
],
]);
});
it("browses an entity graph and linked KnowledgeFS documents through bounded APIs", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
if (
request.url ===
"http://api.test/knowledge-spaces/space-1/graph/traverse?entityId=018f0d60-7a49-7cc2-9c1b-5b36f18f2c81&depth=2&fanout=8&maxNodes=20&timeoutMs=250"
) {
expect(request.headers.get("authorization")).toBe("Bearer read-token");
return Response.json({
entities: [
{
aliases: ["Acme"],
canonicalKey: "organization:acme",
confidence: 0.94,
createdAt: "2026-05-12T00:00:00.000Z",
depth: 0,
extractionVersion: 1,
id: "018f0d60-7a49-7cc2-9c1b-5b36f18f2c81",
knowledgeSpaceId: "space-1",
metadata: { documentCount: 3 },
name: "Acme Payments",
permissionScope: ["tenant:tenant-1"],
sourceNodeIds: ["node-1"],
type: "organization",
updatedAt: "2026-05-12T00:00:00.000Z",
},
],
metrics: {
depthReached: 1,
elapsedMs: 12,
exploredRelations: 2,
fanout: 8,
maxDepth: 2,
maxNodes: 20,
timedOut: false,
},
relations: [
{
confidence: 0.9,
createdAt: "2026-05-12T00:00:00.000Z",
depth: 1,
extractionVersion: 1,
id: "rel-1",
knowledgeSpaceId: "space-1",
metadata: {},
objectEntityId: "entity-policy",
permissionScope: ["tenant:tenant-1"],
sourceNodeIds: ["node-1"],
subjectEntityId: "018f0d60-7a49-7cc2-9c1b-5b36f18f2c81",
type: "references",
updatedAt: "2026-05-12T00:00:00.000Z",
},
],
truncated: false,
});
}
if (
request.url ===
"http://api.test/knowledge-spaces/space-1/fs/ls?path=%2Fknowledge%2Fby-entity%2F018f0d60-7a49-7cc2-9c1b-5b36f18f2c81&limit=5"
) {
return Response.json({
items: [
{
kind: "resource",
metadata: { parserStatus: "parsed" },
name: "roadmap.md",
path: "/knowledge/by-entity/018f0d60-7a49-7cc2-9c1b-5b36f18f2c81/roadmap.md",
resourceType: "document",
targetId: "asset-1",
version: 1,
},
],
path: "/knowledge/by-entity/018f0d60-7a49-7cc2-9c1b-5b36f18f2c81",
truncated: false,
});
}
throw new Error(`Unexpected request ${request.method} ${request.url}`);
},
maxGraphFanout: 10,
maxGraphNodes: 25,
});
await expect(
client.traverseGraph({
depth: 2,
entityId: "018f0d60-7a49-7cc2-9c1b-5b36f18f2c81",
fanout: 8,
knowledgeSpaceId: "space-1",
maxNodes: 20,
timeoutMs: 250,
token: "read-token",
}),
).resolves.toMatchObject({
entities: [{ name: "Acme Payments", type: "organization" }],
relations: [{ type: "references" }],
truncated: false,
});
await expect(
client.listKnowledgeFs({
knowledgeSpaceId: "space-1",
limit: 5,
path: "/knowledge/by-entity/018f0d60-7a49-7cc2-9c1b-5b36f18f2c81",
token: "read-token",
}),
).resolves.toMatchObject({
items: [{ name: "roadmap.md", resourceType: "document" }],
truncated: false,
});
await expect(
client.traverseGraph({
entityId: "entity-1",
fanout: 11,
knowledgeSpaceId: "space-1",
token: "read-token",
}),
).rejects.toThrow("Admin API graph fanout must be between 1 and 10");
await expect(
client.listKnowledgeFs({
knowledgeSpaceId: "space-1",
limit: 101,
path: "/knowledge/by-entity",
token: "read-token",
}),
).rejects.toThrow("Admin API KnowledgeFS list limit must be between 1 and 100");
expect(requests.map((request) => [request.method, request.url])).toEqual([
[
"GET",
"http://api.test/knowledge-spaces/space-1/graph/traverse?entityId=018f0d60-7a49-7cc2-9c1b-5b36f18f2c81&depth=2&fanout=8&maxNodes=20&timeoutMs=250",
],
[
"GET",
"http://api.test/knowledge-spaces/space-1/fs/ls?path=%2Fknowledge%2Fby-entity%2F018f0d60-7a49-7cc2-9c1b-5b36f18f2c81&limit=5",
],
]);
});
it("lists semantic topic/entity/community views through a safe KnowledgeFS path helper", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
if (
request.url ===
"http://api.test/knowledge-spaces/space-1/fs/ls?path=%2Fknowledge%2Fby-topic%2Frenewal-risk&limit=3"
) {
expect(request.headers.get("authorization")).toBe("Bearer read-token");
return Response.json({
items: [
{
kind: "resource",
metadata: {
semanticView: {
buildStatus: "ready",
generatedVersion: "semantic-v4",
staleStatus: "fresh",
},
},
name: "vendor-contract.pdf",
path: "/knowledge/by-topic/renewal-risk/vendor-contract.pdf",
resourceType: "document",
targetId: "asset-contract",
version: 1,
},
],
path: "/knowledge/by-topic/renewal-risk",
truncated: false,
});
}
if (
request.url ===
"http://api.test/knowledge-spaces/space-1/fs/ls?path=%2Fknowledge%2Fby-community%2Facme-risk&limit=3"
) {
expect(request.headers.get("authorization")).toBe("Bearer read-token");
return Response.json({
items: [
{
kind: "directory",
metadata: {
documentCount: 2,
entityCount: 4,
summary: "Acme renewal risk community.",
title: "Acme risk",
},
name: "acme-risk",
path: "/knowledge/by-community/acme-risk",
},
],
path: "/knowledge/by-community/acme-risk",
truncated: false,
});
}
throw new Error(`Unexpected request ${request.method} ${request.url}`);
},
});
await expect(
client.listSemanticView({
key: "renewal-risk",
knowledgeSpaceId: "space-1",
limit: 3,
token: "read-token",
view: "by-topic",
}),
).resolves.toMatchObject({
items: [
{
metadata: {
semanticView: {
buildStatus: "ready",
staleStatus: "fresh",
},
},
name: "vendor-contract.pdf",
},
],
path: "/knowledge/by-topic/renewal-risk",
});
await expect(
client.listSemanticView({
key: "acme-risk",
knowledgeSpaceId: "space-1",
limit: 3,
token: "read-token",
view: "by-community",
}),
).resolves.toMatchObject({
items: [{ metadata: { title: "Acme risk" }, name: "acme-risk" }],
path: "/knowledge/by-community/acme-risk",
});
await expect(
client.listSemanticView({
key: "renewal/risk",
knowledgeSpaceId: "space-1",
limit: 3,
token: "read-token",
view: "by-topic",
}),
).rejects.toThrow("Admin API semantic view key must be a single path segment");
expect(requests.map((request) => [request.method, request.url])).toEqual([
[
"GET",
"http://api.test/knowledge-spaces/space-1/fs/ls?path=%2Fknowledge%2Fby-topic%2Frenewal-risk&limit=3",
],
[
"GET",
"http://api.test/knowledge-spaces/space-1/fs/ls?path=%2Fknowledge%2Fby-community%2Facme-risk&limit=3",
],
]);
});
it("runs semantic operator actions through bounded Admin API client calls", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
if (request.url.endsWith("/semantic-views/topic/materialize")) {
expect(request.headers.get("authorization")).toBe("Bearer write-token");
await expect(request.json()).resolves.toEqual({
generatedVersion: "operator-topic-view-v2",
limit: 25,
topicName: "Uploaded Documents",
topicSlug: "uploaded-documents",
});
return Response.json({
documentCount: 2,
generatedVersion: "operator-topic-view-v2",
knowledgeSpaceId: "space-1",
pathCount: 2,
topicName: "Uploaded Documents",
topicSlug: "uploaded-documents",
});
}
if (request.url.endsWith("/semantic-views/entities/extract")) {
expect(request.headers.get("authorization")).toBe("Bearer write-token");
await expect(request.json()).resolves.toEqual({ limit: 25 });
return Response.json({
entitiesExtracted: 4,
extractionMode: "provider",
graphEntitiesIndexed: 3,
graphRelationsIndexed: 1,
knowledgeSpaceId: "space-1",
nodesScanned: 2,
nodesUpdated: 2,
});
}
if (request.url.endsWith("/semantic-views/communities/materialize")) {
expect(request.headers.get("authorization")).toBe("Bearer write-token");
await expect(request.json()).resolves.toEqual({
generatedVersion: "operator-community-view-v2",
});
return Response.json({
communityCount: 1,
documentCount: 2,
entityCount: 4,
generatedVersion: "operator-community-view-v2",
knowledgeSpaceId: "space-1",
pathCount: 3,
});
}
throw new Error(`Unexpected request ${request.method} ${request.url}`);
},
});
await expect(
client.materializeSemanticTopicView({
generatedVersion: "operator-topic-view-v2",
knowledgeSpaceId: "space-1",
limit: 25,
token: "write-token",
topicName: "Uploaded Documents",
topicSlug: "uploaded-documents",
}),
).resolves.toMatchObject({
documentCount: 2,
pathCount: 2,
topicSlug: "uploaded-documents",
});
await expect(
client.extractSemanticEntities({
knowledgeSpaceId: "space-1",
limit: 25,
token: "write-token",
}),
).resolves.toMatchObject({
entitiesExtracted: 4,
extractionMode: "provider",
graphEntitiesIndexed: 3,
nodesUpdated: 2,
});
await expect(
client.materializeSemanticCommunities({
generatedVersion: "operator-community-view-v2",
knowledgeSpaceId: "space-1",
token: "write-token",
}),
).resolves.toMatchObject({
communityCount: 1,
documentCount: 2,
pathCount: 3,
});
await expect(
client.materializeSemanticTopicView({
knowledgeSpaceId: "space-1",
limit: 101,
token: "write-token",
}),
).rejects.toThrow("Admin API semantic topic materialization limit must be between 1 and 100");
await expect(
client.extractSemanticEntities({
knowledgeSpaceId: "space-1",
limit: 101,
token: "write-token",
}),
).rejects.toThrow("Admin API semantic entity extraction limit must be between 1 and 100");
expect(requests.map((request) => [request.method, request.url])).toEqual([
["POST", "http://api.test/knowledge-spaces/space-1/semantic-views/topic/materialize"],
["POST", "http://api.test/knowledge-spaces/space-1/semantic-views/entities/extract"],
["POST", "http://api.test/knowledge-spaces/space-1/semantic-views/communities/materialize"],
]);
});
it("surfaces bounded API error messages from semantic operator failures", async () => {
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async () =>
Response.json(
{ error: "Semantic entity extraction requires an LLM provider" },
{ status: 400 },
),
});
await expect(
client.extractSemanticEntities({
knowledgeSpaceId: "space-1",
token: "write-token",
}),
).rejects.toThrow(
"Admin API request failed with status 400: Semantic entity extraction requires an LLM provider",
);
});
it("loads text and semantic KnowledgeFS diffs through a bounded diff client", async () => {
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
expect(request.method).toBe("GET");
expect(request.headers.get("authorization")).toBe("Bearer read-token");
expect(request.url).toBe(
"http://api.test/knowledge-spaces/space-1/fs/diff?oldPath=%2Fknowledge%2Fnodes%2Fpolicy-v1.md&newPath=%2Fknowledge%2Fnodes%2Fpolicy-v2.md&mode=word&semantic=true",
);
return Response.json({
mode: "word",
newPath: "/knowledge/nodes/policy-v2.md",
oldPath: "/knowledge/nodes/policy-v1.md",
operations: [
{ kind: "equal", newEnd: 1, newStart: 1, oldEnd: 1, oldStart: 1, text: "alpha" },
{ kind: "delete", oldEnd: 2, oldStart: 2, text: "beta" },
{ kind: "insert", newEnd: 2, newStart: 2, text: "gamma" },
],
semantic: {
changes: [
{
category: "addition",
evidence: ["gamma"],
summary: "Added gamma release note.",
},
],
metadata: { provider: "fake-semantic-diff" },
model: "semantic-diff-test",
summary: "The new version adds gamma while replacing beta.",
},
stats: { delete: 1, equal: 1, insert: 1 },
});
},
});
await expect(
client.diffKnowledgeFs({
knowledgeSpaceId: "space-1",
mode: "word",
newPath: "/knowledge/nodes/policy-v2.md",
oldPath: "/knowledge/nodes/policy-v1.md",
semantic: true,
token: "read-token",
}),
).resolves.toMatchObject({
mode: "word",
operations: [
{ kind: "equal", text: "alpha" },
{ kind: "delete", text: "beta" },
{ kind: "insert", text: "gamma" },
],
semantic: {
changes: [{ category: "addition", summary: "Added gamma release note." }],
summary: "The new version adds gamma while replacing beta.",
},
stats: { delete: 1, equal: 1, insert: 1 },
});
await expect(
client.diffKnowledgeFs({
knowledgeSpaceId: "space-1",
newPath: "knowledge/nodes/policy-v2.md",
oldPath: "/knowledge/nodes/policy-v1.md",
token: "read-token",
}),
).rejects.toThrow("Admin API KnowledgeFS diff paths must be absolute");
});
it("writes and appends KnowledgeFS documents with JSON bodies", async () => {
const requests: Request[] = [];
const client = createAdminApiClient({
baseUrl: "http://api.test/",
fetch: async (input) => {
const request = input instanceof Request ? input : new Request(input);
requests.push(request.clone());
return Response.json({
bytesWritten: 12,
mode: request.url.endsWith("/append") ? "append" : "write",
objectKey: "tenant-1/spaces/space-1/documents/document-1/example.txt",
path: "/knowledge/docs/example.txt",
targetId: "document-1",
version: 1,
});
},
});
await expect(
client.writeKnowledgeFs({
knowledgeSpaceId: "space-1",
path: "/knowledge/docs/example.txt",
text: "hello",
token: "write-token",
}),
).resolves.toMatchObject({
mode: "write",
path: "/knowledge/docs/example.txt",
});
await expect(
client.appendKnowledgeFs({
knowledgeSpaceId: "space-1",
path: "/knowledge/docs/example.txt",
text: " world",
token: "write-token",
}),
).resolves.toMatchObject({
mode: "append",
targetId: "document-1",
});
await expect(
client.writeKnowledgeFs({
knowledgeSpaceId: "space-1",
path: "knowledge/docs/example.txt",
text: "blocked",
token: "write-token",
}),
).rejects.toThrow("Admin API KnowledgeFS path must be absolute");
expect(requests.map((request) => [request.method, request.url])).toEqual([
["POST", "http://api.test/knowledge-spaces/space-1/fs/write"],
["POST", "http://api.test/knowledge-spaces/space-1/fs/append"],
]);
await expect(requests[0]?.json()).resolves.toEqual({
path: "/knowledge/docs/example.txt",
text: "hello",
});
await expect(requests[1]?.json()).resolves.toEqual({
path: "/knowledge/docs/example.txt",
text: " world",
});
});
});
describe("parseSseEvents", () => {
it("parses compact SSE payloads without exposing raw transport details", () => {
expect(parseSseEvents('event: answer.delta\ndata: {"delta":"A"}\n\n')).toEqual([
{ data: { delta: "A" }, event: "answer.delta" },
]);
expect(() => parseSseEvents("event: answer.delta\ndata: {\n\n")).toThrow(
"Admin API SSE event contains invalid JSON",
);
});
});
describe("getAdminApiBase", () => {
it("prefers the private server-side API base and keeps the public base for display", () => {
const originalPrivateBase = process.env.KNOWLEDGE_API_BASE_URL;
const originalPublicBase = process.env.NEXT_PUBLIC_API_BASE_URL;
process.env.KNOWLEDGE_API_BASE_URL = "http://api:8787";
process.env.NEXT_PUBLIC_API_BASE_URL = "http://localhost:8788";
try {
expect(getAdminApiBase()).toBe("http://api:8787");
expect(getAdminPublicApiBase()).toBe("http://localhost:8788");
} finally {
restoreEnv("KNOWLEDGE_API_BASE_URL", originalPrivateBase);
restoreEnv("NEXT_PUBLIC_API_BASE_URL", originalPublicBase);
}
});
it("defaults local source development to the non-workerd API port", () => {
const originalPrivateBase = process.env.KNOWLEDGE_API_BASE_URL;
const originalPublicBase = process.env.NEXT_PUBLIC_API_BASE_URL;
process.env.KNOWLEDGE_API_BASE_URL = "";
process.env.NEXT_PUBLIC_API_BASE_URL = "";
try {
expect(getAdminApiBase()).toBe("http://localhost:8788");
expect(getAdminPublicApiBase()).toBe("http://localhost:8788");
} finally {
restoreEnv("KNOWLEDGE_API_BASE_URL", originalPrivateBase);
restoreEnv("NEXT_PUBLIC_API_BASE_URL", originalPublicBase);
}
});
});
function restoreEnv(name: string, value: string | undefined): void {
if (value === undefined) {
process.env[name] = "";
return;
}
process.env[name] = value;
}
function adminGcCandidate() {
return {
candidateType: "staged-object",
count: 1,
estimatedBytes: 12,
idempotencyKey: "staged-object:doc-1",
reason: "expired staged object",
target: {
objectKey: "tenant-1/spaces/space-1/staging/doc-1.md",
type: "staged-object",
},
};
}
function adminGcDryRun() {
return {
candidates: [adminGcCandidate()],
dryRunId: "gc-dry-run-1",
generatedAt: "2026-05-27T11:05:00.000Z",
knowledgeSpaceId: "space-1",
summary: {
candidateCount: 1,
estimatedBytes: 12,
failedCommitCount: 0,
stagedObjectCount: 1,
},
tenantId: "tenant-1",
};
}