Story 27: Coverage tracking (full-stack)

Add end-to-end coverage tracking: backend collects vitest coverage,
records metrics with threshold/baseline tracking, and blocks acceptance
on regression. Frontend displays coverage in gate/review panels with
a "Collect Coverage" button. Includes 20 Rust tests, 17 Vitest tests,
and 14 Playwright E2E tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-19 14:45:57 +00:00
parent 8f0bc971bf
commit 8f684a6ca4
20 changed files with 1216 additions and 34 deletions

View File

@@ -1,13 +1,13 @@
import { expect, test } from "@playwright/test";
test.describe("App boot smoke test", () => {
test("renders the project selection screen", async ({ page }) => {
await page.goto("/");
test("renders the project selection screen", async ({ page }) => {
await page.goto("/");
await expect(page.getByText("StorkIt")).toBeVisible();
await expect(page.getByPlaceholder("/path/to/project")).toBeVisible();
await expect(
page.getByRole("button", { name: "Open Project" }),
).toBeVisible();
});
await expect(page.getByText("StorkIt")).toBeVisible();
await expect(page.getByPlaceholder("/path/to/project")).toBeVisible();
await expect(
page.getByRole("button", { name: "Open Project" }),
).toBeVisible();
});
});

View File

@@ -0,0 +1,247 @@
import { expect, test } from "@playwright/test";
import type {
AcceptanceResponse,
ReviewListResponse,
} from "../../src/api/workflow";
/**
* Helper: mock all API routes needed to render the Chat view.
*/
function mockChatApis(
page: import("@playwright/test").Page,
overrides: {
acceptance?: AcceptanceResponse;
reviewQueue?: ReviewListResponse;
} = {},
) {
const acceptance: AcceptanceResponse = overrides.acceptance ?? {
can_accept: false,
reasons: ["No test results recorded for the story."],
warning: null,
summary: { total: 0, passed: 0, failed: 0 },
missing_categories: ["unit", "integration"],
};
const reviewQueue: ReviewListResponse = overrides.reviewQueue ?? {
stories: [],
};
return Promise.all([
page.route("**/api/projects", (route) =>
route.fulfill({ json: ["/tmp/test-project"] }),
),
page.route("**/api/io/fs/home", (route) => route.fulfill({ json: "/tmp" })),
page.route("**/api/project", (route) => {
if (route.request().method() === "POST") {
return route.fulfill({ json: "/tmp/test-project" });
}
if (route.request().method() === "DELETE") {
return route.fulfill({ json: true });
}
return route.fulfill({ json: null });
}),
page.route("**/api/ollama/models**", (route) =>
route.fulfill({ json: ["llama3.1"] }),
),
page.route("**/api/anthropic/key/exists", (route) =>
route.fulfill({ json: false }),
),
page.route("**/api/anthropic/models", (route) =>
route.fulfill({ json: [] }),
),
page.route("**/api/model", (route) => {
if (route.request().method() === "POST") {
return route.fulfill({ json: true });
}
return route.fulfill({ json: null });
}),
page.route("**/api/workflow/acceptance", (route) => {
if (route.request().url().includes("/ensure")) return route.fallback();
return route.fulfill({ json: acceptance });
}),
page.route("**/api/workflow/review/all", (route) =>
route.fulfill({ json: reviewQueue }),
),
page.route("**/api/workflow/acceptance/ensure", (route) =>
route.fulfill({ json: true }),
),
page.route("**/api/io/fs/list/absolute**", (route) =>
route.fulfill({ json: [] }),
),
]);
}
async function openProject(page: import("@playwright/test").Page) {
await page.goto("/");
await page.getByPlaceholder("/path/to/project").fill("/tmp/test-project");
await page.getByRole("button", { name: "Open Project" }).click();
await expect(page.getByText("Workflow Gates", { exact: true })).toBeVisible();
}
test.describe("Coverage threshold (AC1)", () => {
test("shows blocked status when coverage is below threshold", async ({
page,
}) => {
await mockChatApis(page, {
acceptance: {
can_accept: false,
reasons: ["Coverage below threshold (55.0% < 80.0%)."],
warning: null,
summary: { total: 3, passed: 3, failed: 0 },
missing_categories: [],
coverage_report: {
current_percent: 55.0,
threshold_percent: 80.0,
baseline_percent: null,
},
},
});
await openProject(page);
await expect(page.getByText("Blocked").first()).toBeVisible();
await expect(page.getByText(/Coverage: 55\.0%/)).toBeVisible();
await expect(page.getByText(/threshold: 80\.0%/)).toBeVisible();
await expect(
page.getByText("Coverage below threshold (55.0% < 80.0%)."),
).toBeVisible();
});
test("shows green coverage when above threshold", async ({ page }) => {
await mockChatApis(page, {
acceptance: {
can_accept: true,
reasons: [],
warning: null,
summary: { total: 5, passed: 5, failed: 0 },
missing_categories: [],
coverage_report: {
current_percent: 92.0,
threshold_percent: 80.0,
baseline_percent: 90.0,
},
},
});
await openProject(page);
await expect(page.getByText("Ready to accept")).toBeVisible();
await expect(page.getByText(/Coverage: 92\.0%/)).toBeVisible();
});
});
test.describe("Coverage regression reporting (AC2)", () => {
test("review panel shows coverage regression reason", async ({ page }) => {
await mockChatApis(page, {
reviewQueue: {
stories: [
{
story_id: "27_protect_tests_and_coverage",
can_accept: false,
reasons: ["Coverage regression: 90.0% → 82.0% (threshold: 80.0%)."],
warning: null,
summary: { total: 4, passed: 4, failed: 0 },
missing_categories: [],
coverage_report: {
current_percent: 82.0,
threshold_percent: 80.0,
baseline_percent: 90.0,
},
},
],
},
});
await openProject(page);
await expect(
page.getByText(/Coverage regression.*90\.0%.*82\.0%/),
).toBeVisible();
await expect(page.getByText(/Coverage: 82\.0%/)).toBeVisible();
const blockedButton = page.getByRole("button", { name: "Blocked" });
await expect(blockedButton).toBeDisabled();
});
test("blocked acceptance with coverage and test failures combined", async ({
page,
}) => {
await mockChatApis(page, {
acceptance: {
can_accept: false,
reasons: [
"1 test(s) are failing; acceptance is blocked.",
"Coverage below threshold (65.0% < 80.0%).",
],
warning: null,
summary: { total: 4, passed: 3, failed: 1 },
missing_categories: [],
coverage_report: {
current_percent: 65.0,
threshold_percent: 80.0,
baseline_percent: null,
},
},
});
await openProject(page);
await expect(page.getByText("Blocked").first()).toBeVisible();
await expect(
page.getByText("Coverage below threshold (65.0% < 80.0%)."),
).toBeVisible();
await expect(page.getByText(/Coverage: 65\.0%/)).toBeVisible();
await expect(page.getByText(/1 test\(s\) are failing/)).toBeVisible();
});
});
test.describe("Coverage collection (E2E)", () => {
test("collect coverage button triggers collection and displays result", async ({
page,
}) => {
await mockChatApis(page);
// Mock the collect coverage endpoint
await page.route("**/api/workflow/coverage/collect", (route) =>
route.fulfill({
json: {
current_percent: 85.0,
threshold_percent: 80.0,
baseline_percent: null,
},
}),
);
await openProject(page);
// Click the Collect Coverage button
const collectButton = page.getByRole("button", {
name: "Collect Coverage",
});
await expect(collectButton).toBeVisible();
// Override acceptance to return coverage data after collection
await page.unroute("**/api/workflow/acceptance");
await page.route("**/api/workflow/acceptance", (route) => {
if (route.request().url().includes("/ensure")) return route.fallback();
return route.fulfill({
json: {
can_accept: true,
reasons: [],
warning: null,
summary: { total: 5, passed: 5, failed: 0 },
missing_categories: [],
coverage_report: {
current_percent: 85.0,
threshold_percent: 80.0,
baseline_percent: null,
},
},
});
});
await collectButton.click();
await expect(page.getByText(/Coverage: 85\.0%/)).toBeVisible();
});
});