Initial commit: SIC harness (backend, web, pi-adapter, configs, docs)
- pnpm monorepo: apps/api (Fastify + SQLite + SSE), apps/web (React+Vite), packages/shared, packages/pi-adapter - Local auth (admin/webhook-runner roles) + Keycloak JWT ready - Multi-session chat with reliable history (user persisted before LLM, assistant persisted after stream) - Markdown knowledge base with /api/docs/search + /api/docs/:id - YAML webhook catalog with backend-only execution, retry/backoff, audit (webhook_runs), and per-user rate limit - Skills config (sre-on-call, blameless-postmortem, security-incident) injected into LLM system prompt - LLM provider failover chain (config/models.yml fallback + LLM_FALLBACK_CHAIN override) - Context-aware webhooks panel + backend id-mention safety net - Per-message stats (time/duration/tokens/model), Markdown+GFM render, code & table copy/download buttons - Vitest suite, end-to-end smoke test (scripts/smoke.mjs), per-session system prompt override - /metrics Prometheus endpoint + /api/metrics JSON, request-id correlation - dotenv with explicit repo-root path; envString/envNumber helpers (handles empty-string env) - Runbooks + SOPs under knowledge/ in English; README, docs, and INDEX.md in English
This commit is contained in:
118
apps/api/test/rag-client.test.ts
Normal file
118
apps/api/test/rag-client.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { createServer, type Server } from "node:http";
|
||||
|
||||
let server: Server;
|
||||
let port = 0;
|
||||
|
||||
beforeEach(async () => {
|
||||
await new Promise<void>((resolve) => {
|
||||
server = createServer((req, res) => {
|
||||
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
||||
// Decode the path so ids with reserved characters (e.g. "runbooks:vpn")
|
||||
// match whether the client encoded the colon as %3A or not.
|
||||
const pathname = decodeURIComponent(url.pathname);
|
||||
if (req.method === "POST" && pathname === "/search") {
|
||||
let body = "";
|
||||
req.on("data", (c) => (body += c));
|
||||
req.on("end", () => {
|
||||
res.writeHead(200, { "content-type": "application/json" });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
items: [
|
||||
{ id: "remote:1", title: "Remote doc", source: "remote", tags: ["remote"], relevance: 0.9, excerpt: "x" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && pathname === "/docs/remote:1") {
|
||||
res.writeHead(200, { "content-type": "application/json" });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
id: "remote:1",
|
||||
title: "Remote doc",
|
||||
source: "remote",
|
||||
tags: ["remote"],
|
||||
headings: ["Section"],
|
||||
content: "Full remote content",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
res.writeHead(404).end();
|
||||
});
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
const a = server.address();
|
||||
port = typeof a === "object" && a ? a.port : 0;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
});
|
||||
|
||||
describe("rag client", () => {
|
||||
it("searches via the configured endpoint when set", async () => {
|
||||
const { searchViaRag } = await import("../src/rag/client.js");
|
||||
const items = await searchViaRag(
|
||||
{
|
||||
endpoint: `http://127.0.0.1:${port}`,
|
||||
authToken: "",
|
||||
timeoutMs: 5000,
|
||||
fallbackToLocal: false,
|
||||
chunkStrategy: "heading",
|
||||
chunkSizeChars: 1500,
|
||||
topK: 5,
|
||||
minRelevance: 0,
|
||||
includeTags: [],
|
||||
excludeTags: [],
|
||||
},
|
||||
"anything",
|
||||
3,
|
||||
);
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0]?.id).toBe("remote:1");
|
||||
expect(items[0]?.relevance).toBe(0.9);
|
||||
});
|
||||
|
||||
it("fetches a single doc via the endpoint", async () => {
|
||||
const { getViaRag } = await import("../src/rag/client.js");
|
||||
const doc = await getViaRag(
|
||||
{
|
||||
endpoint: `http://127.0.0.1:${port}`,
|
||||
authToken: "secret",
|
||||
timeoutMs: 5000,
|
||||
fallbackToLocal: false,
|
||||
chunkStrategy: "heading",
|
||||
chunkSizeChars: 1500,
|
||||
topK: 5,
|
||||
minRelevance: 0,
|
||||
includeTags: [],
|
||||
excludeTags: [],
|
||||
},
|
||||
"remote:1",
|
||||
);
|
||||
expect(doc?.id).toBe("remote:1");
|
||||
expect(doc?.content).toBe("Full remote content");
|
||||
});
|
||||
|
||||
it("isRagRemote returns true when endpoint is set, false otherwise", async () => {
|
||||
const { isRagRemote } = await import("../src/rag/client.js");
|
||||
const base = {
|
||||
authToken: "",
|
||||
timeoutMs: 1000,
|
||||
fallbackToLocal: true,
|
||||
chunkStrategy: "heading" as const,
|
||||
chunkSizeChars: 1500,
|
||||
topK: 5,
|
||||
minRelevance: 0,
|
||||
includeTags: [],
|
||||
excludeTags: [],
|
||||
};
|
||||
expect(isRagRemote({ ...base, endpoint: "" })).toBe(false);
|
||||
expect(isRagRemote({ ...base, endpoint: "http://x" })).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user