@ekodb/ekodb-client 0.7.0 → 0.8.0
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/client.d.ts +180 -13
- package/dist/client.js +257 -32
- package/dist/client.test.d.ts +7 -0
- package/dist/client.test.js +702 -0
- package/dist/query-builder.test.d.ts +4 -0
- package/dist/query-builder.test.js +318 -0
- package/dist/search.d.ts +12 -0
- package/dist/search.js +14 -0
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +3 -3
- package/dist/utils.test.d.ts +4 -0
- package/dist/utils.test.js +411 -0
- package/package.json +7 -4
- package/src/client.test.ts +961 -0
- package/src/client.ts +367 -42
- package/src/query-builder.test.ts +404 -0
- package/src/search.ts +22 -0
- package/src/utils.test.ts +506 -0
- package/src/utils.ts +3 -3
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for ekoDB TypeScript client
|
|
3
|
+
*
|
|
4
|
+
* These tests use vitest and mock fetch to test client methods
|
|
5
|
+
* without requiring a running ekoDB server.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
|
9
|
+
import { EkoDBClient, SerializationFormat } from "./client";
|
|
10
|
+
|
|
11
|
+
// Mock fetch globally
|
|
12
|
+
const mockFetch = vi.fn();
|
|
13
|
+
global.fetch = mockFetch;
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Test Helpers
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
function createTestClient(): EkoDBClient {
|
|
20
|
+
return new EkoDBClient({
|
|
21
|
+
baseURL: "http://localhost:8080",
|
|
22
|
+
apiKey: "test-api-key",
|
|
23
|
+
format: SerializationFormat.Json,
|
|
24
|
+
shouldRetry: false,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function mockTokenResponse(): void {
|
|
29
|
+
mockFetch.mockResolvedValueOnce({
|
|
30
|
+
ok: true,
|
|
31
|
+
status: 200,
|
|
32
|
+
json: async () => ({ token: "test-jwt-token" }),
|
|
33
|
+
headers: new Headers(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mockJsonResponse(data: unknown, status = 200): void {
|
|
38
|
+
mockFetch.mockResolvedValueOnce({
|
|
39
|
+
ok: status >= 200 && status < 300,
|
|
40
|
+
status,
|
|
41
|
+
json: async () => data,
|
|
42
|
+
text: async () => JSON.stringify(data),
|
|
43
|
+
headers: new Headers({
|
|
44
|
+
"content-type": "application/json",
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mockErrorResponse(status: number, message: string): void {
|
|
50
|
+
mockFetch.mockResolvedValueOnce({
|
|
51
|
+
ok: false,
|
|
52
|
+
status,
|
|
53
|
+
json: async () => ({ error: message }),
|
|
54
|
+
text: async () => message,
|
|
55
|
+
headers: new Headers(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Setup/Teardown
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
mockFetch.mockReset();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
vi.clearAllMocks();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Client Configuration Tests
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
describe("EkoDBClient configuration", () => {
|
|
76
|
+
it("creates client with required config", () => {
|
|
77
|
+
const client = new EkoDBClient({
|
|
78
|
+
baseURL: "http://localhost:8080",
|
|
79
|
+
apiKey: "test-key",
|
|
80
|
+
});
|
|
81
|
+
expect(client).toBeInstanceOf(EkoDBClient);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("creates client with all options", () => {
|
|
85
|
+
const client = new EkoDBClient({
|
|
86
|
+
baseURL: "http://localhost:8080",
|
|
87
|
+
apiKey: "test-key",
|
|
88
|
+
timeout: 60000,
|
|
89
|
+
maxRetries: 5,
|
|
90
|
+
shouldRetry: true,
|
|
91
|
+
format: SerializationFormat.Json,
|
|
92
|
+
});
|
|
93
|
+
expect(client).toBeInstanceOf(EkoDBClient);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Health Check Tests
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
// Note: Health check tests require specific response format matching
|
|
102
|
+
// Covered by integration tests
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Insert Tests
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
describe("EkoDBClient insert", () => {
|
|
109
|
+
it("inserts record successfully", async () => {
|
|
110
|
+
const client = createTestClient();
|
|
111
|
+
|
|
112
|
+
mockTokenResponse();
|
|
113
|
+
mockJsonResponse({ id: "record_123", name: "Test" });
|
|
114
|
+
|
|
115
|
+
const result = await client.insert("users", { name: "Test" });
|
|
116
|
+
|
|
117
|
+
expect(result).toHaveProperty("id", "record_123");
|
|
118
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("inserts record with TTL", async () => {
|
|
122
|
+
const client = createTestClient();
|
|
123
|
+
|
|
124
|
+
mockTokenResponse();
|
|
125
|
+
mockJsonResponse({ id: "record_123", name: "Test", ttl: "30m" });
|
|
126
|
+
|
|
127
|
+
const result = await client.insert("users", { name: "Test", ttl: "30m" });
|
|
128
|
+
|
|
129
|
+
expect(result).toHaveProperty("id");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Find Tests
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
describe("EkoDBClient find", () => {
|
|
138
|
+
it("finds records with query", async () => {
|
|
139
|
+
const client = createTestClient();
|
|
140
|
+
|
|
141
|
+
mockTokenResponse();
|
|
142
|
+
mockJsonResponse([
|
|
143
|
+
{ id: "user_1", name: "Alice" },
|
|
144
|
+
{ id: "user_2", name: "Bob" },
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
const result = await client.find("users", { limit: 10 });
|
|
148
|
+
|
|
149
|
+
expect(result).toHaveLength(2);
|
|
150
|
+
expect(result[0]).toHaveProperty("name", "Alice");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("finds record by ID", async () => {
|
|
154
|
+
const client = createTestClient();
|
|
155
|
+
|
|
156
|
+
mockTokenResponse();
|
|
157
|
+
mockJsonResponse({ id: "user_123", name: "Alice" });
|
|
158
|
+
|
|
159
|
+
const result = await client.findById("users", "user_123");
|
|
160
|
+
|
|
161
|
+
expect(result).toHaveProperty("id", "user_123");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("throws error for not found record", async () => {
|
|
165
|
+
const client = createTestClient();
|
|
166
|
+
|
|
167
|
+
mockTokenResponse();
|
|
168
|
+
mockErrorResponse(404, "Not found");
|
|
169
|
+
|
|
170
|
+
await expect(client.findById("users", "nonexistent")).rejects.toThrow();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// Update Tests
|
|
176
|
+
// ============================================================================
|
|
177
|
+
|
|
178
|
+
describe("EkoDBClient update", () => {
|
|
179
|
+
it("updates record successfully", async () => {
|
|
180
|
+
const client = createTestClient();
|
|
181
|
+
|
|
182
|
+
mockTokenResponse();
|
|
183
|
+
mockJsonResponse({ id: "user_123", name: "Updated" });
|
|
184
|
+
|
|
185
|
+
const result = await client.update("users", "user_123", {
|
|
186
|
+
name: "Updated",
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(result).toHaveProperty("name", "Updated");
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Delete Tests
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
describe("EkoDBClient delete", () => {
|
|
198
|
+
it("deletes record successfully", async () => {
|
|
199
|
+
const client = createTestClient();
|
|
200
|
+
|
|
201
|
+
mockTokenResponse();
|
|
202
|
+
mockJsonResponse({ id: "user_123", deleted: true });
|
|
203
|
+
|
|
204
|
+
await expect(client.delete("users", "user_123")).resolves.not.toThrow();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ============================================================================
|
|
209
|
+
// Batch Operations Tests
|
|
210
|
+
// ============================================================================
|
|
211
|
+
|
|
212
|
+
describe("EkoDBClient batch operations", () => {
|
|
213
|
+
it("batch inserts records", async () => {
|
|
214
|
+
const client = createTestClient();
|
|
215
|
+
|
|
216
|
+
mockTokenResponse();
|
|
217
|
+
mockJsonResponse({
|
|
218
|
+
successful: ["id_1", "id_2", "id_3"],
|
|
219
|
+
failed: [],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const records = [{ name: "A" }, { name: "B" }, { name: "C" }];
|
|
223
|
+
const result = await client.batchInsert("users", records);
|
|
224
|
+
|
|
225
|
+
expect(result.successful).toHaveLength(3);
|
|
226
|
+
expect(result.failed).toHaveLength(0);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("batch deletes records", async () => {
|
|
230
|
+
const client = createTestClient();
|
|
231
|
+
|
|
232
|
+
mockTokenResponse();
|
|
233
|
+
mockJsonResponse({
|
|
234
|
+
successful: ["id_1", "id_2"],
|
|
235
|
+
failed: [],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const result = await client.batchDelete("users", ["id_1", "id_2"]);
|
|
239
|
+
|
|
240
|
+
expect(result.successful).toHaveLength(2);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// ============================================================================
|
|
245
|
+
// KV Store Tests
|
|
246
|
+
// ============================================================================
|
|
247
|
+
|
|
248
|
+
describe("EkoDBClient KV store", () => {
|
|
249
|
+
it("sets KV value", async () => {
|
|
250
|
+
const client = createTestClient();
|
|
251
|
+
|
|
252
|
+
mockTokenResponse();
|
|
253
|
+
mockJsonResponse({ success: true });
|
|
254
|
+
|
|
255
|
+
await expect(
|
|
256
|
+
client.kvSet("my_key", { data: "value" }),
|
|
257
|
+
).resolves.not.toThrow();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("gets KV value", async () => {
|
|
261
|
+
const client = createTestClient();
|
|
262
|
+
|
|
263
|
+
mockTokenResponse();
|
|
264
|
+
mockJsonResponse({ value: { data: "stored_value" } });
|
|
265
|
+
|
|
266
|
+
const result = await client.kvGet("my_key");
|
|
267
|
+
|
|
268
|
+
expect(result).toEqual({ data: "stored_value" });
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("deletes KV value", async () => {
|
|
272
|
+
const client = createTestClient();
|
|
273
|
+
|
|
274
|
+
mockTokenResponse();
|
|
275
|
+
mockJsonResponse({ deleted: true });
|
|
276
|
+
|
|
277
|
+
await expect(client.kvDelete("my_key")).resolves.not.toThrow();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("checks KV exists", async () => {
|
|
281
|
+
const client = createTestClient();
|
|
282
|
+
|
|
283
|
+
mockTokenResponse();
|
|
284
|
+
mockJsonResponse({ value: "something" });
|
|
285
|
+
|
|
286
|
+
const result = await client.kvExists("my_key");
|
|
287
|
+
|
|
288
|
+
expect(result).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// Transaction Tests
|
|
294
|
+
// ============================================================================
|
|
295
|
+
|
|
296
|
+
describe("EkoDBClient transactions", () => {
|
|
297
|
+
it("begins transaction", async () => {
|
|
298
|
+
const client = createTestClient();
|
|
299
|
+
|
|
300
|
+
mockTokenResponse();
|
|
301
|
+
mockJsonResponse({ transaction_id: "tx_123456" });
|
|
302
|
+
|
|
303
|
+
const txId = await client.beginTransaction();
|
|
304
|
+
|
|
305
|
+
expect(txId).toBe("tx_123456");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("commits transaction", async () => {
|
|
309
|
+
const client = createTestClient();
|
|
310
|
+
|
|
311
|
+
mockTokenResponse();
|
|
312
|
+
mockJsonResponse({ status: "committed" });
|
|
313
|
+
|
|
314
|
+
await expect(client.commitTransaction("tx_123")).resolves.not.toThrow();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("rolls back transaction", async () => {
|
|
318
|
+
const client = createTestClient();
|
|
319
|
+
|
|
320
|
+
mockTokenResponse();
|
|
321
|
+
mockJsonResponse({ status: "rolled_back" });
|
|
322
|
+
|
|
323
|
+
await expect(client.rollbackTransaction("tx_123")).resolves.not.toThrow();
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ============================================================================
|
|
328
|
+
// Collection Management Tests
|
|
329
|
+
// ============================================================================
|
|
330
|
+
|
|
331
|
+
describe("EkoDBClient collections", () => {
|
|
332
|
+
it("lists collections", async () => {
|
|
333
|
+
const client = createTestClient();
|
|
334
|
+
|
|
335
|
+
mockTokenResponse();
|
|
336
|
+
mockJsonResponse({ collections: ["users", "posts", "comments"] });
|
|
337
|
+
|
|
338
|
+
const result = await client.listCollections();
|
|
339
|
+
|
|
340
|
+
expect(result).toContain("users");
|
|
341
|
+
expect(result).toHaveLength(3);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("deletes collection", async () => {
|
|
345
|
+
const client = createTestClient();
|
|
346
|
+
|
|
347
|
+
mockTokenResponse();
|
|
348
|
+
mockJsonResponse({ status: "deleted" });
|
|
349
|
+
|
|
350
|
+
await expect(
|
|
351
|
+
client.deleteCollection("test_collection"),
|
|
352
|
+
).resolves.not.toThrow();
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Restore Operations Tests
|
|
358
|
+
// ============================================================================
|
|
359
|
+
|
|
360
|
+
describe("EkoDBClient restore operations", () => {
|
|
361
|
+
it("restores deleted record", async () => {
|
|
362
|
+
const client = createTestClient();
|
|
363
|
+
|
|
364
|
+
mockTokenResponse();
|
|
365
|
+
mockJsonResponse({ status: "restored" });
|
|
366
|
+
|
|
367
|
+
await expect(
|
|
368
|
+
client.restoreRecord("users", "record_123"),
|
|
369
|
+
).resolves.not.toThrow();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Note: restoreCollection return type may vary - covered by integration tests
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// ============================================================================
|
|
376
|
+
// Search Tests
|
|
377
|
+
// ============================================================================
|
|
378
|
+
|
|
379
|
+
describe("EkoDBClient search", () => {
|
|
380
|
+
it("performs search", async () => {
|
|
381
|
+
const client = createTestClient();
|
|
382
|
+
|
|
383
|
+
mockTokenResponse();
|
|
384
|
+
mockJsonResponse({
|
|
385
|
+
results: [
|
|
386
|
+
{ id: "doc_1", score: 0.95 },
|
|
387
|
+
{ id: "doc_2", score: 0.85 },
|
|
388
|
+
],
|
|
389
|
+
total: 2,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const result = await client.search("documents", { query: "test" });
|
|
393
|
+
|
|
394
|
+
expect(result.results).toHaveLength(2);
|
|
395
|
+
expect(result.total).toBe(2);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Note: textSearch and hybridSearch require specific mock setup - covered by integration tests
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// ============================================================================
|
|
402
|
+
// Functions/Scripts Tests
|
|
403
|
+
// ============================================================================
|
|
404
|
+
|
|
405
|
+
describe("EkoDBClient functions", () => {
|
|
406
|
+
it("calls script", async () => {
|
|
407
|
+
const client = createTestClient();
|
|
408
|
+
|
|
409
|
+
mockTokenResponse();
|
|
410
|
+
mockJsonResponse({
|
|
411
|
+
results: [{ id: "user_1", name: "Alice" }],
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const result = await client.callScript("my_function", { limit: 10 });
|
|
415
|
+
|
|
416
|
+
expect(result).toHaveProperty("results");
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("lists scripts", async () => {
|
|
420
|
+
const client = createTestClient();
|
|
421
|
+
|
|
422
|
+
mockTokenResponse();
|
|
423
|
+
mockJsonResponse([
|
|
424
|
+
{ id: "func_1", label: "function_1" },
|
|
425
|
+
{ id: "func_2", label: "function_2" },
|
|
426
|
+
]);
|
|
427
|
+
|
|
428
|
+
const result = await client.listScripts();
|
|
429
|
+
|
|
430
|
+
expect(result).toHaveLength(2);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("deletes script", async () => {
|
|
434
|
+
const client = createTestClient();
|
|
435
|
+
|
|
436
|
+
mockTokenResponse();
|
|
437
|
+
mockJsonResponse({ status: "deleted" });
|
|
438
|
+
|
|
439
|
+
await expect(client.deleteScript("func_123")).resolves.not.toThrow();
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// ============================================================================
|
|
444
|
+
// Chat Tests
|
|
445
|
+
// ============================================================================
|
|
446
|
+
|
|
447
|
+
describe("EkoDBClient chat", () => {
|
|
448
|
+
it("creates chat session", async () => {
|
|
449
|
+
const client = createTestClient();
|
|
450
|
+
|
|
451
|
+
mockTokenResponse();
|
|
452
|
+
mockJsonResponse({
|
|
453
|
+
chat_id: "chat_123",
|
|
454
|
+
message_id: "msg_001",
|
|
455
|
+
responses: ["Hello!"],
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const result = await client.createChatSession({
|
|
459
|
+
collections: [{ collection_name: "documents" }],
|
|
460
|
+
llm_provider: "openai",
|
|
461
|
+
llm_model: "gpt-4",
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
expect(result).toHaveProperty("chat_id", "chat_123");
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("sends chat message", async () => {
|
|
468
|
+
const client = createTestClient();
|
|
469
|
+
|
|
470
|
+
mockTokenResponse();
|
|
471
|
+
mockJsonResponse({
|
|
472
|
+
chat_id: "chat_123",
|
|
473
|
+
message_id: "msg_002",
|
|
474
|
+
responses: ["Here is my response."],
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const request = { message: "What is the answer?" };
|
|
478
|
+
const result = await client.chatMessage("chat_123", request);
|
|
479
|
+
|
|
480
|
+
expect(result).toHaveProperty("message_id");
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("lists chat sessions", async () => {
|
|
484
|
+
const client = createTestClient();
|
|
485
|
+
|
|
486
|
+
mockTokenResponse();
|
|
487
|
+
mockJsonResponse({
|
|
488
|
+
sessions: [
|
|
489
|
+
{ id: "chat_1", created_at: "2024-01-01T00:00:00Z" },
|
|
490
|
+
{ id: "chat_2", created_at: "2024-01-02T00:00:00Z" },
|
|
491
|
+
],
|
|
492
|
+
total: 2,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const result = await client.listChatSessions();
|
|
496
|
+
|
|
497
|
+
expect(result.sessions).toHaveLength(2);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it("gets chat session", async () => {
|
|
501
|
+
const client = createTestClient();
|
|
502
|
+
|
|
503
|
+
mockTokenResponse();
|
|
504
|
+
mockJsonResponse({
|
|
505
|
+
id: "chat_123",
|
|
506
|
+
created_at: "2024-01-01T00:00:00Z",
|
|
507
|
+
messages: [],
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const result = await client.getChatSession("chat_123");
|
|
511
|
+
|
|
512
|
+
expect(result).toHaveProperty("id", "chat_123");
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("deletes chat session", async () => {
|
|
516
|
+
const client = createTestClient();
|
|
517
|
+
|
|
518
|
+
mockTokenResponse();
|
|
519
|
+
mockJsonResponse({ status: "deleted" });
|
|
520
|
+
|
|
521
|
+
await expect(client.deleteChatSession("chat_123")).resolves.not.toThrow();
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// ============================================================================
|
|
526
|
+
// Error Handling Tests
|
|
527
|
+
// ============================================================================
|
|
528
|
+
|
|
529
|
+
describe("EkoDBClient error handling", () => {
|
|
530
|
+
it("handles server error", async () => {
|
|
531
|
+
const client = createTestClient();
|
|
532
|
+
|
|
533
|
+
mockTokenResponse();
|
|
534
|
+
mockErrorResponse(500, "Internal Server Error");
|
|
535
|
+
|
|
536
|
+
await expect(client.insert("users", { name: "Test" })).rejects.toThrow();
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("handles rate limit error", async () => {
|
|
540
|
+
const client = createTestClient();
|
|
541
|
+
|
|
542
|
+
mockTokenResponse();
|
|
543
|
+
mockFetch.mockResolvedValueOnce({
|
|
544
|
+
ok: false,
|
|
545
|
+
status: 429,
|
|
546
|
+
json: async () => ({ error: "Rate limit exceeded" }),
|
|
547
|
+
text: async () => "Rate limit exceeded",
|
|
548
|
+
headers: new Headers({
|
|
549
|
+
"Retry-After": "60",
|
|
550
|
+
}),
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
await expect(client.insert("users", { name: "Test" })).rejects.toThrow();
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("handles authentication error", async () => {
|
|
557
|
+
mockFetch.mockResolvedValueOnce({
|
|
558
|
+
ok: false,
|
|
559
|
+
status: 401,
|
|
560
|
+
json: async () => ({ error: "Invalid API key" }),
|
|
561
|
+
text: async () => "Invalid API key",
|
|
562
|
+
headers: new Headers(),
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
const client = createTestClient();
|
|
566
|
+
|
|
567
|
+
await expect(client.insert("users", { name: "Test" })).rejects.toThrow();
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Note: Rate limit info tests require actual HTTP response headers
|
|
572
|
+
// which are handled differently in the real client vs mocks
|
|
573
|
+
// Covered by integration tests
|
|
574
|
+
|
|
575
|
+
// ============================================================================
|
|
576
|
+
// Additional Missing Method Tests - Added Jan 4, 2026
|
|
577
|
+
// ============================================================================
|
|
578
|
+
|
|
579
|
+
// Note: findAll/findAllWithLimit use WebSocket, not HTTP - covered by integration tests
|
|
580
|
+
|
|
581
|
+
describe("EkoDBClient batchUpdate", () => {
|
|
582
|
+
it("batch updates records", async () => {
|
|
583
|
+
const client = createTestClient();
|
|
584
|
+
|
|
585
|
+
mockTokenResponse();
|
|
586
|
+
mockJsonResponse({
|
|
587
|
+
successful: ["id_1", "id_2"],
|
|
588
|
+
failed: [],
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const updates = [
|
|
592
|
+
{ id: "id_1", data: { name: "Updated 1" } },
|
|
593
|
+
{ id: "id_2", data: { name: "Updated 2" } },
|
|
594
|
+
];
|
|
595
|
+
const result = await client.batchUpdate("users", updates);
|
|
596
|
+
|
|
597
|
+
expect(result.successful).toHaveLength(2);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
describe("EkoDBClient collection management", () => {
|
|
602
|
+
it("creates collection", async () => {
|
|
603
|
+
const client = createTestClient();
|
|
604
|
+
|
|
605
|
+
mockTokenResponse();
|
|
606
|
+
mockJsonResponse({ status: "created" });
|
|
607
|
+
|
|
608
|
+
await expect(
|
|
609
|
+
client.createCollection("new_collection", { fields: {} }),
|
|
610
|
+
).resolves.not.toThrow();
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("gets collection metadata", async () => {
|
|
614
|
+
const client = createTestClient();
|
|
615
|
+
|
|
616
|
+
mockTokenResponse();
|
|
617
|
+
mockJsonResponse({
|
|
618
|
+
collection: { fields: { name: { field_type: "String" } } },
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
const result = await client.getCollection("users");
|
|
622
|
+
|
|
623
|
+
expect(result).toBeDefined();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Note: getSchema requires specific response format - covered by integration tests
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
describe("EkoDBClient KV advanced", () => {
|
|
630
|
+
it("queries KV by pattern", async () => {
|
|
631
|
+
const client = createTestClient();
|
|
632
|
+
|
|
633
|
+
mockTokenResponse();
|
|
634
|
+
mockJsonResponse([
|
|
635
|
+
{ key: "config:app", value: "value1" },
|
|
636
|
+
{ key: "config:db", value: "value2" },
|
|
637
|
+
]);
|
|
638
|
+
|
|
639
|
+
const result = await client.kvFind({ pattern: "config:*" });
|
|
640
|
+
|
|
641
|
+
expect(result).toHaveLength(2);
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
describe("EkoDBClient scripts advanced", () => {
|
|
646
|
+
it("saves script", async () => {
|
|
647
|
+
const client = createTestClient();
|
|
648
|
+
|
|
649
|
+
mockTokenResponse();
|
|
650
|
+
mockJsonResponse({ id: "func_123", label: "my_function" });
|
|
651
|
+
|
|
652
|
+
const script = {
|
|
653
|
+
label: "my_function",
|
|
654
|
+
name: "my_function",
|
|
655
|
+
parameters: {},
|
|
656
|
+
functions: [],
|
|
657
|
+
};
|
|
658
|
+
const result = await client.saveScript(script);
|
|
659
|
+
|
|
660
|
+
expect(result).toBeDefined();
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it("gets script by ID", async () => {
|
|
664
|
+
const client = createTestClient();
|
|
665
|
+
|
|
666
|
+
mockTokenResponse();
|
|
667
|
+
mockJsonResponse({ id: "func_123", label: "my_function" });
|
|
668
|
+
|
|
669
|
+
const result = await client.getScript("func_123");
|
|
670
|
+
|
|
671
|
+
expect(result).toBeDefined();
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it("updates script", async () => {
|
|
675
|
+
const client = createTestClient();
|
|
676
|
+
|
|
677
|
+
mockTokenResponse();
|
|
678
|
+
mockJsonResponse({ id: "func_123", label: "updated_function" });
|
|
679
|
+
|
|
680
|
+
const script = {
|
|
681
|
+
label: "updated_function",
|
|
682
|
+
name: "updated_function",
|
|
683
|
+
parameters: {},
|
|
684
|
+
functions: [],
|
|
685
|
+
};
|
|
686
|
+
await expect(
|
|
687
|
+
client.updateScript("func_123", script),
|
|
688
|
+
).resolves.not.toThrow();
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
describe("EkoDBClient chat advanced", () => {
|
|
693
|
+
it("gets chat session messages", async () => {
|
|
694
|
+
const client = createTestClient();
|
|
695
|
+
|
|
696
|
+
mockTokenResponse();
|
|
697
|
+
mockJsonResponse({
|
|
698
|
+
messages: [
|
|
699
|
+
{ id: "msg_1", role: "user", content: "Hello" },
|
|
700
|
+
{ id: "msg_2", role: "assistant", content: "Hi" },
|
|
701
|
+
],
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const result = await client.getChatSessionMessages("chat_123");
|
|
705
|
+
|
|
706
|
+
expect(result).toBeDefined();
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it("updates chat session", async () => {
|
|
710
|
+
const client = createTestClient();
|
|
711
|
+
|
|
712
|
+
mockTokenResponse();
|
|
713
|
+
mockJsonResponse({ id: "chat_123", updated: true });
|
|
714
|
+
|
|
715
|
+
await expect(
|
|
716
|
+
client.updateChatSession("chat_123", { system_prompt: "New prompt" }),
|
|
717
|
+
).resolves.not.toThrow();
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it("branches chat session", async () => {
|
|
721
|
+
const client = createTestClient();
|
|
722
|
+
|
|
723
|
+
mockTokenResponse();
|
|
724
|
+
mockJsonResponse({ chat_id: "chat_456", branched_from: "chat_123" });
|
|
725
|
+
|
|
726
|
+
const result = await client.branchChatSession({
|
|
727
|
+
collections: [{ collection_name: "documents" }],
|
|
728
|
+
llm_provider: "openai",
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
expect(result).toBeDefined();
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it("merges chat sessions", async () => {
|
|
735
|
+
const client = createTestClient();
|
|
736
|
+
|
|
737
|
+
mockTokenResponse();
|
|
738
|
+
mockJsonResponse({ chat_id: "chat_123", merged: true });
|
|
739
|
+
|
|
740
|
+
const result = await client.mergeChatSessions({
|
|
741
|
+
source_chat_ids: ["chat_456"],
|
|
742
|
+
target_chat_id: "chat_123",
|
|
743
|
+
merge_strategy: "Interleaved" as any,
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
expect(result).toBeDefined();
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it("deletes chat message", async () => {
|
|
750
|
+
const client = createTestClient();
|
|
751
|
+
|
|
752
|
+
mockTokenResponse();
|
|
753
|
+
mockJsonResponse({ deleted: true });
|
|
754
|
+
|
|
755
|
+
await expect(
|
|
756
|
+
client.deleteChatMessage("chat_123", "msg_001"),
|
|
757
|
+
).resolves.not.toThrow();
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it("updates chat message", async () => {
|
|
761
|
+
const client = createTestClient();
|
|
762
|
+
|
|
763
|
+
mockTokenResponse();
|
|
764
|
+
mockJsonResponse({ message_id: "msg_001", updated: true });
|
|
765
|
+
|
|
766
|
+
await expect(
|
|
767
|
+
client.updateChatMessage("chat_123", "msg_001", "Updated content"),
|
|
768
|
+
).resolves.not.toThrow();
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it("regenerates chat message", async () => {
|
|
772
|
+
const client = createTestClient();
|
|
773
|
+
|
|
774
|
+
mockTokenResponse();
|
|
775
|
+
mockJsonResponse({ message_id: "msg_002", content: "Regenerated" });
|
|
776
|
+
|
|
777
|
+
const result = await client.regenerateMessage("chat_123", "msg_001");
|
|
778
|
+
|
|
779
|
+
expect(result).toBeDefined();
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
it("toggles forgotten message", async () => {
|
|
783
|
+
const client = createTestClient();
|
|
784
|
+
|
|
785
|
+
mockTokenResponse();
|
|
786
|
+
mockJsonResponse({ message_id: "msg_001", forgotten: true });
|
|
787
|
+
|
|
788
|
+
await expect(
|
|
789
|
+
client.toggleForgottenMessage("chat_123", "msg_001", true),
|
|
790
|
+
).resolves.not.toThrow();
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
describe("EkoDBClient transaction status", () => {
|
|
795
|
+
it("gets transaction status", async () => {
|
|
796
|
+
const client = createTestClient();
|
|
797
|
+
|
|
798
|
+
mockTokenResponse();
|
|
799
|
+
mockJsonResponse({ transaction_id: "tx_123", status: "active" });
|
|
800
|
+
|
|
801
|
+
const result = await client.getTransactionStatus("tx_123");
|
|
802
|
+
|
|
803
|
+
expect(result).toBeDefined();
|
|
804
|
+
});
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// ============================================================================
|
|
808
|
+
// Convenience Methods Tests
|
|
809
|
+
// ============================================================================
|
|
810
|
+
|
|
811
|
+
describe("Convenience methods", () => {
|
|
812
|
+
describe("upsert", () => {
|
|
813
|
+
it("inserts when record not found", async () => {
|
|
814
|
+
mockTokenResponse();
|
|
815
|
+
// Mock update returning 404
|
|
816
|
+
mockErrorResponse(404, "Not found");
|
|
817
|
+
// Mock insert succeeding
|
|
818
|
+
mockJsonResponse({ id: "user123", name: "John Doe" });
|
|
819
|
+
|
|
820
|
+
const client = createTestClient();
|
|
821
|
+
await client.init();
|
|
822
|
+
|
|
823
|
+
const result = await client.upsert("users", "user123", {
|
|
824
|
+
name: "John Doe",
|
|
825
|
+
});
|
|
826
|
+
expect(result).toEqual({ id: "user123", name: "John Doe" });
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it("updates when record exists", async () => {
|
|
830
|
+
mockTokenResponse();
|
|
831
|
+
// Mock update succeeding
|
|
832
|
+
mockJsonResponse({ id: "user123", name: "John Doe Updated" });
|
|
833
|
+
|
|
834
|
+
const client = createTestClient();
|
|
835
|
+
await client.init();
|
|
836
|
+
|
|
837
|
+
const result = await client.upsert("users", "user123", {
|
|
838
|
+
name: "John Doe Updated",
|
|
839
|
+
});
|
|
840
|
+
expect(result).toEqual({ id: "user123", name: "John Doe Updated" });
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it("throws on non-404 errors", async () => {
|
|
844
|
+
mockTokenResponse();
|
|
845
|
+
// Mock update with server error
|
|
846
|
+
mockErrorResponse(500, "Internal server error");
|
|
847
|
+
|
|
848
|
+
const client = createTestClient();
|
|
849
|
+
await client.init();
|
|
850
|
+
|
|
851
|
+
await expect(
|
|
852
|
+
client.upsert("users", "user123", { name: "John Doe" }),
|
|
853
|
+
).rejects.toThrow();
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
describe("findOne", () => {
|
|
858
|
+
it("returns record when found", async () => {
|
|
859
|
+
mockTokenResponse();
|
|
860
|
+
mockJsonResponse([
|
|
861
|
+
{ id: "user123", email: "test@example.com", name: "John" },
|
|
862
|
+
]);
|
|
863
|
+
|
|
864
|
+
const client = createTestClient();
|
|
865
|
+
await client.init();
|
|
866
|
+
|
|
867
|
+
const result = await client.findOne("users", "email", "test@example.com");
|
|
868
|
+
expect(result).toEqual({
|
|
869
|
+
id: "user123",
|
|
870
|
+
email: "test@example.com",
|
|
871
|
+
name: "John",
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
it("returns null when not found", async () => {
|
|
876
|
+
mockTokenResponse();
|
|
877
|
+
mockJsonResponse([]);
|
|
878
|
+
|
|
879
|
+
const client = createTestClient();
|
|
880
|
+
await client.init();
|
|
881
|
+
|
|
882
|
+
const result = await client.findOne(
|
|
883
|
+
"users",
|
|
884
|
+
"email",
|
|
885
|
+
"notfound@example.com",
|
|
886
|
+
);
|
|
887
|
+
expect(result).toBeNull();
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
describe("exists", () => {
|
|
892
|
+
it("returns true when record exists", async () => {
|
|
893
|
+
mockTokenResponse();
|
|
894
|
+
mockJsonResponse({ id: "user123", name: "John" });
|
|
895
|
+
|
|
896
|
+
const client = createTestClient();
|
|
897
|
+
await client.init();
|
|
898
|
+
|
|
899
|
+
const result = await client.exists("users", "user123");
|
|
900
|
+
expect(result).toBe(true);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
it("returns false when record not found", async () => {
|
|
904
|
+
mockTokenResponse();
|
|
905
|
+
mockErrorResponse(404, "Not found");
|
|
906
|
+
|
|
907
|
+
const client = createTestClient();
|
|
908
|
+
await client.init();
|
|
909
|
+
|
|
910
|
+
const result = await client.exists("users", "user123");
|
|
911
|
+
expect(result).toBe(false);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it("throws on non-404 errors", async () => {
|
|
915
|
+
mockTokenResponse();
|
|
916
|
+
mockErrorResponse(500, "Internal server error");
|
|
917
|
+
|
|
918
|
+
const client = createTestClient();
|
|
919
|
+
await client.init();
|
|
920
|
+
|
|
921
|
+
await expect(client.exists("users", "user123")).rejects.toThrow();
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
describe("paginate", () => {
|
|
926
|
+
it("calculates skip correctly for page 2", async () => {
|
|
927
|
+
mockTokenResponse();
|
|
928
|
+
mockJsonResponse([{ id: "user11", name: "User 11" }]);
|
|
929
|
+
|
|
930
|
+
const client = createTestClient();
|
|
931
|
+
await client.init();
|
|
932
|
+
|
|
933
|
+
const result = await client.paginate("users", 2, 10);
|
|
934
|
+
expect(result).toHaveLength(1);
|
|
935
|
+
expect(result[0]).toEqual({ id: "user11", name: "User 11" });
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
it("skips zero records for page 1", async () => {
|
|
939
|
+
mockTokenResponse();
|
|
940
|
+
mockJsonResponse([{ id: "user1", name: "User 1" }]);
|
|
941
|
+
|
|
942
|
+
const client = createTestClient();
|
|
943
|
+
await client.init();
|
|
944
|
+
|
|
945
|
+
const result = await client.paginate("users", 1, 10);
|
|
946
|
+
expect(result).toHaveLength(1);
|
|
947
|
+
expect(result[0]).toEqual({ id: "user1", name: "User 1" });
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
it("returns empty array when no records", async () => {
|
|
951
|
+
mockTokenResponse();
|
|
952
|
+
mockJsonResponse([]);
|
|
953
|
+
|
|
954
|
+
const client = createTestClient();
|
|
955
|
+
await client.init();
|
|
956
|
+
|
|
957
|
+
const result = await client.paginate("users", 5, 10);
|
|
958
|
+
expect(result).toHaveLength(0);
|
|
959
|
+
});
|
|
960
|
+
});
|
|
961
|
+
});
|