From 0641034be51e25c603a89c96c018607833bdc8cd Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 01:37:08 +0100 Subject: [PATCH] test(alerts): exercise overview delivery recovery actions The overview only asserted that recovery buttons were present. Exercise cancellation, failure and deferred action/health completion so regressions cannot silently clear attention or allow conflicting actions while recovery is pending. APIs remain mocked; this does not qualify installed delivery. Change-source: pulse-maintainer --- .../OverviewTab.deliveryactions.test.tsx | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/frontend-modern/src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx b/frontend-modern/src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx index c177bc613..1851cf704 100644 --- a/frontend-modern/src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx +++ b/frontend-modern/src/features/alerts/__tests__/OverviewTab.deliveryactions.test.tsx @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { cleanup, render, screen, waitFor } from '@solidjs/testing-library'; +import { cleanup, fireEvent, render, screen, waitFor } from '@solidjs/testing-library'; import { DEFAULT_LOCALE, setActiveLocale } from '@/i18n'; import type { Alert } from '@/types/api'; import type { NotificationHealth } from '@/api/notifications'; @@ -49,6 +49,8 @@ vi.mock('@/components/Alerts/InvestigateAlertButton', () => ({ InvestigateAlertButton: () => null, })); +import { notificationStore } from '@/stores/notifications'; + import { OverviewTab } from '../OverviewTab'; function degradedHealth(): NotificationHealth { @@ -107,10 +109,15 @@ describe('OverviewTab delivery health actions', () => { getDeliveryDiagnoses.mockReset(); getDeliveryDiagnoses.mockResolvedValue([]); getHealth.mockReset(); + retryTerminalFailures.mockReset(); + dismissTerminalFailures.mockReset(); + vi.mocked(notificationStore.success).mockClear(); + vi.mocked(notificationStore.error).mockClear(); }); afterEach(() => { cleanup(); + vi.restoreAllMocks(); setActiveLocale(DEFAULT_LOCALE); }); @@ -127,4 +134,68 @@ describe('OverviewTab delivery health actions', () => { expect(screen.queryByRole('button', { name: 'Refresh delivery status' })).toBeNull(); expect(screen.getByRole('alert')).toHaveTextContent('Most recent failures: connectivity (1).'); }); + for (const action of [ + { name: 'Retry retained deliveries', api: retryTerminalFailures }, + { name: 'Dismiss retained failures', api: dismissTerminalFailures }, + ]) { + it(`does not mutate or refresh when ${action.name} is cancelled`, async () => { + getHealth.mockResolvedValue(degradedHealth()); + const confirmation = vi.spyOn(window, 'confirm').mockReturnValue(false); + render(() => ); + fireEvent.click(await screen.findByRole('button', { name: action.name })); + + expect(confirmation).toHaveBeenCalledOnce(); + expect(action.api).not.toHaveBeenCalled(); + expect(getHealth).toHaveBeenCalledTimes(1); + expect(screen.getByRole('alert')).toBeTruthy(); + }); + + it(`retains attention and enables another attempt when ${action.name} fails`, async () => { + getHealth.mockResolvedValue(degradedHealth()); + vi.spyOn(window, 'confirm').mockReturnValue(true); + action.api.mockRejectedValue(new Error('queue action unavailable')); + render(() => ); + fireEvent.click(await screen.findByRole('button', { name: action.name })); + + await waitFor(() => expect(notificationStore.error).toHaveBeenCalledOnce()); + expect(notificationStore.success).not.toHaveBeenCalled(); + expect(getHealth).toHaveBeenCalledTimes(1); + expect(screen.getByRole('alert')).toHaveTextContent('connectivity (1)'); + expect(screen.getByRole('button', { name: action.name })).not.toBeDisabled(); + }); + + it(`keeps both actions disabled until ${action.name} and its health refresh finish`, async () => { + const healthy = degradedHealth(); + healthy.overallHealthy = true; + healthy.queue = { ...healthy.queue, status: 'healthy', healthy: true, attentionRequired: 0, deadLetter: 0 }; + let completeAction!: (value: { affected: number }) => void; + let completeHealth!: (value: NotificationHealth) => void; + action.api.mockReturnValue(new Promise(resolve => { completeAction = resolve; })); + getHealth.mockResolvedValueOnce(degradedHealth()).mockReturnValueOnce( + new Promise(resolve => { completeHealth = resolve; }), + ); + vi.spyOn(window, 'confirm').mockReturnValue(true); + render(() => ); + fireEvent.click(await screen.findByRole('button', { name: action.name })); + + const expectActionsDisabled = () => { + const buttons = screen.getByRole('alert').querySelectorAll('button'); + expect(buttons.length).toBe(2); + for (const button of buttons) expect(button).toBeDisabled(); + }; + expectActionsDisabled(); + expect(getHealth).toHaveBeenCalledTimes(1); + completeAction({ affected: 85 }); + await waitFor(() => expect(getHealth).toHaveBeenCalledTimes(2)); + expectActionsDisabled(); + expect(screen.getByRole('alert')).toBeTruthy(); + + completeHealth(healthy); + await waitFor(() => expect(screen.queryByRole('alert')).toBeNull()); + expect(action.api).toHaveBeenCalledOnce(); + expect(notificationStore.success).toHaveBeenCalledOnce(); + expect(notificationStore.error).not.toHaveBeenCalled(); + }); + } + });