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: {
|
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 }>;
|
ensurePeered: (data: { remoteOrigin: string }) => Promise<{ peeringStatus: string; peerId?: string; error?: string }>;
|
||||||
peers: () => Promise<{ peers: FederationPeer[] }>;
|
peers: () => Promise<{ peers: FederationPeer[] }>;
|
||||||
resetEvents: () => Promise<FederationResetEventsResponse>;
|
resetEvents: () => Promise<FederationResetEventsResponse>;
|
||||||
@@ -697,7 +697,7 @@ export class BackspaceApiClient {
|
|||||||
|
|
||||||
this.federation = {
|
this.federation = {
|
||||||
initiatePeering: (data: { remoteOrigin: string }) =>
|
initiatePeering: (data: { remoteOrigin: string }) =>
|
||||||
request<{ peer: FederationPeer }>(
|
request<{ peer: FederationPeer; verified?: boolean }>(
|
||||||
'POST', '/federation/peer/initiate', data
|
'POST', '/federation/peer/initiate', data
|
||||||
),
|
),
|
||||||
ensurePeered: (data: { remoteOrigin: string }) =>
|
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 () => {
|
it('removes an orphaned account via the existing admin delete', async () => {
|
||||||
peers.mockResolvedValue({ peers: [] });
|
peers.mockResolvedValue({ peers: [] });
|
||||||
resetEvents.mockResolvedValue({ events: [resetEvent([orphanedAccount()])] });
|
resetEvents.mockResolvedValue({ events: [resetEvent([orphanedAccount()])] });
|
||||||
|
|||||||
@@ -910,8 +910,15 @@ function ResetCleanup() {
|
|||||||
// Order matters: reset the stale local record BEFORE the fresh handshake,
|
// Order matters: reset the stale local record BEFORE the fresh handshake,
|
||||||
// so activation heals stale friendships/DMs against the new incarnation.
|
// so activation heals stale friendships/DMs against the new incarnation.
|
||||||
await api.federation.resetPeer(peer.id);
|
await api.federation.resetPeer(peer.id);
|
||||||
await api.federation.initiatePeering({ remoteOrigin: peer.origin });
|
const result = await api.federation.initiatePeering({ remoteOrigin: peer.origin });
|
||||||
addToast(`Re-peering initiated with ${peerName(peer)}`, 'success', 3000);
|
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();
|
await fetchAll();
|
||||||
} else {
|
} else {
|
||||||
const { account } = confirmAction;
|
const { account } = confirmAction;
|
||||||
@@ -938,6 +945,14 @@ function ResetCleanup() {
|
|||||||
} else {
|
} else {
|
||||||
addToast(err instanceof Error ? err.message : 'Failed to remove account', 'warning');
|
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 {
|
} else {
|
||||||
addToast(err instanceof Error ? err.message : 'Re-peering failed', 'warning');
|
addToast(err instanceof Error ? err.message : 'Re-peering failed', 'warning');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user