mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-23 15:38:10 +00:00
fix(qa): honor telegram live ready timeout
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user