From de40ac2ef34e2ac51e4bdd9d4145c6ad8c4a90b5 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 19:13:00 +0100 Subject: [PATCH] test(web): cover unavailable reads after accepted recovery Accepted queue actions and failed follow-up reads are different outcomes. Extend actual-tab regression coverage so unavailable health and history cannot erase SMTP edits or misrepresent the accepted action as rejected. Change-source: pulse-maintainer --- .../DestinationsTab.recoveryediting.test.tsx | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/frontend-modern/src/features/alerts/__tests__/DestinationsTab.recoveryediting.test.tsx b/frontend-modern/src/features/alerts/__tests__/DestinationsTab.recoveryediting.test.tsx index ce9ec130c..2307c007c 100644 --- a/frontend-modern/src/features/alerts/__tests__/DestinationsTab.recoveryediting.test.tsx +++ b/frontend-modern/src/features/alerts/__tests__/DestinationsTab.recoveryediting.test.tsx @@ -2,6 +2,7 @@ import { cleanup, fireEvent, render, screen, waitFor } from '@solidjs/testing-li import { createSignal } from 'solid-js'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { NotificationsAPI, type NotificationHealth } from '@/api/notifications'; +import { notificationStore } from '@/stores/notifications'; import { DEFAULT_LOCALE, setActiveLocale } from '@/i18n'; import { DestinationsTab } from '../tabs/DestinationsTab'; import type { UIAppriseConfig, UIEmailConfig } from '../types'; @@ -136,12 +137,14 @@ describe('DestinationsTab recovery while editing SMTP', () => { vi.restoreAllMocks(); }); - it.each([ + it.each(([ ['retryTerminalFailures', 'Retry retained deliveries', 'retry'], ['dismissTerminalFailures', 'Dismiss retained failures', 'dismiss'], - ] as const)( - 'preserves unfinished edits through rejected and accepted %s', - async (action, label, verb) => { + ] as const).flatMap(([action, label, verb]) => + (['available', 'unavailable'] as const).map((refresh) => ({ action, label, verb, refresh })), + ))( + 'preserves unfinished edits through $action with $refresh refresh', + async ({ action, label, verb, refresh }) => { const [emailConfig, setEmailConfig] = createSignal(buildEmailConfig()); const [appriseConfig, setAppriseConfig] = createSignal(buildAppriseConfig()); const dirty = vi.fn(); @@ -218,11 +221,22 @@ describe('DestinationsTab recovery while editing SMTP', () => { failed: 0, deadLetter: 0, }; - vi.mocked(NotificationsAPI.getHealth).mockResolvedValueOnce(healthy); + if (refresh === 'unavailable') { + vi.mocked(NotificationsAPI.getHealth).mockRejectedValueOnce(new Error('health unavailable')); + vi.mocked(NotificationsAPI.getDeliveryLog).mockRejectedValueOnce(new Error('history unavailable')); + } else { + vi.mocked(NotificationsAPI.getHealth).mockResolvedValueOnce(healthy); + } + vi.mocked(notificationStore.error).mockClear(); fireEvent.click(screen.getByRole('button', { name: label })); editor.focus(); accept({ success: true, affected: 85 }); - await waitFor(() => expect(screen.queryByRole('button', { name: label })).toBeNull()); + if (refresh === 'available') { + await waitFor(() => expect(screen.queryByRole('button', { name: label })).toBeNull()); + } else { + await screen.findByText('Notification delivery status is unavailable'); + await waitFor(() => expect(screen.getByRole('button', { name: label })).toBeEnabled()); + } await waitFor(() => expect(NotificationsAPI.getDeliveryLog).toHaveBeenCalledTimes(2)); expect(screen.getByRole('textbox', { name: 'SMTP server' })).toBe(editor); expect(editor).toHaveFocus(); @@ -231,7 +245,17 @@ describe('DestinationsTab recovery while editing SMTP', () => { expect(dirty).not.toHaveBeenCalledWith(false); expect(screen.getByRole('status')).toBe(status); expect(status).toBeEmptyDOMElement(); - expect(screen.getByText('SMTP fixture rejected')).toBeInTheDocument(); + if (refresh === 'unavailable') { + expect(await screen.findByText('Notification delivery status is unavailable')).toBeInTheDocument(); + expect(await screen.findByText('Pulse could not read the delivery log, so recent delivery activity cannot be shown.')).toBeInTheDocument(); + expect(screen.queryByText('SMTP fixture rejected')).not.toBeInTheDocument(); + } else { + expect(screen.getByText('SMTP fixture rejected')).toBeInTheDocument(); + } + // A failed read must not relabel an accepted mutation as rejected. + expect(notificationStore.success).toHaveBeenCalledTimes(1); + expect(notificationStore.error).not.toHaveBeenCalled(); + expect(NotificationsAPI.getHealth).toHaveBeenCalledTimes(2); expect(NotificationsAPI[action]).toHaveBeenCalledTimes(2); }, );