test: stabilize loaded full-suite checks

This commit is contained in:
Peter Steinberger
2026-04-12 11:52:43 -07:00
parent d35cc6ef86
commit e4841d767d
9 changed files with 61 additions and 34 deletions

View File

@@ -5,6 +5,8 @@ import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../../runtime-ap
import { setFeishuRuntime } from "../runtime.js";
import type { ResolvedFeishuAccount } from "../types.js";
const FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS = 10_000;
type InboundDebouncerParams<T> = {
onFlush?: (items: T[]) => Promise<void>;
onError?: (err: unknown, items: T[]) => void;
@@ -303,9 +305,11 @@ export async function replayFeishuLifecycleEvent(params: {
waitForSecond?: () => void | Promise<void>;
}) {
await params.handler(params.event);
await vi.waitFor(params.waitForFirst);
await vi.waitFor(params.waitForFirst, { timeout: FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS });
await params.handler(params.event);
await vi.waitFor(params.waitForSecond ?? params.waitForFirst);
await vi.waitFor(params.waitForSecond ?? params.waitForFirst, {
timeout: FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS,
});
}
export async function runFeishuLifecycleSequence(
@@ -314,7 +318,9 @@ export async function runFeishuLifecycleSequence(
) {
for (const [index, deliver] of deliveries.entries()) {
await deliver();
await vi.waitFor(waits[index] ?? waits.at(-1) ?? (() => {}));
await vi.waitFor(waits[index] ?? waits.at(-1) ?? (() => {}), {
timeout: FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS,
});
}
}
@@ -341,7 +347,7 @@ export async function expectFeishuReplyPipelineDedupedAcrossReplay(params: {
createFeishuReplyDispatcherMock: ReturnType<typeof vi.fn>;
waitTimeoutMs?: number;
}) {
const waitTimeoutMs = params.waitTimeoutMs;
const waitTimeoutMs = params.waitTimeoutMs ?? FEISHU_LIFECYCLE_WAIT_TIMEOUT_MS;
await replayFeishuLifecycleEvent({
handler: params.handler,
event: params.event,

View File

@@ -37,6 +37,12 @@ describe("runQaManualLane", () => {
listenUrl: "http://127.0.0.1:43124",
baseUrl: "http://127.0.0.1:58000",
state: {
reset: vi.fn(),
addInboundMessage: vi.fn(),
addOutboundMessage: vi.fn(),
readMessage: vi.fn(),
searchMessages: vi.fn(() => []),
waitFor: vi.fn(),
getSnapshot: () => ({
messages: [
{

View File

@@ -93,7 +93,9 @@ describe("qa multipass runtime", () => {
expect(script).toContain("pnpm install --frozen-lockfile");
expect(script).toContain("pnpm build");
expect(script).toContain("corepack prepare 'pnpm@10.32.1' --activate");
expect(script).toContain("'pnpm' 'openclaw' 'qa' 'suite' '--provider-mode' 'mock-openai'");
expect(script).toContain(
"'pnpm' 'openclaw' 'qa' 'suite' '--transport' 'qa-channel' '--provider-mode' 'mock-openai'",
);
expect(script).toContain("'--scenario' 'channel-chat-baseline'");
expect(script).toContain("'--scenario' 'thread-follow-up'");
expect(script).toContain("/workspace/openclaw-host/.artifacts/qa-e2e/multipass-test");
@@ -126,7 +128,9 @@ describe("qa multipass runtime", () => {
);
expect(plan.forwardedEnv.OPENAI_API_KEY).toBe("test-openai-key");
expect(script).toContain("OPENAI_API_KEY='test-openai-key'");
expect(script).toContain("'pnpm' 'openclaw' 'qa' 'suite' '--provider-mode' 'live-frontier'");
expect(script).toContain(
"'pnpm' 'openclaw' 'qa' 'suite' '--transport' 'qa-channel' '--provider-mode' 'live-frontier'",
);
});
it("redacts forwarded live secrets in the persisted artifact script", () => {

View File

@@ -9,6 +9,10 @@ export {
normalizeTelegramCommandName,
resolveTelegramCustomCommands,
} from "./src/command-config.js";
export {
listTelegramDirectoryGroupsFromConfig,
listTelegramDirectoryPeersFromConfig,
} from "./src/directory-config.js";
export { parseTelegramTopicConversation } from "./src/topic-conversation.js";
export { singleAccountKeysToMove } from "./src/setup-contract.js";
export { mergeTelegramAccountConfig } from "./src/accounts.js";

View File

@@ -43,8 +43,8 @@ function createExecDryRunBatch(params: { markerPath: string }) {
command: process.execPath,
args: ["-e", script],
allowInsecurePath: true,
timeoutMs: 15_000,
noOutputTimeoutMs: 15_000,
timeoutMs: 60_000,
noOutputTimeoutMs: 60_000,
},
},
{

View File

@@ -13,8 +13,8 @@ const LIVE_RUNTIME_STATE_GUARDS: Record<
forbidden: readonly string[];
}
> = {
[bundledPluginFile("whatsapp", "src/active-listener.ts")]: {
required: ["globalThis", 'Symbol.for("openclaw.whatsapp.activeListenerState")'],
[bundledPluginFile("whatsapp", "src/connection-controller-registry.ts")]: {
required: ["globalThis", 'Symbol.for("openclaw.whatsapp.connectionControllerRegistry")'],
forbidden: ["resolveGlobalSingleton"],
},
};

View File

@@ -25,14 +25,17 @@ type SlackContractApiSurface = Pick<
"listSlackDirectoryPeersFromConfig" | "listSlackDirectoryGroupsFromConfig"
>;
type SlackProbe = import("@openclaw/slack/api.js").SlackProbe;
type TelegramApiSurface = typeof import("@openclaw/telegram/api.js");
type TelegramContractApiSurface = Pick<
typeof import("@openclaw/telegram/contract-api.js"),
"listTelegramDirectoryPeersFromConfig" | "listTelegramDirectoryGroupsFromConfig"
>;
type TelegramProbe = import("@openclaw/telegram/api.js").TelegramProbe;
type TelegramTokenResolution = import("@openclaw/telegram/api.js").TelegramTokenResolution;
type WhatsAppApiSurface = typeof import("@openclaw/whatsapp/api.js");
let discordContractApi: DiscordContractApiSurface | undefined;
let slackContractApi: SlackContractApiSurface | undefined;
let telegramApi: TelegramApiSurface | undefined;
let telegramContractApi: TelegramContractApiSurface | undefined;
let whatsappApi: WhatsAppApiSurface | undefined;
function getDiscordContractApi(): DiscordContractApiSurface {
@@ -45,9 +48,9 @@ function getSlackContractApi(): SlackContractApiSurface {
return slackContractApi;
}
function getTelegramApi(): TelegramApiSurface {
telegramApi ??= loadBundledPluginApiSync<TelegramApiSurface>("telegram");
return telegramApi;
function getTelegramContractApi(): TelegramContractApiSurface {
telegramContractApi ??= loadBundledPluginContractApiSync<TelegramContractApiSurface>("telegram");
return telegramContractApi;
}
function getWhatsAppApi(): WhatsAppApiSurface {
@@ -261,8 +264,8 @@ export function describeSlackPluginsCoreExtensionContract() {
export function describeTelegramPluginsCoreExtensionContract() {
describe("telegram plugins-core extension contract", () => {
const listPeers = () => getTelegramApi().listTelegramDirectoryPeersFromConfig;
const listGroups = () => getTelegramApi().listTelegramDirectoryGroupsFromConfig;
const listPeers = () => getTelegramContractApi().listTelegramDirectoryPeersFromConfig;
const listGroups = () => getTelegramContractApi().listTelegramDirectoryGroupsFromConfig;
it("TelegramProbe satisfies BaseProbeResult", () => {
expectTypeOf<TelegramProbe>().toMatchTypeOf<BaseProbeResult>();

View File

@@ -312,7 +312,7 @@ describe("check-extension-package-tsc-boundary", () => {
"process.exit(2);",
].join(" "),
],
5_000,
20_000,
),
).rejects.toMatchObject({
message: expect.stringContaining("[... 6 earlier lines omitted ...]"),
@@ -320,31 +320,33 @@ describe("check-extension-package-tsc-boundary", () => {
kind: "nonzero-exit",
elapsedMs: expect.any(Number),
});
});
}, 30_000);
it("aborts concurrent sibling steps after the first failure", async () => {
const startedAt = Date.now();
const slowStepTimeoutMs = 60_000;
const abortBudgetMs = 30_000;
await expect(
runNodeStepsWithConcurrency(
[
{
label: "fail-fast",
args: ["--eval", "setTimeout(() => process.exit(2), 10)"],
timeoutMs: 5_000,
args: ["--eval", "process.exit(2)"],
timeoutMs: slowStepTimeoutMs,
},
{
label: "slow-step",
args: ["--eval", "setTimeout(() => {}, 10_000)"],
timeoutMs: 5_000,
args: ["--eval", "setTimeout(() => {}, 60_000)"],
timeoutMs: slowStepTimeoutMs,
},
],
2,
),
).rejects.toThrow("fail-fast");
expect(Date.now() - startedAt).toBeLessThan(2_000);
});
expect(Date.now() - startedAt).toBeLessThan(abortBudgetMs);
}, 45_000);
it("passes successful step timing metadata to onSuccess handlers", async () => {
const elapsedTimes: number[] = [];
@@ -353,8 +355,8 @@ describe("check-extension-package-tsc-boundary", () => {
[
{
label: "demo-step",
args: ["--eval", "setTimeout(() => process.exit(0), 10)"],
timeoutMs: 5_000,
args: ["--eval", "process.exit(0)"],
timeoutMs: 20_000,
onSuccess(result: { elapsedMs: number }) {
elapsedTimes.push(result.elapsedMs);
},
@@ -365,5 +367,5 @@ describe("check-extension-package-tsc-boundary", () => {
expect(elapsedTimes).toHaveLength(1);
expect(elapsedTimes[0]).toBeGreaterThanOrEqual(0);
});
}, 30_000);
});

View File

@@ -36,24 +36,26 @@ describe("prepare-extension-package-boundary-artifacts", () => {
it("aborts sibling steps after the first failure", async () => {
const startedAt = Date.now();
const slowStepTimeoutMs = 60_000;
const abortBudgetMs = 30_000;
await expect(
runNodeStepsInParallel([
{
label: "fail-fast",
args: ["--eval", "setTimeout(() => process.exit(2), 10)"],
timeoutMs: 5_000,
args: ["--eval", "process.exit(2)"],
timeoutMs: slowStepTimeoutMs,
},
{
label: "slow-step",
args: ["--eval", "setTimeout(() => {}, 10_000)"],
timeoutMs: 5_000,
args: ["--eval", "setTimeout(() => {}, 60_000)"],
timeoutMs: slowStepTimeoutMs,
},
]),
).rejects.toThrow("fail-fast failed with exit code 2");
expect(Date.now() - startedAt).toBeLessThan(2_000);
});
expect(Date.now() - startedAt).toBeLessThan(abortBudgetMs);
}, 45_000);
it("treats artifacts as fresh only when outputs are newer than inputs", () => {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-boundary-prep-"));