fix(qa): retry transient Telegram polling failures

This commit is contained in:
Vincent Koc
2026-04-28 18:24:47 -07:00
parent 51119f2ef1
commit 7a88117f42
2 changed files with 79 additions and 1 deletions

View File

@@ -474,6 +474,69 @@ describe("telegram live qa runtime", () => {
).toBe(false);
});
it("retries transient Telegram polling fetch failures while waiting for scenario replies", async () => {
const fetchMock = vi
.fn()
.mockRejectedValueOnce(new TypeError("fetch failed"))
.mockResolvedValueOnce(
new Response(
JSON.stringify({
ok: true,
result: [
{
update_id: 10,
message: {
message_id: 99,
chat: { id: -100123 },
from: { id: 88, is_bot: true, username: "sut_bot" },
text: "Identity\nChannel: telegram",
date: 1_700_000_000,
reply_to_message: { message_id: 55 },
},
},
],
}),
{
status: 200,
headers: {
"content-type": "application/json",
},
},
),
);
vi.stubGlobal("fetch", fetchMock);
const observedMessages: Parameters<
typeof __testing.waitForObservedMessage
>[0]["observedMessages"] = [];
const result = await __testing.waitForObservedMessage({
token: "token",
initialOffset: 7,
timeoutMs: 5_000,
observedMessages,
observationScenarioId: "telegram-whoami-command",
observationScenarioTitle: "Telegram whoami reply",
predicate: (message) =>
__testing.matchesTelegramScenarioReply({
groupId: "-100123",
message,
sentMessageId: 55,
sutBotId: 88,
}),
});
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(result.message.messageId).toBe(99);
expect(result.nextOffset).toBe(11);
expect(observedMessages).toEqual([
expect.objectContaining({
matchedScenario: true,
messageId: 99,
scenarioId: "telegram-whoami-command",
}),
]);
});
it("redacts observed message content by default in artifacts", () => {
expect(
__testing.buildObservedMessagesArtifact({

View File

@@ -595,6 +595,10 @@ async function sendGroupMessage(token: string, groupId: string, text: string) {
});
}
async function waitForTelegramPollRetryDelay(remainingMs: number) {
await new Promise((resolve) => setTimeout(resolve, Math.min(250, Math.max(100, remainingMs))));
}
async function waitForObservedMessage(params: {
token: string;
initialOffset: number;
@@ -606,6 +610,7 @@ async function waitForObservedMessage(params: {
}) {
const startedAt = Date.now();
let offset = params.initialOffset;
let lastPollingError: unknown;
while (Date.now() - startedAt < params.timeoutMs) {
const remainingMs = Math.max(
1_000,
@@ -624,10 +629,13 @@ async function waitForObservedMessage(params: {
},
timeoutSeconds * 1000 + 5_000,
);
lastPollingError = undefined;
} catch (error) {
if (!isRecoverableTelegramQaPollError(error)) {
throw error;
}
lastPollingError = error;
await waitForTelegramPollRetryDelay(params.timeoutMs - (Date.now() - startedAt));
continue;
}
const batchObservedAtMs = Date.now();
@@ -653,7 +661,13 @@ async function waitForObservedMessage(params: {
}
}
}
throw new Error(`timed out after ${params.timeoutMs}ms waiting for Telegram message`);
const timeoutMessage = `timed out after ${params.timeoutMs}ms waiting for Telegram message`;
if (lastPollingError) {
throw new Error(
`${timeoutMessage}; last polling error: ${formatErrorMessage(lastPollingError)}`,
);
}
throw new Error(timeoutMessage);
}
async function waitForTelegramChannelRunning(
@@ -1472,4 +1486,5 @@ export const __testing = {
shouldLogTelegramQaLiveProgress,
formatTelegramQaProgressDetails,
renderTelegramQaMarkdown,
waitForObservedMessage,
};