Files
openclaw/extensions/telegram/src/error-policy.test.ts
Peter Steinberger af3b86091f fix(telegram): preserve direct-message topic routing (#126207)
* fix(telegram): preserve direct message topic identity

* fix(telegram): break topic routing import cycle
2026-08-19 18:00:56 -07:00

245 lines
5.6 KiB
TypeScript

// Telegram tests cover error policy plugin behavior.
import { MAX_DATE_TIMESTAMP_MS } from "openclaw/plugin-sdk/number-runtime";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
buildTelegramErrorScopeKey,
resolveTelegramErrorPolicy,
shouldSuppressTelegramError,
} from "./error-policy.js";
let scopeSequence = 0;
let accountId: string;
describe("telegram error policy", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
accountId = `work-${scopeSequence++}`;
});
afterEach(() => {
vi.useRealTimers();
});
it("resolves policy from the most specific config", () => {
expect(
resolveTelegramErrorPolicy({
accountConfig: { errorPolicy: "once" },
groupConfig: {},
topicConfig: { errorPolicy: "silent" },
}),
).toEqual({
policy: "silent",
cooldownMs: 14_400_000,
});
});
it("suppresses only repeated matching errors within the same scope", () => {
const scopeKey = buildTelegramErrorScopeKey({
accountId,
chatId: 42,
threadSpec: { id: 7, scope: "forum" },
});
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(true);
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "403",
}),
).toBe(false);
});
it("keeps cooldowns per error message within the same scope", () => {
const scopeKey = buildTelegramErrorScopeKey({
accountId,
chatId: 42,
});
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "A",
}),
).toBe(false);
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "B",
}),
).toBe(false);
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "A",
}),
).toBe(true);
});
it("prunes expired cooldowns within a single scope", () => {
const scopeKey = buildTelegramErrorScopeKey({
accountId,
chatId: 42,
});
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "A",
}),
).toBe(false);
vi.advanceTimersByTime(1001);
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "B",
}),
).toBe(false);
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "A",
}),
).toBe(false);
});
it("does not suppress or keep cooldowns when the process clock is invalid", () => {
const scopeKey = buildTelegramErrorScopeKey({
accountId,
chatId: 42,
});
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(true);
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(Number.NaN);
try {
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
} finally {
nowSpy.mockRestore();
}
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
});
it("does not store cooldowns whose expiry would exceed the Date range", () => {
const scopeKey = buildTelegramErrorScopeKey({
accountId,
chatId: 42,
});
vi.setSystemTime(MAX_DATE_TIMESTAMP_MS);
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
expect(
shouldSuppressTelegramError({
scopeKey,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
});
it("does not leak suppression across accounts or threads", () => {
const workMain = buildTelegramErrorScopeKey({
accountId,
chatId: 42,
});
const personalMain = buildTelegramErrorScopeKey({
accountId: "personal",
chatId: 42,
});
const workTopic = buildTelegramErrorScopeKey({
accountId,
chatId: 42,
threadSpec: { id: 9, scope: "forum" },
});
expect(
shouldSuppressTelegramError({
scopeKey: workMain,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
expect(
shouldSuppressTelegramError({
scopeKey: personalMain,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
expect(
shouldSuppressTelegramError({
scopeKey: workTopic,
cooldownMs: 1000,
errorMessage: "429",
}),
).toBe(false);
});
it("keeps forum and direct-message topics with the same id in separate scopes", () => {
const base = { accountId, chatId: 42 };
expect(
buildTelegramErrorScopeKey({
...base,
threadSpec: { id: 9, scope: "forum" },
}),
).not.toBe(
buildTelegramErrorScopeKey({
...base,
threadSpec: { id: 9, scope: "direct-messages" },
}),
);
});
});