@agentuity/core 0.0.69 → 0.0.71
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,819 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import { VectorStorageService } from '../vector';
|
|
3
|
-
import type { VectorUpsertParams } from '../vector';
|
|
4
|
-
import { createMockAdapter } from './mock-adapter';
|
|
5
|
-
|
|
6
|
-
describe('VectorStorageService', () => {
|
|
7
|
-
const baseUrl = 'https://api.example.com';
|
|
8
|
-
|
|
9
|
-
// ===================================================================
|
|
10
|
-
// 1. INPUT VALIDATION TESTS
|
|
11
|
-
// ===================================================================
|
|
12
|
-
|
|
13
|
-
describe('Input Validation - Upsert', () => {
|
|
14
|
-
test('rejects empty storage name', async () => {
|
|
15
|
-
const { adapter } = createMockAdapter([]);
|
|
16
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
17
|
-
|
|
18
|
-
await expect(service.upsert('', { key: 'k1', document: 'text' })).rejects.toThrow(
|
|
19
|
-
'Vector storage name is required and must be a non-empty string'
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('rejects whitespace-only storage name', async () => {
|
|
24
|
-
const { adapter } = createMockAdapter([]);
|
|
25
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
26
|
-
|
|
27
|
-
await expect(service.upsert(' ', { key: 'k1', document: 'text' })).rejects.toThrow(
|
|
28
|
-
'Vector storage name is required and must be a non-empty string'
|
|
29
|
-
);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('rejects when no documents provided', async () => {
|
|
33
|
-
const { adapter } = createMockAdapter([]);
|
|
34
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
35
|
-
|
|
36
|
-
await expect(service.upsert('my-vectors')).rejects.toThrow(
|
|
37
|
-
'Vector storage requires at least one document for this method'
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('rejects document without key', async () => {
|
|
42
|
-
const { adapter } = createMockAdapter([]);
|
|
43
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
44
|
-
|
|
45
|
-
await expect(service.upsert('my-vectors', { key: '', document: 'text' })).rejects.toThrow(
|
|
46
|
-
'Vector storage requires each document to have a non-empty key'
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('rejects document without embeddings or text', async () => {
|
|
51
|
-
const { adapter } = createMockAdapter([]);
|
|
52
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
53
|
-
|
|
54
|
-
await expect(
|
|
55
|
-
service.upsert('my-vectors', { key: 'k1' } as unknown as VectorUpsertParams)
|
|
56
|
-
).rejects.toThrow(
|
|
57
|
-
'Vector storage requires each document to have either embeddings or document property'
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('rejects empty embeddings array', async () => {
|
|
62
|
-
const { adapter } = createMockAdapter([]);
|
|
63
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
64
|
-
|
|
65
|
-
await expect(service.upsert('my-vectors', { key: 'k1', embeddings: [] })).rejects.toThrow(
|
|
66
|
-
'Vector storage requires each embeddings property to have a non-empty array of numbers'
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('rejects empty document text', async () => {
|
|
71
|
-
const { adapter } = createMockAdapter([]);
|
|
72
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
73
|
-
|
|
74
|
-
await expect(service.upsert('my-vectors', { key: 'k1', document: ' ' })).rejects.toThrow(
|
|
75
|
-
'Vector storage requires each document property to have a non-empty string value'
|
|
76
|
-
);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('Input Validation - Get', () => {
|
|
81
|
-
test('rejects empty storage name', async () => {
|
|
82
|
-
const { adapter } = createMockAdapter([]);
|
|
83
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
84
|
-
|
|
85
|
-
await expect(service.get('', 'key1')).rejects.toThrow(
|
|
86
|
-
'Vector storage name is required and must be a non-empty string'
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('rejects empty key', async () => {
|
|
91
|
-
const { adapter } = createMockAdapter([]);
|
|
92
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
93
|
-
|
|
94
|
-
await expect(service.get('my-vectors', '')).rejects.toThrow(
|
|
95
|
-
'Vector storage key is required and must be a non-empty string'
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('Input Validation - GetMany', () => {
|
|
101
|
-
test('rejects empty storage name', async () => {
|
|
102
|
-
const { adapter } = createMockAdapter([]);
|
|
103
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
104
|
-
|
|
105
|
-
await expect(service.getMany('', 'key1')).rejects.toThrow(
|
|
106
|
-
'Vector storage name is required and must be a non-empty string'
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test('returns empty map when no keys provided', async () => {
|
|
111
|
-
const { adapter, calls } = createMockAdapter([]);
|
|
112
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
113
|
-
|
|
114
|
-
const result = await service.getMany('my-vectors');
|
|
115
|
-
expect(result.size).toBe(0);
|
|
116
|
-
expect(calls).toHaveLength(0);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('rejects empty key in array', async () => {
|
|
120
|
-
const { adapter } = createMockAdapter([]);
|
|
121
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
122
|
-
|
|
123
|
-
await expect(service.getMany('my-vectors', 'key1', '', 'key3')).rejects.toThrow(
|
|
124
|
-
'Vector storage requires all keys to be non-empty strings'
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe('Input Validation - Search', () => {
|
|
130
|
-
test('rejects empty storage name', async () => {
|
|
131
|
-
const { adapter } = createMockAdapter([]);
|
|
132
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
133
|
-
|
|
134
|
-
await expect(service.search('', { query: 'test query' })).rejects.toThrow(
|
|
135
|
-
'Vector storage name is required and must be a non-empty string'
|
|
136
|
-
);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('rejects empty query', async () => {
|
|
140
|
-
const { adapter } = createMockAdapter([]);
|
|
141
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
142
|
-
|
|
143
|
-
await expect(service.search('my-vectors', { query: '' })).rejects.toThrow(
|
|
144
|
-
'Vector storage query property is required and must be a non-empty string'
|
|
145
|
-
);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('rejects negative limit', async () => {
|
|
149
|
-
const { adapter } = createMockAdapter([]);
|
|
150
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
151
|
-
|
|
152
|
-
await expect(service.search('my-vectors', { query: 'test', limit: -5 })).rejects.toThrow(
|
|
153
|
-
'Vector storage limit property must be positive number'
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('rejects zero limit', async () => {
|
|
158
|
-
const { adapter } = createMockAdapter([]);
|
|
159
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
160
|
-
|
|
161
|
-
await expect(service.search('my-vectors', { query: 'test', limit: 0 })).rejects.toThrow(
|
|
162
|
-
'Vector storage limit property must be positive number'
|
|
163
|
-
);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('rejects similarity < 0', async () => {
|
|
167
|
-
const { adapter } = createMockAdapter([]);
|
|
168
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
169
|
-
|
|
170
|
-
await expect(
|
|
171
|
-
service.search('my-vectors', { query: 'test', similarity: -0.1 })
|
|
172
|
-
).rejects.toThrow(
|
|
173
|
-
'Vector storage similarity property must be a number between 0.0 and 1.0'
|
|
174
|
-
);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test('rejects similarity > 1', async () => {
|
|
178
|
-
const { adapter } = createMockAdapter([]);
|
|
179
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
180
|
-
|
|
181
|
-
await expect(
|
|
182
|
-
service.search('my-vectors', { query: 'test', similarity: 1.5 })
|
|
183
|
-
).rejects.toThrow(
|
|
184
|
-
'Vector storage similarity property must be a number between 0.0 and 1.0'
|
|
185
|
-
);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test('rejects invalid metadata', async () => {
|
|
189
|
-
const { adapter } = createMockAdapter([]);
|
|
190
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
191
|
-
|
|
192
|
-
await expect(
|
|
193
|
-
service.search('my-vectors', {
|
|
194
|
-
query: 'test',
|
|
195
|
-
metadata: null as unknown as Record<string, unknown>,
|
|
196
|
-
})
|
|
197
|
-
).rejects.toThrow('Vector storage metadata property must be a valid object');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test('accepts boundary similarity values', async () => {
|
|
201
|
-
const { adapter, calls } = createMockAdapter([
|
|
202
|
-
{ ok: true, data: { success: true, data: [] } },
|
|
203
|
-
{ ok: true, data: { success: true, data: [] } },
|
|
204
|
-
]);
|
|
205
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
206
|
-
|
|
207
|
-
await service.search('my-vectors', { query: 'test', similarity: 0.0 });
|
|
208
|
-
expect(calls).toHaveLength(1);
|
|
209
|
-
|
|
210
|
-
await service.search('my-vectors', { query: 'test', similarity: 1.0 });
|
|
211
|
-
expect(calls).toHaveLength(2);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe('Input Validation - Delete', () => {
|
|
216
|
-
test('rejects empty storage name', async () => {
|
|
217
|
-
const { adapter } = createMockAdapter([]);
|
|
218
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
219
|
-
|
|
220
|
-
await expect(service.delete('', 'key1')).rejects.toThrow(
|
|
221
|
-
'Vector storage name is required and must be a non-empty string'
|
|
222
|
-
);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test('rejects empty key in array', async () => {
|
|
226
|
-
const { adapter } = createMockAdapter([]);
|
|
227
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
228
|
-
|
|
229
|
-
await expect(service.delete('my-vectors', 'key1', '', 'key3')).rejects.toThrow(
|
|
230
|
-
'Vector storage requires all keys to be non-empty strings'
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test('returns 0 when no keys provided', async () => {
|
|
235
|
-
const { adapter, calls } = createMockAdapter([]);
|
|
236
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
237
|
-
|
|
238
|
-
const result = await service.delete('my-vectors');
|
|
239
|
-
expect(result).toBe(0);
|
|
240
|
-
expect(calls).toHaveLength(0);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe('Input Validation - Exists', () => {
|
|
245
|
-
test('rejects empty storage name', async () => {
|
|
246
|
-
const { adapter } = createMockAdapter([]);
|
|
247
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
248
|
-
|
|
249
|
-
await expect(service.exists('')).rejects.toThrow(
|
|
250
|
-
'Vector storage name is required and must be a non-empty string'
|
|
251
|
-
);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// ===================================================================
|
|
256
|
-
// 2. API INTEGRATION TESTS
|
|
257
|
-
// ===================================================================
|
|
258
|
-
|
|
259
|
-
describe('Upsert Operation', () => {
|
|
260
|
-
test('successfully upserts with document text', async () => {
|
|
261
|
-
const { adapter, calls } = createMockAdapter([
|
|
262
|
-
{
|
|
263
|
-
ok: true,
|
|
264
|
-
data: { success: true, data: [{ id: 'vec-1' }, { id: 'vec-2' }] },
|
|
265
|
-
},
|
|
266
|
-
]);
|
|
267
|
-
|
|
268
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
269
|
-
|
|
270
|
-
const result = await service.upsert(
|
|
271
|
-
'my-vectors',
|
|
272
|
-
{ key: 'k1', document: 'First document' },
|
|
273
|
-
{ key: 'k2', document: 'Second document' }
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
expect(result).toEqual([
|
|
277
|
-
{ key: 'k1', id: 'vec-1' },
|
|
278
|
-
{ key: 'k2', id: 'vec-2' },
|
|
279
|
-
]);
|
|
280
|
-
expect(calls).toHaveLength(1);
|
|
281
|
-
expect(calls[0].url).toBe(`${baseUrl}/vector/2025-03-17/my-vectors`);
|
|
282
|
-
expect(calls[0].options).toMatchObject({
|
|
283
|
-
method: 'PUT',
|
|
284
|
-
contentType: 'application/json',
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test('successfully upserts with embeddings', async () => {
|
|
289
|
-
const { adapter } = createMockAdapter([
|
|
290
|
-
{ ok: true, data: { success: true, data: [{ id: 'vec-1' }] } },
|
|
291
|
-
]);
|
|
292
|
-
|
|
293
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
294
|
-
|
|
295
|
-
const embeddings = [0.1, 0.2, 0.3, 0.4];
|
|
296
|
-
const result = await service.upsert('my-vectors', {
|
|
297
|
-
key: 'k1',
|
|
298
|
-
embeddings,
|
|
299
|
-
metadata: { source: 'test' },
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
expect(result).toEqual([{ key: 'k1', id: 'vec-1' }]);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test('includes telemetry in upsert request', async () => {
|
|
306
|
-
const { adapter, calls } = createMockAdapter([
|
|
307
|
-
{ ok: true, data: { success: true, data: [{ id: 'vec-1' }] } },
|
|
308
|
-
]);
|
|
309
|
-
|
|
310
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
311
|
-
await service.upsert('my-vectors', { key: 'k1', document: 'test' });
|
|
312
|
-
|
|
313
|
-
expect(calls).toHaveLength(1);
|
|
314
|
-
expect(calls[0].options).toMatchObject({
|
|
315
|
-
telemetry: {
|
|
316
|
-
name: 'agentuity.vector.upsert',
|
|
317
|
-
attributes: {
|
|
318
|
-
name: 'my-vectors',
|
|
319
|
-
count: '1',
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
describe('Get Operation', () => {
|
|
327
|
-
test('returns vector when found', async () => {
|
|
328
|
-
const { adapter } = createMockAdapter([
|
|
329
|
-
{
|
|
330
|
-
ok: true,
|
|
331
|
-
data: {
|
|
332
|
-
success: true,
|
|
333
|
-
data: {
|
|
334
|
-
id: 'vec-1',
|
|
335
|
-
key: 'k1',
|
|
336
|
-
similarity: 1.0,
|
|
337
|
-
document: 'Test document',
|
|
338
|
-
metadata: { source: 'test' },
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
]);
|
|
343
|
-
|
|
344
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
345
|
-
const result = await service.get('my-vectors', 'k1');
|
|
346
|
-
|
|
347
|
-
expect(result.exists).toBe(true);
|
|
348
|
-
if (result.exists) {
|
|
349
|
-
expect(result.data.id).toBe('vec-1');
|
|
350
|
-
expect(result.data.key).toBe('k1');
|
|
351
|
-
expect(result.data.document).toBe('Test document');
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
test('returns exists: false when vector not found', async () => {
|
|
356
|
-
const { adapter } = createMockAdapter([{ ok: false, status: 404 }]);
|
|
357
|
-
|
|
358
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
359
|
-
const result = await service.get('my-vectors', 'non-existent');
|
|
360
|
-
|
|
361
|
-
expect(result.exists).toBe(false);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
test('constructs correct URL with encoding', async () => {
|
|
365
|
-
const { adapter, calls } = createMockAdapter([
|
|
366
|
-
{
|
|
367
|
-
ok: true,
|
|
368
|
-
data: { success: true, data: { id: 'vec-1', key: 'k1', similarity: 1.0 } },
|
|
369
|
-
},
|
|
370
|
-
]);
|
|
371
|
-
|
|
372
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
373
|
-
await service.get('my vectors', 'key/with/slashes');
|
|
374
|
-
|
|
375
|
-
expect(calls[0].url).toContain('my%20vectors');
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
describe('GetMany Operation', () => {
|
|
380
|
-
test('returns map of found vectors', async () => {
|
|
381
|
-
const { adapter } = createMockAdapter([
|
|
382
|
-
{
|
|
383
|
-
ok: true,
|
|
384
|
-
data: {
|
|
385
|
-
success: true,
|
|
386
|
-
data: { id: 'vec-1', key: 'key1', similarity: 1.0, document: 'Doc 1' },
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
ok: true,
|
|
391
|
-
data: {
|
|
392
|
-
success: true,
|
|
393
|
-
data: { id: 'vec-2', key: 'key2', similarity: 1.0, document: 'Doc 2' },
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
{ ok: false, status: 404 },
|
|
397
|
-
]);
|
|
398
|
-
|
|
399
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
400
|
-
const result = await service.getMany('my-vectors', 'key1', 'key2', 'key3');
|
|
401
|
-
|
|
402
|
-
expect(result.size).toBe(2);
|
|
403
|
-
expect(result.has('key1')).toBe(true);
|
|
404
|
-
expect(result.has('key2')).toBe(true);
|
|
405
|
-
expect(result.has('key3')).toBe(false);
|
|
406
|
-
expect(result.get('key1')?.id).toBe('vec-1');
|
|
407
|
-
expect(result.get('key2')?.id).toBe('vec-2');
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test('returns empty map when no keys provided', async () => {
|
|
411
|
-
const { adapter, calls } = createMockAdapter([
|
|
412
|
-
{ ok: true, data: { success: true, data: [] } },
|
|
413
|
-
]);
|
|
414
|
-
|
|
415
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
416
|
-
const result = await service.getMany('my-vectors');
|
|
417
|
-
|
|
418
|
-
expect(result.size).toBe(0);
|
|
419
|
-
expect(calls).toHaveLength(0);
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
describe('Search Operation', () => {
|
|
424
|
-
test('returns matching vectors', async () => {
|
|
425
|
-
const { adapter } = createMockAdapter([
|
|
426
|
-
{
|
|
427
|
-
ok: true,
|
|
428
|
-
data: {
|
|
429
|
-
success: true,
|
|
430
|
-
data: [
|
|
431
|
-
{ id: 'vec-1', key: 'k1', similarity: 0.95, metadata: { category: 'A' } },
|
|
432
|
-
{ id: 'vec-2', key: 'k2', similarity: 0.87, metadata: { category: 'B' } },
|
|
433
|
-
],
|
|
434
|
-
},
|
|
435
|
-
},
|
|
436
|
-
]);
|
|
437
|
-
|
|
438
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
439
|
-
const result = await service.search('my-vectors', {
|
|
440
|
-
query: 'test query',
|
|
441
|
-
limit: 10,
|
|
442
|
-
similarity: 0.8,
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
expect(result).toHaveLength(2);
|
|
446
|
-
expect(result[0].similarity).toBe(0.95);
|
|
447
|
-
expect(result[1].similarity).toBe(0.87);
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
test('returns empty array when storage not found', async () => {
|
|
451
|
-
const { adapter } = createMockAdapter([{ ok: false, status: 404 }]);
|
|
452
|
-
|
|
453
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
454
|
-
const result = await service.search('non-existent', { query: 'test' });
|
|
455
|
-
|
|
456
|
-
expect(result).toEqual([]);
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
test('throws on non-404 errors', async () => {
|
|
460
|
-
const { adapter } = createMockAdapter([
|
|
461
|
-
{ ok: false, status: 400, body: { message: 'Bad Request' } },
|
|
462
|
-
]);
|
|
463
|
-
|
|
464
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
465
|
-
|
|
466
|
-
await expect(service.search('my-vectors', { query: 'test' })).rejects.toThrow();
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test('includes metadata filter in request', async () => {
|
|
470
|
-
const { adapter, calls } = createMockAdapter([
|
|
471
|
-
{ ok: true, data: { success: true, data: [] } },
|
|
472
|
-
]);
|
|
473
|
-
|
|
474
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
475
|
-
await service.search('my-vectors', {
|
|
476
|
-
query: 'test',
|
|
477
|
-
metadata: { category: 'furniture', inStock: true },
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
const body = JSON.parse(calls[0].options.body as string);
|
|
481
|
-
expect(body.metadata).toEqual({ category: 'furniture', inStock: true });
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
test('includes parameters in telemetry', async () => {
|
|
485
|
-
const { adapter, calls } = createMockAdapter([
|
|
486
|
-
{ ok: true, data: { success: true, data: [] } },
|
|
487
|
-
]);
|
|
488
|
-
|
|
489
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
490
|
-
await service.search('my-vectors', {
|
|
491
|
-
query: 'search text',
|
|
492
|
-
limit: 5,
|
|
493
|
-
similarity: 0.75,
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
expect(calls[0].options).toMatchObject({
|
|
497
|
-
telemetry: {
|
|
498
|
-
name: 'agentuity.vector.search',
|
|
499
|
-
attributes: {
|
|
500
|
-
name: 'my-vectors',
|
|
501
|
-
query: 'search text',
|
|
502
|
-
limit: '5',
|
|
503
|
-
similarity: '0.75',
|
|
504
|
-
},
|
|
505
|
-
},
|
|
506
|
-
});
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
describe('Delete Operation', () => {
|
|
511
|
-
test('deletes single key successfully', async () => {
|
|
512
|
-
const { adapter, calls } = createMockAdapter([
|
|
513
|
-
{ ok: true, data: { success: true, data: 1 } },
|
|
514
|
-
]);
|
|
515
|
-
|
|
516
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
517
|
-
const result = await service.delete('my-vectors', 'k1');
|
|
518
|
-
|
|
519
|
-
expect(result).toBe(1);
|
|
520
|
-
expect(calls).toHaveLength(1);
|
|
521
|
-
expect(calls[0].url).toBe(`${baseUrl}/vector/2025-03-17/my-vectors/k1`);
|
|
522
|
-
expect(calls[0].options).toMatchObject({ method: 'DELETE' });
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
test('deletes multiple keys successfully', async () => {
|
|
526
|
-
const { adapter, calls } = createMockAdapter([
|
|
527
|
-
{ ok: true, data: { success: true, data: 3 } },
|
|
528
|
-
]);
|
|
529
|
-
|
|
530
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
531
|
-
const result = await service.delete('my-vectors', 'k1', 'k2', 'k3');
|
|
532
|
-
|
|
533
|
-
expect(result).toBe(3);
|
|
534
|
-
|
|
535
|
-
expect(calls[0].url).toBe(`${baseUrl}/vector/2025-03-17/my-vectors`);
|
|
536
|
-
const body = JSON.parse(calls[0].options.body as string);
|
|
537
|
-
expect(body.keys).toEqual(['k1', 'k2', 'k3']);
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
test('returns 0 without API call when no keys provided', async () => {
|
|
541
|
-
const { adapter, calls } = createMockAdapter([
|
|
542
|
-
{ ok: true, data: { success: true, data: 0 } },
|
|
543
|
-
]);
|
|
544
|
-
|
|
545
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
546
|
-
const result = await service.delete('my-vectors');
|
|
547
|
-
|
|
548
|
-
expect(result).toBe(0);
|
|
549
|
-
expect(calls).toHaveLength(0);
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
test('returns 0 when server reports nothing deleted', async () => {
|
|
553
|
-
const { adapter, calls } = createMockAdapter([
|
|
554
|
-
{ ok: true, data: { success: true, data: 0 } },
|
|
555
|
-
]);
|
|
556
|
-
|
|
557
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
558
|
-
const result = await service.delete('my-vectors', 'non-existent-key');
|
|
559
|
-
|
|
560
|
-
expect(result).toBe(0);
|
|
561
|
-
expect(calls.length).toBeGreaterThan(0);
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
test('throws error when bulk delete fails', async () => {
|
|
565
|
-
const { adapter } = createMockAdapter([
|
|
566
|
-
{ ok: true, data: { success: false, message: 'Bulk delete failed' } },
|
|
567
|
-
]);
|
|
568
|
-
|
|
569
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
570
|
-
|
|
571
|
-
await expect(service.delete('my-vectors', 'k1', 'k2')).rejects.toThrow(
|
|
572
|
-
'Bulk delete failed'
|
|
573
|
-
);
|
|
574
|
-
});
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
describe('Exists Operation', () => {
|
|
578
|
-
test('returns true when storage exists', async () => {
|
|
579
|
-
const { adapter } = createMockAdapter([{ ok: true, data: { success: true, data: [] } }]);
|
|
580
|
-
|
|
581
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
582
|
-
const result = await service.exists('my-vectors');
|
|
583
|
-
|
|
584
|
-
expect(result).toBe(true);
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
test('returns false when storage does not exist', async () => {
|
|
588
|
-
const { adapter } = createMockAdapter([], {
|
|
589
|
-
onBefore: async () => {
|
|
590
|
-
throw new Error('Not Found 404');
|
|
591
|
-
},
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
595
|
-
const result = await service.exists('non-existent');
|
|
596
|
-
|
|
597
|
-
expect(result).toBe(false);
|
|
598
|
-
});
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
// ===================================================================
|
|
602
|
-
// 3. ERROR HANDLING TESTS
|
|
603
|
-
// ===================================================================
|
|
604
|
-
|
|
605
|
-
describe('Error Handling', () => {
|
|
606
|
-
test('throws error on API failure', async () => {
|
|
607
|
-
const { adapter } = createMockAdapter([
|
|
608
|
-
{ ok: false, status: 500, body: { message: 'Internal server error' } },
|
|
609
|
-
]);
|
|
610
|
-
|
|
611
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
612
|
-
|
|
613
|
-
await expect(
|
|
614
|
-
service.upsert('my-vectors', { key: 'k1', document: 'test' })
|
|
615
|
-
).rejects.toThrow();
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
test('throws error when response has success:false', async () => {
|
|
619
|
-
const { adapter } = createMockAdapter([
|
|
620
|
-
{ ok: true, data: { success: false, message: 'Vector storage limit exceeded' } },
|
|
621
|
-
]);
|
|
622
|
-
|
|
623
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
624
|
-
|
|
625
|
-
await expect(
|
|
626
|
-
service.upsert('my-vectors', { key: 'k1', document: 'test' })
|
|
627
|
-
).rejects.toThrow('Vector storage limit exceeded');
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
test('respects timeout for operations', async () => {
|
|
631
|
-
const { adapter, calls } = createMockAdapter([
|
|
632
|
-
{ ok: true, data: { success: true, data: [] } },
|
|
633
|
-
]);
|
|
634
|
-
|
|
635
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
636
|
-
await service.search('my-vectors', { query: 'test' });
|
|
637
|
-
|
|
638
|
-
expect(calls.length).toBe(1);
|
|
639
|
-
expect(calls[0].options.signal).toBeDefined();
|
|
640
|
-
});
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
// ===================================================================
|
|
644
|
-
// 4. EDGE CASE TESTS
|
|
645
|
-
// ===================================================================
|
|
646
|
-
|
|
647
|
-
describe('Edge Cases', () => {
|
|
648
|
-
test('handles very long document text', async () => {
|
|
649
|
-
const { adapter, calls } = createMockAdapter([
|
|
650
|
-
{ ok: true, data: { success: true, data: [{ id: 'vec-1' }] } },
|
|
651
|
-
]);
|
|
652
|
-
|
|
653
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
654
|
-
const longText = 'a'.repeat(100000);
|
|
655
|
-
|
|
656
|
-
await service.upsert('my-vectors', { key: 'k1', document: longText });
|
|
657
|
-
|
|
658
|
-
expect(calls.length).toBe(1);
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
test('handles special characters in storage name and keys', async () => {
|
|
662
|
-
const { adapter, calls } = createMockAdapter([
|
|
663
|
-
{ ok: true, data: { success: true, data: [{ id: 'vec-1' }] } },
|
|
664
|
-
]);
|
|
665
|
-
|
|
666
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
667
|
-
|
|
668
|
-
await service.upsert('my-vectors/special chars!', {
|
|
669
|
-
key: 'key with spaces & symbols',
|
|
670
|
-
document: 'test',
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
const url = calls[0].url;
|
|
674
|
-
expect(url).toContain('my-vectors%2Fspecial');
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
test('handles large embeddings array', async () => {
|
|
678
|
-
const { adapter, calls } = createMockAdapter([
|
|
679
|
-
{ ok: true, data: { success: true, data: [{ id: 'vec-1' }] } },
|
|
680
|
-
]);
|
|
681
|
-
|
|
682
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
683
|
-
const largeEmbeddings = Array.from({ length: 1536 }, (_, i) => i * 0.001);
|
|
684
|
-
|
|
685
|
-
await service.upsert('my-vectors', { key: 'k1', embeddings: largeEmbeddings });
|
|
686
|
-
|
|
687
|
-
expect(calls.length).toBe(1);
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
test('handles complex metadata objects', async () => {
|
|
691
|
-
const { adapter, calls } = createMockAdapter([
|
|
692
|
-
{ ok: true, data: { success: true, data: [{ id: 'vec-1' }] } },
|
|
693
|
-
]);
|
|
694
|
-
|
|
695
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
696
|
-
|
|
697
|
-
await service.upsert('my-vectors', {
|
|
698
|
-
key: 'k1',
|
|
699
|
-
document: 'test',
|
|
700
|
-
metadata: {
|
|
701
|
-
user: { id: 123, name: 'John' },
|
|
702
|
-
tags: ['important', 'verified'],
|
|
703
|
-
timestamp: Date.now(),
|
|
704
|
-
},
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
const body = JSON.parse(calls[0].options.body as string);
|
|
708
|
-
expect(body[0].metadata.user.name).toBe('John');
|
|
709
|
-
expect(body[0].metadata.tags).toEqual(['important', 'verified']);
|
|
710
|
-
});
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
// ===================================================================
|
|
714
|
-
// 5. INTEGRATION TEST
|
|
715
|
-
// ===================================================================
|
|
716
|
-
|
|
717
|
-
describe('Integration Workflow', () => {
|
|
718
|
-
test('complete vector workflow: upsert -> search -> get -> getMany -> delete -> exists', async () => {
|
|
719
|
-
const { adapter } = createMockAdapter([
|
|
720
|
-
// exists (search)
|
|
721
|
-
{ ok: true, data: { success: true, data: [] } },
|
|
722
|
-
// upsert
|
|
723
|
-
{
|
|
724
|
-
ok: true,
|
|
725
|
-
data: { success: true, data: [{ id: 'vec-0' }, { id: 'vec-1' }] },
|
|
726
|
-
},
|
|
727
|
-
// get (doc1)
|
|
728
|
-
{
|
|
729
|
-
ok: true,
|
|
730
|
-
data: {
|
|
731
|
-
success: true,
|
|
732
|
-
data: {
|
|
733
|
-
id: 'vec-1',
|
|
734
|
-
key: 'doc1',
|
|
735
|
-
similarity: 1.0,
|
|
736
|
-
document: 'First document',
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
|
-
},
|
|
740
|
-
// getMany (doc1)
|
|
741
|
-
{
|
|
742
|
-
ok: true,
|
|
743
|
-
data: {
|
|
744
|
-
success: true,
|
|
745
|
-
data: {
|
|
746
|
-
id: 'vec-0',
|
|
747
|
-
key: 'doc1',
|
|
748
|
-
similarity: 1.0,
|
|
749
|
-
document: 'First document',
|
|
750
|
-
},
|
|
751
|
-
},
|
|
752
|
-
},
|
|
753
|
-
// getMany (doc2)
|
|
754
|
-
{
|
|
755
|
-
ok: true,
|
|
756
|
-
data: {
|
|
757
|
-
success: true,
|
|
758
|
-
data: {
|
|
759
|
-
id: 'vec-1',
|
|
760
|
-
key: 'doc2',
|
|
761
|
-
similarity: 1.0,
|
|
762
|
-
document: 'Second document',
|
|
763
|
-
},
|
|
764
|
-
},
|
|
765
|
-
},
|
|
766
|
-
// search
|
|
767
|
-
{
|
|
768
|
-
ok: true,
|
|
769
|
-
data: {
|
|
770
|
-
success: true,
|
|
771
|
-
data: [
|
|
772
|
-
{ id: 'vec-0', key: 'doc1', similarity: 0.9, metadata: { type: 'test' } },
|
|
773
|
-
{ id: 'vec-1', key: 'doc2', similarity: 0.9, metadata: { type: 'test' } },
|
|
774
|
-
],
|
|
775
|
-
},
|
|
776
|
-
},
|
|
777
|
-
// delete
|
|
778
|
-
{ ok: true, data: { success: true, data: 2 } },
|
|
779
|
-
]);
|
|
780
|
-
|
|
781
|
-
const service = new VectorStorageService(baseUrl, adapter);
|
|
782
|
-
|
|
783
|
-
// Check if exists (should exist after creation)
|
|
784
|
-
const existsBefore = await service.exists('test-vectors');
|
|
785
|
-
expect(existsBefore).toBe(true);
|
|
786
|
-
|
|
787
|
-
// Upsert
|
|
788
|
-
const results = await service.upsert(
|
|
789
|
-
'test-vectors',
|
|
790
|
-
{ key: 'doc1', document: 'First document', metadata: { type: 'test' } },
|
|
791
|
-
{ key: 'doc2', document: 'Second document', metadata: { type: 'test' } }
|
|
792
|
-
);
|
|
793
|
-
expect(results).toEqual([
|
|
794
|
-
{ key: 'doc1', id: 'vec-0' },
|
|
795
|
-
{ key: 'doc2', id: 'vec-1' },
|
|
796
|
-
]);
|
|
797
|
-
|
|
798
|
-
// Get single
|
|
799
|
-
const getResult = await service.get('test-vectors', 'doc1');
|
|
800
|
-
expect(getResult.exists).toBe(true);
|
|
801
|
-
if (getResult.exists) {
|
|
802
|
-
expect(getResult.data.key).toBe('doc1');
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// GetMany
|
|
806
|
-
const manyResults = await service.getMany('test-vectors', 'doc1', 'doc2');
|
|
807
|
-
expect(manyResults.size).toBe(2);
|
|
808
|
-
expect(manyResults.has('doc1')).toBe(true);
|
|
809
|
-
|
|
810
|
-
// Search
|
|
811
|
-
const searchResults = await service.search('test-vectors', { query: 'document' });
|
|
812
|
-
expect(searchResults).toHaveLength(2);
|
|
813
|
-
|
|
814
|
-
// Delete
|
|
815
|
-
const deleteCount = await service.delete('test-vectors', 'doc1', 'doc2');
|
|
816
|
-
expect(deleteCount).toBe(2);
|
|
817
|
-
});
|
|
818
|
-
});
|
|
819
|
-
});
|