mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-11 00:43:18 +08:00
重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
This commit is contained in:
parent
4a23b715a2
commit
dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions
140
openclaw/extensions/telegram/src/topic-name-cache.test.ts
Normal file
140
openclaw/extensions/telegram/src/topic-name-cache.test.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import syncFs from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clearTopicNameCache,
|
||||
getTopicEntry,
|
||||
getTopicName,
|
||||
resetTopicNameCacheForTest,
|
||||
topicNameCacheSize,
|
||||
updateTopicName,
|
||||
} from "./topic-name-cache.js";
|
||||
|
||||
describe("topic-name-cache", () => {
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
clearTopicNameCache();
|
||||
resetTopicNameCacheForTest();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("stores and retrieves a topic name", () => {
|
||||
updateTopicName(-100123, 42, { name: "Deployments" });
|
||||
expect(getTopicName(-100123, 42)).toBe("Deployments");
|
||||
});
|
||||
|
||||
it("returns undefined for unknown topics", () => {
|
||||
expect(getTopicName(-100123, 99)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("handles renames via forum_topic_edited (overwrites previous name)", () => {
|
||||
updateTopicName(-100123, 42, { name: "Deployments" });
|
||||
updateTopicName(-100123, 42, { name: "CI/CD" });
|
||||
expect(getTopicName(-100123, 42)).toBe("CI/CD");
|
||||
});
|
||||
|
||||
it("preserves name when patching only closed status", () => {
|
||||
updateTopicName(-100123, 42, { name: "Deployments" });
|
||||
updateTopicName(-100123, 42, { closed: true });
|
||||
expect(getTopicName(-100123, 42)).toBe("Deployments");
|
||||
expect(getTopicEntry(-100123, 42)?.closed).toBe(true);
|
||||
});
|
||||
|
||||
it("marks topic as reopened", () => {
|
||||
updateTopicName(-100123, 42, { name: "Deployments", closed: true });
|
||||
updateTopicName(-100123, 42, { closed: false });
|
||||
expect(getTopicEntry(-100123, 42)?.closed).toBe(false);
|
||||
});
|
||||
|
||||
it("stores icon metadata", () => {
|
||||
updateTopicName(-100123, 42, {
|
||||
name: "Design",
|
||||
iconColor: 0x6fb9f0,
|
||||
iconCustomEmojiId: "emoji123",
|
||||
});
|
||||
const entry = getTopicEntry(-100123, 42);
|
||||
expect(entry?.iconColor).toBe(0x6fb9f0);
|
||||
expect(entry?.iconCustomEmojiId).toBe("emoji123");
|
||||
});
|
||||
|
||||
it("does not store entries with empty name and no prior entry", () => {
|
||||
updateTopicName(-100123, 42, { closed: true });
|
||||
expect(getTopicName(-100123, 42)).toBeUndefined();
|
||||
expect(topicNameCacheSize()).toBe(0);
|
||||
});
|
||||
|
||||
it("updates timestamps on write", async () => {
|
||||
vi.useFakeTimers();
|
||||
updateTopicName(-100123, 42, { name: "A" });
|
||||
const t1 = getTopicEntry(-100123, 42)?.updatedAt ?? 0;
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
updateTopicName(-100123, 42, { name: "B" });
|
||||
const t2 = getTopicEntry(-100123, 42)?.updatedAt ?? 0;
|
||||
expect(t2).toBeGreaterThan(t1);
|
||||
});
|
||||
|
||||
it("works with string chatId and threadId", () => {
|
||||
updateTopicName("-100123", "42", { name: "StringKeys" });
|
||||
expect(getTopicName("-100123", "42")).toBe("StringKeys");
|
||||
});
|
||||
|
||||
it("evicts the oldest entry when cache exceeds 2048", () => {
|
||||
for (let i = 0; i < 2049; i++) {
|
||||
updateTopicName(-100000, i, { name: `Topic ${i}` });
|
||||
}
|
||||
expect(topicNameCacheSize()).toBe(2048);
|
||||
expect(getTopicName(-100000, 0)).toBeUndefined();
|
||||
expect(getTopicName(-100000, 2048)).toBe("Topic 2048");
|
||||
});
|
||||
|
||||
it("refreshes recency on read so active topics survive eviction", async () => {
|
||||
vi.useFakeTimers();
|
||||
updateTopicName(-100000, 1, { name: "Active" });
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
for (let i = 2; i <= 2048; i++) {
|
||||
updateTopicName(-100000, i, { name: `Topic ${i}` });
|
||||
}
|
||||
getTopicName(-100000, 1);
|
||||
updateTopicName(-100000, 9999, { name: "Newcomer" });
|
||||
expect(getTopicName(-100000, 1)).toBe("Active");
|
||||
expect(topicNameCacheSize()).toBe(2048);
|
||||
});
|
||||
|
||||
it("reloads persisted entries from disk", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-topic-cache-"));
|
||||
const persistedPath = path.join(tempDir, "topic-names.json");
|
||||
try {
|
||||
updateTopicName(-100123, 42, { name: "Deployments" }, persistedPath);
|
||||
resetTopicNameCacheForTest();
|
||||
expect(getTopicName(-100123, 42, persistedPath)).toBe("Deployments");
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
resetTopicNameCacheForTest();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps separate in-memory stores for separate persisted paths", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-topic-cache-"));
|
||||
const firstPath = path.join(tempDir, "first-topic-names.json");
|
||||
const secondPath = path.join(tempDir, "second-topic-names.json");
|
||||
try {
|
||||
updateTopicName(-100123, 42, { name: "Deployments" }, firstPath);
|
||||
updateTopicName(-200456, 84, { name: "Incidents" }, secondPath);
|
||||
|
||||
const readFileSpy = vi.spyOn(syncFs, "readFileSync");
|
||||
|
||||
expect(getTopicName(-100123, 42, firstPath)).toBe("Deployments");
|
||||
expect(getTopicName(-200456, 84, secondPath)).toBe("Incidents");
|
||||
expect(readFileSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
vi.restoreAllMocks();
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
resetTopicNameCacheForTest();
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue