@inferencesh/sdk 0.6.14 → 0.6.19
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/README.md +1 -1
- package/dist/agent/actions.js +10 -0
- package/dist/agent/provider.d.ts +2 -1
- package/dist/agent/provider.js +5 -5
- package/dist/agent/types.d.ts +3 -0
- package/dist/api/apps.d.ts +4 -0
- package/dist/api/apps.js +6 -0
- package/dist/api/index.d.ts +3 -3
- package/dist/http/index.d.ts +3 -3
- package/dist/index.d.ts +9 -9
- package/dist/types.d.ts +131 -19
- package/dist/types.js +26 -9
- package/package.json +8 -5
- package/dist/agent/actions.test.d.ts +0 -1
- package/dist/agent/actions.test.js +0 -820
- package/dist/agent/api.test.d.ts +0 -1
- package/dist/agent/api.test.js +0 -246
- package/dist/agent/reducer.test.d.ts +0 -1
- package/dist/agent/reducer.test.js +0 -131
- package/dist/agent/types.test.d.ts +0 -1
- package/dist/agent/types.test.js +0 -75
- package/dist/api/agents.test.d.ts +0 -1
- package/dist/api/agents.test.js +0 -1014
- package/dist/api/api-keys.test.d.ts +0 -1
- package/dist/api/api-keys.test.js +0 -44
- package/dist/api/apps.test.d.ts +0 -1
- package/dist/api/apps.test.js +0 -135
- package/dist/api/chats.test.d.ts +0 -1
- package/dist/api/chats.test.js +0 -85
- package/dist/api/engines.test.d.ts +0 -1
- package/dist/api/engines.test.js +0 -133
- package/dist/api/files.test.d.ts +0 -1
- package/dist/api/files.test.js +0 -256
- package/dist/api/flow-runs.test.d.ts +0 -1
- package/dist/api/flow-runs.test.js +0 -97
- package/dist/api/flows.test.d.ts +0 -1
- package/dist/api/flows.test.js +0 -118
- package/dist/api/integrations.test.d.ts +0 -1
- package/dist/api/integrations.test.js +0 -109
- package/dist/api/knowledge.test.d.ts +0 -1
- package/dist/api/knowledge.test.js +0 -238
- package/dist/api/mcp-servers.test.d.ts +0 -1
- package/dist/api/mcp-servers.test.js +0 -98
- package/dist/api/projects.test.d.ts +0 -1
- package/dist/api/projects.test.js +0 -61
- package/dist/api/search.test.d.ts +0 -1
- package/dist/api/search.test.js +0 -50
- package/dist/api/secrets.test.d.ts +0 -1
- package/dist/api/secrets.test.js +0 -61
- package/dist/api/sessions.test.d.ts +0 -1
- package/dist/api/sessions.test.js +0 -61
- package/dist/api/tasks.test.d.ts +0 -1
- package/dist/api/tasks.test.js +0 -381
- package/dist/api/teams.test.d.ts +0 -1
- package/dist/api/teams.test.js +0 -139
- package/dist/client.test.d.ts +0 -1
- package/dist/client.test.js +0 -446
- package/dist/http/client.test.d.ts +0 -1
- package/dist/http/client.test.js +0 -435
- package/dist/http/errors.test.d.ts +0 -1
- package/dist/http/errors.test.js +0 -66
- package/dist/http/poll.test.d.ts +0 -1
- package/dist/http/poll.test.js +0 -146
- package/dist/http/streamable.test.d.ts +0 -4
- package/dist/http/streamable.test.js +0 -510
- package/dist/integration.test.d.ts +0 -9
- package/dist/integration.test.js +0 -107
- package/dist/proxy/express.test.d.ts +0 -1
- package/dist/proxy/express.test.js +0 -106
- package/dist/proxy/hono.test.d.ts +0 -1
- package/dist/proxy/hono.test.js +0 -90
- package/dist/proxy/index.test.d.ts +0 -1
- package/dist/proxy/index.test.js +0 -173
- package/dist/proxy/nextjs.test.d.ts +0 -1
- package/dist/proxy/nextjs.test.js +0 -125
- package/dist/proxy/remix.test.d.ts +0 -1
- package/dist/proxy/remix.test.js +0 -66
- package/dist/proxy/svelte.test.d.ts +0 -1
- package/dist/proxy/svelte.test.js +0 -69
- package/dist/sessions.integration.test.d.ts +0 -13
- package/dist/sessions.integration.test.js +0 -310
- package/dist/stream.test.d.ts +0 -1
- package/dist/stream.test.js +0 -363
- package/dist/streamable.integration.test.d.ts +0 -11
- package/dist/streamable.integration.test.js +0 -150
- package/dist/tool-builder.test.d.ts +0 -1
- package/dist/tool-builder.test.js +0 -407
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -43
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sessions integration tests for @inferencesh/sdk
|
|
3
|
-
*
|
|
4
|
-
* These tests hit the real API and require INFERENCE_API_KEY to be set.
|
|
5
|
-
* Run with: npm run test:integration
|
|
6
|
-
*
|
|
7
|
-
* Prerequisites:
|
|
8
|
-
* - API and scheduler must be running
|
|
9
|
-
* - Test app deployed: infsh/session-test
|
|
10
|
-
*
|
|
11
|
-
* @jest-environment node
|
|
12
|
-
*/
|
|
13
|
-
import { Inference } from './index';
|
|
14
|
-
import { TaskStatusCompleted } from './types';
|
|
15
|
-
const API_KEY = process.env.INFERENCE_API_KEY;
|
|
16
|
-
const BASE_URL = process.env.INFERENCE_BASE_URL || 'https://api.inference.sh';
|
|
17
|
-
// Session test app with multi-function support
|
|
18
|
-
const SESSION_TEST_APP = 'infsh/session-test';
|
|
19
|
-
const describeIfApiKey = API_KEY ? describe : describe.skip;
|
|
20
|
-
describeIfApiKey('Sessions Integration Tests', () => {
|
|
21
|
-
let client;
|
|
22
|
-
beforeAll(() => {
|
|
23
|
-
client = new Inference({
|
|
24
|
-
apiKey: API_KEY,
|
|
25
|
-
baseUrl: BASE_URL,
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
// ==========================================================================
|
|
29
|
-
// Session Creation Tests
|
|
30
|
-
// ==========================================================================
|
|
31
|
-
describe('Session Creation', () => {
|
|
32
|
-
it('should create a new session and return real session ID', async () => {
|
|
33
|
-
const result = await client.run({
|
|
34
|
-
app: SESSION_TEST_APP,
|
|
35
|
-
function: 'set_value',
|
|
36
|
-
input: { key: 'test_create', value: 'hello' },
|
|
37
|
-
session: 'new',
|
|
38
|
-
});
|
|
39
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
40
|
-
expect(result.session_id).toBeDefined();
|
|
41
|
-
expect(result.session_id).not.toBe('new');
|
|
42
|
-
}, 60000);
|
|
43
|
-
it('should return correct output data', async () => {
|
|
44
|
-
const result = await client.run({
|
|
45
|
-
app: SESSION_TEST_APP,
|
|
46
|
-
function: 'set_value',
|
|
47
|
-
input: { key: 'output_test', value: 'test_value' },
|
|
48
|
-
session: 'new',
|
|
49
|
-
});
|
|
50
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
51
|
-
const output = result.output;
|
|
52
|
-
expect(output.success).toBe(true);
|
|
53
|
-
expect(output.key).toBe('output_test');
|
|
54
|
-
expect(output.value).toBe('test_value');
|
|
55
|
-
}, 60000);
|
|
56
|
-
});
|
|
57
|
-
// ==========================================================================
|
|
58
|
-
// Session Continuity Tests
|
|
59
|
-
// ==========================================================================
|
|
60
|
-
describe('Session Continuity', () => {
|
|
61
|
-
it('should persist state across calls in same session', async () => {
|
|
62
|
-
// Create session with initial value
|
|
63
|
-
const result1 = await client.run({
|
|
64
|
-
app: SESSION_TEST_APP,
|
|
65
|
-
function: 'set_value',
|
|
66
|
-
input: { key: 'persist_key', value: 'persist_value' },
|
|
67
|
-
session: 'new',
|
|
68
|
-
});
|
|
69
|
-
expect(result1.status).toBe(TaskStatusCompleted);
|
|
70
|
-
const sessionId = result1.session_id;
|
|
71
|
-
// Retrieve value from same session
|
|
72
|
-
const result2 = await client.run({
|
|
73
|
-
app: SESSION_TEST_APP,
|
|
74
|
-
function: 'get_value',
|
|
75
|
-
input: { key: 'persist_key' },
|
|
76
|
-
session: sessionId,
|
|
77
|
-
});
|
|
78
|
-
expect(result2.status).toBe(TaskStatusCompleted);
|
|
79
|
-
expect(result2.session_id).toBe(sessionId);
|
|
80
|
-
const output = result2.output;
|
|
81
|
-
expect(output.found).toBe(true);
|
|
82
|
-
expect(output.value).toBe('persist_value');
|
|
83
|
-
}, 120000);
|
|
84
|
-
it('should accumulate multiple values in session', async () => {
|
|
85
|
-
// Create session
|
|
86
|
-
const result1 = await client.run({
|
|
87
|
-
app: SESSION_TEST_APP,
|
|
88
|
-
function: 'set_value',
|
|
89
|
-
input: { key: 'key1', value: 'value1' },
|
|
90
|
-
session: 'new',
|
|
91
|
-
});
|
|
92
|
-
expect(result1.status).toBe(TaskStatusCompleted);
|
|
93
|
-
const sessionId = result1.session_id;
|
|
94
|
-
// Add second value
|
|
95
|
-
const result2 = await client.run({
|
|
96
|
-
app: SESSION_TEST_APP,
|
|
97
|
-
function: 'set_value',
|
|
98
|
-
input: { key: 'key2', value: 'value2' },
|
|
99
|
-
session: sessionId,
|
|
100
|
-
});
|
|
101
|
-
expect(result2.status).toBe(TaskStatusCompleted);
|
|
102
|
-
// Get all state
|
|
103
|
-
const result3 = await client.run({
|
|
104
|
-
app: SESSION_TEST_APP,
|
|
105
|
-
function: 'get_all',
|
|
106
|
-
input: {},
|
|
107
|
-
session: sessionId,
|
|
108
|
-
});
|
|
109
|
-
expect(result3.status).toBe(TaskStatusCompleted);
|
|
110
|
-
const output = result3.output;
|
|
111
|
-
expect(output.state.key1).toBe('value1');
|
|
112
|
-
expect(output.state.key2).toBe('value2');
|
|
113
|
-
}, 120000);
|
|
114
|
-
});
|
|
115
|
-
// ==========================================================================
|
|
116
|
-
// Multi-Function Routing Tests
|
|
117
|
-
// ==========================================================================
|
|
118
|
-
describe('Multi-Function Routing', () => {
|
|
119
|
-
it('should route to increment function correctly', async () => {
|
|
120
|
-
const result = await client.run({
|
|
121
|
-
app: SESSION_TEST_APP,
|
|
122
|
-
function: 'increment',
|
|
123
|
-
input: { key: 'counter', amount: 5 },
|
|
124
|
-
session: 'new',
|
|
125
|
-
});
|
|
126
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
127
|
-
const output = result.output;
|
|
128
|
-
expect(output.key).toBe('counter');
|
|
129
|
-
expect(output.previous).toBe(0);
|
|
130
|
-
expect(output.current).toBe(5);
|
|
131
|
-
}, 60000);
|
|
132
|
-
it('should handle different input schemas per function', async () => {
|
|
133
|
-
// Create session
|
|
134
|
-
const result1 = await client.run({
|
|
135
|
-
app: SESSION_TEST_APP,
|
|
136
|
-
function: 'set_value',
|
|
137
|
-
input: { key: 'schema_test', value: 'v1' },
|
|
138
|
-
session: 'new',
|
|
139
|
-
});
|
|
140
|
-
expect(result1.status).toBe(TaskStatusCompleted);
|
|
141
|
-
const sessionId = result1.session_id;
|
|
142
|
-
// get_value has different schema (no 'value' field)
|
|
143
|
-
const result2 = await client.run({
|
|
144
|
-
app: SESSION_TEST_APP,
|
|
145
|
-
function: 'get_value',
|
|
146
|
-
input: { key: 'schema_test' },
|
|
147
|
-
session: sessionId,
|
|
148
|
-
});
|
|
149
|
-
expect(result2.status).toBe(TaskStatusCompleted);
|
|
150
|
-
// increment has different schema (amount field)
|
|
151
|
-
const result3 = await client.run({
|
|
152
|
-
app: SESSION_TEST_APP,
|
|
153
|
-
function: 'increment',
|
|
154
|
-
input: { key: 'cnt', amount: 10 },
|
|
155
|
-
session: sessionId,
|
|
156
|
-
});
|
|
157
|
-
expect(result3.status).toBe(TaskStatusCompleted);
|
|
158
|
-
// get_all has empty schema
|
|
159
|
-
const result4 = await client.run({
|
|
160
|
-
app: SESSION_TEST_APP,
|
|
161
|
-
function: 'get_all',
|
|
162
|
-
input: {},
|
|
163
|
-
session: sessionId,
|
|
164
|
-
});
|
|
165
|
-
expect(result4.status).toBe(TaskStatusCompleted);
|
|
166
|
-
}, 120000);
|
|
167
|
-
});
|
|
168
|
-
// ==========================================================================
|
|
169
|
-
// Session Isolation Tests
|
|
170
|
-
// ==========================================================================
|
|
171
|
-
describe('Session Isolation', () => {
|
|
172
|
-
it('should create different sessions for different "new" requests', async () => {
|
|
173
|
-
// Create first session
|
|
174
|
-
const result1 = await client.run({
|
|
175
|
-
app: SESSION_TEST_APP,
|
|
176
|
-
function: 'set_value',
|
|
177
|
-
input: { key: 'isolated', value: 'session1' },
|
|
178
|
-
session: 'new',
|
|
179
|
-
});
|
|
180
|
-
expect(result1.status).toBe(TaskStatusCompleted);
|
|
181
|
-
const session1 = result1.session_id;
|
|
182
|
-
// Create second session
|
|
183
|
-
const result2 = await client.run({
|
|
184
|
-
app: SESSION_TEST_APP,
|
|
185
|
-
function: 'set_value',
|
|
186
|
-
input: { key: 'isolated', value: 'session2' },
|
|
187
|
-
session: 'new',
|
|
188
|
-
});
|
|
189
|
-
expect(result2.status).toBe(TaskStatusCompleted);
|
|
190
|
-
const session2 = result2.session_id;
|
|
191
|
-
// Sessions should be different
|
|
192
|
-
expect(session1).not.toBe(session2);
|
|
193
|
-
}, 120000);
|
|
194
|
-
});
|
|
195
|
-
// ==========================================================================
|
|
196
|
-
// Sessions API Tests
|
|
197
|
-
// ==========================================================================
|
|
198
|
-
describe('Sessions API', () => {
|
|
199
|
-
it('should get session info', async () => {
|
|
200
|
-
// Create a session
|
|
201
|
-
const result = await client.run({
|
|
202
|
-
app: SESSION_TEST_APP,
|
|
203
|
-
function: 'set_value',
|
|
204
|
-
input: { key: 'api_test', value: 'hello' },
|
|
205
|
-
session: 'new',
|
|
206
|
-
});
|
|
207
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
208
|
-
const sessionId = result.session_id;
|
|
209
|
-
// Get session info via API
|
|
210
|
-
const sessionInfo = await client.sessions.get(sessionId);
|
|
211
|
-
// API returns flattened structure (Go embedded structs)
|
|
212
|
-
expect(sessionInfo.id).toBe(sessionId);
|
|
213
|
-
expect(sessionInfo.status).toBe('active');
|
|
214
|
-
expect(sessionInfo.call_count).toBeGreaterThanOrEqual(1);
|
|
215
|
-
}, 60000);
|
|
216
|
-
it('should list sessions', async () => {
|
|
217
|
-
const sessions = await client.sessions.list();
|
|
218
|
-
expect(Array.isArray(sessions)).toBe(true);
|
|
219
|
-
// Should have at least one session from previous tests
|
|
220
|
-
}, 30000);
|
|
221
|
-
it('should keepalive a session', async () => {
|
|
222
|
-
// Create a session
|
|
223
|
-
const result = await client.run({
|
|
224
|
-
app: SESSION_TEST_APP,
|
|
225
|
-
function: 'set_value',
|
|
226
|
-
input: { key: 'keepalive_test', value: 'hello' },
|
|
227
|
-
session: 'new',
|
|
228
|
-
});
|
|
229
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
230
|
-
const sessionId = result.session_id;
|
|
231
|
-
// Keepalive
|
|
232
|
-
const updated = await client.sessions.keepalive(sessionId);
|
|
233
|
-
// API returns flattened structure (Go embedded structs)
|
|
234
|
-
expect(updated.id).toBe(sessionId);
|
|
235
|
-
expect(updated.status).toBe('active');
|
|
236
|
-
}, 60000);
|
|
237
|
-
it('should end a session', async () => {
|
|
238
|
-
// Create a session
|
|
239
|
-
const result = await client.run({
|
|
240
|
-
app: SESSION_TEST_APP,
|
|
241
|
-
function: 'set_value',
|
|
242
|
-
input: { key: 'end_test', value: 'goodbye' },
|
|
243
|
-
session: 'new',
|
|
244
|
-
});
|
|
245
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
246
|
-
const sessionId = result.session_id;
|
|
247
|
-
// End the session
|
|
248
|
-
await client.sessions.end(sessionId);
|
|
249
|
-
// Trying to use ended session should fail
|
|
250
|
-
await expect(client.run({
|
|
251
|
-
app: SESSION_TEST_APP,
|
|
252
|
-
function: 'get_value',
|
|
253
|
-
input: { key: 'end_test' },
|
|
254
|
-
session: sessionId,
|
|
255
|
-
})).rejects.toThrow();
|
|
256
|
-
}, 60000);
|
|
257
|
-
});
|
|
258
|
-
// ==========================================================================
|
|
259
|
-
// Error Cases
|
|
260
|
-
// ==========================================================================
|
|
261
|
-
describe('Error Cases', () => {
|
|
262
|
-
it('should fail with invalid session ID', async () => {
|
|
263
|
-
await expect(client.run({
|
|
264
|
-
app: SESSION_TEST_APP,
|
|
265
|
-
function: 'get_value',
|
|
266
|
-
input: { key: 'foo' },
|
|
267
|
-
session: 'sess_invalid_does_not_exist_12345',
|
|
268
|
-
})).rejects.toThrow();
|
|
269
|
-
}, 30000);
|
|
270
|
-
});
|
|
271
|
-
// ==========================================================================
|
|
272
|
-
// Performance Tests
|
|
273
|
-
// ==========================================================================
|
|
274
|
-
describe('Performance', () => {
|
|
275
|
-
it('should be faster on warm worker', async () => {
|
|
276
|
-
// Create session (cold start)
|
|
277
|
-
const result1 = await client.run({
|
|
278
|
-
app: SESSION_TEST_APP,
|
|
279
|
-
function: 'set_value',
|
|
280
|
-
input: { key: 'perf_test', value: '1' },
|
|
281
|
-
session: 'new',
|
|
282
|
-
});
|
|
283
|
-
expect(result1.status).toBe(TaskStatusCompleted);
|
|
284
|
-
const sessionId = result1.session_id;
|
|
285
|
-
// Second call should be fast (warm worker)
|
|
286
|
-
const start = Date.now();
|
|
287
|
-
const result2 = await client.run({
|
|
288
|
-
app: SESSION_TEST_APP,
|
|
289
|
-
function: 'set_value',
|
|
290
|
-
input: { key: 'perf_test', value: '2' },
|
|
291
|
-
session: sessionId,
|
|
292
|
-
});
|
|
293
|
-
const elapsed = Date.now() - start;
|
|
294
|
-
expect(result2.status).toBe(TaskStatusCompleted);
|
|
295
|
-
// Log for visibility
|
|
296
|
-
console.log(`Warm worker call took: ${elapsed}ms`);
|
|
297
|
-
// Should be under 2 seconds for warm worker
|
|
298
|
-
expect(elapsed).toBeLessThan(2000);
|
|
299
|
-
}, 120000);
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
// Placeholder test that always runs
|
|
303
|
-
describe('Sessions Integration Test Setup', () => {
|
|
304
|
-
it('should have API key check', () => {
|
|
305
|
-
if (!API_KEY) {
|
|
306
|
-
console.log('⚠️ Skipping sessions integration tests - INFERENCE_API_KEY not set');
|
|
307
|
-
}
|
|
308
|
-
expect(true).toBe(true);
|
|
309
|
-
});
|
|
310
|
-
});
|
package/dist/stream.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/stream.test.js
DELETED
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
import { StreamManager } from './http/stream';
|
|
2
|
-
describe('StreamManager', () => {
|
|
3
|
-
let mockEventSource;
|
|
4
|
-
beforeEach(() => {
|
|
5
|
-
mockEventSource = {
|
|
6
|
-
onmessage: null,
|
|
7
|
-
onerror: null,
|
|
8
|
-
close: jest.fn(),
|
|
9
|
-
};
|
|
10
|
-
});
|
|
11
|
-
describe('constructor', () => {
|
|
12
|
-
it('should create instance with default options', () => {
|
|
13
|
-
const manager = new StreamManager({
|
|
14
|
-
createEventSource: async () => mockEventSource,
|
|
15
|
-
});
|
|
16
|
-
expect(manager).toBeDefined();
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
describe('connect', () => {
|
|
20
|
-
it('should call createEventSource', async () => {
|
|
21
|
-
const createEventSource = jest
|
|
22
|
-
.fn()
|
|
23
|
-
.mockResolvedValue(mockEventSource);
|
|
24
|
-
const manager = new StreamManager({ createEventSource });
|
|
25
|
-
await manager.connect();
|
|
26
|
-
expect(createEventSource).toHaveBeenCalled();
|
|
27
|
-
});
|
|
28
|
-
it('should call onStart when connected', async () => {
|
|
29
|
-
const onStart = jest.fn();
|
|
30
|
-
const manager = new StreamManager({
|
|
31
|
-
createEventSource: async () => mockEventSource,
|
|
32
|
-
onStart,
|
|
33
|
-
});
|
|
34
|
-
await manager.connect();
|
|
35
|
-
expect(onStart).toHaveBeenCalled();
|
|
36
|
-
});
|
|
37
|
-
it('should not call onStart when createEventSource returns null', async () => {
|
|
38
|
-
const onStart = jest.fn();
|
|
39
|
-
const manager = new StreamManager({
|
|
40
|
-
createEventSource: async () => null,
|
|
41
|
-
onStart,
|
|
42
|
-
});
|
|
43
|
-
await manager.connect();
|
|
44
|
-
expect(onStart).not.toHaveBeenCalled();
|
|
45
|
-
});
|
|
46
|
-
it('should parse JSON messages and call onData', async () => {
|
|
47
|
-
const onData = jest.fn();
|
|
48
|
-
const manager = new StreamManager({
|
|
49
|
-
createEventSource: async () => mockEventSource,
|
|
50
|
-
onData,
|
|
51
|
-
});
|
|
52
|
-
await manager.connect();
|
|
53
|
-
// Simulate receiving a message
|
|
54
|
-
const testData = { status: 'running', id: 'task-123' };
|
|
55
|
-
mockEventSource.onmessage?.({ data: JSON.stringify(testData) });
|
|
56
|
-
expect(onData).toHaveBeenCalledWith(testData);
|
|
57
|
-
});
|
|
58
|
-
it('should extract data from partial data wrapper and call onData', async () => {
|
|
59
|
-
const onData = jest.fn();
|
|
60
|
-
const manager = new StreamManager({
|
|
61
|
-
createEventSource: async () => mockEventSource,
|
|
62
|
-
onData,
|
|
63
|
-
});
|
|
64
|
-
await manager.connect();
|
|
65
|
-
// Simulate receiving a partial data wrapper from server
|
|
66
|
-
const innerData = { status: 7, id: 'task-123', logs: ['log1'] };
|
|
67
|
-
const partialWrapper = { data: innerData, fields: ['status', 'logs'] };
|
|
68
|
-
mockEventSource.onmessage?.({ data: JSON.stringify(partialWrapper) });
|
|
69
|
-
// onData should receive the extracted inner data, not the wrapper
|
|
70
|
-
expect(onData).toHaveBeenCalledWith(innerData);
|
|
71
|
-
});
|
|
72
|
-
it('should call onPartialData with data and fields for partial updates', async () => {
|
|
73
|
-
const onData = jest.fn();
|
|
74
|
-
const onPartialData = jest.fn();
|
|
75
|
-
const manager = new StreamManager({
|
|
76
|
-
createEventSource: async () => mockEventSource,
|
|
77
|
-
onData,
|
|
78
|
-
onPartialData,
|
|
79
|
-
});
|
|
80
|
-
await manager.connect();
|
|
81
|
-
// Simulate receiving a partial data wrapper
|
|
82
|
-
const innerData = { status: 7, id: 'task-123' };
|
|
83
|
-
const fields = ['status'];
|
|
84
|
-
const partialWrapper = { data: innerData, fields };
|
|
85
|
-
mockEventSource.onmessage?.({ data: JSON.stringify(partialWrapper) });
|
|
86
|
-
// When onPartialData is provided, only it is called (not onData)
|
|
87
|
-
expect(onPartialData).toHaveBeenCalledWith(innerData, fields);
|
|
88
|
-
expect(onData).not.toHaveBeenCalled();
|
|
89
|
-
});
|
|
90
|
-
it('should not call onPartialData for non-partial data', async () => {
|
|
91
|
-
const onData = jest.fn();
|
|
92
|
-
const onPartialData = jest.fn();
|
|
93
|
-
const manager = new StreamManager({
|
|
94
|
-
createEventSource: async () => mockEventSource,
|
|
95
|
-
onData,
|
|
96
|
-
onPartialData,
|
|
97
|
-
});
|
|
98
|
-
await manager.connect();
|
|
99
|
-
// Regular data without partial wrapper
|
|
100
|
-
const regularData = { status: 9, id: 'task-123' };
|
|
101
|
-
mockEventSource.onmessage?.({ data: JSON.stringify(regularData) });
|
|
102
|
-
expect(onPartialData).not.toHaveBeenCalled();
|
|
103
|
-
expect(onData).toHaveBeenCalledWith(regularData);
|
|
104
|
-
});
|
|
105
|
-
it('should call onError for invalid JSON', async () => {
|
|
106
|
-
const onError = jest.fn();
|
|
107
|
-
const manager = new StreamManager({
|
|
108
|
-
createEventSource: async () => mockEventSource,
|
|
109
|
-
onError,
|
|
110
|
-
});
|
|
111
|
-
await manager.connect();
|
|
112
|
-
mockEventSource.onmessage?.({ data: 'invalid json' });
|
|
113
|
-
expect(onError).toHaveBeenCalled();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
describe('stop', () => {
|
|
117
|
-
it('should close the event source', async () => {
|
|
118
|
-
const onStop = jest.fn();
|
|
119
|
-
const manager = new StreamManager({
|
|
120
|
-
createEventSource: async () => mockEventSource,
|
|
121
|
-
onStop,
|
|
122
|
-
});
|
|
123
|
-
await manager.connect();
|
|
124
|
-
manager.stop();
|
|
125
|
-
expect(mockEventSource.close).toHaveBeenCalled();
|
|
126
|
-
expect(onStop).toHaveBeenCalled();
|
|
127
|
-
});
|
|
128
|
-
it('should not process messages after stop', async () => {
|
|
129
|
-
const onData = jest.fn();
|
|
130
|
-
const manager = new StreamManager({
|
|
131
|
-
createEventSource: async () => mockEventSource,
|
|
132
|
-
onData,
|
|
133
|
-
});
|
|
134
|
-
await manager.connect();
|
|
135
|
-
manager.stop();
|
|
136
|
-
mockEventSource.onmessage?.({ data: '{"status":"running"}' });
|
|
137
|
-
expect(onData).not.toHaveBeenCalled();
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
describe('addEventListener', () => {
|
|
141
|
-
it('should deliver typed SSE events to registered listeners', async () => {
|
|
142
|
-
const typedListeners = {};
|
|
143
|
-
const eventSource = {
|
|
144
|
-
onmessage: null,
|
|
145
|
-
onerror: null,
|
|
146
|
-
close: jest.fn(),
|
|
147
|
-
addEventListener: (eventName, handler) => {
|
|
148
|
-
const listeners = typedListeners[eventName] || new Set();
|
|
149
|
-
listeners.add(handler);
|
|
150
|
-
typedListeners[eventName] = listeners;
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
const manager = new StreamManager({
|
|
154
|
-
createEventSource: async () => eventSource,
|
|
155
|
-
});
|
|
156
|
-
const onChat = jest.fn();
|
|
157
|
-
manager.addEventListener('chats', onChat);
|
|
158
|
-
await manager.connect();
|
|
159
|
-
const payload = { id: 'chat-1', status: 'open' };
|
|
160
|
-
typedListeners.chats?.forEach((handler) => handler({ data: JSON.stringify(payload) }));
|
|
161
|
-
expect(onChat).toHaveBeenCalledWith(payload);
|
|
162
|
-
});
|
|
163
|
-
it('should unwrap partial wrappers for typed events', async () => {
|
|
164
|
-
const typedListeners = {};
|
|
165
|
-
const eventSource = {
|
|
166
|
-
onmessage: null,
|
|
167
|
-
onerror: null,
|
|
168
|
-
close: jest.fn(),
|
|
169
|
-
addEventListener: (eventName, handler) => {
|
|
170
|
-
const listeners = typedListeners[eventName] || new Set();
|
|
171
|
-
listeners.add(handler);
|
|
172
|
-
typedListeners[eventName] = listeners;
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
const manager = new StreamManager({
|
|
176
|
-
createEventSource: async () => eventSource,
|
|
177
|
-
});
|
|
178
|
-
const onMessage = jest.fn();
|
|
179
|
-
manager.addEventListener('chat_messages', onMessage);
|
|
180
|
-
await manager.connect();
|
|
181
|
-
const inner = { id: 'msg-1', order: 1 };
|
|
182
|
-
typedListeners.chat_messages?.forEach((handler) => handler({
|
|
183
|
-
data: JSON.stringify({ data: inner, fields: ['order'] }),
|
|
184
|
-
}));
|
|
185
|
-
expect(onMessage).toHaveBeenCalledWith(inner);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
describe('reconnection', () => {
|
|
189
|
-
it('should attempt reconnection on error when autoReconnect is true', async () => {
|
|
190
|
-
jest.useFakeTimers();
|
|
191
|
-
const createEventSource = jest
|
|
192
|
-
.fn()
|
|
193
|
-
.mockResolvedValue(mockEventSource);
|
|
194
|
-
const manager = new StreamManager({
|
|
195
|
-
createEventSource,
|
|
196
|
-
autoReconnect: true,
|
|
197
|
-
reconnectDelayMs: 100,
|
|
198
|
-
});
|
|
199
|
-
await manager.connect();
|
|
200
|
-
expect(createEventSource).toHaveBeenCalledTimes(1);
|
|
201
|
-
// Simulate error
|
|
202
|
-
mockEventSource.onerror?.({});
|
|
203
|
-
// Fast-forward past reconnect delay
|
|
204
|
-
jest.advanceTimersByTime(100);
|
|
205
|
-
await Promise.resolve(); // Flush promises
|
|
206
|
-
expect(createEventSource).toHaveBeenCalledTimes(2);
|
|
207
|
-
jest.useRealTimers();
|
|
208
|
-
});
|
|
209
|
-
it('should not reconnect when autoReconnect is false', async () => {
|
|
210
|
-
const createEventSource = jest
|
|
211
|
-
.fn()
|
|
212
|
-
.mockResolvedValue(mockEventSource);
|
|
213
|
-
const manager = new StreamManager({
|
|
214
|
-
createEventSource,
|
|
215
|
-
autoReconnect: false,
|
|
216
|
-
});
|
|
217
|
-
await manager.connect();
|
|
218
|
-
mockEventSource.onerror?.({});
|
|
219
|
-
// Wait a bit
|
|
220
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
221
|
-
expect(createEventSource).toHaveBeenCalledTimes(1);
|
|
222
|
-
});
|
|
223
|
-
it('should stop reconnecting after maxReconnects initial failures', async () => {
|
|
224
|
-
jest.useFakeTimers();
|
|
225
|
-
const onError = jest.fn();
|
|
226
|
-
const createEventSource = jest
|
|
227
|
-
.fn()
|
|
228
|
-
.mockResolvedValue(mockEventSource);
|
|
229
|
-
const manager = new StreamManager({
|
|
230
|
-
createEventSource,
|
|
231
|
-
autoReconnect: true,
|
|
232
|
-
maxReconnects: 2,
|
|
233
|
-
reconnectDelayMs: 100,
|
|
234
|
-
onError,
|
|
235
|
-
});
|
|
236
|
-
await manager.connect();
|
|
237
|
-
mockEventSource.onerror?.({});
|
|
238
|
-
jest.advanceTimersByTime(100);
|
|
239
|
-
await Promise.resolve();
|
|
240
|
-
expect(createEventSource).toHaveBeenCalledTimes(2);
|
|
241
|
-
mockEventSource.onerror?.({});
|
|
242
|
-
jest.advanceTimersByTime(100);
|
|
243
|
-
await Promise.resolve();
|
|
244
|
-
expect(createEventSource).toHaveBeenCalledTimes(3);
|
|
245
|
-
mockEventSource.onerror?.({});
|
|
246
|
-
jest.advanceTimersByTime(100);
|
|
247
|
-
await Promise.resolve();
|
|
248
|
-
expect(createEventSource).toHaveBeenCalledTimes(3);
|
|
249
|
-
jest.useRealTimers();
|
|
250
|
-
});
|
|
251
|
-
it('should call onError and stop when createEventSource throws', async () => {
|
|
252
|
-
jest.useFakeTimers();
|
|
253
|
-
const onError = jest.fn();
|
|
254
|
-
const createEventSource = jest
|
|
255
|
-
.fn()
|
|
256
|
-
.mockRejectedValue(new Error('connection refused'));
|
|
257
|
-
const manager = new StreamManager({
|
|
258
|
-
createEventSource,
|
|
259
|
-
autoReconnect: false,
|
|
260
|
-
onError,
|
|
261
|
-
});
|
|
262
|
-
await manager.connect();
|
|
263
|
-
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'connection refused' }));
|
|
264
|
-
jest.useRealTimers();
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
describe('stopAfter and clearStopTimeout', () => {
|
|
268
|
-
it('should stop after the configured delay', async () => {
|
|
269
|
-
jest.useFakeTimers();
|
|
270
|
-
const onStop = jest.fn();
|
|
271
|
-
const manager = new StreamManager({
|
|
272
|
-
createEventSource: async () => mockEventSource,
|
|
273
|
-
onStop,
|
|
274
|
-
});
|
|
275
|
-
await manager.connect();
|
|
276
|
-
manager.stopAfter(5000);
|
|
277
|
-
jest.advanceTimersByTime(5000);
|
|
278
|
-
expect(mockEventSource.close).toHaveBeenCalled();
|
|
279
|
-
expect(onStop).toHaveBeenCalled();
|
|
280
|
-
jest.useRealTimers();
|
|
281
|
-
});
|
|
282
|
-
it('should cancel a pending stop when clearStopTimeout is called', async () => {
|
|
283
|
-
jest.useFakeTimers();
|
|
284
|
-
const manager = new StreamManager({
|
|
285
|
-
createEventSource: async () => mockEventSource,
|
|
286
|
-
});
|
|
287
|
-
await manager.connect();
|
|
288
|
-
manager.stopAfter(5000);
|
|
289
|
-
manager.clearStopTimeout();
|
|
290
|
-
jest.advanceTimersByTime(5000);
|
|
291
|
-
expect(mockEventSource.close).not.toHaveBeenCalled();
|
|
292
|
-
jest.useRealTimers();
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
describe('addEventListener lifecycle', () => {
|
|
296
|
-
it('should register listeners on an existing connection when added after connect', async () => {
|
|
297
|
-
const typedListeners = {};
|
|
298
|
-
const eventSource = {
|
|
299
|
-
onmessage: null,
|
|
300
|
-
onerror: null,
|
|
301
|
-
close: jest.fn(),
|
|
302
|
-
addEventListener: (eventName, handler) => {
|
|
303
|
-
const listeners = typedListeners[eventName] || new Set();
|
|
304
|
-
listeners.add(handler);
|
|
305
|
-
typedListeners[eventName] = listeners;
|
|
306
|
-
},
|
|
307
|
-
};
|
|
308
|
-
const manager = new StreamManager({
|
|
309
|
-
createEventSource: async () => eventSource,
|
|
310
|
-
});
|
|
311
|
-
await manager.connect();
|
|
312
|
-
const onChat = jest.fn();
|
|
313
|
-
manager.addEventListener('chats', onChat);
|
|
314
|
-
const payload = { id: 'chat-2' };
|
|
315
|
-
typedListeners.chats?.forEach((handler) => handler({ data: JSON.stringify(payload) }));
|
|
316
|
-
expect(onChat).toHaveBeenCalledWith(payload);
|
|
317
|
-
});
|
|
318
|
-
it('should remove a listener when the cleanup function is called', async () => {
|
|
319
|
-
const typedListeners = {};
|
|
320
|
-
const eventSource = {
|
|
321
|
-
onmessage: null,
|
|
322
|
-
onerror: null,
|
|
323
|
-
close: jest.fn(),
|
|
324
|
-
addEventListener: (eventName, handler) => {
|
|
325
|
-
const listeners = typedListeners[eventName] || new Set();
|
|
326
|
-
listeners.add(handler);
|
|
327
|
-
typedListeners[eventName] = listeners;
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
|
-
const manager = new StreamManager({
|
|
331
|
-
createEventSource: async () => eventSource,
|
|
332
|
-
});
|
|
333
|
-
const onChat = jest.fn();
|
|
334
|
-
const unsubscribe = manager.addEventListener('chats', onChat);
|
|
335
|
-
await manager.connect();
|
|
336
|
-
unsubscribe();
|
|
337
|
-
typedListeners.chats?.forEach((handler) => handler({ data: '{"id":"chat-3"}' }));
|
|
338
|
-
expect(onChat).not.toHaveBeenCalled();
|
|
339
|
-
});
|
|
340
|
-
it('should call onError when typed event payload is invalid JSON', async () => {
|
|
341
|
-
const typedListeners = {};
|
|
342
|
-
const eventSource = {
|
|
343
|
-
onmessage: null,
|
|
344
|
-
onerror: null,
|
|
345
|
-
close: jest.fn(),
|
|
346
|
-
addEventListener: (eventName, handler) => {
|
|
347
|
-
const listeners = typedListeners[eventName] || new Set();
|
|
348
|
-
listeners.add(handler);
|
|
349
|
-
typedListeners[eventName] = listeners;
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
const onError = jest.fn();
|
|
353
|
-
const manager = new StreamManager({
|
|
354
|
-
createEventSource: async () => eventSource,
|
|
355
|
-
onError,
|
|
356
|
-
});
|
|
357
|
-
manager.addEventListener('chats', jest.fn());
|
|
358
|
-
await manager.connect();
|
|
359
|
-
typedListeners.chats?.forEach((handler) => handler({ data: 'not-json' }));
|
|
360
|
-
expect(onError).toHaveBeenCalled();
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for streamable HTTP client
|
|
3
|
-
*
|
|
4
|
-
* Tests NDJSON streaming against the real API.
|
|
5
|
-
* Run with: INFERENCE_API_KEY=xxx npm run test:integration
|
|
6
|
-
*
|
|
7
|
-
* Note: Format negotiation tests may be skipped if server doesn't support NDJSON yet.
|
|
8
|
-
*
|
|
9
|
-
* @jest-environment node
|
|
10
|
-
*/
|
|
11
|
-
export {};
|