Files
openclaw/extensions/telegram/src/error-policy.test.ts
Chinar Amrutkar 74b9f22a42 fix: add Telegram error suppression controls (#51914) (thanks @chinar-amrutkar)
* feat(telegram): add error policy for suppressing repetitive error messages

Introduces per-account error policy configuration that can suppress
repetitive error messages (e.g., 429 rate limit, ECONNRESET) to
prevent noisy error floods in Telegram channels.

Closes #34498

* fix(telegram): track error cooldown per message

* fix(telegram): prune expired error cooldowns

* fix: add Telegram error suppression controls (#51914) (thanks @chinar-amrutkar)

---------

Co-authored-by: chinar-amrutkar <chinar-amrutkar@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-01 17:52:28 +05:30

161 lines
3.6 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
buildTelegramErrorScopeKey,
resolveTelegramErrorPolicy,
resetTelegramErrorPolicyStoreForTest,
shouldSuppressTelegramError,
} from "./error-policy.js";
describe("telegram error policy", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
resetTelegramErrorPolicyStoreForTest();
});
afterEach(() => {
resetTelegramErrorPolicyStoreForTest();
vi.useRealTimers();
});
it("resolves policy and cooldown from the most specific config", () => {
expect(
resolveTelegramErrorPolicy({
accountConfig: { errorPolicy: "once", errorCooldownMs: 1000 },
groupConfig: { errorCooldownMs: 2000 },
topicConfig: { errorPolicy: "silent" },
}),
).toEqual({
policy: "silent",
cooldownMs: 2000,
});
});
it("suppresses only repeated matching errors within the same scope", () => {
const scopeKey = buildTelegramErrorScopeKey({
accountId: "work",
chatId: 42,
threadId: 7,
});
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: "work",
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: "work",
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 leak suppression across accounts or threads", () => {
const workMain = buildTelegramErrorScopeKey({
accountId: "work",
chatId: 42,
});
const personalMain = buildTelegramErrorScopeKey({
accountId: "personal",
chatId: 42,
});
const workTopic = buildTelegramErrorScopeKey({
accountId: "work",
chatId: 42,
threadId: 9,
});
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);
});
});