diff --git a/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.resetCleanup.test.tsx b/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.resetCleanup.test.tsx index 9632ea90..59f0afc9 100644 --- a/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.resetCleanup.test.tsx +++ b/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.resetCleanup.test.tsx @@ -131,14 +131,16 @@ describe('FederationPanel — Reset cleanup', () => { await waitFor(() => expect(deleteUser).toHaveBeenCalledWith('acc1')); }); - it('surfaces "transfer ownership first" when Remove hits owns-spaces 400', async () => { + it('surfaces "transfer ownership first" only when the 400 carries an ownedSpaces payload', async () => { peers.mockResolvedValue({ peers: [] }); - resetEvents.mockResolvedValue({ - events: [resetEvent([orphanedAccount({ ownedSpaces: [{ id: 's1', name: 'My Space' }] })])], - }); + // Isolate the classifier: the fixture account does NOT own spaces locally + // (default ownedSpaces: []), so the transfer-first message can only come + // from the error's `ownedSpaces` payload — the real server 400 shape. + resetEvents.mockResolvedValue({ events: [resetEvent([orphanedAccount()])] }); deleteUser.mockRejectedValue( Object.assign(new Error('User owns spaces — transfer ownership first'), { - status: 400, + error: 'User owns spaces — transfer ownership first', + statusCode: 400, ownedSpaces: [{ id: 's1', name: 'My Space' }], }), ); @@ -159,4 +161,30 @@ describe('FederationPanel — Reset cleanup', () => { ), ); }); + + it('shows the generic failure toast for a rejection WITHOUT an ownedSpaces payload', async () => { + peers.mockResolvedValue({ peers: [] }); + resetEvents.mockResolvedValue({ events: [resetEvent([orphanedAccount()])] }); + deleteUser.mockRejectedValue( + Object.assign(new Error('Internal server error'), { statusCode: 400 }), + ); + + render(); + + const removeBtn = await screen.findByRole('button', { name: 'Remove' }); + fireEvent.click(removeBtn); + + const confirmBtn = await screen.findByRole('button', { name: 'Delete permanently' }); + fireEvent.click(confirmBtn); + + await waitFor(() => expect(deleteUser).toHaveBeenCalledWith('acc1')); + await waitFor(() => + expect(addToast).toHaveBeenCalledWith('Internal server error', 'warning'), + ); + // The classifier must NOT mislabel a generic 400 as an ownership problem. + expect(addToast).not.toHaveBeenCalledWith( + expect.stringContaining('transfer ownership first'), + 'warning', + ); + }); }); diff --git a/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.tsx b/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.tsx index 13eda124..db915456 100644 --- a/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.tsx +++ b/packages/web/src/components/modals/instanceSettingsPanels/FederationPanel.tsx @@ -926,11 +926,7 @@ function ResetCleanup() { } } catch (err) { if (confirmAction.kind === 'remove') { - const status = (err as { status?: number } | null | undefined)?.status; - const ownsSpaces = - status === 400 || - (err != null && typeof err === 'object' && 'ownedSpaces' in err) || - confirmAction.account.ownedSpaces.length > 0; + const ownsSpaces = err != null && typeof err === 'object' && 'ownedSpaces' in err; if (ownsSpaces) { addToast( `${confirmAction.account.username} owns spaces — transfer ownership first (Space Settings → Ownership).`,