@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,510 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for streamable HTTP client
|
|
3
|
-
*/
|
|
4
|
-
import { streamable, streamableRaw, StreamableManager } from './streamable';
|
|
5
|
-
// Mock fetch for unit tests
|
|
6
|
-
const mockFetch = (chunks) => {
|
|
7
|
-
let chunkIndex = 0;
|
|
8
|
-
const mockReader = {
|
|
9
|
-
read: jest.fn().mockImplementation(async () => {
|
|
10
|
-
if (chunkIndex >= chunks.length) {
|
|
11
|
-
return { done: true, value: undefined };
|
|
12
|
-
}
|
|
13
|
-
const chunk = chunks[chunkIndex++];
|
|
14
|
-
return { done: false, value: new TextEncoder().encode(chunk) };
|
|
15
|
-
}),
|
|
16
|
-
releaseLock: jest.fn(),
|
|
17
|
-
};
|
|
18
|
-
return jest.fn().mockResolvedValue({
|
|
19
|
-
ok: true,
|
|
20
|
-
status: 200,
|
|
21
|
-
body: {
|
|
22
|
-
getReader: () => mockReader,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
describe('streamable', () => {
|
|
27
|
-
const originalFetch = global.fetch;
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
global.fetch = originalFetch;
|
|
30
|
-
});
|
|
31
|
-
it('should parse NDJSON lines', async () => {
|
|
32
|
-
global.fetch = mockFetch([
|
|
33
|
-
'{"id":1,"name":"first"}\n',
|
|
34
|
-
'{"id":2,"name":"second"}\n',
|
|
35
|
-
]);
|
|
36
|
-
const results = [];
|
|
37
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
38
|
-
results.push(item);
|
|
39
|
-
}
|
|
40
|
-
expect(results).toHaveLength(2);
|
|
41
|
-
expect(results[0]).toEqual({ id: 1, name: 'first' });
|
|
42
|
-
expect(results[1]).toEqual({ id: 2, name: 'second' });
|
|
43
|
-
});
|
|
44
|
-
it('should skip heartbeats by default', async () => {
|
|
45
|
-
global.fetch = mockFetch([
|
|
46
|
-
'{"id":1}\n',
|
|
47
|
-
'{"type":"heartbeat"}\n',
|
|
48
|
-
'{"id":2}\n',
|
|
49
|
-
]);
|
|
50
|
-
const results = [];
|
|
51
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
52
|
-
results.push(item);
|
|
53
|
-
}
|
|
54
|
-
expect(results).toHaveLength(2);
|
|
55
|
-
expect(results[0]).toEqual({ id: 1 });
|
|
56
|
-
expect(results[1]).toEqual({ id: 2 });
|
|
57
|
-
});
|
|
58
|
-
it('should include heartbeats when skipHeartbeats is false', async () => {
|
|
59
|
-
global.fetch = mockFetch([
|
|
60
|
-
'{"id":1}\n',
|
|
61
|
-
'{"type":"heartbeat"}\n',
|
|
62
|
-
'{"id":2}\n',
|
|
63
|
-
]);
|
|
64
|
-
const results = [];
|
|
65
|
-
for await (const item of streamable('http://test.com/stream', { skipHeartbeats: false })) {
|
|
66
|
-
results.push(item);
|
|
67
|
-
}
|
|
68
|
-
expect(results).toHaveLength(3);
|
|
69
|
-
});
|
|
70
|
-
it('should unwrap data from wrapped messages', async () => {
|
|
71
|
-
global.fetch = mockFetch([
|
|
72
|
-
'{"data":{"id":1},"fields":["id"]}\n',
|
|
73
|
-
'{"event":"update","data":{"id":2}}\n',
|
|
74
|
-
]);
|
|
75
|
-
const results = [];
|
|
76
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
77
|
-
results.push(item);
|
|
78
|
-
}
|
|
79
|
-
expect(results).toHaveLength(2);
|
|
80
|
-
expect(results[0]).toEqual({ id: 1 });
|
|
81
|
-
expect(results[1]).toEqual({ id: 2 });
|
|
82
|
-
});
|
|
83
|
-
it('should handle chunked data across multiple reads', async () => {
|
|
84
|
-
global.fetch = mockFetch([
|
|
85
|
-
'{"id":1}\n{"id":', // First chunk ends mid-JSON
|
|
86
|
-
'2}\n', // Second chunk completes it
|
|
87
|
-
]);
|
|
88
|
-
const results = [];
|
|
89
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
90
|
-
results.push(item);
|
|
91
|
-
}
|
|
92
|
-
expect(results).toHaveLength(2);
|
|
93
|
-
expect(results[0]).toEqual({ id: 1 });
|
|
94
|
-
expect(results[1]).toEqual({ id: 2 });
|
|
95
|
-
});
|
|
96
|
-
it('should handle empty lines', async () => {
|
|
97
|
-
global.fetch = mockFetch([
|
|
98
|
-
'{"id":1}\n',
|
|
99
|
-
'\n',
|
|
100
|
-
'{"id":2}\n',
|
|
101
|
-
'\n\n',
|
|
102
|
-
]);
|
|
103
|
-
const results = [];
|
|
104
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
105
|
-
results.push(item);
|
|
106
|
-
}
|
|
107
|
-
expect(results).toHaveLength(2);
|
|
108
|
-
});
|
|
109
|
-
it('should set correct headers', async () => {
|
|
110
|
-
const mockFetchFn = mockFetch(['{"ok":true}\n']);
|
|
111
|
-
global.fetch = mockFetchFn;
|
|
112
|
-
const results = [];
|
|
113
|
-
for await (const item of streamable('http://test.com/stream', {
|
|
114
|
-
headers: { 'Authorization': 'Bearer token' },
|
|
115
|
-
})) {
|
|
116
|
-
results.push(item);
|
|
117
|
-
}
|
|
118
|
-
expect(mockFetchFn).toHaveBeenCalledWith('http://test.com/stream', expect.objectContaining({
|
|
119
|
-
method: 'GET',
|
|
120
|
-
headers: expect.objectContaining({
|
|
121
|
-
'Accept': 'application/x-ndjson',
|
|
122
|
-
'Authorization': 'Bearer token',
|
|
123
|
-
}),
|
|
124
|
-
}));
|
|
125
|
-
});
|
|
126
|
-
it('should use POST when body is provided', async () => {
|
|
127
|
-
const mockFetchFn = mockFetch(['{"ok":true}\n']);
|
|
128
|
-
global.fetch = mockFetchFn;
|
|
129
|
-
const results = [];
|
|
130
|
-
for await (const item of streamable('http://test.com/stream', {
|
|
131
|
-
body: { query: 'test' },
|
|
132
|
-
})) {
|
|
133
|
-
results.push(item);
|
|
134
|
-
}
|
|
135
|
-
expect(mockFetchFn).toHaveBeenCalledWith('http://test.com/stream', expect.objectContaining({
|
|
136
|
-
method: 'POST',
|
|
137
|
-
headers: expect.objectContaining({
|
|
138
|
-
'Content-Type': 'application/json',
|
|
139
|
-
}),
|
|
140
|
-
body: JSON.stringify({ query: 'test' }),
|
|
141
|
-
}));
|
|
142
|
-
});
|
|
143
|
-
it('should throw on HTTP error', async () => {
|
|
144
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
145
|
-
ok: false,
|
|
146
|
-
status: 401,
|
|
147
|
-
text: async () => 'Unauthorized',
|
|
148
|
-
});
|
|
149
|
-
await expect(async () => {
|
|
150
|
-
for await (const _ of streamable('http://test.com/stream')) {
|
|
151
|
-
// consume
|
|
152
|
-
}
|
|
153
|
-
}).rejects.toThrow('HTTP 401: Unauthorized');
|
|
154
|
-
});
|
|
155
|
-
it('should throw when the response has no body', async () => {
|
|
156
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
157
|
-
ok: true,
|
|
158
|
-
status: 200,
|
|
159
|
-
body: null,
|
|
160
|
-
});
|
|
161
|
-
await expect(async () => {
|
|
162
|
-
for await (const _ of streamable('http://test.com/stream')) {
|
|
163
|
-
// consume
|
|
164
|
-
}
|
|
165
|
-
}).rejects.toThrow('No response body');
|
|
166
|
-
});
|
|
167
|
-
// Edge cases for chunking/buffering
|
|
168
|
-
describe('chunking edge cases', () => {
|
|
169
|
-
it('should handle message split across many chunks', async () => {
|
|
170
|
-
// Split a single JSON object into 5 tiny chunks
|
|
171
|
-
global.fetch = mockFetch([
|
|
172
|
-
'{"id":',
|
|
173
|
-
'123,',
|
|
174
|
-
'"name"',
|
|
175
|
-
':"test',
|
|
176
|
-
'"}\n',
|
|
177
|
-
]);
|
|
178
|
-
const results = [];
|
|
179
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
180
|
-
results.push(item);
|
|
181
|
-
}
|
|
182
|
-
expect(results).toHaveLength(1);
|
|
183
|
-
expect(results[0]).toEqual({ id: 123, name: 'test' });
|
|
184
|
-
});
|
|
185
|
-
it('should handle single-byte chunks', async () => {
|
|
186
|
-
const json = '{"id":1}\n';
|
|
187
|
-
const chunks = json.split('').map(c => c); // One char per chunk
|
|
188
|
-
global.fetch = mockFetch(chunks);
|
|
189
|
-
const results = [];
|
|
190
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
191
|
-
results.push(item);
|
|
192
|
-
}
|
|
193
|
-
expect(results).toHaveLength(1);
|
|
194
|
-
expect(results[0]).toEqual({ id: 1 });
|
|
195
|
-
});
|
|
196
|
-
it('should handle newline exactly at chunk boundary', async () => {
|
|
197
|
-
global.fetch = mockFetch([
|
|
198
|
-
'{"id":1}',
|
|
199
|
-
'\n',
|
|
200
|
-
'{"id":2}\n',
|
|
201
|
-
]);
|
|
202
|
-
const results = [];
|
|
203
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
204
|
-
results.push(item);
|
|
205
|
-
}
|
|
206
|
-
expect(results).toHaveLength(2);
|
|
207
|
-
});
|
|
208
|
-
it('should handle multiple messages in single chunk', async () => {
|
|
209
|
-
global.fetch = mockFetch([
|
|
210
|
-
'{"id":1}\n{"id":2}\n{"id":3}\n',
|
|
211
|
-
]);
|
|
212
|
-
const results = [];
|
|
213
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
214
|
-
results.push(item);
|
|
215
|
-
}
|
|
216
|
-
expect(results).toHaveLength(3);
|
|
217
|
-
});
|
|
218
|
-
it('should handle empty chunks', async () => {
|
|
219
|
-
global.fetch = mockFetch([
|
|
220
|
-
'{"id":1}\n',
|
|
221
|
-
'',
|
|
222
|
-
'',
|
|
223
|
-
'{"id":2}\n',
|
|
224
|
-
]);
|
|
225
|
-
const results = [];
|
|
226
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
227
|
-
results.push(item);
|
|
228
|
-
}
|
|
229
|
-
expect(results).toHaveLength(2);
|
|
230
|
-
});
|
|
231
|
-
it('should handle trailing data without final newline', async () => {
|
|
232
|
-
global.fetch = mockFetch([
|
|
233
|
-
'{"id":1}\n',
|
|
234
|
-
'{"id":2}', // No trailing newline
|
|
235
|
-
]);
|
|
236
|
-
const results = [];
|
|
237
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
238
|
-
results.push(item);
|
|
239
|
-
}
|
|
240
|
-
expect(results).toHaveLength(2);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
describe('unicode handling', () => {
|
|
244
|
-
it('should handle unicode in messages', async () => {
|
|
245
|
-
global.fetch = mockFetch([
|
|
246
|
-
'{"text":"Hello 世界 🎉"}\n',
|
|
247
|
-
]);
|
|
248
|
-
const results = [];
|
|
249
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
250
|
-
results.push(item);
|
|
251
|
-
}
|
|
252
|
-
expect(results[0]).toEqual({ text: 'Hello 世界 🎉' });
|
|
253
|
-
});
|
|
254
|
-
it('should handle unicode split across chunks', async () => {
|
|
255
|
-
// 世 is 3 bytes in UTF-8: E4 B8 96
|
|
256
|
-
// Split it across chunks
|
|
257
|
-
const encoder = new TextEncoder();
|
|
258
|
-
const fullText = '{"text":"世"}\n';
|
|
259
|
-
const bytes = encoder.encode(fullText);
|
|
260
|
-
// Create mock that returns raw bytes split mid-character
|
|
261
|
-
let chunkIndex = 0;
|
|
262
|
-
const chunks = [
|
|
263
|
-
bytes.slice(0, 10), // '{"text":"' + first byte of 世
|
|
264
|
-
bytes.slice(10), // rest of 世 + '"}\n'
|
|
265
|
-
];
|
|
266
|
-
const mockReader = {
|
|
267
|
-
read: jest.fn().mockImplementation(async () => {
|
|
268
|
-
if (chunkIndex >= chunks.length) {
|
|
269
|
-
return { done: true, value: undefined };
|
|
270
|
-
}
|
|
271
|
-
return { done: false, value: chunks[chunkIndex++] };
|
|
272
|
-
}),
|
|
273
|
-
releaseLock: jest.fn(),
|
|
274
|
-
};
|
|
275
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
276
|
-
ok: true,
|
|
277
|
-
body: { getReader: () => mockReader },
|
|
278
|
-
});
|
|
279
|
-
const results = [];
|
|
280
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
281
|
-
results.push(item);
|
|
282
|
-
}
|
|
283
|
-
expect(results[0]).toEqual({ text: '世' });
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
describe('error handling', () => {
|
|
287
|
-
it('should handle invalid JSON gracefully', async () => {
|
|
288
|
-
global.fetch = mockFetch([
|
|
289
|
-
'{"id":1}\n',
|
|
290
|
-
'not valid json\n',
|
|
291
|
-
'{"id":2}\n',
|
|
292
|
-
]);
|
|
293
|
-
const results = [];
|
|
294
|
-
await expect(async () => {
|
|
295
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
296
|
-
results.push(item);
|
|
297
|
-
}
|
|
298
|
-
}).rejects.toThrow(); // Should throw on invalid JSON
|
|
299
|
-
// First valid message should have been processed
|
|
300
|
-
expect(results).toHaveLength(1);
|
|
301
|
-
});
|
|
302
|
-
it('should handle network error mid-stream', async () => {
|
|
303
|
-
let readCount = 0;
|
|
304
|
-
const mockReader = {
|
|
305
|
-
read: jest.fn().mockImplementation(async () => {
|
|
306
|
-
readCount++;
|
|
307
|
-
if (readCount === 1) {
|
|
308
|
-
return { done: false, value: new TextEncoder().encode('{"id":1}\n') };
|
|
309
|
-
}
|
|
310
|
-
throw new Error('Network error');
|
|
311
|
-
}),
|
|
312
|
-
releaseLock: jest.fn(),
|
|
313
|
-
};
|
|
314
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
315
|
-
ok: true,
|
|
316
|
-
body: { getReader: () => mockReader },
|
|
317
|
-
});
|
|
318
|
-
const results = [];
|
|
319
|
-
await expect(async () => {
|
|
320
|
-
for await (const item of streamable('http://test.com/stream')) {
|
|
321
|
-
results.push(item);
|
|
322
|
-
}
|
|
323
|
-
}).rejects.toThrow('Network error');
|
|
324
|
-
expect(results).toHaveLength(1);
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
describe('streamableRaw', () => {
|
|
329
|
-
const originalFetch = global.fetch;
|
|
330
|
-
afterEach(() => {
|
|
331
|
-
global.fetch = originalFetch;
|
|
332
|
-
});
|
|
333
|
-
it('should preserve event and fields in raw mode', async () => {
|
|
334
|
-
global.fetch = mockFetch([
|
|
335
|
-
'{"event":"update","data":{"id":1},"fields":["id"]}\n',
|
|
336
|
-
]);
|
|
337
|
-
const results = [];
|
|
338
|
-
for await (const item of streamableRaw('http://test.com/stream')) {
|
|
339
|
-
results.push(item);
|
|
340
|
-
}
|
|
341
|
-
expect(results).toHaveLength(1);
|
|
342
|
-
expect(results[0]).toEqual({
|
|
343
|
-
event: 'update',
|
|
344
|
-
data: { id: 1 },
|
|
345
|
-
fields: ['id'],
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
describe('StreamableManager', () => {
|
|
350
|
-
const originalFetch = global.fetch;
|
|
351
|
-
afterEach(() => {
|
|
352
|
-
global.fetch = originalFetch;
|
|
353
|
-
});
|
|
354
|
-
it('should call onData for each message', async () => {
|
|
355
|
-
global.fetch = mockFetch([
|
|
356
|
-
'{"id":1}\n',
|
|
357
|
-
'{"id":2}\n',
|
|
358
|
-
]);
|
|
359
|
-
const messages = [];
|
|
360
|
-
const manager = new StreamableManager({
|
|
361
|
-
url: 'http://test.com/stream',
|
|
362
|
-
onData: (data) => messages.push(data),
|
|
363
|
-
});
|
|
364
|
-
await manager.start();
|
|
365
|
-
expect(messages).toHaveLength(2);
|
|
366
|
-
expect(messages[0]).toEqual({ id: 1 });
|
|
367
|
-
expect(messages[1]).toEqual({ id: 2 });
|
|
368
|
-
});
|
|
369
|
-
it('should call onPartialData for partial updates', async () => {
|
|
370
|
-
global.fetch = mockFetch([
|
|
371
|
-
'{"data":{"id":1},"fields":["id"]}\n',
|
|
372
|
-
]);
|
|
373
|
-
const partials = [];
|
|
374
|
-
const manager = new StreamableManager({
|
|
375
|
-
url: 'http://test.com/stream',
|
|
376
|
-
onPartialData: (data, fields) => partials.push({ data, fields }),
|
|
377
|
-
});
|
|
378
|
-
await manager.start();
|
|
379
|
-
expect(partials).toHaveLength(1);
|
|
380
|
-
expect(partials[0]).toEqual({ data: { id: 1 }, fields: ['id'] });
|
|
381
|
-
});
|
|
382
|
-
it('should call lifecycle callbacks', async () => {
|
|
383
|
-
global.fetch = mockFetch(['{"ok":true}\n']);
|
|
384
|
-
const events = [];
|
|
385
|
-
const manager = new StreamableManager({
|
|
386
|
-
url: 'http://test.com/stream',
|
|
387
|
-
onStart: () => events.push('start'),
|
|
388
|
-
onEnd: () => events.push('end'),
|
|
389
|
-
onData: () => events.push('data'),
|
|
390
|
-
});
|
|
391
|
-
await manager.start();
|
|
392
|
-
expect(events).toEqual(['start', 'data', 'end']);
|
|
393
|
-
});
|
|
394
|
-
it('should dispatch typed events to addEventListener subscribers', async () => {
|
|
395
|
-
global.fetch = mockFetch([
|
|
396
|
-
'{"event":"chats","data":{"id":"chat-1","status":"busy"}}\n',
|
|
397
|
-
'{"event":"chat_messages","data":{"id":"msg-1","content":"hi"}}\n',
|
|
398
|
-
]);
|
|
399
|
-
const onChat = jest.fn();
|
|
400
|
-
const onMessage = jest.fn();
|
|
401
|
-
const manager = new StreamableManager({
|
|
402
|
-
url: 'http://test.com/stream',
|
|
403
|
-
onData: jest.fn(),
|
|
404
|
-
});
|
|
405
|
-
manager.addEventListener('chats', onChat);
|
|
406
|
-
manager.addEventListener('chat_messages', onMessage);
|
|
407
|
-
await manager.start();
|
|
408
|
-
expect(onChat).toHaveBeenCalledWith({ id: 'chat-1', status: 'busy' });
|
|
409
|
-
expect(onMessage).toHaveBeenCalledWith({ id: 'msg-1', content: 'hi' });
|
|
410
|
-
});
|
|
411
|
-
it('should stop dispatching to an unsubscribed listener', async () => {
|
|
412
|
-
global.fetch = mockFetch([
|
|
413
|
-
'{"event":"chats","data":{"id":"chat-1"}}\n',
|
|
414
|
-
]);
|
|
415
|
-
const removedListener = jest.fn();
|
|
416
|
-
const activeListener = jest.fn();
|
|
417
|
-
const manager = new StreamableManager({
|
|
418
|
-
url: 'http://test.com/stream',
|
|
419
|
-
});
|
|
420
|
-
const unsubscribe = manager.addEventListener('chats', removedListener);
|
|
421
|
-
manager.addEventListener('chats', activeListener);
|
|
422
|
-
unsubscribe();
|
|
423
|
-
await manager.start();
|
|
424
|
-
expect(removedListener).not.toHaveBeenCalled();
|
|
425
|
-
expect(activeListener).toHaveBeenCalledWith({ id: 'chat-1' });
|
|
426
|
-
});
|
|
427
|
-
it('should ignore start() when a stream is already running', async () => {
|
|
428
|
-
let readCount = 0;
|
|
429
|
-
const mockReader = {
|
|
430
|
-
read: jest.fn().mockImplementation(async () => {
|
|
431
|
-
readCount++;
|
|
432
|
-
if (readCount > 3) {
|
|
433
|
-
return { done: true, value: undefined };
|
|
434
|
-
}
|
|
435
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
436
|
-
return { done: false, value: new TextEncoder().encode('{"id":1}\n') };
|
|
437
|
-
}),
|
|
438
|
-
releaseLock: jest.fn(),
|
|
439
|
-
};
|
|
440
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
441
|
-
ok: true,
|
|
442
|
-
body: { getReader: () => mockReader },
|
|
443
|
-
});
|
|
444
|
-
const onStart = jest.fn();
|
|
445
|
-
const manager = new StreamableManager({
|
|
446
|
-
url: 'http://test.com/stream',
|
|
447
|
-
onStart,
|
|
448
|
-
onData: () => { },
|
|
449
|
-
});
|
|
450
|
-
const firstStart = manager.start();
|
|
451
|
-
await manager.start();
|
|
452
|
-
await firstStart;
|
|
453
|
-
expect(onStart).toHaveBeenCalledTimes(1);
|
|
454
|
-
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
455
|
-
});
|
|
456
|
-
it('should handle stop()', async () => {
|
|
457
|
-
let readCount = 0;
|
|
458
|
-
const mockReader = {
|
|
459
|
-
read: jest.fn().mockImplementation(async () => {
|
|
460
|
-
readCount++;
|
|
461
|
-
if (readCount > 10) {
|
|
462
|
-
return { done: true, value: undefined };
|
|
463
|
-
}
|
|
464
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
465
|
-
return { done: false, value: new TextEncoder().encode('{"id":1}\n') };
|
|
466
|
-
}),
|
|
467
|
-
releaseLock: jest.fn(),
|
|
468
|
-
};
|
|
469
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
470
|
-
ok: true,
|
|
471
|
-
body: { getReader: () => mockReader },
|
|
472
|
-
});
|
|
473
|
-
const manager = new StreamableManager({
|
|
474
|
-
url: 'http://test.com/stream',
|
|
475
|
-
onData: () => { },
|
|
476
|
-
});
|
|
477
|
-
// Start and immediately stop
|
|
478
|
-
const startPromise = manager.start();
|
|
479
|
-
setTimeout(() => manager.stop(), 25);
|
|
480
|
-
await startPromise;
|
|
481
|
-
// Should have stopped before reading all 10
|
|
482
|
-
expect(readCount).toBeLessThan(10);
|
|
483
|
-
});
|
|
484
|
-
it('should call onError when the stream request fails', async () => {
|
|
485
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
486
|
-
ok: false,
|
|
487
|
-
status: 503,
|
|
488
|
-
text: async () => 'Service unavailable',
|
|
489
|
-
});
|
|
490
|
-
const onError = jest.fn();
|
|
491
|
-
const manager = new StreamableManager({
|
|
492
|
-
url: 'http://test.com/stream',
|
|
493
|
-
onError,
|
|
494
|
-
});
|
|
495
|
-
await manager.start();
|
|
496
|
-
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'HTTP 503: Service unavailable' }));
|
|
497
|
-
});
|
|
498
|
-
it('should route partial updates to onData when onPartialData is not provided', async () => {
|
|
499
|
-
global.fetch = mockFetch([
|
|
500
|
-
'{"data":{"id":1,"status":"busy"},"fields":["status"]}\n',
|
|
501
|
-
]);
|
|
502
|
-
const onData = jest.fn();
|
|
503
|
-
const manager = new StreamableManager({
|
|
504
|
-
url: 'http://test.com/stream',
|
|
505
|
-
onData,
|
|
506
|
-
});
|
|
507
|
-
await manager.start();
|
|
508
|
-
expect(onData).toHaveBeenCalledWith({ id: 1, status: 'busy' });
|
|
509
|
-
});
|
|
510
|
-
});
|
package/dist/integration.test.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 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
|
-
* @jest-environment node
|
|
8
|
-
*/
|
|
9
|
-
import { Inference, inference } from './index';
|
|
10
|
-
import { TaskStatusCompleted, TaskStatusFailed } from './types';
|
|
11
|
-
// Skip all tests if no API key is set
|
|
12
|
-
const API_KEY = process.env.INFERENCE_API_KEY;
|
|
13
|
-
const BASE_URL = process.env.INFERENCE_BASE_URL || 'https://api.inference.sh';
|
|
14
|
-
// Use a pinned app version that's known to work
|
|
15
|
-
const TEST_APP = 'infsh/text-templating@53bk0yzk';
|
|
16
|
-
const describeIfApiKey = API_KEY ? describe : describe.skip;
|
|
17
|
-
describeIfApiKey('Integration Tests', () => {
|
|
18
|
-
let client;
|
|
19
|
-
beforeAll(() => {
|
|
20
|
-
client = new Inference({
|
|
21
|
-
apiKey: API_KEY,
|
|
22
|
-
baseUrl: BASE_URL,
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
describe('Basic Run', () => {
|
|
26
|
-
it('should run a simple task and wait for completion', async () => {
|
|
27
|
-
const result = await client.run({
|
|
28
|
-
app: TEST_APP,
|
|
29
|
-
input: { template: 'Hello {1}!', strings: ['Jest'] },
|
|
30
|
-
});
|
|
31
|
-
expect(result).toBeDefined();
|
|
32
|
-
expect(result.id).toBeDefined();
|
|
33
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
34
|
-
expect(result.output).toBeDefined();
|
|
35
|
-
}, 60000); // 60 second timeout for API call
|
|
36
|
-
});
|
|
37
|
-
describe('Run with Updates', () => {
|
|
38
|
-
it('should receive status updates during task execution', async () => {
|
|
39
|
-
const updates = [];
|
|
40
|
-
const result = await client.run({
|
|
41
|
-
app: TEST_APP,
|
|
42
|
-
input: { template: 'Testing {1}', strings: ['SDK'] },
|
|
43
|
-
}, {
|
|
44
|
-
onUpdate: (update) => {
|
|
45
|
-
updates.push(update.status);
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
49
|
-
expect(updates.length).toBeGreaterThan(0);
|
|
50
|
-
}, 60000);
|
|
51
|
-
});
|
|
52
|
-
describe('Fire and Forget', () => {
|
|
53
|
-
it('should submit a task without waiting for completion', async () => {
|
|
54
|
-
const result = await client.run({
|
|
55
|
-
app: TEST_APP,
|
|
56
|
-
input: { template: '{1}', strings: ['Fire and forget'] },
|
|
57
|
-
}, { wait: false });
|
|
58
|
-
expect(result).toBeDefined();
|
|
59
|
-
expect(result.id).toBeDefined();
|
|
60
|
-
// Status should NOT be completed yet (task was just submitted)
|
|
61
|
-
expect(result.status).not.toBe(TaskStatusCompleted);
|
|
62
|
-
expect(result.status).not.toBe(TaskStatusFailed);
|
|
63
|
-
}, 30000);
|
|
64
|
-
});
|
|
65
|
-
describe('Factory Function', () => {
|
|
66
|
-
it('should work with lowercase inference() factory', async () => {
|
|
67
|
-
const factoryClient = inference({
|
|
68
|
-
apiKey: API_KEY,
|
|
69
|
-
baseUrl: BASE_URL,
|
|
70
|
-
});
|
|
71
|
-
const result = await factoryClient.run({
|
|
72
|
-
app: TEST_APP,
|
|
73
|
-
input: { template: '{1}', strings: ['Factory test'] },
|
|
74
|
-
}, { wait: false });
|
|
75
|
-
expect(result).toBeDefined();
|
|
76
|
-
expect(result.id).toBeDefined();
|
|
77
|
-
}, 30000);
|
|
78
|
-
});
|
|
79
|
-
describe('Webhook', () => {
|
|
80
|
-
it('should deliver webhook on task completion', async () => {
|
|
81
|
-
const result = await client.run({
|
|
82
|
-
app: TEST_APP,
|
|
83
|
-
input: { template: 'Webhook test {1}', strings: ['hello'] },
|
|
84
|
-
webhook: 'https://webhook.site/bbe9ba01-3ab2-4056-a1b1-1cf6969987d5',
|
|
85
|
-
});
|
|
86
|
-
expect(result.status).toBe(TaskStatusCompleted);
|
|
87
|
-
expect(result.output).toBeDefined();
|
|
88
|
-
}, 60000);
|
|
89
|
-
});
|
|
90
|
-
describe('Error Handling', () => {
|
|
91
|
-
it('should throw an error for non-existent app', async () => {
|
|
92
|
-
await expect(client.run({
|
|
93
|
-
app: 'non-existent/app-that-does-not-exist@xyz123',
|
|
94
|
-
input: {},
|
|
95
|
-
}, { wait: false })).rejects.toThrow();
|
|
96
|
-
}, 30000);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
// Add a simple test that always runs to ensure Jest doesn't complain about no tests
|
|
100
|
-
describe('Integration Test Setup', () => {
|
|
101
|
-
it('should have API key check', () => {
|
|
102
|
-
if (!API_KEY) {
|
|
103
|
-
console.log('⚠️ Skipping integration tests - INFERENCE_API_KEY not set');
|
|
104
|
-
}
|
|
105
|
-
expect(true).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|