From 563a3797c2a753252373c793a07388fc6c86a4fa Mon Sep 17 00:00:00 2001 From: Iouri Goussev Date: Sat, 21 Mar 2026 15:23:25 -0400 Subject: [PATCH] test: add missing git-service coverage for 6 untested paths (#1837) - MergeConflictError constructor fields (conflictedFiles, strategy, branch, mainBranch, name, message content, instanceof checks) - writeIntegrationBranch rejects gsd/quick/* ephemeral branches - resolveMilestoneIntegrationBranch returns status:missing when no metadata file exists - resolveMilestoneIntegrationBranch returns status:missing when both recorded and configured main_branch are absent (full fallback chain) - buildTaskCommitMessage appends Resolves #N trailer when issueNumber set - runPreMergeCheck skips gracefully when no package.json found Co-authored-by: Claude Sonnet 4.6 --- .../extensions/gsd/tests/git-service.test.ts | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/resources/extensions/gsd/tests/git-service.test.ts b/src/resources/extensions/gsd/tests/git-service.test.ts index 0a201b6f4..4dee06271 100644 --- a/src/resources/extensions/gsd/tests/git-service.test.ts +++ b/src/resources/extensions/gsd/tests/git-service.test.ts @@ -7,6 +7,7 @@ import { inferCommitType, buildTaskCommitMessage, GitServiceImpl, + MergeConflictError, RUNTIME_EXCLUSION_PATHS, VALID_BRANCH_NAME, runGit, @@ -1303,6 +1304,113 @@ async function main(): Promise { rmSync(repo, { recursive: true, force: true }); } + // ─── MergeConflictError: constructor fields ─────────────────────────────── + + console.log("\n=== MergeConflictError: constructor fields ==="); + { + const err = new MergeConflictError( + ["src/foo.ts", "src/bar.ts"], + "squash", + "gsd/M001/S01", + "main", + ); + assertEq(err.conflictedFiles, ["src/foo.ts", "src/bar.ts"], "MergeConflictError.conflictedFiles populated"); + assertEq(err.strategy, "squash", "MergeConflictError.strategy set"); + assertEq(err.branch, "gsd/M001/S01", "MergeConflictError.branch set"); + assertEq(err.mainBranch, "main", "MergeConflictError.mainBranch set"); + assertEq(err.name, "MergeConflictError", "MergeConflictError.name is MergeConflictError"); + assertTrue(err.message.includes("src/foo.ts"), "MergeConflictError message lists conflicted files"); + assertTrue(err.message.toLowerCase().includes("squash"), "MergeConflictError message mentions strategy"); + assertTrue(err instanceof MergeConflictError, "MergeConflictError is an instanceof MergeConflictError"); + assertTrue(err instanceof Error, "MergeConflictError is an Error instance"); + } + + // ─── Integration branch: rejects gsd/quick/* branches ──────────────────── + + console.log("\n=== Integration branch: rejects gsd/quick/* branches ==="); + { + const repo = initBranchTestRepo(); + + writeIntegrationBranch(repo, "M001", "gsd/quick/1234-some-task"); + assertEq(readIntegrationBranch(repo, "M001"), null, "gsd/quick/* branches are not recorded as integration branch"); + + rmSync(repo, { recursive: true, force: true }); + } + + // ─── Integration branch: resolver returns missing when no metadata ──────── + + console.log("\n=== Integration branch: resolver returns missing when no metadata ==="); + { + const repo = initBranchTestRepo(); + + // No writeIntegrationBranch call — no metadata file exists + const resolved = resolveMilestoneIntegrationBranch(repo, "M999"); + assertEq(resolved.status, "missing", "resolver reports missing when no metadata file"); + assertEq(resolved.recordedBranch, null, "resolver recordedBranch is null when no metadata"); + assertEq(resolved.effectiveBranch, null, "resolver effectiveBranch is null when no metadata"); + assertTrue(resolved.reason.includes("M999"), "resolver reason mentions the milestone ID"); + + rmSync(repo, { recursive: true, force: true }); + } + + // ─── Integration branch: resolver missing when both recorded and configured branches gone ─── + + console.log("\n=== Integration branch: resolver missing when both recorded and configured branches gone ==="); + { + const repo = initBranchTestRepo(); + + // Record a branch that doesn't exist + writeIntegrationBranch(repo, "M001", "deleted-feature"); + // configured main_branch also doesn't exist + const resolved = resolveMilestoneIntegrationBranch(repo, "M001", { main_branch: "nonexistent-branch" }); + assertEq(resolved.status, "missing", "resolver reports missing when recorded branch and configured main_branch both absent"); + assertEq(resolved.recordedBranch, "deleted-feature", "resolver preserves stale recorded branch"); + assertEq(resolved.effectiveBranch, null, "resolver effectiveBranch is null when no safe fallback"); + assertTrue( + resolved.reason.includes("deleted-feature") && resolved.reason.includes("nonexistent-branch"), + "reason mentions both stale branch and unavailable configured branch", + ); + + rmSync(repo, { recursive: true, force: true }); + } + + // ─── buildTaskCommitMessage: issueNumber appends Resolves trailer ───────── + + console.log("\n=== buildTaskCommitMessage: issueNumber appends Resolves trailer ==="); + { + const msg = buildTaskCommitMessage({ + taskId: "S01/T03", + taskTitle: "fix login redirect", + issueNumber: 42, + }); + assertTrue(msg.includes("Resolves #42"), "buildTaskCommitMessage includes Resolves #N trailer when issueNumber is set"); + assertTrue(msg.startsWith("fix(S01/T03):"), "buildTaskCommitMessage infers fix type"); + } + + { + // No issueNumber — no Resolves trailer + const msg = buildTaskCommitMessage({ + taskId: "S01/T04", + taskTitle: "add dashboard widget", + }); + assertTrue(!msg.includes("Resolves"), "buildTaskCommitMessage omits Resolves trailer when issueNumber is absent"); + } + + // ─── runPreMergeCheck: skips when no package.json ──────────────────────── + + console.log("\n=== runPreMergeCheck: skips when no package.json ==="); + { + const repo = initBranchTestRepo(); + // No package.json created — auto-detect should skip gracefully + const svc = new GitServiceImpl(repo, { pre_merge_check: true }); + const result: PreMergeCheckResult = svc.runPreMergeCheck(); + + assertEq(result.passed, true, "runPreMergeCheck passes when no package.json (skip)"); + assertEq(result.skipped, true, "runPreMergeCheck skips when no package.json found"); + + rmSync(repo, { recursive: true, force: true }); + } + report(); }