From d0df71fc4a180c7009d32e355a485e37086bf808 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 23:18:48 +0100 Subject: [PATCH] Guard unsaved connection edits against polling regressions Exercise the NodeCredentialSlot dirty-edit path across repeated server snapshots and verify Save retains operator values and the current edit target. Removing the dirty guard reproduces the failure. Contract-Neutral: regression coverage only; no runtime or API changes Change-source: pulse-maintainer --- .../__tests__/NodeCredentialSlot.test.tsx | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/__tests__/NodeCredentialSlot.test.tsx b/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/__tests__/NodeCredentialSlot.test.tsx index 8f10eee58..f8698bef8 100644 --- a/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/__tests__/NodeCredentialSlot.test.tsx +++ b/frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/__tests__/NodeCredentialSlot.test.tsx @@ -64,6 +64,58 @@ describe('NodeCredentialSlot', () => { }); }); + it('preserves unsaved connection edits across polls and saves to the refreshed edit target', async () => { + const settings = createSettings(); + const onSaved = vi.fn(); + const initialNode = { + id: 'pve-0', + type: 'pve', + name: 'homelab', + host: 'https://pve1.local:8006', + user: '', + tokenName: 'root@pam!pulse', + hasToken: true, + verifySSL: true, + status: 'connected', + } as unknown as NodeConfigWithStatus; + const [editingNode, setEditingNode] = createSignal(initialNode); + + render(() => ( + + )); + + const endpoint = screen.getByLabelText(/^Endpoint URL/); + const verifySSL = screen.getByRole('checkbox', { name: 'Verify SSL certificate' }); + fireEvent.input(endpoint, { target: { value: 'https://operator-edit.local:8006' } }); + fireEvent.click(verifySSL); + + for (let poll = 1; poll <= 2; poll += 1) { + setEditingNode({ + ...initialNode, + host: `https://server-poll-${poll}.local:8006`, + }); + await waitFor(() => { + expect(screen.getByLabelText(/^Endpoint URL/)).toBe(endpoint); + expect(endpoint).toHaveValue('https://operator-edit.local:8006'); + expect(verifySSL).not.toBeChecked(); + }); + } + + expect(settings.saveNode).not.toHaveBeenCalled(); + fireEvent.submit(endpoint.closest('form')!); + await waitFor(() => expect(onSaved).toHaveBeenCalledTimes(1)); + expect(settings.saveNode).toHaveBeenCalledExactlyOnceWith( + expect.objectContaining({ host: 'https://operator-edit.local:8006', verifySSL: false }), + editingNode(), + ); + }); + it('requires candidate import plan approval before guided setup handoff or manual save', () => { render(() => (