Files
openclaw/extensions/bluebubbles/src/monitor-self-chat-cache.test.ts
Vincent Koc 241e8cc553 fix(bluebubbles): dedupe reflected self-chat duplicates (#38442)
* BlueBubbles: drop reflected self-chat duplicates

* Changelog: add BlueBubbles self-chat echo dedupe entry

* BlueBubbles: gate self-chat cache and expand coverage

* BlueBubbles: require explicit sender ids for self-chat dedupe

* BlueBubbles: harden self-chat cache

* BlueBubbles: move self-chat cache identity into cache

* BlueBubbles: gate self-chat cache to confirmed outbound sends

* Update CHANGELOG.md

* BlueBubbles: bound self-chat cache input work

* Tests: cover BlueBubbles cache cap under cleanup throttle

* BlueBubbles: canonicalize self-chat DM scope

* Tests: cover BlueBubbles mixed self-chat scope aliases
2026-03-12 03:11:43 -04:00

191 lines
4.4 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import {
hasBlueBubblesSelfChatCopy,
rememberBlueBubblesSelfChatCopy,
resetBlueBubblesSelfChatCache,
} from "./monitor-self-chat-cache.js";
describe("BlueBubbles self-chat cache", () => {
const directLookup = {
accountId: "default",
chatGuid: "iMessage;-;+15551234567",
senderId: "+15551234567",
} as const;
afterEach(() => {
resetBlueBubblesSelfChatCache();
vi.useRealTimers();
});
it("matches repeated lookups for the same scope, timestamp, and text", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-07T00:00:00Z"));
rememberBlueBubblesSelfChatCopy({
...directLookup,
body: " hello\r\nworld ",
timestamp: 123,
});
expect(
hasBlueBubblesSelfChatCopy({
...directLookup,
body: "hello\nworld",
timestamp: 123,
}),
).toBe(true);
});
it("canonicalizes DM scope across chatIdentifier and chatGuid", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-07T00:00:00Z"));
rememberBlueBubblesSelfChatCopy({
accountId: "default",
chatIdentifier: "+15551234567",
senderId: "+15551234567",
body: "hello",
timestamp: 123,
});
expect(
hasBlueBubblesSelfChatCopy({
accountId: "default",
chatGuid: "iMessage;-;+15551234567",
senderId: "+15551234567",
body: "hello",
timestamp: 123,
}),
).toBe(true);
resetBlueBubblesSelfChatCache();
rememberBlueBubblesSelfChatCopy({
accountId: "default",
chatGuid: "iMessage;-;+15551234567",
senderId: "+15551234567",
body: "hello",
timestamp: 123,
});
expect(
hasBlueBubblesSelfChatCopy({
accountId: "default",
chatIdentifier: "+15551234567",
senderId: "+15551234567",
body: "hello",
timestamp: 123,
}),
).toBe(true);
});
it("expires entries after the ttl window", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-07T00:00:00Z"));
rememberBlueBubblesSelfChatCopy({
...directLookup,
body: "hello",
timestamp: 123,
});
vi.advanceTimersByTime(11_001);
expect(
hasBlueBubblesSelfChatCopy({
...directLookup,
body: "hello",
timestamp: 123,
}),
).toBe(false);
});
it("evicts older entries when the cache exceeds its cap", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-07T00:00:00Z"));
for (let i = 0; i < 513; i += 1) {
rememberBlueBubblesSelfChatCopy({
...directLookup,
body: `message-${i}`,
timestamp: i,
});
vi.advanceTimersByTime(1_001);
}
expect(
hasBlueBubblesSelfChatCopy({
...directLookup,
body: "message-0",
timestamp: 0,
}),
).toBe(false);
expect(
hasBlueBubblesSelfChatCopy({
...directLookup,
body: "message-512",
timestamp: 512,
}),
).toBe(true);
});
it("enforces the cache cap even when cleanup is throttled", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-07T00:00:00Z"));
for (let i = 0; i < 513; i += 1) {
rememberBlueBubblesSelfChatCopy({
...directLookup,
body: `burst-${i}`,
timestamp: i,
});
}
expect(
hasBlueBubblesSelfChatCopy({
...directLookup,
body: "burst-0",
timestamp: 0,
}),
).toBe(false);
expect(
hasBlueBubblesSelfChatCopy({
...directLookup,
body: "burst-512",
timestamp: 512,
}),
).toBe(true);
});
it("does not collide long texts that differ only in the middle", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-07T00:00:00Z"));
const prefix = "a".repeat(256);
const suffix = "b".repeat(256);
const longBodyA = `${prefix}${"x".repeat(300)}${suffix}`;
const longBodyB = `${prefix}${"y".repeat(300)}${suffix}`;
rememberBlueBubblesSelfChatCopy({
...directLookup,
body: longBodyA,
timestamp: 123,
});
expect(
hasBlueBubblesSelfChatCopy({
...directLookup,
body: longBodyA,
timestamp: 123,
}),
).toBe(true);
expect(
hasBlueBubblesSelfChatCopy({
...directLookup,
body: longBodyB,
timestamp: 123,
}),
).toBe(false);
});
});