openclaw/src/slack/sent-thread-cache.test.ts
Vincent Koc 4ca84acf24
fix(runtime): duplicate messages, share singleton state across bundled chunks (#43683)
* Tests: add fresh module import helper

* Process: share command queue runtime state

* Agents: share embedded run runtime state

* Reply: share followup queue runtime state

* Reply: share followup drain callback state

* Reply: share queued message dedupe state

* Reply: share inbound dedupe state

* Tests: cover shared command queue runtime state

* Tests: cover shared embedded run runtime state

* Tests: cover shared followup queue runtime state

* Tests: cover shared inbound dedupe state

* Tests: cover shared Slack thread participation state

* Slack: share sent thread participation state

* Tests: document fresh import helper

* Telegram: share draft stream runtime state

* Tests: cover shared Telegram draft stream state

* Telegram: share sent message cache state

* Tests: cover shared Telegram sent message cache

* Telegram: share thread binding runtime state

* Tests: cover shared Telegram thread binding state

* Tests: avoid duplicate shared queue reset

* refactor(runtime): centralize global singleton access

* refactor(runtime): preserve undefined global singleton values

* test(runtime): cover undefined global singleton values

---------

Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
2026-03-12 14:59:27 -04:00

92 lines
3.8 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../../test/helpers/import-fresh.js";
import {
clearSlackThreadParticipationCache,
hasSlackThreadParticipation,
recordSlackThreadParticipation,
} from "./sent-thread-cache.js";
describe("slack sent-thread-cache", () => {
afterEach(() => {
clearSlackThreadParticipationCache();
vi.restoreAllMocks();
});
it("records and checks thread participation", () => {
recordSlackThreadParticipation("A1", "C123", "1700000000.000001");
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(true);
});
it("returns false for unrecorded threads", () => {
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(false);
});
it("distinguishes different channels and threads", () => {
recordSlackThreadParticipation("A1", "C123", "1700000000.000001");
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000002")).toBe(false);
expect(hasSlackThreadParticipation("A1", "C456", "1700000000.000001")).toBe(false);
});
it("scopes participation by accountId", () => {
recordSlackThreadParticipation("A1", "C123", "1700000000.000001");
expect(hasSlackThreadParticipation("A2", "C123", "1700000000.000001")).toBe(false);
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(true);
});
it("ignores empty accountId, channelId, or threadTs", () => {
recordSlackThreadParticipation("", "C123", "1700000000.000001");
recordSlackThreadParticipation("A1", "", "1700000000.000001");
recordSlackThreadParticipation("A1", "C123", "");
expect(hasSlackThreadParticipation("", "C123", "1700000000.000001")).toBe(false);
expect(hasSlackThreadParticipation("A1", "", "1700000000.000001")).toBe(false);
expect(hasSlackThreadParticipation("A1", "C123", "")).toBe(false);
});
it("clears all entries", () => {
recordSlackThreadParticipation("A1", "C123", "1700000000.000001");
recordSlackThreadParticipation("A1", "C456", "1700000000.000002");
clearSlackThreadParticipationCache();
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(false);
expect(hasSlackThreadParticipation("A1", "C456", "1700000000.000002")).toBe(false);
});
it("shares thread participation across distinct module instances", async () => {
const cacheA = await importFreshModule<typeof import("./sent-thread-cache.js")>(
import.meta.url,
"./sent-thread-cache.js?scope=shared-a",
);
const cacheB = await importFreshModule<typeof import("./sent-thread-cache.js")>(
import.meta.url,
"./sent-thread-cache.js?scope=shared-b",
);
cacheA.clearSlackThreadParticipationCache();
try {
cacheA.recordSlackThreadParticipation("A1", "C123", "1700000000.000001");
expect(cacheB.hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(true);
cacheB.clearSlackThreadParticipationCache();
expect(cacheA.hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(false);
} finally {
cacheA.clearSlackThreadParticipationCache();
}
});
it("expired entries return false and are cleaned up on read", () => {
recordSlackThreadParticipation("A1", "C123", "1700000000.000001");
// Advance time past the 24-hour TTL
vi.spyOn(Date, "now").mockReturnValue(Date.now() + 25 * 60 * 60 * 1000);
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000001")).toBe(false);
});
it("enforces maximum entries by evicting oldest fresh entries", () => {
for (let i = 0; i < 5001; i += 1) {
recordSlackThreadParticipation("A1", "C123", `1700000000.${String(i).padStart(6, "0")}`);
}
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.000000")).toBe(false);
expect(hasSlackThreadParticipation("A1", "C123", "1700000000.005000")).toBe(true);
});
});