fix(sso): surface config load, test, and removal errors in the SSO settings UI (#1265)

The SSO provider settings previously swallowed three failure paths: the
provider-config load on mount, the connection test, and provider removal.
A transient backend error, a tier-gate rejection, or a failed removal left
the admin with no feedback at all.

Each path now surfaces the backend message (or a clear fallback) through the
standard error toast, matching the existing save handler. The connection-test
handler also checks the response status before treating the body as a test
result, so an API or authorization failure is no longer reported as a failed
identity-provider connection.

Adds component tests covering the non-ok and network-failure branches for each
handler, including the unparseable-error-body fallback.
This commit is contained in:
Anso
2026-06-01 09:00:35 -04:00
committed by GitHub
parent bf61ecae3e
commit 4248ac0e72
2 changed files with 146 additions and 9 deletions
+27 -9
View File
@@ -105,15 +105,23 @@ function ProviderCard({ providerId, type, label, initialConfig, onSave }: {
setTestResult(null);
try {
const res = await apiFetch(`/sso/config/${providerId}/test`, { method: 'POST' });
const data = await res.json();
const data = await res.json().catch(() => null);
if (!res.ok) {
const message = data?.error || data?.message || 'Connection test failed';
setTestResult({ success: false, error: message });
toast.error(message);
return;
}
setTestResult(data);
if (data.success) {
if (data?.success) {
toast.success('Connection successful');
} else {
toast.error(data.error || 'Connection failed');
toast.error(data?.error || 'Connection failed');
}
} catch {
setTestResult({ success: false, error: 'Connection test failed' });
} catch (error: unknown) {
const message = (error as Error)?.message || 'Connection test failed';
setTestResult({ success: false, error: message });
toast.error(message);
} finally {
setTesting(false);
}
@@ -127,9 +135,12 @@ function ProviderCard({ providerId, type, label, initialConfig, onSave }: {
setConfig({ enabled: false });
setExpanded(false);
onSave();
} else {
const data = await res.json().catch(() => null);
toast.error(data?.error || data?.message || 'Failed to remove provider');
}
} catch {
toast.error('Failed to remove provider');
} catch (error: unknown) {
toast.error((error as Error)?.message || 'Failed to remove provider');
}
};
@@ -408,8 +419,15 @@ export function SSOSection() {
const fetchConfigs = async () => {
try {
const res = await apiFetch('/sso/config');
if (res.ok) setConfigs(await res.json());
} catch { /* ignore fetch errors */ }
if (res.ok) {
setConfigs(await res.json());
} else {
const data = await res.json().catch(() => null);
toast.error(data?.error || data?.message || 'Failed to load SSO configuration');
}
} catch (error: unknown) {
toast.error((error as Error)?.message || 'Failed to load SSO configuration');
}
};
// eslint-disable-next-line react-hooks/set-state-in-effect