fix(telegram): defer replay commit until update succeeds

This commit is contained in:
Vincent Koc
2026-04-13 17:03:09 +01:00
parent 019f32cdb8
commit 7c71255948
2 changed files with 107 additions and 2 deletions

View File

@@ -2680,4 +2680,70 @@ describe("createTelegramBot", () => {
expect(replySpy).toHaveBeenCalledTimes(1);
});
it("retries native command updates after a bubbled handler failure", async () => {
loadConfig.mockReturnValue({
commands: { native: true },
channels: {
telegram: {
dmPolicy: "open",
allowFrom: ["*"],
},
},
});
createTelegramBot({ token: "tok" });
const verboseHandler = commandSpy.mock.calls.find((call) => call[0] === "verbose")?.[1] as
| ((ctx: Record<string, unknown>) => Promise<void>)
| undefined;
if (!verboseHandler) {
throw new Error("verbose command handler missing");
}
const middlewares = middlewareUseSpy.mock.calls
.map((call) => call[0])
.filter(
(fn): fn is (ctx: Record<string, unknown>, next: () => Promise<void>) => Promise<void> =>
typeof fn === "function",
);
const runMiddlewareChain = async (ctx: Record<string, unknown>) => {
let idx = -1;
const dispatch = async (i: number): Promise<void> => {
if (i <= idx) {
throw new Error("middleware dispatch called multiple times");
}
idx = i;
const fn = middlewares[i];
if (!fn) {
await verboseHandler(ctx);
return;
}
await fn(ctx, async () => dispatch(i + 1));
};
await dispatch(0);
};
const ctx = {
update: { update_id: 333 },
message: {
chat: { id: 12345, type: "private" },
from: { id: 12345, username: "testuser" },
text: "/verbose on",
date: 1736380800,
message_id: 42,
},
match: "on",
};
const loadConfigCallsBeforeRetry = loadConfig.mock.calls.length;
loadConfig.mockImplementationOnce(() => {
throw new Error("cfg boom");
});
await expect(runMiddlewareChain(ctx)).rejects.toThrow("cfg boom");
const loadConfigCallsAfterFailure = loadConfig.mock.calls.length;
await runMiddlewareChain(ctx);
expect(loadConfigCallsAfterFailure).toBe(loadConfigCallsBeforeRetry + 1);
expect(loadConfig.mock.calls.length).toBeGreaterThan(loadConfigCallsAfterFailure);
});
});