test(FN-2358): harden clean-worktree CI verification tests
- Assert contributing docs explicitly state that pnpm test must run without prior build outputs - Add CI workflow test coverage to keep docs and verify:workspace ordering aligned - Expand Vitest workspace alias assertions to include @fusion/test-utils and src-only replacements - Validate real symbol imports from workspace packages when dist directories are absent
This commit is contained in:
@@ -55,6 +55,7 @@ pnpm typecheck # workspace typechecks
|
|||||||
Fusion codifies workspace verification as a deterministic contract:
|
Fusion codifies workspace verification as a deterministic contract:
|
||||||
|
|
||||||
- `pnpm test` must be runnable in a clean worktree without requiring a prior `pnpm build`.
|
- `pnpm test` must be runnable in a clean worktree without requiring a prior `pnpm build`.
|
||||||
|
- This includes clean states where `packages/core/dist`, `packages/engine/dist`, and `packages/dashboard/dist` are absent.
|
||||||
- `pnpm verify:workspace` is the canonical pre-merge gate and runs in strict order:
|
- `pnpm verify:workspace` is the canonical pre-merge gate and runs in strict order:
|
||||||
1. `pnpm lint`
|
1. `pnpm lint`
|
||||||
2. `pnpm test`
|
2. `pnpm test`
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
|
|||||||
let workflow: any;
|
let workflow: any;
|
||||||
let content: string;
|
let content: string;
|
||||||
let ciSteps: any[];
|
let ciSteps: any[];
|
||||||
|
let contributingContent: string;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const result = loadWorkflow("ci.yml");
|
const result = loadWorkflow("ci.yml");
|
||||||
workflow = result.parsed;
|
workflow = result.parsed;
|
||||||
content = result.content;
|
content = result.content;
|
||||||
ciSteps = workflow.jobs?.ci?.steps ?? [];
|
ciSteps = workflow.jobs?.ci?.steps ?? [];
|
||||||
|
contributingContent = readFileSync(join(workspaceRoot, "docs", "contributing.md"), "utf-8");
|
||||||
});
|
});
|
||||||
|
|
||||||
const findStepByRun = (runSnippet: string) => ciSteps.find((step) => typeof step.run === "string" && step.run.includes(runSnippet));
|
const findStepByRun = (runSnippet: string) => ciSteps.find((step) => typeof step.run === "string" && step.run.includes(runSnippet));
|
||||||
@@ -74,6 +76,14 @@ describe("CI workflow (.github/workflows/ci.yml)", () => {
|
|||||||
expect(buildExeIdx).toBeGreaterThan(verifyIdx);
|
expect(buildExeIdx).toBeGreaterThan(verifyIdx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps contributing docs aligned with the clean-worktree verification contract", () => {
|
||||||
|
expect(contributingContent).toContain("pnpm test` must be runnable in a clean worktree without requiring a prior `pnpm build`.");
|
||||||
|
expect(contributingContent).toContain("`pnpm verify:workspace` is the canonical pre-merge gate");
|
||||||
|
expect(contributingContent).toContain("1. `pnpm lint`");
|
||||||
|
expect(contributingContent).toContain("2. `pnpm test`");
|
||||||
|
expect(contributingContent).toContain("3. `pnpm build`");
|
||||||
|
});
|
||||||
|
|
||||||
it("includes binary build step", () => {
|
it("includes binary build step", () => {
|
||||||
expect(content).toContain("build:exe");
|
expect(content).toContain("build:exe");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -64,17 +64,27 @@ describe("CLI Vitest workspace resolution", () => {
|
|||||||
find: String(/^@fusion\/engine$/),
|
find: String(/^@fusion\/engine$/),
|
||||||
replacement: join(workspaceRoot, "packages", "engine", "src", "index.ts"),
|
replacement: join(workspaceRoot, "packages", "engine", "src", "index.ts"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
find: String(/^@fusion\/dashboard\/planning$/),
|
||||||
|
replacement: join(workspaceRoot, "packages", "dashboard", "src", "planning.ts"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
find: String(/^@fusion\/dashboard$/),
|
find: String(/^@fusion\/dashboard$/),
|
||||||
replacement: join(workspaceRoot, "packages", "dashboard", "src", "index.ts"),
|
replacement: join(workspaceRoot, "packages", "dashboard", "src", "index.ts"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: String(/^@fusion\/dashboard\/planning$/),
|
find: String(/^@fusion\/test-utils$/),
|
||||||
replacement: join(workspaceRoot, "packages", "dashboard", "src", "planning.ts"),
|
replacement: join(workspaceRoot, "packages", "core", "src", "__test-utils__", "workspace.ts"),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (const entry of normalized) {
|
||||||
|
expect(entry.replacement).toContain(`${join("packages", "")}`);
|
||||||
|
expect(entry.replacement).toContain(`${join("src", "")}`);
|
||||||
|
expect(entry.replacement).not.toContain(`${join("dist", "")}`);
|
||||||
|
}
|
||||||
|
|
||||||
const coreGhCliIndex = normalized.findIndex((entry) => entry.find === String(/^@fusion\/core\/gh-cli$/));
|
const coreGhCliIndex = normalized.findIndex((entry) => entry.find === String(/^@fusion\/core\/gh-cli$/));
|
||||||
const coreIndex = normalized.findIndex((entry) => entry.find === String(/^@fusion\/core$/));
|
const coreIndex = normalized.findIndex((entry) => entry.find === String(/^@fusion\/core$/));
|
||||||
const planningIndex = normalized.findIndex((entry) => entry.find === String(/^@fusion\/dashboard\/planning$/));
|
const planningIndex = normalized.findIndex((entry) => entry.find === String(/^@fusion\/dashboard\/planning$/));
|
||||||
@@ -86,11 +96,25 @@ describe("CLI Vitest workspace resolution", () => {
|
|||||||
expect(dashboardIndex).toBeGreaterThan(planningIndex);
|
expect(dashboardIndex).toBeGreaterThan(planningIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves internal workspace imports during CLI tests when dist outputs are absent", async () => {
|
it("resolves non-mocked symbols from internal workspace packages when dist outputs are absent", async () => {
|
||||||
await expect(import("@fusion/core")).resolves.toBeTruthy();
|
const [{ tempWorkspace }, { PRIORITY_EXECUTE }, { createRuntimeLogger }, { parseAgentResponse }] = await Promise.all([
|
||||||
await expect(import("@fusion/core/gh-cli")).resolves.toBeTruthy();
|
import("@fusion/test-utils"),
|
||||||
await expect(import("@fusion/engine")).resolves.toBeTruthy();
|
import("@fusion/engine"),
|
||||||
await expect(import("@fusion/dashboard")).resolves.toBeTruthy();
|
import("@fusion/dashboard"),
|
||||||
await expect(import("@fusion/dashboard/planning")).resolves.toBeTruthy();
|
import("@fusion/dashboard/planning"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(typeof tempWorkspace).toBe("function");
|
||||||
|
expect(typeof PRIORITY_EXECUTE).toBe("number");
|
||||||
|
expect(typeof createRuntimeLogger).toBe("function");
|
||||||
|
expect(parseAgentResponse('{"type":"question","data":{"id":"q","type":"confirm","question":"Ok?","description":"desc"}}')).toEqual({
|
||||||
|
type: "question",
|
||||||
|
data: {
|
||||||
|
id: "q",
|
||||||
|
type: "confirm",
|
||||||
|
question: "Ok?",
|
||||||
|
description: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
}, 30_000);
|
}, 30_000);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user