FN-5800: fix shared planning branch-group setup context binding
Prevent unbound this crashes when shared-branch planning subtasks initialize branch groups. - call ensureBranchGroupForSource through the store object so method context is preserved - update the planning routes in both shared-branch creation flows to use the bound store call - harden the planning routes test mock to rely on this.getBranchGroupBySource and keep branch-group creation behavior realistic - add regression coverage for creating shared branch groups from subtask breakdown task creation Files changed: packages/dashboard/src/__tests__/routes-planning.test.ts | 61 ++++++++++++++++++++-- packages/dashboard/src/routes/register-planning-subtask-routes.ts | 8 +-- 2 files changed, 61 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-5800 Fusion-Task-Lineage: c4ce9257-0bd2-4b46-b88a-672992ecd969
This commit is contained in:
@@ -217,9 +217,8 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
|
||||
updatePrInfo: vi.fn().mockResolvedValue(undefined),
|
||||
updateIssueInfo: vi.fn().mockResolvedValue(undefined),
|
||||
getRootDir: vi.fn().mockReturnValue("/fake/root"),
|
||||
ensureBranchGroupForSource: vi.fn((sourceType: "planning" | "mission", sourceId: string, init: { branchName: string; autoMerge?: boolean }) => {
|
||||
const key = `${sourceType}:${sourceId}`;
|
||||
const existing = branchGroups.get(key);
|
||||
ensureBranchGroupForSource: vi.fn(function (this: TaskStore, sourceType: "planning" | "mission", sourceId: string, init: { branchName: string; autoMerge?: boolean }) {
|
||||
const existing = this.getBranchGroupBySource(sourceType, sourceId);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
@@ -234,7 +233,7 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
branchGroups.set(key, created);
|
||||
branchGroups.set(`${sourceType}:${sourceId}`, created);
|
||||
return created;
|
||||
}),
|
||||
getBranchGroupBySource: vi.fn((sourceType: "planning" | "mission", sourceId: string) =>
|
||||
@@ -2126,6 +2125,60 @@ describe("Planning Mode Routes", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("ensures shared branch groups when creating subtask breakdown tasks", async () => {
|
||||
(store.createTask as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({
|
||||
id: "FN-281",
|
||||
description: "First",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
id: "FN-282",
|
||||
description: "Second",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
});
|
||||
|
||||
const subtaskRes = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/subtasks/start-streaming",
|
||||
JSON.stringify({ description: "Break down auth scope" }),
|
||||
{ "Content-Type": "application/json" },
|
||||
);
|
||||
expect(subtaskRes.status).toBe(201);
|
||||
|
||||
const sessionId = subtaskRes.body.sessionId as string;
|
||||
const createRes = await REQUEST(
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/subtasks/create-tasks",
|
||||
JSON.stringify({
|
||||
sessionId,
|
||||
branchSelection: { mode: "custom-new", branchName: "feature/auth-breakdown", baseBranch: "main" },
|
||||
branchAssignment: { mode: "shared" },
|
||||
subtasks: [
|
||||
{ tempId: "temp-1", title: "Auth backend", description: "Implement backend" },
|
||||
{ tempId: "temp-2", title: "Auth UI", description: "Implement UI", dependsOn: ["temp-1"] },
|
||||
],
|
||||
}),
|
||||
{ "Content-Type": "application/json" },
|
||||
);
|
||||
|
||||
expect(createRes.status).toBe(201);
|
||||
expect(store.ensureBranchGroupForSource).toHaveBeenCalledWith(
|
||||
"planning",
|
||||
sessionId,
|
||||
expect.objectContaining({ branchName: "feature/auth-breakdown", autoMerge: false }),
|
||||
);
|
||||
expect(store.getBranchGroupBySource).toHaveBeenCalledWith("planning", sessionId);
|
||||
});
|
||||
|
||||
it("prefers session autoMerge override when creating shared planning subtasks", async () => {
|
||||
(store.createTask as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce({
|
||||
|
||||
@@ -224,8 +224,8 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
|
||||
? settings.defaultBranch
|
||||
: "main";
|
||||
const settingsAutoMerge = typeof settings.autoMerge === "boolean" ? settings.autoMerge : false;
|
||||
const ensureBranchGroupForSource = (scopedStore as { ensureBranchGroupForSource?: TaskStore["ensureBranchGroupForSource"] }).ensureBranchGroupForSource;
|
||||
ensureBranchGroupForSource?.("planning", sessionId, {
|
||||
const branchGroupStore = scopedStore as { ensureBranchGroupForSource?: TaskStore["ensureBranchGroupForSource"] };
|
||||
branchGroupStore.ensureBranchGroupForSource?.("planning", sessionId, {
|
||||
branchName: resolvedBranch ?? resolvedBaseBranch ?? settingsDefaultBranch,
|
||||
autoMerge: session.autoMerge ?? settingsAutoMerge,
|
||||
});
|
||||
@@ -1279,8 +1279,8 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
|
||||
? settings.defaultBranch
|
||||
: "main";
|
||||
const settingsAutoMerge = typeof settings.autoMerge === "boolean" ? settings.autoMerge : false;
|
||||
const ensureBranchGroupForSource = (scopedStore as { ensureBranchGroupForSource?: TaskStore["ensureBranchGroupForSource"] }).ensureBranchGroupForSource;
|
||||
ensureBranchGroupForSource?.("planning", planningSessionId, {
|
||||
const branchGroupStore = scopedStore as { ensureBranchGroupForSource?: TaskStore["ensureBranchGroupForSource"] };
|
||||
branchGroupStore.ensureBranchGroupForSource?.("planning", planningSessionId, {
|
||||
branchName: resolvedBranch ?? resolvedBaseBranch ?? settingsDefaultBranch,
|
||||
autoMerge: session.autoMerge ?? settingsAutoMerge,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user