fix(FN-branch-group): review fixes — fast-path mergeTargetSource + open-PR reuse only
Code review (Tier 2) found two P1s: (1) the early no-op fast-path persisted mergeConfirmed/mergeTargetBranch without mergeTargetSource, so a shared-group member landing via it could never satisfy the strict completion predicate — promotion permanently blocked; thread mergeTarget.source through like the standard landing sites. (2) createGroupPrCallback's findPrForBranch used state:'all' and could reuse a closed/merged PR from a prior group, persisting a terminal prState onto a fresh promotion; create path now matches open PRs only.
This commit is contained in:
@@ -34,6 +34,7 @@ vi.mock("@fusion/core", async () => {
|
||||
import { activeSessionRegistry } from "@fusion/engine";
|
||||
import {
|
||||
cleanupMergedTaskArtifacts,
|
||||
createGroupPrCallback,
|
||||
processPullRequestMergeTask,
|
||||
getTaskBranchName,
|
||||
syncGroupPrCallback,
|
||||
@@ -1373,3 +1374,76 @@ describe("syncGroupPrCallback (U6)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("createGroupPrCallback", () => {
|
||||
beforeEach(() => {
|
||||
execMock.mockReset();
|
||||
execMock.mockImplementation(() => "");
|
||||
});
|
||||
|
||||
const group = {
|
||||
id: "BG-1",
|
||||
sourceType: "planning" as const,
|
||||
sourceId: "P-1",
|
||||
branchName: "fusion/groups/p-1",
|
||||
autoMerge: false,
|
||||
prState: "none" as const,
|
||||
status: "open" as const,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
const members = [{ id: "FN-A", title: "Alpha", description: "a", column: "in-review" } as never];
|
||||
|
||||
it("queries only OPEN PRs for the head branch (does not reuse terminal PRs)", async () => {
|
||||
const github = {
|
||||
findPrForBranch: vi.fn(async () => null),
|
||||
createPr: vi.fn(async () => ({
|
||||
number: 99,
|
||||
url: "https://github.com/owner/repo/pull/99",
|
||||
status: "open" as const,
|
||||
})),
|
||||
};
|
||||
|
||||
const callback = createGroupPrCallback(github as never);
|
||||
await callback({
|
||||
cwd: "/repo",
|
||||
group: group as never,
|
||||
members,
|
||||
headBranch: group.branchName,
|
||||
baseBranch: "main",
|
||||
});
|
||||
|
||||
expect(github.findPrForBranch).toHaveBeenCalledWith({ head: group.branchName, state: "open" });
|
||||
});
|
||||
|
||||
it("does not reuse a closed PR from a prior group — creates a fresh one", async () => {
|
||||
// With state:"open", findPrForBranch returns null for a head whose only PR
|
||||
// is closed/merged, so the create path runs instead of resurrecting the
|
||||
// terminal PR (which would poison the newly promoted group's prState).
|
||||
const github = {
|
||||
findPrForBranch: vi.fn(async () => null),
|
||||
createPr: vi.fn(async () => ({
|
||||
number: 123,
|
||||
url: "https://github.com/owner/repo/pull/123",
|
||||
status: "open" as const,
|
||||
})),
|
||||
};
|
||||
|
||||
const callback = createGroupPrCallback(github as never);
|
||||
const result = await callback({
|
||||
cwd: "/repo",
|
||||
group: group as never,
|
||||
members,
|
||||
headBranch: group.branchName,
|
||||
baseBranch: "main",
|
||||
});
|
||||
|
||||
expect(github.findPrForBranch).toHaveBeenCalledWith({ head: group.branchName, state: "open" });
|
||||
expect(github.createPr).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual({
|
||||
prNumber: 123,
|
||||
prUrl: "https://github.com/owner/repo/pull/123",
|
||||
prState: "open",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ export function createGroupPrCallback(
|
||||
github: Pick<GitHubOperations, "findPrForBranch" | "createPr">,
|
||||
): CreateGroupPrFn {
|
||||
return async ({ cwd, group, members, headBranch, baseBranch }) => {
|
||||
const existing = await github.findPrForBranch({ head: headBranch, state: "all" });
|
||||
const existing = await github.findPrForBranch({ head: headBranch, state: "open" });
|
||||
if (existing) {
|
||||
return { prNumber: existing.number, prUrl: existing.url, prState: toBranchGroupPrState(existing) };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user