Files
openclaw/extensions/msteams/src/sent-message-cache.test.ts
Alex Knight 6ae09d029c msteams: persist sent-message markers best-effort (#75585)
* msteams: persist sent-message markers best-effort

* docs: clarify Teams restart persistence changelog

* msteams: remove redundant sent-message TTL comment

* msteams: preserve sent-message marker TTL on recovery
2026-05-03 17:25:20 +10:00

106 lines
3.4 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { setMSTeamsRuntime } from "./runtime.js";
import {
clearMSTeamsSentMessageCache,
recordMSTeamsSentMessage,
wasMSTeamsMessageSent,
wasMSTeamsMessageSentWithPersistence,
} from "./sent-message-cache.js";
const TTL_MS = 24 * 60 * 60 * 1000;
describe("msteams sent message cache", () => {
afterEach(() => {
clearMSTeamsSentMessageCache();
vi.restoreAllMocks();
});
it("records and resolves sent message ids", () => {
recordMSTeamsSentMessage("conv-1", "msg-1");
expect(wasMSTeamsMessageSent("conv-1", "msg-1")).toBe(true);
expect(wasMSTeamsMessageSent("conv-1", "msg-2")).toBe(false);
});
it("persists sent message ids when runtime state is available", async () => {
const register = vi.fn().mockResolvedValue(undefined);
const lookup = vi.fn().mockResolvedValue({ sentAt: Date.now() });
const openKeyedStore = vi.fn(() => ({
register,
lookup,
consume: vi.fn(),
delete: vi.fn(),
entries: vi.fn(),
clear: vi.fn(),
}));
setMSTeamsRuntime({
state: { openKeyedStore },
logging: { getChildLogger: () => ({ warn: vi.fn() }) },
} as never);
recordMSTeamsSentMessage("conv-1", "msg-2");
await vi.waitFor(() => expect(register).toHaveBeenCalledTimes(1));
expect(register).toHaveBeenCalledWith("conv-1:msg-2", { sentAt: expect.any(Number) });
clearMSTeamsSentMessageCache();
await expect(
wasMSTeamsMessageSentWithPersistence({ conversationId: "conv-1", messageId: "msg-2" }),
).resolves.toBe(true);
expect(openKeyedStore).toHaveBeenCalledTimes(2);
expect(lookup).toHaveBeenCalledWith("conv-1:msg-2");
lookup.mockClear();
await expect(
wasMSTeamsMessageSentWithPersistence({ conversationId: "conv-1", messageId: "msg-2" }),
).resolves.toBe(true);
expect(wasMSTeamsMessageSent("conv-1", "msg-2")).toBe(true);
expect(lookup).not.toHaveBeenCalled();
});
it("preserves the original TTL when recovering sent-message ids from persistent state", async () => {
const sentAt = 1_000_000;
const lookup = vi.fn().mockResolvedValue({ sentAt });
const openKeyedStore = vi.fn(() => ({
register: vi.fn(),
lookup,
consume: vi.fn(),
delete: vi.fn(),
entries: vi.fn(),
clear: vi.fn(),
}));
setMSTeamsRuntime({
state: { openKeyedStore },
logging: { getChildLogger: () => ({ warn: vi.fn() }) },
} as never);
vi.spyOn(Date, "now").mockReturnValue(sentAt + TTL_MS - 1);
await expect(
wasMSTeamsMessageSentWithPersistence({ conversationId: "conv-1", messageId: "msg-4" }),
).resolves.toBe(true);
expect(wasMSTeamsMessageSent("conv-1", "msg-4")).toBe(true);
lookup.mockClear();
vi.mocked(Date.now).mockReturnValue(sentAt + TTL_MS + 1);
expect(wasMSTeamsMessageSent("conv-1", "msg-4")).toBe(false);
expect(lookup).not.toHaveBeenCalled();
});
it("falls back to in-memory sent-message markers when persistent state cannot open", () => {
const warn = vi.fn();
setMSTeamsRuntime({
state: {
openKeyedStore: vi.fn(() => {
throw new Error("sqlite unavailable");
}),
},
logging: { getChildLogger: () => ({ warn }) },
} as never);
recordMSTeamsSentMessage("conv-1", "msg-3");
expect(wasMSTeamsMessageSent("conv-1", "msg-3")).toBe(true);
expect(warn).toHaveBeenCalled();
});
});