Files
openclaw/extensions/qa-lab/src/coverage-report.test.ts
Peter Steinberger 7170a6231a feat(agents): unify agent status into a durable progress_card (#125125)
* feat(agents): unify agent status into a durable progress_card

Replace the write-only update_plan to-do tool and the fragmented plan
rendering with one durable status artifact per session: progress_card
({plan?, markdown?}, replace-on-write, 8 KiB markdown / 50-step caps).
Cards persist in a lazy-additive session_progress_cards table in the
per-agent DB (no schema-version bump), broadcast progressCard.changed,
and render from the store with exactly one live placement per view
(session rail when visible, else the composer-adjacent bar); transcripts
collapse to one-line receipts, and the sidebar hovercard shows other
sessions' cards inline (markdown + <progress>, DOMPurify allowlist, no
iframes). The three stream-derived plan renderers and their dedup
heuristics are deleted.

Codex runs disable the native plan tool per thread
(tools.update_plan.enabled=false) and receive progress_card via the
dynamic-tool bridge; compaction restore now reinjects the card (steps +
bounded markdown). Card writes still emit the legacy plan stream event so
native apps and channels keep working until their per-platform
migrations. Policy names map update_plan -> progress_card; the shipped
tools.updatePlan=false kill switch is honored.

Net -277 production LOC; -480 test LOC.

* test(agents): regenerate Codex prompt snapshots for update_plan thread-config disable

* chore(protocol): allowlist progressCard.changed for native apps pending card migration

* fix(ci): repair progress card integration checks

* fix(codex): canonicalize native progress cards

* test(gateway): reconcile progress card method order

* test(codex): stabilize native approval fixture
2026-08-17 09:44:04 -07:00

832 lines
29 KiB
TypeScript

