Improve backend error handling, fix lean logo on login screen
This commit is contained in:
@@ -57,4 +57,24 @@ describe('api fetch injection', () => {
|
||||
expect(injectedFetch.mock.calls[0]?.[0]).toBe(`http://127.0.0.1:8000${path}`);
|
||||
expect(globalFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows a backend-unavailable message for login network failures', async () => {
|
||||
globalThis.fetch = vi.fn(async () => {
|
||||
throw new TypeError('Failed to fetch');
|
||||
}) as typeof fetch;
|
||||
|
||||
await expect(api.clientLogin('user@example.com', 'secret')).rejects.toThrow(
|
||||
'Unable to reach the server. Check that the backend is running and try again.'
|
||||
);
|
||||
});
|
||||
|
||||
it('shows a backend-unavailable message for authenticated read network failures', async () => {
|
||||
const injectedFetch = vi.fn(async () => {
|
||||
throw new TypeError('NetworkError when attempting to fetch resource.');
|
||||
}) as typeof fetch;
|
||||
|
||||
await expect(api.rawMaterials(injectedFetch)).rejects.toThrow(
|
||||
'Unable to reach the server. Check that the backend is running and try again.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+43
-20
@@ -37,6 +37,7 @@ import type {
|
||||
import { getStoredAdminSession, getStoredClientSession } from '$lib/session';
|
||||
|
||||
const DEFAULT_API_PORT = env.PUBLIC_API_PORT || '8000';
|
||||
const BACKEND_UNAVAILABLE_MESSAGE = 'Unable to reach the server. Check that the backend is running and try again.';
|
||||
|
||||
type AuthMode = 'none' | 'client' | 'admin' | 'manager';
|
||||
type ApiFetch = typeof fetch;
|
||||
@@ -85,6 +86,24 @@ function getToken(auth: AuthMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeRequestError(error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
const message = error.message.trim();
|
||||
const isNetworkFetchFailure =
|
||||
/failed to fetch|fetch failed|networkerror when attempting to fetch resource|load failed|network request failed/i.test(
|
||||
message
|
||||
) || error.name === 'NetworkError';
|
||||
|
||||
if (isNetworkFetchFailure) {
|
||||
return new Error(BACKEND_UNAVAILABLE_MESSAGE);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
return new Error('An unexpected error occurred while contacting the server.');
|
||||
}
|
||||
|
||||
async function fetchJson<T>(path: string, fallback: T, auth: AuthMode = 'none', fetcher: ApiFetch = fetch): Promise<T> {
|
||||
try {
|
||||
const token = getToken(auth);
|
||||
@@ -100,7 +119,7 @@ async function fetchJson<T>(path: string, fallback: T, auth: AuthMode = 'none',
|
||||
return (await response.json()) as T;
|
||||
} catch (error) {
|
||||
if (auth !== 'none') {
|
||||
throw error;
|
||||
throw normalizeRequestError(error);
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
@@ -112,30 +131,34 @@ async function request<T>(
|
||||
auth: AuthMode = 'none',
|
||||
fetcher: ApiFetch = fetch
|
||||
): Promise<T> {
|
||||
const token = getToken(auth);
|
||||
const response = await fetcher(buildApiUrl(path), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(options.headers ?? {})
|
||||
},
|
||||
...options
|
||||
});
|
||||
try {
|
||||
const token = getToken(auth);
|
||||
const response = await fetcher(buildApiUrl(path), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(options.headers ?? {})
|
||||
},
|
||||
...options
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let message = 'Request failed';
|
||||
if (!response.ok) {
|
||||
let message = 'Request failed';
|
||||
|
||||
try {
|
||||
const body = (await response.json()) as { detail?: string };
|
||||
message = body.detail ?? message;
|
||||
} catch {
|
||||
message = response.statusText || message;
|
||||
try {
|
||||
const body = (await response.json()) as { detail?: string };
|
||||
message = body.detail ?? message;
|
||||
} catch {
|
||||
message = response.statusText || message;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
return (await response.json()) as T;
|
||||
} catch (error) {
|
||||
throw normalizeRequestError(error);
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
|
||||
Reference in New Issue
Block a user