























@@ -11,6 +11,11 @@ import {
1111validateQaScenarioExecutionConfig,
1212} from "./scenario-catalog.js";
131314+type CatalogScenario = ReturnType<typeof readQaScenarioPack>["scenarios"][number];
15+type FlowCatalogScenario = CatalogScenario & {
16+execution: Extract<CatalogScenario["execution"], { kind: "flow" }>;
17+};
18+1419function listScenarioMarkdownPaths(dir = "qa/scenarios"): string[] {
1520return fs
1621.readdirSync(dir, { withFileTypes: true })
@@ -24,6 +29,32 @@ function listScenarioMarkdownPaths(dir = "qa/scenarios"): string[] {
2429.toSorted();
2530}
263132+function isFlowScenario(scenario: CatalogScenario): scenario is FlowCatalogScenario {
33+return scenario.execution.kind === "flow";
34+}
35+36+function requireFlowScenario(scenario: CatalogScenario): FlowCatalogScenario {
37+expect(scenario.execution.kind).toBe("flow");
38+if (!isFlowScenario(scenario)) {
39+throw new Error(`expected ${scenario.id} to be a flow scenario`);
40+}
41+return scenario;
42+}
43+44+function flowContainsCall(value: unknown, callName: string): boolean {
45+if (Array.isArray(value)) {
46+return value.some((entry) => flowContainsCall(entry, callName));
47+}
48+if (!value || typeof value !== "object") {
49+return false;
50+}
51+const record = value as Record<string, unknown>;
52+return (
53+record.call === callName ||
54+Object.values(record).some((entry) => flowContainsCall(entry, callName))
55+);
56+}
57+2758describe("qa scenario catalog", () => {
2859const dottedCoverageIdPattern = /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)+$/;
2960@@ -144,6 +175,34 @@ describe("qa scenario catalog", () => {
144175expect(fanoutConfig?.expectedReplyGroups?.flat()).toContain("subagent-2: ok");
145176});
146177178+it("loads explicit suite isolation metadata from per-scenario YAML", () => {
179+const staleLinks = requireFlowScenario(readQaScenarioById("subagent-stale-child-links"));
180+const kitchenSink = requireFlowScenario(readQaScenarioById("kitchen-sink-live-openai"));
181+182+expect(staleLinks.execution.suiteIsolation).toBe("isolated");
183+expect(staleLinks.execution.isolationReason).toContain("gateway session");
184+expect(kitchenSink.execution.suiteIsolation).toBe("isolated");
185+expect(kitchenSink.execution.isolationReason).toContain("plugin/channel/tool config");
186+});
187+188+it("requires explicit suite isolation for gateway state restart scenarios", () => {
189+const scenarios = readQaScenarioPack()
190+.scenarios.filter(isFlowScenario)
191+.filter((scenario) =>
192+flowContainsCall(scenario.execution.flow, "env.gateway.restartAfterStateMutation"),
193+);
194+195+expect(scenarios.map((scenario) => scenario.id).toSorted()).toEqual([
196+"kitchen-sink-live-openai",
197+"subagent-stale-child-links",
198+]);
199+expect(
200+scenarios
201+.filter((scenario) => scenario.execution.suiteIsolation !== "isolated")
202+.map((scenario) => scenario.id),
203+).toEqual([]);
204+});
205+147206it("loads scenario-declared gateway runtime options from YAML", () => {
148207const scenario = readQaScenarioById("control-ui-qa-channel-image-roundtrip");
149208const otelStdout = readQaScenarioById("otel-stdout-log-smoke");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。