Fix no-op task branch recovery by recognizing canonical branches with no unique diff before rejecting inherited foreign trailers. - Add no-diff ownership classification for already-merged detection when canonical task branches inherit another task's landed commit. - Teach self-healing and branch-misbound recovery to ignore foreign branch-tip trailers only for branches proven to have no unique task diff. - Skip synthetic verify:fast typechecks for JavaScript alias packages without tsconfig files and cover the behavior with tests. - Add regression coverage and a patch changeset for the recovery fix. Files changed: .changeset/fn-7486-merge-recovery-noop-ownership.md | 7 ++ .../already-merged-detector.real-git.test.ts | 68 +++++++++++++++++ .../self-healing-already-merged.real-git.test.ts | 89 ++++++++++++++++++++-- packages/engine/src/already-merged-detector.ts | 88 +++++++++++++++------ packages/engine/src/self-healing.ts | 61 +++++++++++++-- scripts/__tests__/verify-fast.test.mjs | 16 +++- scripts/verify-fast.mjs | 15 +++- 7 files changed, 303 insertions(+), 41 deletions(-) Fusion-Task-Id: FN-7486 Fusion-Task-Lineage: 4185c6ed-9731-4ffc-b033-34bf0c3a83ad Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
189 lines
8.0 KiB
JavaScript
189 lines
8.0 KiB
JavaScript
/**
|
|
* Unit tests for scripts/verify-fast.mjs
|
|
*
|
|
* Runner: node --test scripts/__tests__/verify-fast.test.mjs
|
|
*
|
|
* These exercise the PURE planning / arg-construction logic only. They never
|
|
* spawn real tsc / build / vitest — the test-free verification command's value
|
|
* is its deterministic plan, so that is what we pin.
|
|
*/
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
buildTypecheckStep,
|
|
buildBuildStep,
|
|
buildBootSmokeStep,
|
|
buildArtifactBootstrapStep,
|
|
buildVerifyPlan,
|
|
VERIFY_EXCLUDED_PACKAGES,
|
|
BOOT_SMOKE_REQUIRED_BUILD_PACKAGES,
|
|
} from "../verify-fast.mjs";
|
|
|
|
import { resolveAffectedPackages } from "../test-changed.mjs";
|
|
|
|
const SMOKE = "/repo/scripts/boot-smoke.mjs";
|
|
const BOOTSTRAP = "/repo/scripts/ensure-test-artifacts.mjs";
|
|
const NODE = "/usr/bin/node";
|
|
|
|
function stepIds(plan) {
|
|
return plan.steps.map((s) => s.id);
|
|
}
|
|
function stepByKind(plan, kind) {
|
|
return plan.steps.filter((s) => s.kind === kind);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// buildTypecheckStep
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test("buildTypecheckStep: uses the package's typecheck script when present", () => {
|
|
const step = buildTypecheckStep("@fusion/engine", { hasTypecheck: true });
|
|
assert.equal(step.command, "pnpm");
|
|
assert.deepEqual(step.args, ["--filter", "@fusion/engine", "typecheck"]);
|
|
assert.equal(step.klass, "changed");
|
|
});
|
|
|
|
test("buildTypecheckStep: falls back to scoped tsc --noEmit when no typecheck script", () => {
|
|
const step = buildTypecheckStep("@fusion/widget", { hasTypecheck: false });
|
|
assert.deepEqual(step.args, ["--filter", "@fusion/widget", "exec", "tsc", "--noEmit", "-p", "."]);
|
|
});
|
|
|
|
test("buildTypecheckStep: defaults to the tsc fallback when meta omitted", () => {
|
|
const step = buildTypecheckStep("@fusion/widget");
|
|
assert.deepEqual(step.args, ["--filter", "@fusion/widget", "exec", "tsc", "--noEmit", "-p", "."]);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// buildBuildStep / buildBootSmokeStep / buildArtifactBootstrapStep
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test("buildBuildStep: scoped pnpm build for the package", () => {
|
|
const step = buildBuildStep("@fusion/cli");
|
|
assert.deepEqual(step.args, ["--filter", "@fusion/cli", "build"]);
|
|
assert.equal(step.kind, "build");
|
|
});
|
|
|
|
test("buildBootSmokeStep: runs the boot-smoke script via node", () => {
|
|
const step = buildBootSmokeStep(SMOKE, NODE);
|
|
assert.equal(step.command, NODE);
|
|
assert.deepEqual(step.args, [SMOKE]);
|
|
assert.equal(step.kind, "boot-smoke");
|
|
});
|
|
|
|
test("buildArtifactBootstrapStep: runs the artifact bootstrap script via node", () => {
|
|
const step = buildArtifactBootstrapStep(BOOTSTRAP, NODE);
|
|
assert.equal(step.command, NODE);
|
|
assert.deepEqual(step.args, [BOOTSTRAP]);
|
|
assert.equal(step.kind, "bootstrap-artifacts");
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// buildVerifyPlan
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test("buildVerifyPlan: no packages -> CLI prerequisite build then boot smoke", () => {
|
|
const plan = buildVerifyPlan({ packages: [], bootSmokeScriptPath: SMOKE, nodeBin: NODE });
|
|
assert.deepEqual(stepIds(plan), ["bootstrap-artifacts", "build:@runfusion/fusion", "boot-smoke"]);
|
|
assert.deepEqual(plan.eligiblePackages, []);
|
|
assert.deepEqual(plan.requiredBootBuildPackages, ["@runfusion/fusion"]);
|
|
});
|
|
|
|
test("buildVerifyPlan: typecheck for all eligible, then builds, then boot smoke (ordered)", () => {
|
|
const packageMeta = new Map([
|
|
["@fusion/engine", { hasTypecheck: true, hasBuild: true }],
|
|
["@fusion/core", { hasTypecheck: true, hasBuild: true }],
|
|
]);
|
|
const plan = buildVerifyPlan({ packages: ["@fusion/engine", "@fusion/core"], packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
|
|
assert.deepEqual(stepIds(plan), [
|
|
"bootstrap-artifacts",
|
|
"typecheck:@fusion/engine",
|
|
"typecheck:@fusion/core",
|
|
"build:@fusion/engine",
|
|
"build:@fusion/core",
|
|
"build:@runfusion/fusion",
|
|
"boot-smoke",
|
|
]);
|
|
});
|
|
|
|
test("buildVerifyPlan: a package without a build script gets a typecheck step but no build step", () => {
|
|
const packageMeta = new Map([
|
|
["@fusion/engine", { hasTypecheck: true, hasBuild: true }],
|
|
["@fusion/test-only", { hasTypecheck: false, hasTsconfig: true, hasBuild: false }],
|
|
]);
|
|
const plan = buildVerifyPlan({ packages: ["@fusion/engine", "@fusion/test-only"], packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
|
|
assert.deepEqual(stepIds(plan), [
|
|
"bootstrap-artifacts",
|
|
"typecheck:@fusion/engine",
|
|
"typecheck:@fusion/test-only",
|
|
"build:@fusion/engine",
|
|
"build:@runfusion/fusion",
|
|
"boot-smoke",
|
|
]);
|
|
// The test-only package's typecheck uses the tsc fallback (no typecheck script).
|
|
const tc = stepByKind(plan, "typecheck").find((s) => s.pkg === "@fusion/test-only");
|
|
assert.deepEqual(tc.args, ["--filter", "@fusion/test-only", "exec", "tsc", "--noEmit", "-p", "."]);
|
|
});
|
|
|
|
test("buildVerifyPlan: skips synthetic typecheck for JavaScript alias packages with no tsconfig", () => {
|
|
const packageMeta = new Map([
|
|
["runfusion.ai", { hasTypecheck: false, hasTsconfig: false, hasBuild: false }],
|
|
["@runfusion/fusion", { hasTypecheck: true, hasTsconfig: true, hasBuild: true }],
|
|
]);
|
|
const plan = buildVerifyPlan({ packages: ["runfusion.ai", "@runfusion/fusion"], packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
|
|
assert.deepEqual(stepIds(plan), [
|
|
"bootstrap-artifacts",
|
|
"typecheck:@runfusion/fusion",
|
|
"build:@runfusion/fusion",
|
|
"boot-smoke",
|
|
]);
|
|
});
|
|
|
|
test("buildVerifyPlan: desktop/mobile are excluded from scoped steps but boot smoke still runs", () => {
|
|
const packageMeta = new Map([
|
|
["@fusion/engine", { hasTypecheck: true, hasBuild: true }],
|
|
["@fusion/desktop", { hasTypecheck: true, hasBuild: true }],
|
|
["@fusion/mobile", { hasTypecheck: true, hasBuild: true }],
|
|
]);
|
|
const plan = buildVerifyPlan({
|
|
packages: ["@fusion/engine", "@fusion/desktop", "@fusion/mobile"],
|
|
packageMeta,
|
|
bootSmokeScriptPath: SMOKE,
|
|
nodeBin: NODE,
|
|
});
|
|
assert.deepEqual(plan.eligiblePackages, ["@fusion/engine"]);
|
|
assert.deepEqual(plan.excludedPackages.sort(), ["@fusion/desktop", "@fusion/mobile"]);
|
|
assert.deepEqual(stepIds(plan), ["bootstrap-artifacts", "typecheck:@fusion/engine", "build:@fusion/engine", "build:@runfusion/fusion", "boot-smoke"]);
|
|
});
|
|
|
|
test("VERIFY_EXCLUDED_PACKAGES mirrors the root build/typecheck exclusions", () => {
|
|
assert.ok(VERIFY_EXCLUDED_PACKAGES.has("@fusion/desktop"));
|
|
assert.ok(VERIFY_EXCLUDED_PACKAGES.has("@fusion/mobile"));
|
|
});
|
|
|
|
test("BOOT_SMOKE_REQUIRED_BUILD_PACKAGES includes the source-checkout CLI", () => {
|
|
assert.deepEqual(BOOT_SMOKE_REQUIRED_BUILD_PACKAGES, ["@runfusion/fusion"]);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Integration: reuse test-changed's resolveAffectedPackages to scope the plan
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test("buildVerifyPlan: scopes to exactly the packages resolveAffectedPackages selects", () => {
|
|
// packageNameByDir as test-changed builds it (dir -> name, with a bare alias).
|
|
const packageNameByDir = new Map([
|
|
["packages/engine", "@fusion/engine"],
|
|
["engine", "@fusion/engine"],
|
|
["packages/dashboard", "@fusion/dashboard"],
|
|
["dashboard", "@fusion/dashboard"],
|
|
]);
|
|
const changedFiles = ["packages/engine/src/merger.ts", "docs/testing.md"];
|
|
const affected = resolveAffectedPackages(changedFiles, packageNameByDir);
|
|
assert.deepEqual(affected, ["@fusion/engine"]); // docs/ change does not add a package
|
|
|
|
const packageMeta = new Map([["@fusion/engine", { hasTypecheck: true, hasBuild: true }]]);
|
|
const plan = buildVerifyPlan({ packages: affected, packageMeta, bootSmokeScriptPath: SMOKE, nodeBin: NODE });
|
|
assert.deepEqual(stepIds(plan), ["bootstrap-artifacts", "typecheck:@fusion/engine", "build:@fusion/engine", "build:@runfusion/fusion", "boot-smoke"]);
|
|
});
|