Read QQ account databases and attribute identities to their owning processes. Share snapshots across REST, watcher updates and login reconciliation, discard stale results, and report detection failures.
108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { createApiClient } from '../src/lib/api/client';
|
|
|
|
const tokenStore = {
|
|
load: () => 'test-token',
|
|
save: () => undefined,
|
|
};
|
|
|
|
describe('API client request deadline', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it('aborts a pending request and reports a retryable timeout', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(
|
|
(_url: string, init?: RequestInit) =>
|
|
new Promise<Response>((_resolve, reject) => {
|
|
init?.signal?.addEventListener(
|
|
'abort',
|
|
() => {
|
|
reject(new DOMException('Aborted', 'AbortError'));
|
|
},
|
|
{ once: true },
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
const client = createApiClient({ tokenStore });
|
|
|
|
const request = client.request('/api/processes');
|
|
const rejection = expect(request).rejects.toMatchObject({
|
|
message: '请求超时,请重试',
|
|
code: 'REQUEST_TIMEOUT',
|
|
});
|
|
await vi.advanceTimersByTimeAsync(30_000);
|
|
|
|
await rejection;
|
|
});
|
|
|
|
it('cancels a request without preventing a later retry', async () => {
|
|
const fetchMock = vi
|
|
.fn()
|
|
.mockImplementationOnce(
|
|
(_url: string, init?: RequestInit) =>
|
|
new Promise<Response>((_resolve, reject) => {
|
|
init?.signal?.addEventListener(
|
|
'abort',
|
|
() => {
|
|
reject(new DOMException('Aborted', 'AbortError'));
|
|
},
|
|
{ once: true },
|
|
);
|
|
}),
|
|
)
|
|
.mockResolvedValueOnce(
|
|
new Response(JSON.stringify({ list: [] }), {
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' },
|
|
}),
|
|
);
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
const client = createApiClient({ tokenStore });
|
|
const controller = new AbortController();
|
|
|
|
const cancelled = client.request('/api/processes', { signal: controller.signal });
|
|
controller.abort();
|
|
|
|
await expect(cancelled).rejects.toMatchObject({ name: 'AbortError' });
|
|
await expect(
|
|
client.request('/api/processes').then((response) => response.json()),
|
|
).resolves.toEqual({ list: [] });
|
|
});
|
|
|
|
it('settles a pending login with a retryable timeout message', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(
|
|
(_url: string, init?: RequestInit) =>
|
|
new Promise<Response>((_resolve, reject) => {
|
|
init?.signal?.addEventListener(
|
|
'abort',
|
|
() => {
|
|
reject(new DOMException('Aborted', 'AbortError'));
|
|
},
|
|
{ once: true },
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
const client = createApiClient({ tokenStore });
|
|
|
|
const login = client.login('password');
|
|
await vi.advanceTimersByTimeAsync(30_000);
|
|
|
|
await expect(login).resolves.toEqual({
|
|
ok: false,
|
|
message: '请求超时,请重试',
|
|
});
|
|
});
|
|
});
|