test(FN-5147): add autoMerge-off sweep guards coverage
Fusion-Task-Id: FN-5147 Fusion-Task-Lineage: 2a0e5bd4-b2ea-498c-95ef-85126819e824
This commit is contained in:
committed by
gsxdsm
parent
44ea2a7075
commit
a249490ef9
@@ -1871,6 +1871,10 @@ describe("SelfHealingManager", () => {
|
||||
});
|
||||
|
||||
describe("recoverMissingWorktreeReviewFailures", () => {
|
||||
beforeEach(() => {
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ autoMerge: true, globalPause: false, enginePaused: false });
|
||||
});
|
||||
|
||||
it("does not hard-code unusable-worktree assertion literals in self-healing", async () => {
|
||||
const source = await readFile(new URL("../self-healing.ts", import.meta.url), "utf8");
|
||||
expect(source).not.toMatch(/Refusing to start coding agent/);
|
||||
@@ -2225,6 +2229,10 @@ describe("SelfHealingManager", () => {
|
||||
});
|
||||
|
||||
describe("recoverPartialProgressNoTaskDoneFailures", () => {
|
||||
beforeEach(() => {
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ autoMerge: true, globalPause: false, enginePaused: false });
|
||||
});
|
||||
|
||||
it("requeues partial-progress no-task_done failures with bounded retry count", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, {
|
||||
rootDir: "/tmp/test-project",
|
||||
@@ -2364,6 +2372,10 @@ describe("SelfHealingManager", () => {
|
||||
});
|
||||
|
||||
describe("recoverMergedReviewTasks", () => {
|
||||
beforeEach(() => {
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ autoMerge: true, globalPause: false, enginePaused: false, taskStuckTimeoutMs: 1_000 });
|
||||
});
|
||||
|
||||
it("finalizes stale merging tasks when a task commit already landed", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, {
|
||||
rootDir: "/tmp/test-project",
|
||||
@@ -7579,3 +7591,56 @@ describe("SelfHealingManager no-commits-expected audit", () => {
|
||||
expect(store.moveTask).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("autoMerge gating for mutating in-review sweeps (FN-5147)", () => {
|
||||
let store: TaskStore & EventEmitter;
|
||||
let manager: SelfHealingManager;
|
||||
|
||||
beforeEach(() => {
|
||||
store = createMockStore();
|
||||
manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
autoMerge: false,
|
||||
globalPause: false,
|
||||
enginePaused: false,
|
||||
taskStuckTimeoutMs: 1_000,
|
||||
maxPostReviewFixes: 1,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
manager.stop();
|
||||
});
|
||||
|
||||
it.each([
|
||||
"recoverReviewTasksWithFailedPreMergeSteps",
|
||||
"recoverStaleIncompleteReviewTasks",
|
||||
"recoverGhostReviewTasks",
|
||||
"recoverInterruptedMergingTasks",
|
||||
"recoverMergedReviewTasks",
|
||||
"recoverStuckMergeDeadlocks",
|
||||
"recoverOrphanOnlyScopeViolations",
|
||||
"recoverAlreadyMergedReviewTasks",
|
||||
"recoverMissingWorktreeReviewFailures",
|
||||
"recoverPartialProgressNoTaskDoneFailures",
|
||||
] as const)("skips entirely when autoMerge is disabled (respects PR-based review flow): %s", async (methodName) => {
|
||||
if (methodName === "recoverReviewTasksWithFailedPreMergeSteps") {
|
||||
manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project", recoverFailedPreMergeStep: vi.fn() });
|
||||
}
|
||||
const result = await (manager as any)[methodName]();
|
||||
expect(result).toBe(0);
|
||||
expect(store.listTasks).not.toHaveBeenCalled();
|
||||
expect(store.moveTask).not.toHaveBeenCalled();
|
||||
expect(store.updateTask).not.toHaveBeenCalled();
|
||||
expect(store.logEntry).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips entirely when autoMerge is disabled (respects PR-based review flow): recoverCompletionHandoffLimbo", async () => {
|
||||
const result = await manager.recoverCompletionHandoffLimbo();
|
||||
expect(result).toBeUndefined();
|
||||
expect(store.listTasks).not.toHaveBeenCalled();
|
||||
expect(store.moveTask).not.toHaveBeenCalled();
|
||||
expect(store.updateTask).not.toHaveBeenCalled();
|
||||
expect(store.logEntry).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3427,11 +3427,14 @@ export class SelfHealingManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op when `settings.autoMerge === false` — PR-based review flow owns lifecycle until human merge.
|
||||
*/
|
||||
async finalizeNoOpReviewTasks(): Promise<number> {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
||||
const candidates = tasks.filter((t) =>
|
||||
@@ -3685,7 +3688,7 @@ export class SelfHealingManager {
|
||||
// "pull-request"`) — see GitHub issue #21.
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
||||
|
||||
@@ -3800,7 +3803,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
const maxFixes = settings.maxPostReviewFixes ?? 1;
|
||||
if (!Number.isFinite(maxFixes) || maxFixes <= 0) return 0;
|
||||
|
||||
@@ -3890,7 +3893,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
const timeoutMs = settings.taskStuckTimeoutMs;
|
||||
if (!timeoutMs || timeoutMs <= 0) return 0;
|
||||
|
||||
@@ -3966,7 +3969,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const cycleStartMs = Date.now();
|
||||
const timeoutMs = settings.taskStuckTimeoutMs;
|
||||
@@ -4097,7 +4100,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const cycleStartMs = Date.now();
|
||||
const thresholdMs = settings.inReviewStalledThresholdMs;
|
||||
@@ -4254,7 +4257,7 @@ export class SelfHealingManager {
|
||||
const timeoutMs = settings.taskStuckTimeoutMs;
|
||||
if (!timeoutMs || timeoutMs <= 0) return 0;
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const now = Date.now();
|
||||
const executingIds = this.options.getExecutingTaskIds?.() ?? new Set<string>();
|
||||
@@ -4319,7 +4322,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
const timeoutMs = settings.taskStuckTimeoutMs;
|
||||
if (!timeoutMs || timeoutMs <= 0) return 0;
|
||||
|
||||
@@ -4606,7 +4609,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
||||
|
||||
@@ -4705,7 +4708,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const now = Date.now();
|
||||
const inReview = await this.store.listTasks({ column: "in-review", slim: true });
|
||||
@@ -4861,7 +4864,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const executingIds = this.options.getExecutingTaskIds?.() ?? new Set<string>();
|
||||
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
||||
@@ -5019,7 +5022,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const executingIds = this.options.getExecutingTaskIds?.() ?? new Set<string>();
|
||||
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
||||
@@ -5141,7 +5144,7 @@ export class SelfHealingManager {
|
||||
async recoverCompletionHandoffLimbo(): Promise<void> {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return;
|
||||
if (!settings.autoMerge) return;
|
||||
if (settings.autoMerge === false) return;
|
||||
|
||||
const tasks = await this.store.listTasks({ column: "in-review", slim: false });
|
||||
const now = Date.now();
|
||||
@@ -6065,7 +6068,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
||||
const candidates = tasks.filter((task) =>
|
||||
@@ -6135,7 +6138,7 @@ export class SelfHealingManager {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (!settings.autoMerge) return 0;
|
||||
if (settings.autoMerge === false) return 0;
|
||||
|
||||
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user