@agentuity/core 0.0.69 → 0.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/_util.d.ts.map +1 -1
- package/dist/services/_util.js +11 -1
- package/dist/services/_util.js.map +1 -1
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +0 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/stream.d.ts.map +1 -1
- package/dist/services/stream.js +6 -5
- package/dist/services/stream.js.map +1 -1
- package/package.json +2 -1
- package/src/services/_util.ts +10 -1
- package/src/services/index.ts +0 -1
- package/src/services/stream.ts +10 -8
- package/dist/services/objectstore.d.ts +0 -257
- package/dist/services/objectstore.d.ts.map +0 -1
- package/dist/services/objectstore.js +0 -422
- package/dist/services/objectstore.js.map +0 -1
- package/src/__test__/error.test.ts +0 -431
- package/src/services/__test__/keyvalue.test.ts +0 -402
- package/src/services/__test__/mock-adapter.ts +0 -114
- package/src/services/__test__/objectstore.test.ts +0 -431
- package/src/services/__test__/stream.test.ts +0 -554
- package/src/services/__test__/vector.test.ts +0 -819
- package/src/services/objectstore.ts +0 -816
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import { KeyValueStorageService } from '../keyvalue';
|
|
3
|
-
import { createMockAdapter } from './mock-adapter';
|
|
4
|
-
import { ServiceException } from '../exception';
|
|
5
|
-
|
|
6
|
-
describe('KeyValueStorageService', () => {
|
|
7
|
-
const baseUrl = 'https://api.example.com';
|
|
8
|
-
|
|
9
|
-
describe('get', () => {
|
|
10
|
-
test('should return data when key exists', async () => {
|
|
11
|
-
const mockData = { foo: 'bar' };
|
|
12
|
-
const { adapter, calls } = createMockAdapter([{ ok: true, data: mockData, status: 200 }]);
|
|
13
|
-
|
|
14
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
15
|
-
const result = await service.get('mystore', 'mykey');
|
|
16
|
-
|
|
17
|
-
expect(result.exists).toBe(true);
|
|
18
|
-
if (result.exists) {
|
|
19
|
-
expect(result.data).toEqual(mockData);
|
|
20
|
-
}
|
|
21
|
-
expect(calls).toHaveLength(1);
|
|
22
|
-
expect(calls[0].url).toBe(`${baseUrl}/kv/2025-03-17/mystore/mykey`);
|
|
23
|
-
expect(calls[0].options?.method).toBe('GET');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('should return not found when key does not exist', async () => {
|
|
27
|
-
const { adapter, calls } = createMockAdapter([{ ok: false, status: 404 }]);
|
|
28
|
-
|
|
29
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
30
|
-
const result = await service.get('mystore', 'missing');
|
|
31
|
-
|
|
32
|
-
expect(result.exists).toBe(false);
|
|
33
|
-
expect(calls).toHaveLength(1);
|
|
34
|
-
expect(calls[0].url).toBe(`${baseUrl}/kv/2025-03-17/mystore/missing`);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('should encode special characters in name and key', async () => {
|
|
38
|
-
const { adapter, calls } = createMockAdapter([{ ok: true, data: 'test' }]);
|
|
39
|
-
|
|
40
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
41
|
-
await service.get('my store', 'my/key');
|
|
42
|
-
|
|
43
|
-
expect(calls[0].url).toBe(`${baseUrl}/kv/2025-03-17/my%20store/my%2Fkey`);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('should throw ServiceException on error response', async () => {
|
|
47
|
-
const { adapter } = createMockAdapter([
|
|
48
|
-
{ ok: false, status: 500, body: { error: 'Internal Server Error' } },
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
52
|
-
|
|
53
|
-
await expect(service.get('mystore', 'mykey')).rejects.toThrow(ServiceException);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('should set timeout signal', async () => {
|
|
57
|
-
const { adapter, calls } = createMockAdapter([{ ok: true, data: 'test' }]);
|
|
58
|
-
|
|
59
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
60
|
-
await service.get('mystore', 'mykey');
|
|
61
|
-
|
|
62
|
-
expect(calls[0].options?.signal).toBeDefined();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('set', () => {
|
|
67
|
-
test('should set string value', async () => {
|
|
68
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
69
|
-
|
|
70
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
71
|
-
await service.set('mystore', 'mykey', 'myvalue');
|
|
72
|
-
|
|
73
|
-
expect(calls).toHaveLength(1);
|
|
74
|
-
expect(calls[0].url).toBe(`${baseUrl}/kv/2025-03-17/mystore/mykey`);
|
|
75
|
-
expect(calls[0].options?.method).toBe('PUT');
|
|
76
|
-
expect(calls[0].options?.body).toBe('myvalue');
|
|
77
|
-
expect(calls[0].options?.contentType).toBe('text/plain');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('should set object value', async () => {
|
|
81
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
82
|
-
|
|
83
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
84
|
-
const obj = { foo: 'bar', num: 42 };
|
|
85
|
-
await service.set('mystore', 'mykey', obj);
|
|
86
|
-
|
|
87
|
-
expect(calls[0].options?.body).toBe(JSON.stringify(obj));
|
|
88
|
-
expect(calls[0].options?.contentType).toBe('application/json');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('should set number value', async () => {
|
|
92
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
93
|
-
|
|
94
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
95
|
-
await service.set('mystore', 'mykey', 123);
|
|
96
|
-
|
|
97
|
-
expect(calls[0].options?.body).toBe('123');
|
|
98
|
-
expect(calls[0].options?.contentType).toBe('text/plain');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('should set boolean value', async () => {
|
|
102
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
103
|
-
|
|
104
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
105
|
-
await service.set('mystore', 'mykey', true);
|
|
106
|
-
|
|
107
|
-
expect(calls[0].options?.body).toBe('true');
|
|
108
|
-
expect(calls[0].options?.contentType).toBe('text/plain');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('should include ttl in url when provided', async () => {
|
|
112
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
113
|
-
|
|
114
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
115
|
-
await service.set('mystore', 'mykey', 'value', { ttl: 3600 });
|
|
116
|
-
|
|
117
|
-
expect(calls[0].url).toBe(`${baseUrl}/kv/2025-03-17/mystore/mykey/3600`);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test('should throw error when ttl is less than 60 seconds', async () => {
|
|
121
|
-
const { adapter } = createMockAdapter([{ ok: true }]);
|
|
122
|
-
|
|
123
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
124
|
-
|
|
125
|
-
await expect(service.set('mystore', 'mykey', 'value', { ttl: 30 })).rejects.toThrow(
|
|
126
|
-
'ttl for keyvalue set must be at least 60 seconds, got 30'
|
|
127
|
-
);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('should use custom contentType when provided', async () => {
|
|
131
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
132
|
-
|
|
133
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
134
|
-
await service.set('mystore', 'mykey', 'value', { contentType: 'text/html' });
|
|
135
|
-
|
|
136
|
-
expect(calls[0].options?.contentType).toBe('text/html');
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('should encode special characters in name and key', async () => {
|
|
140
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
141
|
-
|
|
142
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
143
|
-
await service.set('my store', 'my/key', 'value');
|
|
144
|
-
|
|
145
|
-
expect(calls[0].url).toBe(`${baseUrl}/kv/2025-03-17/my%20store/my%2Fkey`);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('should throw ServiceException on error response', async () => {
|
|
149
|
-
const { adapter } = createMockAdapter([
|
|
150
|
-
{ ok: false, status: 400, body: { error: 'Bad Request' } },
|
|
151
|
-
]);
|
|
152
|
-
|
|
153
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
154
|
-
|
|
155
|
-
await expect(service.set('mystore', 'mykey', 'value')).rejects.toThrow(ServiceException);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test('should set timeout signal', async () => {
|
|
159
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
160
|
-
|
|
161
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
162
|
-
await service.set('mystore', 'mykey', 'value');
|
|
163
|
-
|
|
164
|
-
expect(calls[0].options?.signal).toBeDefined();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('delete', () => {
|
|
169
|
-
test('should delete key', async () => {
|
|
170
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
171
|
-
|
|
172
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
173
|
-
await service.delete('mystore', 'mykey');
|
|
174
|
-
|
|
175
|
-
expect(calls).toHaveLength(1);
|
|
176
|
-
expect(calls[0].url).toBe(`${baseUrl}/kv/2025-03-17/mystore/mykey`);
|
|
177
|
-
expect(calls[0].options?.method).toBe('DELETE');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('should encode special characters in name and key', async () => {
|
|
181
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
182
|
-
|
|
183
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
184
|
-
await service.delete('my store', 'my/key');
|
|
185
|
-
|
|
186
|
-
expect(calls[0].url).toBe(`${baseUrl}/kv/2025-03-17/my%20store/my%2Fkey`);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('should throw ServiceException on error response', async () => {
|
|
190
|
-
const { adapter } = createMockAdapter([
|
|
191
|
-
{ ok: false, status: 403, body: { error: 'Forbidden' } },
|
|
192
|
-
]);
|
|
193
|
-
|
|
194
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
195
|
-
|
|
196
|
-
await expect(service.delete('mystore', 'mykey')).rejects.toThrow(ServiceException);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('should set timeout signal', async () => {
|
|
200
|
-
const { adapter, calls } = createMockAdapter([{ ok: true }]);
|
|
201
|
-
|
|
202
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
203
|
-
await service.delete('mystore', 'mykey');
|
|
204
|
-
|
|
205
|
-
expect(calls[0].options?.signal).toBeDefined();
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
describe('onBefore hook', () => {
|
|
210
|
-
test('should call onBefore for get operation', async () => {
|
|
211
|
-
const onBeforeCalls: { url: string; method: string }[] = [];
|
|
212
|
-
const { adapter, beforeCalls } = createMockAdapter([{ ok: true, data: { foo: 'bar' } }], {
|
|
213
|
-
onBefore: async (url, options, invoke) => {
|
|
214
|
-
onBeforeCalls.push({ url, method: options.method });
|
|
215
|
-
await invoke();
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
220
|
-
await service.get('mystore', 'mykey');
|
|
221
|
-
|
|
222
|
-
expect(beforeCalls).toHaveLength(1);
|
|
223
|
-
expect(onBeforeCalls).toHaveLength(1);
|
|
224
|
-
expect(onBeforeCalls[0].method).toBe('GET');
|
|
225
|
-
expect(onBeforeCalls[0].url).toBe(`${baseUrl}/kv/2025-03-17/mystore/mykey`);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test('should call onBefore for set operation', async () => {
|
|
229
|
-
const onBeforeCalls: { url: string; method: string }[] = [];
|
|
230
|
-
const { adapter, beforeCalls } = createMockAdapter([{ ok: true }], {
|
|
231
|
-
onBefore: async (url, options, invoke) => {
|
|
232
|
-
onBeforeCalls.push({ url, method: options.method });
|
|
233
|
-
await invoke();
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
238
|
-
await service.set('mystore', 'mykey', 'value');
|
|
239
|
-
|
|
240
|
-
expect(beforeCalls).toHaveLength(1);
|
|
241
|
-
expect(onBeforeCalls[0].method).toBe('PUT');
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test('should call onBefore for delete operation', async () => {
|
|
245
|
-
const onBeforeCalls: { url: string; method: string }[] = [];
|
|
246
|
-
const { adapter, beforeCalls } = createMockAdapter([{ ok: true }], {
|
|
247
|
-
onBefore: async (url, options, invoke) => {
|
|
248
|
-
onBeforeCalls.push({ url, method: options.method });
|
|
249
|
-
await invoke();
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
254
|
-
await service.delete('mystore', 'mykey');
|
|
255
|
-
|
|
256
|
-
expect(beforeCalls).toHaveLength(1);
|
|
257
|
-
expect(onBeforeCalls[0].method).toBe('DELETE');
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test('should allow onBefore to modify request', async () => {
|
|
261
|
-
let modifiedUrl = '';
|
|
262
|
-
const { adapter } = createMockAdapter([{ ok: true, data: 'test' }], {
|
|
263
|
-
onBefore: async (url, options, invoke) => {
|
|
264
|
-
modifiedUrl = url + '?modified=true';
|
|
265
|
-
await invoke();
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
270
|
-
await service.get('mystore', 'mykey');
|
|
271
|
-
|
|
272
|
-
expect(modifiedUrl).toContain('modified=true');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test('should pass mutated headers from onBefore to server', async () => {
|
|
276
|
-
const { adapter, calls } = createMockAdapter([{ ok: true, data: { foo: 'bar' } }], {
|
|
277
|
-
onBefore: async (url, options, invoke) => {
|
|
278
|
-
options.headers = {
|
|
279
|
-
...options.headers,
|
|
280
|
-
'X-Custom-Header': 'test-value',
|
|
281
|
-
Authorization: 'Bearer token123',
|
|
282
|
-
};
|
|
283
|
-
await invoke();
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
288
|
-
await service.get('mystore', 'mykey');
|
|
289
|
-
|
|
290
|
-
expect(calls).toHaveLength(1);
|
|
291
|
-
expect(calls[0].options.headers).toBeDefined();
|
|
292
|
-
expect(calls[0].options.headers?.['X-Custom-Header']).toBe('test-value');
|
|
293
|
-
expect(calls[0].options.headers?.['Authorization']).toBe('Bearer token123');
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
describe('onAfter hook', () => {
|
|
298
|
-
test('should call onAfter on successful get', async () => {
|
|
299
|
-
const afterCalls: { status: number; hasError: boolean }[] = [];
|
|
300
|
-
const { adapter } = createMockAdapter([{ ok: true, data: { foo: 'bar' } }], {
|
|
301
|
-
onAfter: async (response, error) => {
|
|
302
|
-
afterCalls.push({ status: response.status, hasError: !!error });
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
307
|
-
await service.get('mystore', 'mykey');
|
|
308
|
-
|
|
309
|
-
expect(afterCalls).toHaveLength(1);
|
|
310
|
-
expect(afterCalls[0].status).toBe(200);
|
|
311
|
-
expect(afterCalls[0].hasError).toBe(false);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
test('should call onAfter on successful set', async () => {
|
|
315
|
-
const afterCalls: Response[] = [];
|
|
316
|
-
const { adapter } = createMockAdapter([{ ok: true }], {
|
|
317
|
-
onAfter: async (response) => {
|
|
318
|
-
afterCalls.push(response);
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
323
|
-
await service.set('mystore', 'mykey', 'value');
|
|
324
|
-
|
|
325
|
-
expect(afterCalls).toHaveLength(1);
|
|
326
|
-
expect(afterCalls[0].status).toBe(200);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
test('should call onAfter on error response', async () => {
|
|
330
|
-
const afterCalls: { status: number; hasError: boolean }[] = [];
|
|
331
|
-
const { adapter } = createMockAdapter([{ ok: false, status: 500 }], {
|
|
332
|
-
onAfter: async (response, error) => {
|
|
333
|
-
afterCalls.push({ status: response.status, hasError: !!error });
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
338
|
-
await service.get('mystore', 'mykey').catch(() => ({ exists: false }));
|
|
339
|
-
|
|
340
|
-
expect(afterCalls).toHaveLength(1);
|
|
341
|
-
expect(afterCalls[0].status).toBe(500);
|
|
342
|
-
expect(afterCalls[0].hasError).toBe(true);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
test('should receive error in onAfter when request fails', async () => {
|
|
346
|
-
let receivedError: Error | undefined;
|
|
347
|
-
const { adapter } = createMockAdapter(
|
|
348
|
-
[{ ok: false, status: 403, statusText: 'Forbidden' }],
|
|
349
|
-
{
|
|
350
|
-
onAfter: async (response, error) => {
|
|
351
|
-
receivedError = error;
|
|
352
|
-
},
|
|
353
|
-
}
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
357
|
-
await service.delete('mystore', 'mykey').catch(() => {});
|
|
358
|
-
|
|
359
|
-
expect(receivedError).toBeDefined();
|
|
360
|
-
expect(receivedError?.message).toBe('Forbidden');
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
describe('onBefore and onAfter combined', () => {
|
|
365
|
-
test('should call both hooks in correct order', async () => {
|
|
366
|
-
const executionOrder: string[] = [];
|
|
367
|
-
const { adapter } = createMockAdapter([{ ok: true, data: 'test' }], {
|
|
368
|
-
onBefore: async (url, options, invoke) => {
|
|
369
|
-
executionOrder.push('before-start');
|
|
370
|
-
await invoke();
|
|
371
|
-
executionOrder.push('before-end');
|
|
372
|
-
},
|
|
373
|
-
onAfter: async () => {
|
|
374
|
-
executionOrder.push('after');
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
379
|
-
await service.get('mystore', 'mykey');
|
|
380
|
-
|
|
381
|
-
expect(executionOrder).toEqual(['before-start', 'after', 'before-end']);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
test('should handle telemetry metadata in hooks', async () => {
|
|
385
|
-
let capturedTelemetry: { name: string; attributes?: Record<string, string> } | undefined;
|
|
386
|
-
const { adapter } = createMockAdapter([{ ok: true }], {
|
|
387
|
-
onBefore: async (url, options, invoke) => {
|
|
388
|
-
capturedTelemetry = options.telemetry;
|
|
389
|
-
await invoke();
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
const service = new KeyValueStorageService(baseUrl, adapter);
|
|
394
|
-
await service.set('mystore', 'mykey', 'value', { ttl: 300 });
|
|
395
|
-
|
|
396
|
-
expect(capturedTelemetry).toBeDefined();
|
|
397
|
-
expect(capturedTelemetry?.name).toBe('agentuity.keyvalue.set');
|
|
398
|
-
expect(capturedTelemetry?.attributes?.name).toBe('mystore');
|
|
399
|
-
expect(capturedTelemetry?.attributes?.key).toBe('mykey');
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import type { FetchAdapter, FetchRequest, FetchResponse } from '../adapter';
|
|
2
|
-
|
|
3
|
-
export interface MockAdapterCall {
|
|
4
|
-
url: string;
|
|
5
|
-
options: FetchRequest;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface MockAdapterResponse<T = unknown> {
|
|
9
|
-
ok: boolean;
|
|
10
|
-
data?: T;
|
|
11
|
-
status?: number;
|
|
12
|
-
statusText?: string;
|
|
13
|
-
headers?: Record<string, string>;
|
|
14
|
-
body?: unknown;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface MockAdapterConfig {
|
|
18
|
-
onBefore?: (url: string, options: FetchRequest, invoke: () => Promise<void>) => Promise<void>;
|
|
19
|
-
onAfter?: (response: Response, error?: Error) => Promise<void>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function createMockAdapter<T = unknown>(
|
|
23
|
-
responses: MockAdapterResponse<T>[] = [],
|
|
24
|
-
config?: MockAdapterConfig
|
|
25
|
-
): {
|
|
26
|
-
adapter: FetchAdapter;
|
|
27
|
-
calls: MockAdapterCall[];
|
|
28
|
-
beforeCalls: MockAdapterCall[];
|
|
29
|
-
afterCalls: Response[];
|
|
30
|
-
} {
|
|
31
|
-
const calls: MockAdapterCall[] = [];
|
|
32
|
-
const beforeCalls: MockAdapterCall[] = [];
|
|
33
|
-
const afterCalls: Response[] = [];
|
|
34
|
-
let callIndex = 0;
|
|
35
|
-
|
|
36
|
-
async function drainBody(body: unknown) {
|
|
37
|
-
if (body && typeof (body as ReadableStream<Uint8Array>).getReader === 'function') {
|
|
38
|
-
const reader = (body as ReadableStream<Uint8Array>).getReader();
|
|
39
|
-
while (true) {
|
|
40
|
-
const { done } = await reader.read();
|
|
41
|
-
if (done) break;
|
|
42
|
-
}
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (body && typeof (body as AsyncIterable<unknown>)[Symbol.asyncIterator] === 'function') {
|
|
46
|
-
for await (const _chunk of body as AsyncIterable<unknown>) {
|
|
47
|
-
void _chunk;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const adapter: FetchAdapter = {
|
|
53
|
-
invoke: async <TData>(url: string, options: FetchRequest) => {
|
|
54
|
-
const executeInvoke = async () => {
|
|
55
|
-
calls.push({ url, options });
|
|
56
|
-
|
|
57
|
-
if (options.method === 'PUT' && options.body) {
|
|
58
|
-
await drainBody(options.body);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const mockResponse = responses[callIndex++] || { ok: true, data: undefined as TData };
|
|
62
|
-
|
|
63
|
-
const response = new Response(
|
|
64
|
-
mockResponse.body !== undefined
|
|
65
|
-
? JSON.stringify(mockResponse.body)
|
|
66
|
-
: mockResponse.data !== undefined
|
|
67
|
-
? JSON.stringify(mockResponse.data)
|
|
68
|
-
: null,
|
|
69
|
-
{
|
|
70
|
-
status: mockResponse.status || (mockResponse.ok ? 200 : 500),
|
|
71
|
-
statusText: mockResponse.statusText || (mockResponse.ok ? 'OK' : 'Error'),
|
|
72
|
-
headers: mockResponse.headers || {},
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
if (mockResponse.ok) {
|
|
77
|
-
if (config?.onAfter) {
|
|
78
|
-
await config.onAfter(response);
|
|
79
|
-
}
|
|
80
|
-
afterCalls.push(response);
|
|
81
|
-
return {
|
|
82
|
-
ok: true as const,
|
|
83
|
-
data: mockResponse.data as TData,
|
|
84
|
-
response,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const error = new Error(mockResponse.statusText || 'Error');
|
|
89
|
-
if (config?.onAfter) {
|
|
90
|
-
await config.onAfter(response, error);
|
|
91
|
-
}
|
|
92
|
-
afterCalls.push(response);
|
|
93
|
-
return {
|
|
94
|
-
ok: false as const,
|
|
95
|
-
data: undefined as never,
|
|
96
|
-
response,
|
|
97
|
-
};
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
if (config?.onBefore) {
|
|
101
|
-
beforeCalls.push({ url, options });
|
|
102
|
-
let result: FetchResponse<TData> | undefined;
|
|
103
|
-
await config.onBefore(url, options, async () => {
|
|
104
|
-
result = await executeInvoke();
|
|
105
|
-
});
|
|
106
|
-
return result!;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return executeInvoke();
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
return { adapter, calls, beforeCalls, afterCalls };
|
|
114
|
-
}
|