// Qa Lab tests cover coverage report plugin behavior.
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it, vi } from "vitest";
import YAML from "yaml";
import {
buildQaCoverageInventory,
findQaScenarioMatches,
renderQaCoverageMarkdownReport,
renderQaScenarioMatchesMarkdownReport,
} from "./coverage-report.js";
import { readQaScenarioPack, type QaSeedScenarioWithSource } from "./scenario-catalog.js";
import { readQaScorecardTaxonomyReport, type QaMaturityTaxonomy } from "./scorecard-taxonomy.js";
const TEST_EXECUTABLE_CATEGORY_ID = "agent-runtime.agent-turn-execution";
const TEST_EXECUTABLE_COVERAGE_ID = "agent-runtime.dm";
const TEST_BROWSER_CATEGORY_ID = "control-ui.browser-ui";
const TEST_BROWSER_COVERAGE_ID = "control-ui.gateway-hosted-ui-control";
const TEST_WEBCHAT_COVERAGE_ID = "agent-runtime.webchat";
const TWO_PART_COVERAGE_ID_PATTERN = /^[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/;
function buildQaScorecardTaxonomyReport(params: {
taxonomy: QaMaturityTaxonomy;
repoRoot: string;
scenarios: readonly QaSeedScenarioWithSource[];
}) {
expect(params.repoRoot).toBe(process.cwd());
const parseSpy = vi.spyOn(YAML, "parse").mockReturnValueOnce(params.taxonomy);
try {
return readQaScorecardTaxonomyReport(params.scenarios);
} finally {
parseSpy.mockRestore();
}
}
function testMaturityTaxonomy(params?: {
categoryId?: string;
coverageIds?: readonly string[];
includeAllCategories?: boolean;
includeArchivedSurface?: boolean;
}): QaMaturityTaxonomy {
const categoryId = params?.categoryId ?? TEST_EXECUTABLE_CATEGORY_ID;
const firstDot = categoryId.indexOf(".");
const surfaceId = firstDot === -1 ? categoryId : categoryId.slice(0, firstDot);
const categoryLocalId = firstDot === -1 ? categoryId : categoryId.slice(firstDot + 1);
return {
version: 1 as const,
title: "Test taxonomy",
levels: [],
profiles: [
{
id: "smoke-ci",
description: "Test smoke profile.",
includeAllCategories: false,
channelDriver: "crabline" as const,
categoryIds: [categoryId],
coverageIds: [],
},
{
id: "release",
description: "Test release profile.",
includeAllCategories: params?.includeAllCategories ?? false,
channelDriver: "qa-channel" as const,
categoryIds: params?.includeAllCategories ? [] : [categoryId],
coverageIds: [],
},
],
surfaces: [
{
id: surfaceId,
name: "Test surface",
family: "test",
level: "experimental",
categories: [
{
id: categoryLocalId,
name: "Test category",
category_note: "test-category.md",
docs: [],
search_anchors: [],
features: (params?.coverageIds ?? [TEST_EXECUTABLE_COVERAGE_ID]).map((coverageId) => ({
name: coverageId,
coverageIds: [coverageId],
})),
},
],
},
...(params?.includeArchivedSurface
? [
{
id: "archived-surface",
name: "Archived surface",
family: "test",
level: "experimental",
archived: true,
categories: [
{
id: "legacy-category",
name: "Legacy category",
category_note: "legacy-category.md",
docs: [],
search_anchors: [],
features: [
{
name: "Legacy feature",
coverageIds: ["archived-surface.legacy-feature"],
},
],
},
],
},
]
: []),
],
};
}
function scenarioWithCoverage(params: {
primary?: readonly string[];
secondary?: readonly string[];
sourcePath?: string;
executionKind?: "flow" | "script" | "vitest" | "playwright";
executionPath?: string;
}): QaSeedScenarioWithSource {
const execution =
params.executionKind === "script" ||
params.executionKind === "vitest" ||
params.executionKind === "playwright"
? {
kind: params.executionKind,
path: params.executionPath ?? "src/test.test.ts",
}
: {
kind: "flow" as const,
flow: {
steps: [
{
name: "noop",
actions: [{ set: "ok", value: true }],
},
],
},
};
return {
id: "test-scenario",
title: "Test scenario",
surface: "test",
coverage: {
primary: [...(params.primary ?? [])],
...(params.secondary ? { secondary: [...params.secondary] } : {}),
},
objective: "Exercise test coverage.",
successCriteria: ["Evidence is recorded."],
sourcePath: params.sourcePath ?? "qa/scenarios/test/test-scenario.yaml",
execution,
};
}
describe("qa coverage report", () => {
it("groups scenario coverage metadata by theme and surface", () => {
const scenarios = readQaScenarioPack().scenarios;
const inventory = buildQaCoverageInventory(scenarios);
expect(inventory.scenarioCount).toBeGreaterThan(0);
expect(inventory.coverageIdCount).toBeGreaterThan(0);
expect(inventory.primaryCoverageIdCount).toBeGreaterThan(0);
expect(inventory.secondaryCoverageIdCount).toBeGreaterThan(0);
expect(inventory.overlappingCoverage.length).toBeGreaterThan(0);
expect(inventory.missingCoverage).toStrictEqual([]);
expect(inventory.scorecardTaxonomy.profileCount).toBe(5);
expect(
inventory.scorecardTaxonomy.profiles.find((profile) => profile.id === "smoke-ci"),
).toMatchObject({
channelDriver: "crabline",
evidenceMode: "slim",
});
expect(
inventory.scorecardTaxonomy.profiles.find((profile) => profile.id === "release"),
).toMatchObject({
channelDriver: "live",
});
for (const [categoryId, scenarioRef] of [
["containers.container-setup", "qa/scenarios/runtime/compose-setup.yaml"],
[
"containers.image-release-and-validation",
"qa/scenarios/runtime/docker-package-install.yaml",
],
] as const) {
const category = inventory.scorecardTaxonomy.categories.find(
(entry) => entry.id === categoryId,
);
expect(category?.profiles).toContain("release");
expect(category?.profiles).not.toContain("smoke-ci");
expect(scenarios.find((scenario) => scenario.sourcePath === scenarioRef)?.category).toBe(
categoryId,
);
}
expect(
inventory.scorecardTaxonomy.profiles.find((profile) => profile.id === "all"),
).toMatchObject({
channelDriver: "live",
categoryIds: expect.arrayContaining(["tools.tool-invocation-and-execution"]),
});
expect(inventory.scorecardTaxonomy.categoryCount).toBeGreaterThan(200);
expect(inventory.scorecardTaxonomy.requiredCategoryCount).toBeGreaterThan(0);
expect(inventory.scorecardTaxonomy.requiredCategoryCount).toBeLessThanOrEqual(
inventory.scorecardTaxonomy.categoryCount,
);
expect(inventory.scorecardTaxonomy.requiredCoverageIdCount).toBeGreaterThan(0);
expect(inventory.scorecardTaxonomy.inventoriedCoverageIdCount).toBeGreaterThan(0);
expect(inventory.scorecardTaxonomy.coverageIdInventoryPercent).toBeGreaterThan(0);
expect(inventory.scorecardTaxonomy.inventoryRefCount).toBeGreaterThan(0);
expect(inventory.scorecardTaxonomy.scenarioCoverageIdCount).toBeGreaterThan(0);
expect(inventory.scorecardTaxonomy.unknownCoverageIdCount).toBe(0);
expect(
inventory.scorecardTaxonomy.categories
.flatMap((category) => category.coverageIds)
.every((coverageId) => TWO_PART_COVERAGE_ID_PATTERN.test(coverageId)),
).toBe(true);
expect(inventory.scorecardTaxonomy.validationIssues.length).toBeGreaterThan(0);
expect(
inventory.scorecardTaxonomy.validationIssues.some((issue) =>
issue.code.endsWith("not-found"),
),
).toBe(false);
expect(
inventory.scorecardTaxonomy.validationIssues.some(
(issue) => issue.code === "coverage-id-missing-primary-inventory",
),
).toBe(true);
expect(
inventory.scorecardTaxonomy.categories.find(
(category) => category.id === TEST_BROWSER_CATEGORY_ID,
)?.inventoryRefs,
).toEqual(
expect.arrayContaining([
{
coverageId: TEST_BROWSER_COVERAGE_ID,
kind: "qa-scenario",
path: null,
role: "primary",
scenarioRefs: ["qa/scenarios/ui/control-ui-qa-channel-image-roundtrip.yaml"],
},
{
coverageId: TEST_BROWSER_COVERAGE_ID,
kind: "playwright",
path: "ui/src/e2e/chat-flow.messaging.e2e.test.ts",
role: "secondary",
scenarioRefs: ["qa/scenarios/ui/control-ui-chat-flow-playwright.yaml"],
},
{
coverageId: TEST_BROWSER_COVERAGE_ID,
kind: "playwright",
path: "ui/src/e2e/session-progress-live-placement.e2e.test.ts",
role: "secondary",
scenarioRefs: ["qa/scenarios/ui/control-ui-progress-card-live-placement.yaml"],
},
]),
);
expect(
expectDefined(inventory.byTheme.memory, "memory QA theme").map((coverage) => coverage.id),
).toContain("session-memory.memory-recall");
expect(
expectDefined(inventory.bySurface["session-memory"], "memory QA surface").map(
(coverage) => coverage.id,
),
).toContain("session-memory.memory-recall");
});
it("inventories runnable multi-actor turn ordering as primary runtime evidence", () => {
const coverageId = "agent-runtime.session-turn-ordering";
const sourcePath = "qa/scenarios/channels/channel-multi-actor-ordering.yaml";
const scenarios = readQaScenarioPack().scenarios;
const scenario = expectDefined(
scenarios.find((candidate) => candidate.id === "channel-multi-actor-ordering"),
"multi-actor turn ordering scenario",
);
expect(scenario.execution.kind).toBe("flow");
expect(scenario.coverage?.primary).toEqual(["channels.room-allowlist", coverageId]);
const orderingActions = (
scenario.execution.flow?.steps.flatMap((step) => step.actions) ?? []
).filter(
(action): action is Record<string, unknown> =>
typeof action === "object" &&
action !== null &&
("sendInbound" in action ||
"waitForNoOutbound" in action ||
"waitForOutbound" in action ||
"assert" in action),
);
expect(orderingActions).toMatchObject([
{
sendInbound: {
conversation: { id: { ref: "config.conversationId" } },
senderId: "observer",
},
},
{ waitForNoOutbound: { sinceIndex: { ref: "outboundStartIndex" } } },
{
sendInbound: {
conversation: { id: { ref: "config.conversationId" } },
senderId: "driver",
},
},
{
waitForOutbound: {
conversation: { id: { ref: "config.conversationId" } },
textIncludes: { ref: "config.expectedMarker" },
},
},
{ assert: { expr: expect.stringContaining("config.blockedMarker") } },
]);
const inventory = buildQaCoverageInventory(scenarios);
const coverage = expectDefined(
inventory.coverageIds.find((candidate) => candidate.id === coverageId),
"session turn ordering coverage inventory",
);
expect(coverage.scenarios).toContainEqual(
expect.objectContaining({
id: scenario.id,
sourcePath,
intent: "primary",
}),
);
const category = expectDefined(
inventory.scorecardTaxonomy.categories.find(
(candidate) => candidate.id === TEST_EXECUTABLE_CATEGORY_ID,
),
"agent turn execution scorecard category",
);
expect(category.inventoryRefs).toContainEqual({
coverageId,
kind: "qa-scenario",
path: null,
role: "primary",
scenarioRefs: [sourcePath],
});
expect(inventory.scorecardTaxonomy.validationIssues).not.toContainEqual(
expect.objectContaining({
code: "coverage-id-missing-primary-inventory",
ref: coverageId,
}),
);
});
it("rejects duplicate ownership across YAML and non-YAML catalogs", () => {
const scenario = scenarioWithCoverage({
primary: [TEST_EXECUTABLE_COVERAGE_ID],
executionKind: "script",
executionPath: "scripts/test-scenario.ts",
});
expect(() =>
buildQaCoverageInventory([scenario], {
nonYamlScenarios: [
{
id: scenario.id,
sourcePath: "extensions/qa-lab/src/live-transports/discord/discord-live.runtime.ts",
},
],
}),
).toThrow(
"duplicate qa scenario id(s): test-scenario (qa/scenarios/test/test-scenario.yaml, extensions/qa-lab/src/live-transports/discord/discord-live.runtime.ts)",
);
});
it("renders a compact markdown inventory", () => {
const report = renderQaCoverageMarkdownReport(
buildQaCoverageInventory(readQaScenarioPack().scenarios),
);
expect(report).toContain("# QA Coverage Inventory");
expect(report).toContain("- Missing coverage metadata: 0");
expect(report).toContain("- Overlapping coverage IDs:");
expect(report).toContain("session-memory.embedding-search-recall");
expect(report).toContain("primary: memory-recall (qa/scenarios/memory/memory-recall.yaml)");
expect(report).toContain("secondary: active-memory-preprompt-recall");
expect(report).toContain("personal-share-safe-diagnostics-artifact");
expect(report).toContain("## Scorecard Taxonomy");
expect(report).toContain("- Taxonomy: taxonomy.yaml");
expect(report).toContain("- Inventoried taxonomy categories:");
expect(report).toContain("- Inventoried taxonomy coverage IDs:");
expect(report).toContain("- Inventory refs:");
expect(report).toContain("- Scenario coverage IDs:");
expect(report).toContain(
"- tools.tool-invocation-and-execution (tools / Tool Invocation and Execution; partial): profiles: all, release; coverage IDs:",
);
expect(report).toContain(
"primary:qa-scenario:qa/scenarios/ui/control-ui-qa-channel-image-roundtrip.yaml (control-ui.gateway-hosted-ui-control)",
);
for (const executionPath of [
"ui/src/e2e/chat-flow.messaging.e2e.test.ts",
"ui/src/e2e/session-progress-live-placement.e2e.test.ts",
]) {
expect(report).toContain(
`secondary:playwright:${executionPath} (${TEST_BROWSER_COVERAGE_ID})`,
);
expect(report).not.toContain(
`primary:playwright:${executionPath} (${TEST_BROWSER_COVERAGE_ID})`,
);
}
expect(report).not.toContain("### Unknown Scenario Coverage IDs");
});
it("renders Playwright matches as qa suite targets", () => {
const matches = findQaScenarioMatches(
readQaScenarioPack().scenarios,
"chat-flow.messaging.e2e",
);
const report = renderQaScenarioMatchesMarkdownReport({
query: "chat-flow.messaging.e2e",
matches,
});
expect(report).toContain(
"- Suite command: `pnpm openclaw qa suite --scenario control-ui-chat-flow-playwright`",
);
expect(report).toContain(
" - execution: playwright ui/src/e2e/chat-flow.messaging.e2e.test.ts",
);
expect(report).not.toContain("Native test refs");
});
it("includes a runnable channel driver choice in scenario match commands", () => {
const matches = findQaScenarioMatches(
readQaScenarioPack().scenarios,
"whatsapp-access-control-group-disabled",
);
const report = renderQaScenarioMatchesMarkdownReport({
query: "whatsapp-access-control-group-disabled",
matches,
});
expect(report).toContain(
"- Suite command: `pnpm openclaw qa suite --channel-driver live --channel whatsapp --scenario whatsapp-access-control-group-disabled`",
);
});
it("keeps qa-channel scenario commands on the default driver", () => {
const matches = findQaScenarioMatches(
readQaScenarioPack().scenarios,
"instruction-followthrough-repo-contract",
);
const report = renderQaScenarioMatchesMarkdownReport({
query: "instruction-followthrough-repo-contract",
matches,
});
expect(report).toContain(
"- Suite command: `pnpm openclaw qa suite --scenario instruction-followthrough-repo-contract`",
);
expect(report).not.toContain("--channel-driver live --channel qa-channel");
});
it("uses the live lane as the coverage-report default for channel scenarios", () => {
const matches = findQaScenarioMatches(readQaScenarioPack().scenarios, "dm-per-room-session");
const report = renderQaScenarioMatchesMarkdownReport({
query: "dm-per-room-session",
matches,
});
expect(report).toContain(
"- Suite command: `pnpm openclaw qa suite --channel-driver live --channel matrix --scenario dm-per-room-session`",
);
});
it("splits flow commands across channel lanes", () => {
const scenarios = readQaScenarioPack().scenarios;
const matches = [
...findQaScenarioMatches(scenarios, "dm-per-room-session"),
...findQaScenarioMatches(scenarios, "whatsapp-access-control-group-disabled"),
];
const report = renderQaScenarioMatchesMarkdownReport({ query: "channel lanes", matches });
expect(report).toContain(
"--channel-driver live --channel matrix --scenario dm-per-room-session",
);
expect(report).toContain(
"--channel-driver live --channel whatsapp --scenario whatsapp-access-control-group-disabled",
);
});
it("splits qa suite targets when matches mix execution kinds", () => {
const playwrightExecutionPath = "ui/src/e2e/chat-flow.messaging.e2e.test.ts";
const flowScenario = scenarioWithCoverage({
primary: [TEST_EXECUTABLE_COVERAGE_ID],
});
const playwrightScenario = scenarioWithCoverage({
primary: [TEST_BROWSER_COVERAGE_ID],
executionKind: "playwright",
executionPath: playwrightExecutionPath,
sourcePath: "qa/scenarios/ui/control-ui-chat-flow-playwright.yaml",
});
const report = renderQaScenarioMatchesMarkdownReport({
query: "mixed",
matches: [
{
...flowScenario,
id: "flow-proof",
theme: "test",
surfaces: [flowScenario.surface],
risk: "unassigned",
coverageIds: [
...(flowScenario.coverage?.primary ?? []),
...(flowScenario.coverage?.secondary ?? []),
],
docsRefs: [],
codeRefs: [],
executionKind: flowScenario.execution.kind,
},
{
...playwrightScenario,
id: "playwright-proof",
theme: "test",
surfaces: [playwrightScenario.surface],
risk: "unassigned",
coverageIds: [
...(playwrightScenario.coverage?.primary ?? []),
...(playwrightScenario.coverage?.secondary ?? []),
],
docsRefs: [],
codeRefs: [],
executionKind: playwrightScenario.execution.kind,
executionPath: playwrightExecutionPath,
},
],
});
expect(report).toContain("- Suite commands:");
expect(report).toContain(" - flow: `pnpm openclaw qa suite --scenario flow-proof`");
expect(report).toContain(
" - playwright: `pnpm openclaw qa suite --scenario playwright-proof`",
);
});
it("reports missing taxonomy coverage refs without treating them as inventoried", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy(),
repoRoot: process.cwd(),
scenarios: [
scenarioWithCoverage({
primary: ["agents.missing-coverage"],
}),
],
});
expect(report.inventoriedCoverageIdCount).toBe(0);
expect(report.categories[0]?.inventoryStatus).toBe("missing");
expect(report.validationIssues.map((issue) => issue.code)).toEqual([
"coverage-id-not-found",
"coverage-id-missing-primary-inventory",
"profile-category-missing-inventory",
]);
});
it("inventories explicit native test declarations", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy({
categoryId: TEST_BROWSER_CATEGORY_ID,
coverageIds: [TEST_BROWSER_COVERAGE_ID],
}),
repoRoot: process.cwd(),
scenarios: [
scenarioWithCoverage({
primary: [TEST_BROWSER_COVERAGE_ID],
sourcePath: "qa/scenarios/ui/control-ui-chat-flow-playwright.yaml",
executionKind: "playwright",
executionPath: "ui/src/e2e/chat-flow.messaging.e2e.test.ts",
}),
],
});
expect(report.validationIssues).toStrictEqual([]);
expect(report.inventoriedCategoryCount).toBe(1);
expect(report.inventoriedCoverageIdCount).toBe(1);
expect(report.categories[0]?.inventoryStatus).toBe("complete");
expect(report.categories[0]?.scenarioRefs).toStrictEqual([
"qa/scenarios/ui/control-ui-chat-flow-playwright.yaml",
]);
expect(report.categories[0]?.inventoryRefs).toStrictEqual([
{
coverageId: TEST_BROWSER_COVERAGE_ID,
kind: "playwright",
path: "ui/src/e2e/chat-flow.messaging.e2e.test.ts",
role: "primary",
scenarioRefs: ["qa/scenarios/ui/control-ui-chat-flow-playwright.yaml"],
},
]);
});
it("counts partial coverage IDs proportionately for taxonomy inventory", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy({
coverageIds: [TEST_EXECUTABLE_COVERAGE_ID, TEST_WEBCHAT_COVERAGE_ID],
}),
repoRoot: process.cwd(),
scenarios: [
scenarioWithCoverage({
primary: [TEST_EXECUTABLE_COVERAGE_ID],
secondary: [TEST_WEBCHAT_COVERAGE_ID],
sourcePath: "qa/scenarios/channels/dm-chat-baseline.yaml",
}),
],
});
expect(report.inventoriedCategoryCount).toBe(0);
expect(report.requiredCoverageIdCount).toBe(2);
expect(report.inventoriedCoverageIdCount).toBe(1);
expect(report.coverageIdInventoryPercent).toBe(50);
expect(report.categories[0]?.inventoryStatus).toBe("partial");
expect(report.categories[0]?.inventoriedCoverageIds).toStrictEqual([
TEST_EXECUTABLE_COVERAGE_ID,
]);
expect(report.validationIssues).toContainEqual(
expect.objectContaining({
code: "coverage-id-missing-primary-inventory",
ref: TEST_WEBCHAT_COVERAGE_ID,
}),
);
});
it("rejects one coverage ID assigned to different exact features", () => {
const taxonomy: QaMaturityTaxonomy = {
...testMaturityTaxonomy(),
profiles: [
{
id: "release",
description: "Test release profile.",
includeAllCategories: false,
channelDriver: "qa-channel",
categoryIds: ["agent-runtime.agent-turn-execution"],
coverageIds: [],
},
],
surfaces: [
{
id: "agent-runtime",
name: "Agent Runtime",
family: "test",
level: "experimental",
categories: [
{
id: "agent-turn-execution",
name: "Agent Turn Execution",
category_note: "agent-turn-execution.md",
docs: [],
search_anchors: [],
features: [
{
name: "shared",
coverageIds: [TEST_EXECUTABLE_COVERAGE_ID],
},
{
name: "shared",
coverageIds: [TEST_EXECUTABLE_COVERAGE_ID],
},
],
},
],
},
],
};
expect(() =>
buildQaScorecardTaxonomyReport({
taxonomy,
repoRoot: process.cwd(),
scenarios: [],
}),
).toThrow(
`coverage ID ${TEST_EXECUTABLE_COVERAGE_ID} already belongs to agent-runtime.agent-turn-execution feature shared; coverage IDs must identify exactly one taxonomy feature`,
);
});
it("requires one two-part ID owned by each exact taxonomy feature", () => {
const taxonomy = testMaturityTaxonomy();
const feature = expectDefined(
taxonomy.surfaces[0]?.categories[0]?.features[0],
"test taxonomy feature",
);
feature.coverageIds = [TEST_EXECUTABLE_COVERAGE_ID, TEST_WEBCHAT_COVERAGE_ID];
expect(() =>
buildQaScorecardTaxonomyReport({ taxonomy, repoRoot: process.cwd(), scenarios: [] }),
).toThrow("taxonomy features must define exactly one coverage ID");
feature.coverageIds = ["agents.delivery.group"];
expect(() =>
buildQaScorecardTaxonomyReport({ taxonomy, repoRoot: process.cwd(), scenarios: [] }),
).toThrow("coverage ids must use exactly <surface-id>.<feature-id>");
feature.coverageIds = [TEST_BROWSER_COVERAGE_ID];
expect(() =>
buildQaScorecardTaxonomyReport({ taxonomy, repoRoot: process.cwd(), scenarios: [] }),
).toThrow(`coverage ID ${TEST_BROWSER_COVERAGE_ID} must belong to surface agent-runtime`);
});
it("inventories script producer declarations", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy({
categoryId: TEST_BROWSER_CATEGORY_ID,
coverageIds: [TEST_BROWSER_COVERAGE_ID],
}),
repoRoot: process.cwd(),
scenarios: [
scenarioWithCoverage({
primary: [TEST_BROWSER_COVERAGE_ID],
sourcePath: "qa/scenarios/ui/script-evidence-producer.yaml",
executionKind: "script",
executionPath: "scripts/check-no-conflict-markers.mjs",
}),
],
});
expect(report.validationIssues).toStrictEqual([]);
expect(report.inventoriedCategoryCount).toBe(1);
expect(report.inventoriedCoverageIdCount).toBe(1);
expect(report.categories[0]?.inventoryRefs).toStrictEqual([
{
coverageId: TEST_BROWSER_COVERAGE_ID,
kind: "script",
path: "scripts/check-no-conflict-markers.mjs",
role: "primary",
scenarioRefs: ["qa/scenarios/ui/script-evidence-producer.yaml"],
},
]);
});
it("resolves all-category profiles from taxonomy categories", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy({
includeAllCategories: true,
includeArchivedSurface: true,
}),
repoRoot: process.cwd(),
scenarios: [],
});
expect(report.profiles.find((profile) => profile.id === "release")?.categoryIds).toStrictEqual([
TEST_EXECUTABLE_CATEGORY_ID,
]);
expect(report.requiredCategoryCount).toBe(1);
expect(report.categoryCount).toBe(1);
expect(report.profiles.find((profile) => profile.id === "release")?.categoryIds).not.toContain(
"archived-surface.legacy-category",
);
});
it("reports profile categories missing primary coverage inventory", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy(),
repoRoot: process.cwd(),
scenarios: [],
});
expect(report.validationIssues.map((issue) => issue.code)).toEqual([
"coverage-id-missing-primary-inventory",
"profile-category-missing-inventory",
]);
});
it("reports native test inventory targets outside the repository", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy(),
repoRoot: process.cwd(),
scenarios: [
scenarioWithCoverage({
primary: [TEST_EXECUTABLE_COVERAGE_ID],
executionKind: "playwright",
executionPath: "../outside-openclaw.test.ts",
}),
],
});
expect(report.validationIssues.map((issue) => issue.code)).toEqual([
"inventory-ref-not-found",
"coverage-id-missing-primary-inventory",
"profile-category-missing-inventory",
]);
});
it("inventories runnable scenario coverage metadata", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy(),
repoRoot: process.cwd(),
scenarios: [
scenarioWithCoverage({
primary: [TEST_EXECUTABLE_COVERAGE_ID],
sourcePath: "qa/scenarios/channels/dm-chat-baseline.yaml",
}),
],
});
expect(report.validationIssues).toStrictEqual([]);
expect(report.categories[0]?.scenarioRefs).toStrictEqual([
"qa/scenarios/channels/dm-chat-baseline.yaml",
]);
expect(report.categories[0]?.inventoryRefs).toStrictEqual([
{
coverageId: TEST_EXECUTABLE_COVERAGE_ID,
kind: "qa-scenario",
path: null,
role: "primary",
scenarioRefs: ["qa/scenarios/channels/dm-chat-baseline.yaml"],
},
]);
});
it("counts secondary scenario metadata as inventory but not primary inventory", () => {
const report = buildQaScorecardTaxonomyReport({
taxonomy: testMaturityTaxonomy(),
repoRoot: process.cwd(),
scenarios: [
scenarioWithCoverage({
primary: [TEST_WEBCHAT_COVERAGE_ID],
secondary: [TEST_EXECUTABLE_COVERAGE_ID],
}),
],
});
expect(report.inventoriedCoverageIdCount).toBe(0);
expect(report.categories[0]?.inventoryStatus).toBe("partial");
expect(report.validationIssues.map((issue) => issue.code)).toEqual([
"coverage-id-not-found",
"coverage-id-missing-primary-inventory",
"profile-category-missing-inventory",
]);
});
});