mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 22:58:26 +00:00
Tests: cover QMD scope, reads, and citation clamp
This commit is contained in:
committed by
Vignesh
parent
1861e76360
commit
3d1c3b78ec
@@ -1,5 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
let backend: "builtin" | "qmd" = "builtin";
|
||||||
const stubManager = {
|
const stubManager = {
|
||||||
search: vi.fn(async () => [
|
search: vi.fn(async () => [
|
||||||
{
|
{
|
||||||
@@ -13,7 +14,7 @@ const stubManager = {
|
|||||||
]),
|
]),
|
||||||
readFile: vi.fn(),
|
readFile: vi.fn(),
|
||||||
status: () => ({
|
status: () => ({
|
||||||
backend: "builtin" as const,
|
backend,
|
||||||
files: 1,
|
files: 1,
|
||||||
chunks: 1,
|
chunks: 1,
|
||||||
dirty: false,
|
dirty: false,
|
||||||
@@ -44,6 +45,7 @@ beforeEach(() => {
|
|||||||
|
|
||||||
describe("memory search citations", () => {
|
describe("memory search citations", () => {
|
||||||
it("appends source information when citations are enabled", async () => {
|
it("appends source information when citations are enabled", async () => {
|
||||||
|
backend = "builtin";
|
||||||
const cfg = { memory: { citations: "on" }, agents: { list: [{ id: "main", default: true }] } };
|
const cfg = { memory: { citations: "on" }, agents: { list: [{ id: "main", default: true }] } };
|
||||||
const tool = createMemorySearchTool({ config: cfg });
|
const tool = createMemorySearchTool({ config: cfg });
|
||||||
if (!tool) throw new Error("tool missing");
|
if (!tool) throw new Error("tool missing");
|
||||||
@@ -54,6 +56,7 @@ describe("memory search citations", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("leaves snippet untouched when citations are off", async () => {
|
it("leaves snippet untouched when citations are off", async () => {
|
||||||
|
backend = "builtin";
|
||||||
const cfg = { memory: { citations: "off" }, agents: { list: [{ id: "main", default: true }] } };
|
const cfg = { memory: { citations: "off" }, agents: { list: [{ id: "main", default: true }] } };
|
||||||
const tool = createMemorySearchTool({ config: cfg });
|
const tool = createMemorySearchTool({ config: cfg });
|
||||||
if (!tool) throw new Error("tool missing");
|
if (!tool) throw new Error("tool missing");
|
||||||
@@ -62,4 +65,17 @@ describe("memory search citations", () => {
|
|||||||
expect(details.results[0]?.snippet).not.toMatch(/Source:/);
|
expect(details.results[0]?.snippet).not.toMatch(/Source:/);
|
||||||
expect(details.results[0]?.citation).toBeUndefined();
|
expect(details.results[0]?.citation).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("clamps decorated snippets to qmd injected budget", async () => {
|
||||||
|
backend = "qmd";
|
||||||
|
const cfg = {
|
||||||
|
memory: { citations: "on", backend: "qmd", qmd: { limits: { maxInjectedChars: 20 } } },
|
||||||
|
agents: { list: [{ id: "main", default: true }] },
|
||||||
|
};
|
||||||
|
const tool = createMemorySearchTool({ config: cfg });
|
||||||
|
if (!tool) throw new Error("tool missing");
|
||||||
|
const result = await tool.execute("call_citations_qmd", { query: "notes" });
|
||||||
|
const details = result.details as { results: Array<{ snippet: string; citation?: string }> };
|
||||||
|
expect(details.results[0]?.snippet.length).toBeLessThanOrEqual(20);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,4 +95,56 @@ describe("QmdMemoryManager", () => {
|
|||||||
|
|
||||||
await manager.close();
|
await manager.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("scopes by channel for agent-prefixed session keys", async () => {
|
||||||
|
cfg = {
|
||||||
|
...cfg,
|
||||||
|
memory: {
|
||||||
|
backend: "qmd",
|
||||||
|
qmd: {
|
||||||
|
includeDefaultMemory: false,
|
||||||
|
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
|
||||||
|
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
|
||||||
|
scope: {
|
||||||
|
default: "deny",
|
||||||
|
rules: [{ action: "allow", match: { channel: "slack" } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as MoltbotConfig;
|
||||||
|
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
|
||||||
|
const manager = await QmdMemoryManager.create({ cfg, agentId, resolved });
|
||||||
|
expect(manager).toBeTruthy();
|
||||||
|
if (!manager) throw new Error("manager missing");
|
||||||
|
|
||||||
|
const isAllowed = (key?: string) =>
|
||||||
|
(manager as unknown as { isScopeAllowed: (key?: string) => boolean }).isScopeAllowed(key);
|
||||||
|
expect(isAllowed("agent:main:slack:channel:c123")).toBe(true);
|
||||||
|
expect(isAllowed("agent:main:discord:channel:c123")).toBe(false);
|
||||||
|
|
||||||
|
await manager.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks non-markdown or symlink reads for qmd paths", async () => {
|
||||||
|
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
|
||||||
|
const manager = await QmdMemoryManager.create({ cfg, agentId, resolved });
|
||||||
|
expect(manager).toBeTruthy();
|
||||||
|
if (!manager) throw new Error("manager missing");
|
||||||
|
|
||||||
|
const textPath = path.join(workspaceDir, "secret.txt");
|
||||||
|
await fs.writeFile(textPath, "nope", "utf-8");
|
||||||
|
await expect(manager.readFile({ relPath: "qmd/workspace/secret.txt" })).rejects.toThrow(
|
||||||
|
"path required",
|
||||||
|
);
|
||||||
|
|
||||||
|
const target = path.join(workspaceDir, "target.md");
|
||||||
|
await fs.writeFile(target, "ok", "utf-8");
|
||||||
|
const link = path.join(workspaceDir, "link.md");
|
||||||
|
await fs.symlink(target, link);
|
||||||
|
await expect(manager.readFile({ relPath: "qmd/workspace/link.md" })).rejects.toThrow(
|
||||||
|
"path required",
|
||||||
|
);
|
||||||
|
|
||||||
|
await manager.close();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user