@soleri/core 1.0.0 → 2.0.1
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/brain/brain.d.ts +12 -3
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +147 -12
- package/dist/brain/brain.js.map +1 -1
- package/dist/cognee/client.d.ts +35 -0
- package/dist/cognee/client.d.ts.map +1 -0
- package/dist/cognee/client.js +291 -0
- package/dist/cognee/client.js.map +1 -0
- package/dist/cognee/types.d.ts +46 -0
- package/dist/cognee/types.d.ts.map +1 -0
- package/dist/cognee/types.js +3 -0
- package/dist/cognee/types.js.map +1 -0
- package/dist/facades/types.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/brain.test.ts +265 -27
- package/src/__tests__/cognee-client.test.ts +524 -0
- package/src/brain/brain.ts +176 -12
- package/src/cognee/client.ts +352 -0
- package/src/cognee/types.ts +62 -0
- package/src/index.ts +11 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
2
|
+
import { CogneeClient } from '../cognee/client.js';
|
|
3
|
+
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
4
|
+
|
|
5
|
+
function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
|
|
6
|
+
return {
|
|
7
|
+
id: overrides.id ?? 'test-1',
|
|
8
|
+
type: overrides.type ?? 'pattern',
|
|
9
|
+
domain: overrides.domain ?? 'testing',
|
|
10
|
+
title: overrides.title ?? 'Test Pattern',
|
|
11
|
+
severity: overrides.severity ?? 'warning',
|
|
12
|
+
description: overrides.description ?? 'A test pattern for unit tests.',
|
|
13
|
+
tags: overrides.tags ?? ['testing', 'assertions'],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function mockFetch(handler: (url: string, init?: RequestInit) => Promise<Response>) {
|
|
18
|
+
const mock = vi.fn(handler);
|
|
19
|
+
vi.stubGlobal('fetch', mock);
|
|
20
|
+
return mock;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Helper: route health (GET /) vs auth vs API calls */
|
|
24
|
+
function isHealthCheck(url: string, init?: RequestInit): boolean {
|
|
25
|
+
return url.endsWith(':8000/') || (url.endsWith('/') && (!init?.method || init.method === 'GET'));
|
|
26
|
+
}
|
|
27
|
+
function isAuthCall(url: string): boolean {
|
|
28
|
+
return url.includes('/api/v1/auth/');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Default mock that accepts health + auto-auth + API calls */
|
|
32
|
+
function mockWithAuth(apiHandler?: (url: string, init?: RequestInit) => Promise<Response>) {
|
|
33
|
+
return mockFetch(async (url, init) => {
|
|
34
|
+
if (isHealthCheck(url, init)) return new Response('ok', { status: 200 });
|
|
35
|
+
if (isAuthCall(url)) {
|
|
36
|
+
if (url.includes('/login')) {
|
|
37
|
+
return new Response(JSON.stringify({ access_token: 'test-jwt' }), { status: 200 });
|
|
38
|
+
}
|
|
39
|
+
if (url.includes('/register')) {
|
|
40
|
+
return new Response(JSON.stringify({ id: 'new-user' }), { status: 200 });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (apiHandler) return apiHandler(url, init);
|
|
44
|
+
return new Response('ok', { status: 200 });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('CogneeClient', () => {
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
vi.restoreAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ─── Constructor ──────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
describe('constructor', () => {
|
|
56
|
+
it('should use default config when none provided', () => {
|
|
57
|
+
const client = new CogneeClient();
|
|
58
|
+
const config = client.getConfig();
|
|
59
|
+
expect(config.baseUrl).toBe('http://localhost:8000');
|
|
60
|
+
expect(config.dataset).toBe('vault');
|
|
61
|
+
expect(config.timeoutMs).toBe(30000);
|
|
62
|
+
expect(config.searchTimeoutMs).toBe(120000);
|
|
63
|
+
expect(config.healthTimeoutMs).toBe(5000);
|
|
64
|
+
expect(config.cognifyDebounceMs).toBe(30000);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should merge partial config with defaults', () => {
|
|
68
|
+
const client = new CogneeClient({ baseUrl: 'http://cognee:9000', dataset: 'my-vault' });
|
|
69
|
+
const config = client.getConfig();
|
|
70
|
+
expect(config.baseUrl).toBe('http://cognee:9000');
|
|
71
|
+
expect(config.dataset).toBe('my-vault');
|
|
72
|
+
expect(config.timeoutMs).toBe(30000);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should strip trailing slash from baseUrl', () => {
|
|
76
|
+
const client = new CogneeClient({ baseUrl: 'http://localhost:8000/' });
|
|
77
|
+
expect(client.getConfig().baseUrl).toBe('http://localhost:8000');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should accept apiToken config', () => {
|
|
81
|
+
const client = new CogneeClient({ apiToken: 'my-secret' });
|
|
82
|
+
expect(client.getConfig().apiToken).toBe('my-secret');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ─── Health Check ─────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
describe('healthCheck', () => {
|
|
89
|
+
it('should return available when Cognee responds OK', async () => {
|
|
90
|
+
mockFetch(async () => new Response('ok', { status: 200 }));
|
|
91
|
+
const client = new CogneeClient();
|
|
92
|
+
const status = await client.healthCheck();
|
|
93
|
+
expect(status.available).toBe(true);
|
|
94
|
+
expect(status.url).toBe('http://localhost:8000');
|
|
95
|
+
expect(status.latencyMs).toBeGreaterThanOrEqual(0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should hit GET / for health check', async () => {
|
|
99
|
+
const fetchMock = mockFetch(async () => new Response('ok', { status: 200 }));
|
|
100
|
+
const client = new CogneeClient();
|
|
101
|
+
await client.healthCheck();
|
|
102
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
103
|
+
'http://localhost:8000/',
|
|
104
|
+
expect.objectContaining({ signal: expect.any(AbortSignal) }),
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return unavailable on HTTP error', async () => {
|
|
109
|
+
mockFetch(async () => new Response('error', { status: 500 }));
|
|
110
|
+
const client = new CogneeClient();
|
|
111
|
+
const status = await client.healthCheck();
|
|
112
|
+
expect(status.available).toBe(false);
|
|
113
|
+
expect(status.error).toBe('HTTP 500');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should return unavailable on network error', async () => {
|
|
117
|
+
mockFetch(async () => {
|
|
118
|
+
throw new Error('ECONNREFUSED');
|
|
119
|
+
});
|
|
120
|
+
const client = new CogneeClient();
|
|
121
|
+
const status = await client.healthCheck();
|
|
122
|
+
expect(status.available).toBe(false);
|
|
123
|
+
expect(status.error).toBe('ECONNREFUSED');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should cache health status', async () => {
|
|
127
|
+
const fetchMock = mockFetch(async () => new Response('ok', { status: 200 }));
|
|
128
|
+
const client = new CogneeClient();
|
|
129
|
+
await client.healthCheck();
|
|
130
|
+
expect(client.isAvailable).toBe(true);
|
|
131
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(client.isAvailable).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should expire health cache after TTL', async () => {
|
|
136
|
+
mockFetch(async () => new Response('ok', { status: 200 }));
|
|
137
|
+
const client = new CogneeClient({ healthCacheTtlMs: 1 });
|
|
138
|
+
await client.healthCheck();
|
|
139
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
140
|
+
expect(client.isAvailable).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ─── isAvailable ──────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
describe('isAvailable', () => {
|
|
147
|
+
it('should be false before health check', () => {
|
|
148
|
+
const client = new CogneeClient();
|
|
149
|
+
expect(client.isAvailable).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should be true after successful health check', async () => {
|
|
153
|
+
mockFetch(async () => new Response('ok', { status: 200 }));
|
|
154
|
+
const client = new CogneeClient();
|
|
155
|
+
await client.healthCheck();
|
|
156
|
+
expect(client.isAvailable).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should be false after failed health check', async () => {
|
|
160
|
+
mockFetch(async () => {
|
|
161
|
+
throw new Error('down');
|
|
162
|
+
});
|
|
163
|
+
const client = new CogneeClient();
|
|
164
|
+
await client.healthCheck();
|
|
165
|
+
expect(client.isAvailable).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ─── addEntries ───────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
describe('addEntries', () => {
|
|
172
|
+
it('should return added count on success', async () => {
|
|
173
|
+
mockWithAuth();
|
|
174
|
+
const client = new CogneeClient();
|
|
175
|
+
await client.healthCheck();
|
|
176
|
+
const result = await client.addEntries([makeEntry(), makeEntry({ id: 'test-2' })]);
|
|
177
|
+
expect(result.added).toBe(2);
|
|
178
|
+
client.resetPendingCognify();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should return 0 when not available', async () => {
|
|
182
|
+
const client = new CogneeClient();
|
|
183
|
+
const result = await client.addEntries([makeEntry()]);
|
|
184
|
+
expect(result.added).toBe(0);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should return 0 for empty entries', async () => {
|
|
188
|
+
mockWithAuth();
|
|
189
|
+
const client = new CogneeClient();
|
|
190
|
+
await client.healthCheck();
|
|
191
|
+
const result = await client.addEntries([]);
|
|
192
|
+
expect(result.added).toBe(0);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should return 0 on HTTP error', async () => {
|
|
196
|
+
mockWithAuth(async () => new Response('error', { status: 500 }));
|
|
197
|
+
const client = new CogneeClient();
|
|
198
|
+
await client.healthCheck();
|
|
199
|
+
const result = await client.addEntries([makeEntry()]);
|
|
200
|
+
expect(result.added).toBe(0);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should send FormData with datasetName and files', async () => {
|
|
204
|
+
let capturedBody: FormData | null = null;
|
|
205
|
+
mockWithAuth(async (_url, init) => {
|
|
206
|
+
capturedBody = init?.body as FormData;
|
|
207
|
+
return new Response('ok', { status: 200 });
|
|
208
|
+
});
|
|
209
|
+
const client = new CogneeClient({ dataset: 'test-ds' });
|
|
210
|
+
await client.healthCheck();
|
|
211
|
+
await client.addEntries([makeEntry({ id: 'e1', title: 'My Title' })]);
|
|
212
|
+
expect(capturedBody).toBeInstanceOf(FormData);
|
|
213
|
+
expect(capturedBody!.get('datasetName')).toBe('test-ds');
|
|
214
|
+
const file = capturedBody!.get('data') as File;
|
|
215
|
+
expect(file).toBeTruthy();
|
|
216
|
+
const text = await file.text();
|
|
217
|
+
expect(text).toContain('My Title');
|
|
218
|
+
client.resetPendingCognify();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ─── cognify ──────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
describe('cognify', () => {
|
|
225
|
+
it('should return ok on success', async () => {
|
|
226
|
+
mockWithAuth();
|
|
227
|
+
const client = new CogneeClient();
|
|
228
|
+
await client.healthCheck();
|
|
229
|
+
const result = await client.cognify();
|
|
230
|
+
expect(result.status).toBe('ok');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should send datasets array in body', async () => {
|
|
234
|
+
let capturedBody = '';
|
|
235
|
+
mockWithAuth(async (_url, init) => {
|
|
236
|
+
capturedBody = init?.body as string;
|
|
237
|
+
return new Response('ok', { status: 200 });
|
|
238
|
+
});
|
|
239
|
+
const client = new CogneeClient({ dataset: 'my-ds' });
|
|
240
|
+
await client.healthCheck();
|
|
241
|
+
await client.cognify();
|
|
242
|
+
const parsed = JSON.parse(capturedBody);
|
|
243
|
+
expect(parsed.datasets).toEqual(['my-ds']);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should return unavailable when not connected', async () => {
|
|
247
|
+
const client = new CogneeClient();
|
|
248
|
+
const result = await client.cognify();
|
|
249
|
+
expect(result.status).toBe('unavailable');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should return error on HTTP failure', async () => {
|
|
253
|
+
mockWithAuth(async () => new Response('error', { status: 503 }));
|
|
254
|
+
const client = new CogneeClient();
|
|
255
|
+
await client.healthCheck();
|
|
256
|
+
const result = await client.cognify();
|
|
257
|
+
expect(result.status).toContain('error');
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ─── search ───────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
describe('search', () => {
|
|
264
|
+
it('should return parsed results with scores', async () => {
|
|
265
|
+
mockWithAuth(
|
|
266
|
+
async () =>
|
|
267
|
+
new Response(
|
|
268
|
+
JSON.stringify([
|
|
269
|
+
{ id: 'r1', text: 'Result one', score: 0.95, payload: { id: 'vault-1' } },
|
|
270
|
+
{ id: 'r2', text: 'Result two', score: 0.8, payload: { id: 'vault-2' } },
|
|
271
|
+
]),
|
|
272
|
+
{ status: 200 },
|
|
273
|
+
),
|
|
274
|
+
);
|
|
275
|
+
const client = new CogneeClient();
|
|
276
|
+
await client.healthCheck();
|
|
277
|
+
const results = await client.search('test query');
|
|
278
|
+
expect(results).toHaveLength(2);
|
|
279
|
+
expect(results[0].id).toBe('vault-1');
|
|
280
|
+
expect(results[0].score).toBe(0.95);
|
|
281
|
+
expect(results[0].text).toBe('Result one');
|
|
282
|
+
expect(results[0].searchType).toBe('CHUNKS');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should use position-based scoring when scores are missing', async () => {
|
|
286
|
+
mockWithAuth(
|
|
287
|
+
async () =>
|
|
288
|
+
new Response(
|
|
289
|
+
JSON.stringify([
|
|
290
|
+
{ id: 'r1', text: 'First' },
|
|
291
|
+
{ id: 'r2', text: 'Second' },
|
|
292
|
+
{ id: 'r3', text: 'Third' },
|
|
293
|
+
]),
|
|
294
|
+
{ status: 200 },
|
|
295
|
+
),
|
|
296
|
+
);
|
|
297
|
+
const client = new CogneeClient();
|
|
298
|
+
await client.healthCheck();
|
|
299
|
+
const results = await client.search('query');
|
|
300
|
+
expect(results[0].score).toBe(1.0);
|
|
301
|
+
expect(results[1].score).toBeCloseTo(0.525, 2);
|
|
302
|
+
expect(results[2].score).toBeCloseTo(0.05, 2);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should respect limit', async () => {
|
|
306
|
+
mockWithAuth(
|
|
307
|
+
async () =>
|
|
308
|
+
new Response(
|
|
309
|
+
JSON.stringify([
|
|
310
|
+
{ id: 'r1', text: 'A', score: 0.9 },
|
|
311
|
+
{ id: 'r2', text: 'B', score: 0.8 },
|
|
312
|
+
{ id: 'r3', text: 'C', score: 0.7 },
|
|
313
|
+
]),
|
|
314
|
+
{ status: 200 },
|
|
315
|
+
),
|
|
316
|
+
);
|
|
317
|
+
const client = new CogneeClient();
|
|
318
|
+
await client.healthCheck();
|
|
319
|
+
const results = await client.search('query', { limit: 2 });
|
|
320
|
+
expect(results).toHaveLength(2);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should return empty when not available', async () => {
|
|
324
|
+
const client = new CogneeClient();
|
|
325
|
+
const results = await client.search('query');
|
|
326
|
+
expect(results).toEqual([]);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should return empty on HTTP error', async () => {
|
|
330
|
+
mockWithAuth(async () => new Response('error', { status: 500 }));
|
|
331
|
+
const client = new CogneeClient();
|
|
332
|
+
await client.healthCheck();
|
|
333
|
+
const results = await client.search('query');
|
|
334
|
+
expect(results).toEqual([]);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should send correct search body fields', async () => {
|
|
338
|
+
let capturedBody = '';
|
|
339
|
+
mockWithAuth(async (_url, init) => {
|
|
340
|
+
capturedBody = init?.body as string;
|
|
341
|
+
return new Response(JSON.stringify([]), { status: 200 });
|
|
342
|
+
});
|
|
343
|
+
const client = new CogneeClient({ dataset: 'my-ds' });
|
|
344
|
+
await client.healthCheck();
|
|
345
|
+
await client.search('my query', { searchType: 'CHUNKS', limit: 5 });
|
|
346
|
+
const parsed = JSON.parse(capturedBody);
|
|
347
|
+
expect(parsed.query).toBe('my query');
|
|
348
|
+
expect(parsed.search_type).toBe('CHUNKS');
|
|
349
|
+
expect(parsed.topK).toBe(5);
|
|
350
|
+
expect(parsed.datasets).toEqual(['my-ds']);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should default to CHUNKS search type', async () => {
|
|
354
|
+
let capturedBody = '';
|
|
355
|
+
mockWithAuth(async (_url, init) => {
|
|
356
|
+
capturedBody = init?.body as string;
|
|
357
|
+
return new Response(JSON.stringify([]), { status: 200 });
|
|
358
|
+
});
|
|
359
|
+
const client = new CogneeClient();
|
|
360
|
+
await client.healthCheck();
|
|
361
|
+
await client.search('query');
|
|
362
|
+
const parsed = JSON.parse(capturedBody);
|
|
363
|
+
expect(parsed.search_type).toBe('CHUNKS');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should handle results without payload.id gracefully', async () => {
|
|
367
|
+
mockWithAuth(
|
|
368
|
+
async () =>
|
|
369
|
+
new Response(JSON.stringify([{ id: 'fallback-id', text: 'No payload', score: 0.5 }]), {
|
|
370
|
+
status: 200,
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
const client = new CogneeClient();
|
|
374
|
+
await client.healthCheck();
|
|
375
|
+
const results = await client.search('query');
|
|
376
|
+
expect(results[0].id).toBe('fallback-id');
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// ─── Auth ─────────────────────────────────────────────────────
|
|
381
|
+
|
|
382
|
+
describe('auth', () => {
|
|
383
|
+
it('should auto-login with service account on first API call', async () => {
|
|
384
|
+
const calls: string[] = [];
|
|
385
|
+
mockFetch(async (url, init) => {
|
|
386
|
+
calls.push(url);
|
|
387
|
+
if (isHealthCheck(url, init)) return new Response('ok', { status: 200 });
|
|
388
|
+
if (url.includes('/auth/login')) {
|
|
389
|
+
return new Response(JSON.stringify({ access_token: 'auto-jwt' }), { status: 200 });
|
|
390
|
+
}
|
|
391
|
+
return new Response('ok', { status: 200 });
|
|
392
|
+
});
|
|
393
|
+
const client = new CogneeClient();
|
|
394
|
+
await client.healthCheck();
|
|
395
|
+
await client.cognify();
|
|
396
|
+
expect(calls.some((c) => c.includes('/auth/login'))).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should register then login if login fails', async () => {
|
|
400
|
+
const calls: string[] = [];
|
|
401
|
+
let loginAttempts = 0;
|
|
402
|
+
mockFetch(async (url, init) => {
|
|
403
|
+
calls.push(url);
|
|
404
|
+
if (isHealthCheck(url, init)) return new Response('ok', { status: 200 });
|
|
405
|
+
if (url.includes('/auth/login')) {
|
|
406
|
+
loginAttempts++;
|
|
407
|
+
if (loginAttempts === 1) return new Response('bad', { status: 400 });
|
|
408
|
+
return new Response(JSON.stringify({ access_token: 'new-jwt' }), { status: 200 });
|
|
409
|
+
}
|
|
410
|
+
if (url.includes('/auth/register')) {
|
|
411
|
+
return new Response(JSON.stringify({ id: 'new-user' }), { status: 200 });
|
|
412
|
+
}
|
|
413
|
+
return new Response('ok', { status: 200 });
|
|
414
|
+
});
|
|
415
|
+
const client = new CogneeClient();
|
|
416
|
+
await client.healthCheck();
|
|
417
|
+
await client.cognify();
|
|
418
|
+
expect(calls.filter((c) => c.includes('/auth/login'))).toHaveLength(2);
|
|
419
|
+
expect(calls.filter((c) => c.includes('/auth/register'))).toHaveLength(1);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should use pre-configured apiToken without login', async () => {
|
|
423
|
+
const calls: string[] = [];
|
|
424
|
+
mockFetch(async (url, init) => {
|
|
425
|
+
calls.push(url);
|
|
426
|
+
if (isHealthCheck(url, init)) return new Response('ok', { status: 200 });
|
|
427
|
+
return new Response('ok', { status: 200 });
|
|
428
|
+
});
|
|
429
|
+
const client = new CogneeClient({ apiToken: 'pre-configured' });
|
|
430
|
+
await client.healthCheck();
|
|
431
|
+
await client.cognify();
|
|
432
|
+
expect(calls.some((c) => c.includes('/auth/'))).toBe(false);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should fall back to no auth if login and register both fail', async () => {
|
|
436
|
+
mockFetch(async (url, init) => {
|
|
437
|
+
if (isHealthCheck(url, init)) return new Response('ok', { status: 200 });
|
|
438
|
+
if (isAuthCall(url)) return new Response('fail', { status: 500 });
|
|
439
|
+
return new Response('ok', { status: 200 });
|
|
440
|
+
});
|
|
441
|
+
const client = new CogneeClient();
|
|
442
|
+
await client.healthCheck();
|
|
443
|
+
// Should not throw — falls back to no auth
|
|
444
|
+
const result = await client.cognify();
|
|
445
|
+
expect(result.status).toBe('ok');
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('should reject default credentials for non-local endpoints', async () => {
|
|
449
|
+
const fetchMock = mockFetch(async (url, init) => {
|
|
450
|
+
if (url.includes('cognee.example.com') && url.endsWith('/'))
|
|
451
|
+
return new Response('ok', { status: 200 });
|
|
452
|
+
if (isAuthCall(url)) return new Response('fail', { status: 401 });
|
|
453
|
+
return new Response('ok', { status: 200 });
|
|
454
|
+
});
|
|
455
|
+
// Remote endpoint without explicit credentials — guard should prevent auto-register/login
|
|
456
|
+
const client = new CogneeClient({ baseUrl: 'https://cognee.example.com' });
|
|
457
|
+
await client.healthCheck();
|
|
458
|
+
await client.cognify();
|
|
459
|
+
// The guard should prevent any auth calls to the remote endpoint
|
|
460
|
+
const authCalls = fetchMock.mock.calls.filter(([url]: [string]) => isAuthCall(url));
|
|
461
|
+
expect(authCalls).toHaveLength(0);
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// ─── Cognify debounce ─────────────────────────────────────────
|
|
466
|
+
|
|
467
|
+
describe('cognify debounce', () => {
|
|
468
|
+
it('should schedule cognify after addEntries', async () => {
|
|
469
|
+
mockWithAuth();
|
|
470
|
+
const client = new CogneeClient({ cognifyDebounceMs: 50 });
|
|
471
|
+
await client.healthCheck();
|
|
472
|
+
await client.addEntries([makeEntry()]);
|
|
473
|
+
// Cognify not yet fired
|
|
474
|
+
expect(client.getConfig()).toBeTruthy(); // just verify no crash
|
|
475
|
+
client.resetPendingCognify();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should flush pending cognify on demand', async () => {
|
|
479
|
+
let cognifyCalled = false;
|
|
480
|
+
mockWithAuth(async (url) => {
|
|
481
|
+
if (url.includes('/cognify')) cognifyCalled = true;
|
|
482
|
+
return new Response('ok', { status: 200 });
|
|
483
|
+
});
|
|
484
|
+
const client = new CogneeClient({ cognifyDebounceMs: 60_000 });
|
|
485
|
+
await client.healthCheck();
|
|
486
|
+
await client.addEntries([makeEntry()]);
|
|
487
|
+
expect(cognifyCalled).toBe(false);
|
|
488
|
+
await client.flushPendingCognify();
|
|
489
|
+
expect(cognifyCalled).toBe(true);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('should reset pending cognify without firing', async () => {
|
|
493
|
+
let cognifyCalled = false;
|
|
494
|
+
mockWithAuth(async (url) => {
|
|
495
|
+
if (url.includes('/cognify')) cognifyCalled = true;
|
|
496
|
+
return new Response('ok', { status: 200 });
|
|
497
|
+
});
|
|
498
|
+
const client = new CogneeClient({ cognifyDebounceMs: 60_000 });
|
|
499
|
+
await client.healthCheck();
|
|
500
|
+
await client.addEntries([makeEntry()]);
|
|
501
|
+
client.resetPendingCognify();
|
|
502
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
503
|
+
expect(cognifyCalled).toBe(false);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// ─── getStatus ────────────────────────────────────────────────
|
|
508
|
+
|
|
509
|
+
describe('getStatus', () => {
|
|
510
|
+
it('should return null before health check', () => {
|
|
511
|
+
const client = new CogneeClient();
|
|
512
|
+
expect(client.getStatus()).toBeNull();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should return cached status after health check', async () => {
|
|
516
|
+
mockFetch(async () => new Response('ok', { status: 200 }));
|
|
517
|
+
const client = new CogneeClient();
|
|
518
|
+
await client.healthCheck();
|
|
519
|
+
const status = client.getStatus();
|
|
520
|
+
expect(status).not.toBeNull();
|
|
521
|
+
expect(status!.available).toBe(true);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
});
|