Handle replay hydration file fetch timeouts
This commit is contained in:
@@ -98,6 +98,29 @@ describe('api proxy test timeout handling', () => {
|
|||||||
await expect(promise).resolves.toMatchObject({ message: '请求超时(30s)' });
|
await expect(promise).resolves.toMatchObject({ message: '请求超时(30s)' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('times out replay hydration file-content fetches after 30 seconds', async () => {
|
||||||
|
installPendingFetch();
|
||||||
|
|
||||||
|
const getProxyFileContentDataUrl = (api as Record<string, any>).getProxyFileContentDataUrl;
|
||||||
|
let settled = false;
|
||||||
|
const handled = getProxyFileContentDataUrl?.('file-metapi-123')
|
||||||
|
.then(() => ({ ok: true as const }))
|
||||||
|
.catch((error: Error) => ({ ok: false as const, error }))
|
||||||
|
.finally(() => {
|
||||||
|
settled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await vi.advanceTimersByTimeAsync(30_000);
|
||||||
|
expect(settled).toBe(true);
|
||||||
|
|
||||||
|
const result = await handled;
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) {
|
||||||
|
throw new Error('Expected replay hydration file-content fetch to time out');
|
||||||
|
}
|
||||||
|
expect(result.error.message).toBe('请求超时(30s)');
|
||||||
|
});
|
||||||
|
|
||||||
it('loads proxy file content as a data URL for replay hydration', async () => {
|
it('loads proxy file content as a data URL for replay hydration', async () => {
|
||||||
const fetchMock = vi.fn().mockResolvedValue(new Response(
|
const fetchMock = vi.fn().mockResolvedValue(new Response(
|
||||||
new Blob([Buffer.from('PDF')], { type: 'application/pdf' }),
|
new Blob([Buffer.from('PDF')], { type: 'application/pdf' }),
|
||||||
|
|||||||
+19
-20
@@ -71,7 +71,7 @@ function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|||||||
return btoa(binary);
|
return btoa(binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function request(url: string, options: RequestOptions = {}) {
|
async function fetchAuthenticatedResponse(url: string, options: RequestOptions = {}): Promise<Response> {
|
||||||
const { timeoutMs = 30_000, signal: externalSignal, ...fetchOptions } = options;
|
const { timeoutMs = 30_000, signal: externalSignal, ...fetchOptions } = options;
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
let timeoutHandle: ReturnType<typeof setTimeout> | null = setTimeout(() => {
|
let timeoutHandle: ReturnType<typeof setTimeout> | null = setTimeout(() => {
|
||||||
@@ -107,13 +107,12 @@ async function request(url: string, options: RequestOptions = {}) {
|
|||||||
if (res.status === 401 || res.status === 403) {
|
if (res.status === 401 || res.status === 403) {
|
||||||
const hadToken = !!getAuthToken(localStorage);
|
const hadToken = !!getAuthToken(localStorage);
|
||||||
clearAuthSession(localStorage);
|
clearAuthSession(localStorage);
|
||||||
if (hadToken) window.location.reload();
|
if (hadToken && typeof window !== 'undefined' && typeof window.location?.reload === 'function') {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
throw new Error('Session expired');
|
throw new Error('Session expired');
|
||||||
}
|
}
|
||||||
if (!res.ok) {
|
return res;
|
||||||
throw new Error(await extractResponseErrorMessage(res));
|
|
||||||
}
|
|
||||||
return res.json();
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.name === 'AbortError') {
|
if (error?.name === 'AbortError') {
|
||||||
if (externalSignal?.aborted) throw error;
|
if (externalSignal?.aborted) throw error;
|
||||||
@@ -129,6 +128,14 @@ async function request(url: string, options: RequestOptions = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function request(url: string, options: RequestOptions = {}) {
|
||||||
|
const res = await fetchAuthenticatedResponse(url, options);
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(await extractResponseErrorMessage(res));
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
function buildQueryString(params?: Record<string, string | number | boolean | null | undefined>) {
|
function buildQueryString(params?: Record<string, string | number | boolean | null | undefined>) {
|
||||||
if (!params) return '';
|
if (!params) return '';
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
@@ -664,22 +671,14 @@ export const api = {
|
|||||||
}),
|
}),
|
||||||
getProxyTestJob: (jobId: string) => request(`/api/test/proxy/jobs/${encodeURIComponent(jobId)}`),
|
getProxyTestJob: (jobId: string) => request(`/api/test/proxy/jobs/${encodeURIComponent(jobId)}`),
|
||||||
deleteProxyTestJob: (jobId: string) => request(`/api/test/proxy/jobs/${encodeURIComponent(jobId)}`, { method: 'DELETE' }),
|
deleteProxyTestJob: (jobId: string) => request(`/api/test/proxy/jobs/${encodeURIComponent(jobId)}`, { method: 'DELETE' }),
|
||||||
getProxyFileContentDataUrl: async (fileId: string) => {
|
getProxyFileContentDataUrl: async (
|
||||||
const token = requireAuthToken();
|
fileId: string,
|
||||||
const response = await fetch(`/v1/files/${encodeURIComponent(fileId)}/content`, {
|
options: Pick<RequestOptions, 'signal' | 'timeoutMs'> = {},
|
||||||
|
) => {
|
||||||
|
const response = await fetchAuthenticatedResponse(`/v1/files/${encodeURIComponent(fileId)}/content`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
...options,
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (response.status === 401 || response.status === 403) {
|
|
||||||
const hadToken = !!getAuthToken(localStorage);
|
|
||||||
clearAuthSession(localStorage);
|
|
||||||
if (hadToken && typeof window !== 'undefined' && typeof window.location?.reload === 'function') {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
throw new Error('Session expired');
|
|
||||||
}
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(await extractResponseErrorMessage(response));
|
throw new Error(await extractResponseErrorMessage(response));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user