forked from Lilly-Protocol/lily-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconcurrent-http-client.test.ts
More file actions
114 lines (90 loc) · 3.2 KB
/
Copy pathconcurrent-http-client.test.ts
File metadata and controls
114 lines (90 loc) · 3.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createFetchHttpClient } from '../src/http/fetch-http-client';
describe('concurrent http client stress test', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('handles concurrent requests with isolated timeouts and retries', async () => {
const requestCount = 10;
let callIndex = 0;
const fetchSpy = vi.fn(async (_input: URL | RequestInfo, init?: RequestInit) => {
const index = callIndex++;
await new Promise((resolve) => setTimeout(resolve, (index + 1) * 10));
if (init?.signal?.aborted) {
throw new Error('Aborted');
}
return new Response(
JSON.stringify({ id: index, status: 'ok' }),
{
status: 200,
headers: { 'content-type': 'application/json' },
},
);
});
const httpClient = createFetchHttpClient({
baseUrl: new URL('https://api.lily.test/'),
timeoutMs: 500,
retry: { retries: 0, retryDelayMs: 0, retryableStatusCodes: [] },
defaultHeaders: {},
userAgent: 'lily-sdk/test',
fetch: fetchSpy,
});
const promises = Array.from({ length: requestCount }, (_, i) =>
httpClient.request<{ id: number; status: string }>({
method: 'GET',
path: `/v1/resource/${String(i)}`,
}),
);
await vi.runAllTimersAsync();
const results = await Promise.all(promises);
expect(results).toHaveLength(requestCount);
expect(fetchSpy).toHaveBeenCalledTimes(requestCount);
for (let i = 0; i < requestCount; i++) {
expect(results[i]?.data.id).toBe(i);
expect(results[i]?.status).toBe(200);
}
expect(vi.getTimerCount()).toBe(0);
});
it('isolates retry state across concurrent failing requests', async () => {
const attemptsPerRequest: number[] = [];
let callIndex = 0;
const fetchSpy = vi.fn(async () => {
const currentCall = callIndex++;
attemptsPerRequest[currentCall] = (attemptsPerRequest[currentCall] ?? 0) + 1;
const isFirstAttempt = currentCall < 3;
await new Promise((resolve) => setTimeout(resolve, 10));
if (isFirstAttempt) {
return new Response(JSON.stringify({ error: 'temp' }), {
status: 500,
headers: { 'content-type': 'application/json' },
});
}
return new Response(JSON.stringify({ recovered: true }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
});
const httpClient = createFetchHttpClient({
baseUrl: new URL('https://api.lily.test/'),
timeoutMs: 500,
retry: { retries: 1, retryDelayMs: 5, retryableStatusCodes: [500] },
defaultHeaders: {},
userAgent: 'lily-sdk/test',
fetch: fetchSpy,
});
const promises = Array.from({ length: 3 }, () =>
httpClient.request<{ recovered: boolean }>({
method: 'GET',
path: '/v1/flaky',
}),
);
await vi.runAllTimersAsync();
const results = await Promise.all(promises);
expect(results.every((r) => r.data.recovered)).toBe(true);
expect(fetchSpy).toHaveBeenCalledTimes(6);
expect(vi.getTimerCount()).toBe(0);
});
});