test(FN-5046): complete Step 3 — cover terminal-only failure notification behavior
Fusion-Task-Id: FN-5046 Fusion-Task-Lineage: 0748fd0e-70c3-44b9-a599-0df5255d4d60
This commit is contained in:
committed by
gsxdsm
parent
7167eebe6b
commit
c31bf013df
@@ -127,6 +127,100 @@ describe("NotificationService deferred failure notifications", () => {
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
it("terminal-only suppresses non-terminal failures after grace", async () => {
|
||||
const { store, service, sendNotification } = await setup({
|
||||
failureNotificationMode: "terminal-only",
|
||||
failureNotificationDelayMs: 50,
|
||||
});
|
||||
store.setTask(task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
store.emit("task:updated", task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
|
||||
await vi.advanceTimersByTimeAsync(50);
|
||||
|
||||
expect(sendNotification).not.toHaveBeenCalledWith("failed", expect.anything());
|
||||
expect(service.getMetrics().failureNotificationSuppressedCount).toBe(1);
|
||||
expect(schedulerLog.log).toHaveBeenCalledWith("[notify] FN-1 non-terminal failure — suppressed (mode=terminal-only)");
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
it("terminal-only dispatches when failed task is paused", async () => {
|
||||
const { store, service, sendNotification } = await setup({
|
||||
failureNotificationMode: "terminal-only",
|
||||
failureNotificationDelayMs: 50,
|
||||
});
|
||||
store.setTask(task({ id: "FN-1", status: "failed", paused: true, column: "in-progress" }));
|
||||
store.emit("task:updated", task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
|
||||
await vi.advanceTimersByTimeAsync(50);
|
||||
|
||||
expect(sendNotification).toHaveBeenCalledTimes(1);
|
||||
expect(sendNotification).toHaveBeenCalledWith("failed", expect.objectContaining({ taskId: "FN-1" }));
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
it("terminal-only dispatches when failed task is in-review", async () => {
|
||||
const { store, service, sendNotification } = await setup({
|
||||
failureNotificationMode: "terminal-only",
|
||||
failureNotificationDelayMs: 50,
|
||||
});
|
||||
store.setTask(task({ id: "FN-1", status: "failed", paused: false, column: "in-review" }));
|
||||
store.emit("task:updated", task({ id: "FN-1", status: "failed", paused: false, column: "todo" }));
|
||||
|
||||
await vi.advanceTimersByTimeAsync(50);
|
||||
|
||||
expect(sendNotification).toHaveBeenCalledTimes(1);
|
||||
expect(sendNotification).toHaveBeenCalledWith("failed", expect.objectContaining({ taskId: "FN-1" }));
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
it("terminal-only still uses recovery suppression when task self-recovers before grace", async () => {
|
||||
const { store, service, sendNotification } = await setup({
|
||||
failureNotificationMode: "terminal-only",
|
||||
failureNotificationDelayMs: 50,
|
||||
});
|
||||
store.setTask(task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
store.emit("task:updated", task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
|
||||
store.setTask(task({ id: "FN-1", status: undefined, column: "done" }));
|
||||
store.emit("task:moved", { task: task({ id: "FN-1", status: undefined, column: "done" }), from: "in-progress", to: "done" });
|
||||
await vi.advanceTimersByTimeAsync(50);
|
||||
|
||||
expect(sendNotification).not.toHaveBeenCalledWith("failed", expect.anything());
|
||||
expect(service.getMetrics().failureNotificationSuppressedCount).toBe(1);
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
it("sticky-only still notifies persistent failed tasks", async () => {
|
||||
const { store, service, sendNotification } = await setup({
|
||||
failureNotificationMode: "sticky-only",
|
||||
failureNotificationDelayMs: 50,
|
||||
});
|
||||
store.setTask(task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
store.emit("task:updated", task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
|
||||
await vi.advanceTimersByTimeAsync(50);
|
||||
|
||||
expect(sendNotification).toHaveBeenCalledWith("failed", expect.objectContaining({ taskId: "FN-1" }));
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
it("terminal-only with delay 0 still uses deferred path and suppresses non-terminal", async () => {
|
||||
const { store, service, sendNotification } = await setup({
|
||||
failureNotificationMode: "terminal-only",
|
||||
failureNotificationDelayMs: 0,
|
||||
});
|
||||
store.setTask(task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
store.emit("task:updated", task({ id: "FN-1", status: "failed", paused: false, column: "in-progress" }));
|
||||
|
||||
expect(sendNotification).not.toHaveBeenCalled();
|
||||
expect(service.getPendingFailureCount()).toBe(1);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
expect(sendNotification).not.toHaveBeenCalledWith("failed", expect.anything());
|
||||
expect(service.getMetrics().failureNotificationSuppressedCount).toBe(1);
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
it("stop clears pending timers without firing", async () => {
|
||||
const { store, service, sendNotification } = await setup();
|
||||
store.setTask(task({ id: "FN-1", status: "failed" }));
|
||||
|
||||
Reference in New Issue
Block a user