mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
test(release): harden QA and live validation (#108589)
This commit is contained in:
@@ -623,7 +623,7 @@ describe("qa scenario catalog", () => {
|
||||
const scenario = readQaScenarioById("luna-thinking-visibility-switch");
|
||||
const config = readQaScenarioExecutionConfig("luna-thinking-visibility-switch") as
|
||||
| {
|
||||
requiredProvider?: string;
|
||||
liveProvider?: string;
|
||||
requiredModel?: string;
|
||||
offDirective?: string;
|
||||
maxDirective?: string;
|
||||
@@ -632,7 +632,7 @@ describe("qa scenario catalog", () => {
|
||||
| undefined;
|
||||
|
||||
expect(scenario.sourcePath).toBe("qa/scenarios/models/luna-thinking-visibility-switch.yaml");
|
||||
expect(config?.requiredProvider).toBe("openai");
|
||||
expect(config?.liveProvider).toBe("openai");
|
||||
expect(config?.requiredModel).toBe("gpt-5.6-luna");
|
||||
expect(config?.offDirective).toBe("/think off");
|
||||
expect(config?.maxDirective).toBe("/think medium");
|
||||
@@ -939,6 +939,19 @@ describe("qa scenario catalog", () => {
|
||||
expect(subagentFanout.execution.suiteIsolation).toBe("isolated");
|
||||
});
|
||||
|
||||
it("settles subagent completions before reading the SQLite session store", () => {
|
||||
const scenario = requireFlowScenario(readQaScenarioById("subagent-fanout-synthesis"));
|
||||
const flow = JSON.stringify(scenario.execution.flow);
|
||||
const completionWaits = [...flow.matchAll(/expectedChildCompletionMarkers/gu)].map(
|
||||
(match) => match.index,
|
||||
);
|
||||
const storeReads = [...flow.matchAll(/readRawQaSessionStore/gu)].map((match) => match.index);
|
||||
|
||||
expect(completionWaits).toHaveLength(2);
|
||||
expect(storeReads).toHaveLength(2);
|
||||
expect(completionWaits.every((wait, index) => wait < (storeReads[index] ?? -1))).toBe(true);
|
||||
});
|
||||
|
||||
it("adds a dreaming shadow trial report scenario", () => {
|
||||
const scenario = readQaScenarioById("dreaming-shadow-trial-report");
|
||||
const config = readQaScenarioExecutionConfig("dreaming-shadow-trial-report") as
|
||||
|
||||
@@ -60,7 +60,6 @@ const hasDiscoveryLabels = vi.hoisted(() => vi.fn());
|
||||
const reportsDiscoveryScopeLeak = vi.hoisted(() => vi.fn());
|
||||
const reportsMissingDiscoveryFiles = vi.hoisted(() => vi.fn());
|
||||
const hasModelSwitchContinuitySignal = vi.hoisted(() => vi.fn());
|
||||
const qaChannelPlugin = vi.hoisted(() => ({ id: "qa-channel" }));
|
||||
const scanGatewayLogSentinels = vi.hoisted(() => vi.fn());
|
||||
const assertNoGatewayLogSentinels = vi.hoisted(() => vi.fn());
|
||||
|
||||
@@ -159,10 +158,6 @@ vi.mock("./model-switch-eval.js", () => ({
|
||||
hasModelSwitchContinuitySignal,
|
||||
}));
|
||||
|
||||
vi.mock("./runtime-api.js", () => ({
|
||||
qaChannelPlugin,
|
||||
}));
|
||||
|
||||
vi.mock("./gateway-log-sentinel.js", () => ({
|
||||
scanGatewayLogSentinels,
|
||||
assertNoGatewayLogSentinels,
|
||||
@@ -296,7 +291,6 @@ describe("qa suite runtime flow", () => {
|
||||
envArg: typeof env,
|
||||
configArg: Record<string, unknown>,
|
||||
) => Promise<unknown>;
|
||||
qaChannelPlugin: typeof qaChannelPlugin;
|
||||
webOpenPage: (params: { url: string }) => Promise<unknown>;
|
||||
};
|
||||
constants: {
|
||||
@@ -330,7 +324,6 @@ describe("qa suite runtime flow", () => {
|
||||
ensureImageGenerationConfigured,
|
||||
},
|
||||
);
|
||||
expect(call.deps.qaChannelPlugin).toBe(qaChannelPlugin);
|
||||
expect(call.constants).toEqual({
|
||||
imageUnderstandingPngBase64: "small",
|
||||
imageUnderstandingLargePngBase64: "large",
|
||||
|
||||
@@ -111,6 +111,23 @@ flow:
|
||||
expr: "config.expectedReplyGroups.every((group) => group.some((needle) => normalizeLowercaseStringOrEmpty(text).includes(needle)))"
|
||||
- expr: "30000"
|
||||
- expr: "env.providerMode === 'mock-openai' ? 100 : 250"
|
||||
- if:
|
||||
# The parent reply can arrive before child transcript writes finish.
|
||||
# Wait before opening the store so its integrity check sees settled FTS state.
|
||||
expr: "Boolean(env.mock) && env.gateway.runtimeEnv.OPENCLAW_QA_FORCE_RUNTIME !== 'codex'"
|
||||
then:
|
||||
- forEach:
|
||||
items:
|
||||
expr: "config.expectedChildCompletionMarkers"
|
||||
item: childCompletionMarker
|
||||
actions:
|
||||
- call: waitForOutboundMessage
|
||||
args:
|
||||
- ref: state
|
||||
- lambda:
|
||||
params: [candidate]
|
||||
expr: "String(candidate.text ?? '').trim() === childCompletionMarker"
|
||||
- 30000
|
||||
- if:
|
||||
expr: "Boolean(env.mock)"
|
||||
then:
|
||||
@@ -163,6 +180,21 @@ flow:
|
||||
- if:
|
||||
expr: "/timed out after/i.test(formatErrorMessage(attemptError))"
|
||||
then:
|
||||
- if:
|
||||
expr: "Boolean(env.mock) && env.gateway.runtimeEnv.OPENCLAW_QA_FORCE_RUNTIME !== 'codex'"
|
||||
then:
|
||||
- forEach:
|
||||
items:
|
||||
expr: "config.expectedChildCompletionMarkers"
|
||||
item: childCompletionMarker
|
||||
actions:
|
||||
- call: waitForOutboundMessage
|
||||
args:
|
||||
- ref: state
|
||||
- lambda:
|
||||
params: [candidate]
|
||||
expr: "String(candidate.text ?? '').trim() === childCompletionMarker"
|
||||
- 30000
|
||||
- call: readRawQaSessionStore
|
||||
saveAs: timeoutStore
|
||||
args:
|
||||
@@ -240,23 +272,4 @@ flow:
|
||||
expr: "lastError === '__done__'"
|
||||
message:
|
||||
expr: "lastError instanceof Error ? formatErrorMessage(lastError) : String(lastError ?? 'fanout retry exhausted')"
|
||||
- if:
|
||||
# Codex completes child sessions through its app-server path but
|
||||
# does not relay the child marker back onto the parent QA channel.
|
||||
# The shared assertions above already prove both child tool calls
|
||||
# and child session rows; keep this transport-only proof OpenClaw-specific.
|
||||
expr: "Boolean(env.mock) && env.gateway.runtimeEnv.OPENCLAW_QA_FORCE_RUNTIME !== 'codex'"
|
||||
then:
|
||||
- forEach:
|
||||
items:
|
||||
expr: "config.expectedChildCompletionMarkers"
|
||||
item: childCompletionMarker
|
||||
actions:
|
||||
- call: waitForOutboundMessage
|
||||
args:
|
||||
- ref: state
|
||||
- lambda:
|
||||
params: [candidate]
|
||||
expr: "String(candidate.text ?? '').trim() === childCompletionMarker"
|
||||
- 30000
|
||||
detailsExpr: "details"
|
||||
|
||||
@@ -29,7 +29,7 @@ scenario:
|
||||
kind: flow
|
||||
summary: Toggle reasoning display and GPT-5.6 Luna thinking between off/none and medium, then verify visible reasoning only on the medium turn.
|
||||
config:
|
||||
requiredProvider: openai
|
||||
liveProvider: openai
|
||||
requiredModel: gpt-5.6-luna
|
||||
offDirective: /think off
|
||||
maxDirective: /think medium
|
||||
@@ -57,7 +57,7 @@ flow:
|
||||
value:
|
||||
expr: splitModelRef(env.primaryModel)
|
||||
- assert:
|
||||
expr: "env.providerMode !== 'live-frontier' || (selected?.provider === config.requiredProvider && selected?.model === config.requiredModel)"
|
||||
expr: "env.providerMode !== 'live-frontier' || (selected?.provider === config.liveProvider && selected?.model === config.requiredModel)"
|
||||
message:
|
||||
expr: "`expected live GPT-5.6 Luna, got ${env.primaryModel}`"
|
||||
- sendInbound:
|
||||
|
||||
@@ -35,14 +35,21 @@ async function expectModelReturnsAssistantText(
|
||||
contextWindow: modelId === "glm-5.2" ? 1_000_000 : 202_800,
|
||||
maxTokens: modelId === "glm-5.2" ? 131_072 : 131_100,
|
||||
};
|
||||
const res = await completeSimple(
|
||||
model,
|
||||
{
|
||||
messages: createSingleUserPromptMessage(),
|
||||
},
|
||||
{ apiKey: ZAI_KEY, maxTokens: 64 },
|
||||
);
|
||||
const text = extractNonEmptyAssistantText(res.content);
|
||||
const runProbe = (maxTokens: number) =>
|
||||
completeSimple(
|
||||
model,
|
||||
{
|
||||
messages: createSingleUserPromptMessage(),
|
||||
},
|
||||
{ apiKey: ZAI_KEY, maxTokens },
|
||||
);
|
||||
const res = await runProbe(64);
|
||||
let text = extractNonEmptyAssistantText(res.content);
|
||||
// GLM can spend a tiny cap entirely on hidden reasoning. Retry only a
|
||||
// provider-declared truncation; other empty responses remain failures.
|
||||
if (text.length === 0 && res.stopReason === "length") {
|
||||
text = extractNonEmptyAssistantText((await runProbe(256)).content);
|
||||
}
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const publicSdkContractNarrowingTiers = [
|
||||
{
|
||||
name: "bundled-only public export",
|
||||
codeSuffix: "-public-demotion",
|
||||
count: 158,
|
||||
count: 157,
|
||||
replacement:
|
||||
"subpath becomes internal (private-local-only); no external successor — no known external consumers",
|
||||
releaseNote: /public export.*module stays available for bundled plugins.*private-local-only/u,
|
||||
|
||||
Reference in New Issue
Block a user