mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
refactor(qa): migrate Telegram scenarios into QA Lab (#108430)
* refactor(qa): migrate Telegram scenarios into QA Lab * refactor(qa): remove retired Telegram runner exports
This commit is contained in:
@@ -6,6 +6,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import type { QaProviderMode } from "../../extensions/qa-lab/src/run-config.ts";
|
||||
import type { QaSuiteRoundTripProbe } from "../../extensions/qa-lab/src/suite-round-trip.ts";
|
||||
|
||||
function parseBoolean(value: string | undefined) {
|
||||
const normalized = value?.trim().toLowerCase();
|
||||
@@ -53,24 +54,47 @@ function resolvePackageTelegramOutputDir(env: NodeJS.ProcessEnv, repoRoot: strin
|
||||
);
|
||||
}
|
||||
|
||||
const DEFAULT_RTT_CHECK_ID = "telegram-mentioned-message-reply";
|
||||
const DEFAULT_RTT_CHECK_ID = "channel-canary";
|
||||
|
||||
function resolveRttOptions(env: NodeJS.ProcessEnv, selectedScenarioIds: readonly string[] = []) {
|
||||
const explicitCheckIds = splitCsv(env.OPENCLAW_NPM_TELEGRAM_RTT_CHECKS);
|
||||
const checkIds = explicitCheckIds.length > 0 ? explicitCheckIds : [DEFAULT_RTT_CHECK_ID];
|
||||
const unknownCheckIds = checkIds.filter((checkId) => checkId !== DEFAULT_RTT_CHECK_ID);
|
||||
if (unknownCheckIds.length > 0) {
|
||||
throw new Error(`unknown Telegram QA RTT check: ${unknownCheckIds[0]}`);
|
||||
}
|
||||
if (
|
||||
explicitCheckIds.length === 0 &&
|
||||
selectedScenarioIds.length > 0 &&
|
||||
!selectedScenarioIds.includes(DEFAULT_RTT_CHECK_ID)
|
||||
) {
|
||||
return {};
|
||||
return undefined;
|
||||
}
|
||||
const rttCount = parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_SAMPLES") ?? 20;
|
||||
const count = parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_SAMPLES") ?? 20;
|
||||
return {
|
||||
rttCount,
|
||||
rttTimeoutMs: parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_TIMEOUT_MS"),
|
||||
maxRttFailures:
|
||||
parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_MAX_FAILURES") ?? rttCount,
|
||||
rttCheckIds: explicitCheckIds,
|
||||
scenarioId: DEFAULT_RTT_CHECK_ID,
|
||||
count,
|
||||
timeoutMs: parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_TIMEOUT_MS") ?? 30_000,
|
||||
maxFailures: parsePositiveIntegerEnv(env, "OPENCLAW_NPM_TELEGRAM_RTT_MAX_FAILURES") ?? count,
|
||||
};
|
||||
}
|
||||
|
||||
function createRoundTripProbe(
|
||||
options: ReturnType<typeof resolveRttOptions>,
|
||||
): QaSuiteRoundTripProbe | undefined {
|
||||
if (!options) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...options,
|
||||
markerPrefix: "QA-TELEGRAM-RTT",
|
||||
input: {
|
||||
conversation: { id: "telegram-rtt-room", kind: "group" },
|
||||
senderId: "qa-rtt-driver",
|
||||
senderName: "QA RTT Driver",
|
||||
},
|
||||
textPrefix: "@openclaw Telegram RTT check. Reply exactly: ",
|
||||
chainReplies: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -117,8 +141,8 @@ async function resolveTrustedOpenClawCommand(
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { runTelegramQaLive } =
|
||||
await import("../../extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime.ts");
|
||||
const { runQaTelegramSuite } =
|
||||
await import("../../extensions/qa-lab/src/live-transports/telegram/cli.runtime.ts");
|
||||
const rawSutOpenClawCommand = process.env.OPENCLAW_NPM_TELEGRAM_SUT_COMMAND?.trim();
|
||||
if (!rawSutOpenClawCommand) {
|
||||
throw new Error("Missing OPENCLAW_NPM_TELEGRAM_SUT_COMMAND.");
|
||||
@@ -128,8 +152,8 @@ async function main() {
|
||||
const repoRoot = path.resolve(process.env.OPENCLAW_NPM_TELEGRAM_REPO_ROOT ?? process.cwd());
|
||||
const outputDir = resolvePackageTelegramOutputDir(process.env, repoRoot);
|
||||
const scenarioIds = splitCsv(process.env.OPENCLAW_NPM_TELEGRAM_SCENARIOS);
|
||||
const result = await runTelegramQaLive({
|
||||
env: process.env,
|
||||
const result = await runQaTelegramSuite({
|
||||
allowFailures: true,
|
||||
repoRoot,
|
||||
outputDir,
|
||||
sutOpenClawCommand,
|
||||
@@ -138,11 +162,14 @@ async function main() {
|
||||
alternateModel: process.env.OPENCLAW_NPM_TELEGRAM_ALT_MODEL,
|
||||
fastMode: parseBoolean(process.env.OPENCLAW_NPM_TELEGRAM_FAST),
|
||||
scenarioIds,
|
||||
...resolveRttOptions(process.env, scenarioIds),
|
||||
roundTripProbe: createRoundTripProbe(resolveRttOptions(process.env, scenarioIds)),
|
||||
sutAccountId: process.env.OPENCLAW_NPM_TELEGRAM_SUT_ACCOUNT,
|
||||
credentialSource: resolveCredentialSource(process.env),
|
||||
credentialRole: resolveCredentialRole(process.env),
|
||||
});
|
||||
if (!result) {
|
||||
throw new Error("Package Telegram QA did not produce suite artifacts.");
|
||||
}
|
||||
|
||||
process.stdout.write(`Package Telegram QA report: ${result.reportPath}\n`);
|
||||
process.stdout.write(`Package Telegram QA summary: ${result.summaryPath}\n`);
|
||||
@@ -180,6 +207,7 @@ export const testing = {
|
||||
resolvePackageTelegramOutputDir,
|
||||
resolveCredentialRole,
|
||||
resolveCredentialSource,
|
||||
createRoundTripProbe,
|
||||
resolveRttOptions,
|
||||
resolveTrustedOpenClawCommand,
|
||||
shouldFailPackageTelegramRun,
|
||||
|
||||
@@ -70,62 +70,6 @@ function evidenceCredentialSource(summary) {
|
||||
);
|
||||
}
|
||||
|
||||
// Historical Telegram summary artifacts can still appear in old Mantis uploads.
|
||||
// Current QA producers write qa-evidence.json for gate inputs.
|
||||
function legacyTelegramSummaryToEvidenceSummary(summary) {
|
||||
const scenarios = Array.isArray(summary.scenarios) ? summary.scenarios : [];
|
||||
return {
|
||||
kind: "openclaw.qa.evidence-summary",
|
||||
schemaVersion: 2,
|
||||
generatedAt: new Date().toISOString(),
|
||||
entries: scenarios.map((scenario) => ({
|
||||
test: {
|
||||
kind: "legacy-telegram-qa-scenario",
|
||||
id: scenario?.id ?? "legacy-telegram-scenario",
|
||||
title: scenario?.title ?? scenario?.id ?? "Legacy Telegram scenario",
|
||||
},
|
||||
mapping: {
|
||||
profile: "release",
|
||||
coverage: [],
|
||||
},
|
||||
execution: {
|
||||
runner: "legacy-telegram-qa",
|
||||
environment: {
|
||||
ref: null,
|
||||
os: "unknown",
|
||||
nodeVersion: "unknown",
|
||||
},
|
||||
provider: {
|
||||
id: "unknown",
|
||||
live: true,
|
||||
model: { name: null, ref: null },
|
||||
auth: summary.credentials?.source ?? "unknown",
|
||||
},
|
||||
channel: {
|
||||
id: "telegram",
|
||||
live: true,
|
||||
driver: "native",
|
||||
},
|
||||
packageSource: { kind: "unknown" },
|
||||
artifacts: [
|
||||
{
|
||||
kind: "summary",
|
||||
path: "telegram-qa-summary.json",
|
||||
source: "legacy-telegram-qa",
|
||||
},
|
||||
],
|
||||
},
|
||||
result: {
|
||||
status: scenario?.status === "skip" ? "skipped" : (scenario?.status ?? "fail"),
|
||||
...(scenario?.status === "pass"
|
||||
? {}
|
||||
: { failure: { reason: scenario?.details ?? "legacy scenario did not pass" } }),
|
||||
...(typeof scenario?.rttMs === "number" ? { timing: { rttMs: scenario.rttMs } } : {}),
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function renderScenarioList(summary) {
|
||||
const entries = evidenceEntries(summary);
|
||||
if (entries.length === 0) {
|
||||
@@ -420,7 +364,7 @@ export function buildTelegramEvidenceManifest({
|
||||
kind: "report",
|
||||
lane: "run",
|
||||
label: "Telegram QA report",
|
||||
path: "telegram-qa-report.md",
|
||||
path: "qa-suite-report.md",
|
||||
targetPath: "report.md",
|
||||
},
|
||||
];
|
||||
@@ -453,17 +397,11 @@ export function writeTelegramEvidence(rawArgs = process.argv.slice(2)) {
|
||||
const outputDir = path.resolve(args.output_dir);
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
const evidenceSummaryPath = path.join(outputDir, "qa-evidence.json");
|
||||
const legacySummaryPath = path.join(outputDir, "telegram-qa-summary.json");
|
||||
const usesCurrentEvidenceSummary = existsSync(evidenceSummaryPath);
|
||||
const summaryPath = usesCurrentEvidenceSummary ? evidenceSummaryPath : legacySummaryPath;
|
||||
const observedPath = path.join(outputDir, "telegram-qa-observed-messages.json");
|
||||
const reportPath = path.join(outputDir, "telegram-qa-report.md");
|
||||
if (!existsSync(summaryPath)) {
|
||||
const reportPath = path.join(outputDir, "qa-suite-report.md");
|
||||
if (!existsSync(evidenceSummaryPath)) {
|
||||
throw new Error(`Missing Telegram QA evidence summary: ${evidenceSummaryPath}`);
|
||||
}
|
||||
const summary = usesCurrentEvidenceSummary
|
||||
? readJson(evidenceSummaryPath)
|
||||
: legacyTelegramSummaryToEvidenceSummary(readJson(legacySummaryPath));
|
||||
const summary = readJson(evidenceSummaryPath);
|
||||
const counts = evidenceCounts(summary);
|
||||
const pass = counts.failed === 0 && Number(counts.total ?? 0) > 0;
|
||||
if (!existsSync(reportPath)) {
|
||||
@@ -472,17 +410,15 @@ export function writeTelegramEvidence(rawArgs = process.argv.slice(2)) {
|
||||
}
|
||||
writeFileSync(reportPath, "# Mantis Telegram Live QA\n\nTelegram QA report was unavailable.\n");
|
||||
}
|
||||
const hasLegacyObservedMessages = !usesCurrentEvidenceSummary && existsSync(observedPath);
|
||||
const observedMessages = hasLegacyObservedMessages ? readJson(observedPath) : [];
|
||||
const transcriptHtml = renderTelegramEvidenceHtml({ observedMessages, summary });
|
||||
const transcriptHtml = renderTelegramEvidenceHtml({ observedMessages: [], summary });
|
||||
writeFileSync(path.join(outputDir, "telegram-live-transcript.html"), transcriptHtml, "utf8");
|
||||
const manifest = buildTelegramEvidenceManifest({
|
||||
candidateRef: args.candidate_ref,
|
||||
candidateSha: args.candidate_sha,
|
||||
hasObservedMessages: hasLegacyObservedMessages,
|
||||
hasObservedMessages: false,
|
||||
scenarioLabel: args.scenario_label,
|
||||
summary,
|
||||
summaryArtifactPath: path.basename(summaryPath),
|
||||
summaryArtifactPath: path.basename(evidenceSummaryPath),
|
||||
});
|
||||
writeFileSync(
|
||||
path.join(outputDir, "mantis-evidence.json"),
|
||||
|
||||
@@ -449,7 +449,7 @@ async function main(): Promise<void> {
|
||||
if (telegramRunId) {
|
||||
await pollRun(options.repo, telegramRunId);
|
||||
const artifactDir = downloadTelegramArtifact(options.repo, telegramRunId);
|
||||
const report = findFile(artifactDir, "telegram-qa-report.md");
|
||||
const report = findFile(artifactDir, "qa-suite-report.md");
|
||||
if (report && existsSync(report)) {
|
||||
console.log(`\nTelegram report: ${report}\n`);
|
||||
console.log(readFileSync(report, "utf8"));
|
||||
|
||||
Reference in New Issue
Block a user