fix(qa): honor telegram live ready timeout

This commit is contained in:
Vincent Koc
2026-06-19 08:36:32 +02:00
parent 3c01716c82
commit b972feb3f7
3 changed files with 98 additions and 5 deletions

View File

@@ -532,6 +532,7 @@ jobs:
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.scenario || '' }}
run: |
set -euo pipefail

View File

@@ -156,6 +156,54 @@ describe("telegram live qa runtime", () => {
expect(gateway.call).toHaveBeenCalledTimes(2);
});
it("normalizes the Telegram QA transport ready timeout env", () => {
expect(testing.resolveTelegramQaReadyTimeoutMs({})).toBe(45_000);
expect(
testing.resolveTelegramQaReadyTimeoutMs({
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000",
}),
).toBe(180_000);
expect(
testing.resolveTelegramQaReadyTimeoutMs({
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "bad",
}),
).toBe(45_000);
for (const value of ["0x10", "1e3", "10.5"]) {
expect(
testing.resolveTelegramQaReadyTimeoutMs({
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: value,
}),
).toBe(45_000);
}
});
it("includes the last Telegram readiness status when the account stays unavailable", async () => {
const gateway = {
call: vi.fn().mockResolvedValue({
channelAccounts: {
telegram: [
{
accountId: "sut",
connected: false,
lastError: "Telegram getUpdates conflict",
restartPending: true,
running: true,
},
],
},
}),
};
await expect(
testing.waitForTelegramChannelRunning(gateway as never, "sut", {
pollMs: 1,
timeoutMs: 5,
}),
).rejects.toThrow(
'telegram account "sut" did not become ready; last status: {"connected":false,"lastError":"Telegram getUpdates conflict","restartPending":true,"running":true}',
);
});
it("normalizes the Telegram QA canary timeout env", () => {
expect(testing.resolveTelegramQaCanaryTimeoutMs({})).toBe(30_000);
expect(

View File

@@ -126,6 +126,7 @@ type TelegramObservedMessage = {
};
const DEFAULT_TELEGRAM_QA_CANARY_TIMEOUT_MS = 30_000;
const TELEGRAM_QA_DEFAULT_READY_TIMEOUT_MS = 45_000;
type TelegramQaScenarioResult = LiveTransportCheckResult;
@@ -157,6 +158,15 @@ type TelegramQaRunResult = {
scenarios: TelegramQaScenarioResult[];
};
type TelegramChannelStatus = {
connected?: boolean;
lastConnectedAt?: number;
lastDisconnect?: unknown;
lastError?: string | null;
restartPending?: boolean;
running?: boolean;
};
class TelegramQaCanaryError extends Error {
phase: TelegramQaCanaryPhase;
context: Record<string, string | number | undefined>;
@@ -617,6 +627,14 @@ function resolveTelegramQaScenarioTimeoutMs(
return parsePositiveTelegramQaEnvMs(env, "OPENCLAW_QA_TELEGRAM_SCENARIO_TIMEOUT_MS", fallbackMs);
}
function resolveTelegramQaReadyTimeoutMs(env: NodeJS.ProcessEnv = process.env) {
const raw = env.OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS;
if (!raw) {
return TELEGRAM_QA_DEFAULT_READY_TIMEOUT_MS;
}
return parseStrictPositiveInteger(raw) ?? TELEGRAM_QA_DEFAULT_READY_TIMEOUT_MS;
}
function normalizeTelegramQaRttOptions(params: {
count?: number;
checkIds?: readonly string[];
@@ -1230,13 +1248,16 @@ async function waitForTelegramChannelRunning(
gateway: Awaited<ReturnType<typeof startQaGatewayChild>>,
accountId: string,
options?: {
env?: NodeJS.ProcessEnv;
pollMs?: number;
timeoutMs?: number;
},
) {
const startedAt = Date.now();
const timeoutMs = options?.timeoutMs ?? 45_000;
const timeoutMs = options?.timeoutMs ?? resolveTelegramQaReadyTimeoutMs(options?.env);
const pollMs = options?.pollMs ?? 500;
let lastProbeError: string | undefined;
let lastStatus: TelegramChannelStatus | undefined;
while (Date.now() - startedAt < timeoutMs) {
try {
const payload = (await gateway.call(
@@ -1249,6 +1270,9 @@ async function waitForTelegramChannelRunning(
Array<{
accountId?: string;
connected?: boolean;
lastConnectedAt?: number;
lastDisconnect?: unknown;
lastError?: string | null;
running?: boolean;
restartPending?: boolean;
}>
@@ -1256,21 +1280,39 @@ async function waitForTelegramChannelRunning(
};
const accounts = payload.channelAccounts?.telegram ?? [];
const match = accounts.find((entry) => entry.accountId === accountId);
lastProbeError = undefined;
lastStatus = match
? {
connected: match.connected,
lastConnectedAt: match.lastConnectedAt,
lastDisconnect: match.lastDisconnect,
lastError: match.lastError,
restartPending: match.restartPending,
running: match.running,
}
: undefined;
if (match?.running && match.connected === true && match.restartPending !== true) {
return;
}
} catch {
} catch (error) {
lastProbeError = formatErrorMessage(error);
// retry
}
await new Promise((resolve) => {
setTimeout(resolve, pollMs);
});
}
throw new Error(`telegram account "${accountId}" did not become ready`);
const details = lastStatus
? `; last status: ${JSON.stringify(lastStatus)}`
: lastProbeError
? `; last probe error: ${lastProbeError}`
: "";
throw new Error(`telegram account "${accountId}" did not become ready${details}`);
}
async function setTelegramQaDriverGroupAuthorization(params: {
driverBotId: number;
env: NodeJS.ProcessEnv;
gateway: Awaited<ReturnType<typeof startQaGatewayChild>>;
groupId: string;
sutAccountId: string;
@@ -1293,7 +1335,7 @@ async function setTelegramQaDriverGroupAuthorization(params: {
mode: 0o600,
});
});
await waitForTelegramChannelRunning(params.gateway, params.sutAccountId);
await waitForTelegramChannelRunning(params.gateway, params.sutAccountId, { env: params.env });
}
function renderTelegramQaMarkdown(params: {
@@ -1903,7 +1945,7 @@ export async function runTelegramQaLive(params: {
}),
});
try {
await waitForTelegramChannelRunning(gatewayHarness.gateway, sutAccountId);
await waitForTelegramChannelRunning(gatewayHarness.gateway, sutAccountId, { env });
assertLeaseHealthy();
let latestSutMessageId: number | undefined;
try {
@@ -1982,6 +2024,7 @@ export async function runTelegramQaLive(params: {
if (step.driverGroupAuthorization) {
await setTelegramQaDriverGroupAuthorization({
driverBotId: driverIdentity.id,
env,
gateway: gatewayHarness.gateway,
groupId: runtimeEnv.groupId,
sutAccountId,
@@ -2259,6 +2302,7 @@ export const testing = {
parseTelegramQaCredentialPayload,
normalizeTelegramQaRttOptions,
resolveTelegramQaCanaryTimeoutMs,
resolveTelegramQaReadyTimeoutMs,
resolveTelegramQaScenarioTimeoutMs,
resolveTelegramQaRuntimeEnv,
sanitizeTelegramQaProgressValue,