fix(web): Re-peer surfaces incomplete outcome instead of false success (BUG-2)
This commit is contained in:
@@ -277,7 +277,7 @@ export class BackspaceApiClient {
|
||||
};
|
||||
|
||||
readonly federation: {
|
||||
initiatePeering: (data: { remoteOrigin: string }) => Promise<{ peer: FederationPeer }>;
|
||||
initiatePeering: (data: { remoteOrigin: string }) => Promise<{ peer: FederationPeer; verified?: boolean }>;
|
||||
ensurePeered: (data: { remoteOrigin: string }) => Promise<{ peeringStatus: string; peerId?: string; error?: string }>;
|
||||
peers: () => Promise<{ peers: FederationPeer[] }>;
|
||||
resetEvents: () => Promise<FederationResetEventsResponse>;
|
||||
@@ -697,7 +697,7 @@ export class BackspaceApiClient {
|
||||
|
||||
this.federation = {
|
||||
initiatePeering: (data: { remoteOrigin: string }) =>
|
||||
request<{ peer: FederationPeer }>(
|
||||
request<{ peer: FederationPeer; verified?: boolean }>(
|
||||
'POST', '/federation/peer/initiate', data
|
||||
),
|
||||
ensurePeered: (data: { remoteOrigin: string }) =>
|
||||
|
||||
+78
@@ -122,6 +122,84 @@ describe('FederationPanel — Reset cleanup', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('warns when Re-peer completes but the handshake is unverified (verified:false)', async () => {
|
||||
peers.mockResolvedValue({ peers: [resetPeerFixture] });
|
||||
resetEvents.mockResolvedValue({ events: [] });
|
||||
resetPeer.mockResolvedValue({ success: true });
|
||||
initiatePeering.mockResolvedValue({
|
||||
peer: { ...resetPeerFixture, status: 'needs_attention' },
|
||||
verified: false,
|
||||
});
|
||||
|
||||
render(<FederationPanel />);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Re-peer' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Re-peer & heal' }));
|
||||
|
||||
await waitFor(() => expect(initiatePeering).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(addToast).toHaveBeenCalledWith(
|
||||
'Re-peer incomplete — Peer still holds stale peering for you. Its admin must reset their side, then Re-peer again.',
|
||||
'warning',
|
||||
),
|
||||
);
|
||||
expect(addToast).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining('Re-peering initiated'),
|
||||
'success',
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('warns when the remote rejects with 409 PEER_EXISTS_RESET_REQUIRED', async () => {
|
||||
peers.mockResolvedValue({ peers: [resetPeerFixture] });
|
||||
resetEvents.mockResolvedValue({ events: [] });
|
||||
resetPeer.mockResolvedValue({ success: true });
|
||||
initiatePeering.mockRejectedValue(
|
||||
new HttpError(409, 'Peer exists — reset required', {
|
||||
error: 'Peer exists — reset required',
|
||||
statusCode: 409,
|
||||
code: 'PEER_EXISTS_RESET_REQUIRED',
|
||||
}),
|
||||
);
|
||||
|
||||
render(<FederationPanel />);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Re-peer' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Re-peer & heal' }));
|
||||
|
||||
await waitFor(() => expect(initiatePeering).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(addToast).toHaveBeenCalledWith(
|
||||
'The remote instance still holds stale peering for you. Ask its admin to reset their side, then Re-peer again.',
|
||||
'warning',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('shows the success toast when the handshake is verified (verified:true)', async () => {
|
||||
peers.mockResolvedValue({ peers: [resetPeerFixture] });
|
||||
resetEvents.mockResolvedValue({ events: [] });
|
||||
resetPeer.mockResolvedValue({ success: true });
|
||||
initiatePeering.mockResolvedValue({
|
||||
peer: { ...resetPeerFixture, status: 'active' },
|
||||
verified: true,
|
||||
});
|
||||
|
||||
render(<FederationPanel />);
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Re-peer' }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Re-peer & heal' }));
|
||||
|
||||
await waitFor(() => expect(initiatePeering).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(addToast).toHaveBeenCalledWith('Re-peering initiated with Peer', 'success', 3000),
|
||||
);
|
||||
expect(addToast).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining('still holds stale peering'),
|
||||
'warning',
|
||||
);
|
||||
});
|
||||
|
||||
it('removes an orphaned account via the existing admin delete', async () => {
|
||||
peers.mockResolvedValue({ peers: [] });
|
||||
resetEvents.mockResolvedValue({ events: [resetEvent([orphanedAccount()])] });
|
||||
|
||||
@@ -910,8 +910,15 @@ function ResetCleanup() {
|
||||
// Order matters: reset the stale local record BEFORE the fresh handshake,
|
||||
// so activation heals stale friendships/DMs against the new incarnation.
|
||||
await api.federation.resetPeer(peer.id);
|
||||
await api.federation.initiatePeering({ remoteOrigin: peer.origin });
|
||||
addToast(`Re-peering initiated with ${peerName(peer)}`, 'success', 3000);
|
||||
const result = await api.federation.initiatePeering({ remoteOrigin: peer.origin });
|
||||
if (result.verified === false || result.peer?.status === 'needs_attention') {
|
||||
addToast(
|
||||
`Re-peer incomplete — ${peerName(peer)} still holds stale peering for you. Its admin must reset their side, then Re-peer again.`,
|
||||
'warning',
|
||||
);
|
||||
} else {
|
||||
addToast(`Re-peering initiated with ${peerName(peer)}`, 'success', 3000);
|
||||
}
|
||||
await fetchAll();
|
||||
} else {
|
||||
const { account } = confirmAction;
|
||||
@@ -938,6 +945,14 @@ function ResetCleanup() {
|
||||
} else {
|
||||
addToast(err instanceof Error ? err.message : 'Failed to remove account', 'warning');
|
||||
}
|
||||
} else if (
|
||||
err instanceof HttpError && err.status === 409 &&
|
||||
(err.body as { code?: string } | undefined)?.code === 'PEER_EXISTS_RESET_REQUIRED'
|
||||
) {
|
||||
addToast(
|
||||
`The remote instance still holds stale peering for you. Ask its admin to reset their side, then Re-peer again.`,
|
||||
'warning',
|
||||
);
|
||||
} else {
|
||||
addToast(err instanceof Error ? err.message : 'Re-peering failed', 'warning');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user