modelmix 3.8.2 → 3.8.6
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/.claude/settings.local.json +5 -1
- package/README.md +8 -7
- package/demo/demo.mjs +8 -8
- package/demo/images.mjs +9 -0
- package/demo/img.png +0 -0
- package/demo/mcp-simple.mjs +166 -0
- package/demo/mcp-tools.mjs +344 -0
- package/index.js +284 -50
- package/mcp-tools.js +96 -0
- package/package.json +22 -7
- package/test/README.md +158 -0
- package/test/bottleneck.test.js +483 -0
- package/test/fallback.test.js +387 -0
- package/test/fixtures/data.json +36 -0
- package/test/fixtures/img.png +0 -0
- package/test/fixtures/template.txt +15 -0
- package/test/images.test.js +87 -0
- package/test/json.test.js +295 -0
- package/test/live.mcp.js +555 -0
- package/test/live.test.js +356 -0
- package/test/mocha.opts +5 -0
- package/test/setup.js +176 -0
- package/test/templates.test.js +473 -0
- package/test/test-runner.js +75 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
const { expect } = require('chai');
|
|
2
|
+
const { ModelMix, MixOpenAI, MixAnthropic, MixGoogle } = require('../index.js');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fixturesPath = path.join(__dirname, 'fixtures');
|
|
5
|
+
|
|
6
|
+
const blueSquareBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMS1jMDAzIDc5Ljk2OTBhODdmYywgMjAyNS8wMy8wNi0yMDo1MDoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI2LjkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REM2QzQ3NEQ2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REM2QzQ3NEU2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEQzZDNDc0QjZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEQzZDNDc0QzZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvArxh0AAAAGUExURQAA/wAAAHtivz4AAAAjSURBVHja7MGBAAAAAMOg+VNf4QBVAQAAAAAAAAAAAI8JMAAndAABi7SX2gAAAABJRU5ErkJggg==';
|
|
7
|
+
|
|
8
|
+
const setup = {
|
|
9
|
+
options: { temperature: 0 },
|
|
10
|
+
config: { debug: false, max_history: 2 }
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('Live Integration Tests', function () {
|
|
14
|
+
// Increase timeout for real API calls
|
|
15
|
+
this.timeout(30000);
|
|
16
|
+
|
|
17
|
+
describe('Image Processing', function () {
|
|
18
|
+
|
|
19
|
+
it('should process images with OpenAI GPT-5nano', async function () {
|
|
20
|
+
const model = ModelMix.new(setup).gpt5nano();
|
|
21
|
+
|
|
22
|
+
model.addImageFromUrl(blueSquareBase64)
|
|
23
|
+
.addText('What color is this image? Answer in one word only.');
|
|
24
|
+
|
|
25
|
+
const response = await model.message();
|
|
26
|
+
|
|
27
|
+
console.log(`OpenAI GPT-4o response: ${response}`);
|
|
28
|
+
|
|
29
|
+
expect(response).to.be.a('string');
|
|
30
|
+
expect(response.toLowerCase()).to.include('blue');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should process images with Anthropic Claude', async function () {
|
|
34
|
+
const model = ModelMix.new(setup).sonnet35();
|
|
35
|
+
|
|
36
|
+
model.addImageFromUrl(blueSquareBase64)
|
|
37
|
+
.addText('What color is this image? Answer in one word only.');
|
|
38
|
+
|
|
39
|
+
const response = await model.message();
|
|
40
|
+
console.log(`Anthropic Claude response: ${response}`);
|
|
41
|
+
|
|
42
|
+
expect(response).to.be.a('string');
|
|
43
|
+
expect(response.toLowerCase()).to.include('blue');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should process images with Google Gemini', async function () {
|
|
47
|
+
const model = ModelMix.new(setup).gemini25flash();
|
|
48
|
+
|
|
49
|
+
model.addImageFromUrl(blueSquareBase64)
|
|
50
|
+
.addText('What color is this image? Answer in one word only.');
|
|
51
|
+
|
|
52
|
+
const response = await model.message();
|
|
53
|
+
console.log(`Google Gemini response: ${response}`);
|
|
54
|
+
|
|
55
|
+
expect(response).to.be.a('string');
|
|
56
|
+
expect(response.toLowerCase()).to.include('blue');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('JSON Structured Output', function () {
|
|
62
|
+
|
|
63
|
+
it('should return structured JSON with OpenAI', async function () {
|
|
64
|
+
const model = ModelMix.new(setup).gpt5mini();
|
|
65
|
+
|
|
66
|
+
model.addText('Generate information about a fictional character.');
|
|
67
|
+
|
|
68
|
+
const result = await model.json({
|
|
69
|
+
name: "John Doe",
|
|
70
|
+
age: 30,
|
|
71
|
+
occupation: "Engineer",
|
|
72
|
+
skills: ["JavaScript", "Python"]
|
|
73
|
+
}, {}, { addNote: true });
|
|
74
|
+
|
|
75
|
+
console.log(`OpenAI JSON result:`, result);
|
|
76
|
+
|
|
77
|
+
expect(result).to.be.an('object');
|
|
78
|
+
expect(result).to.have.property('name');
|
|
79
|
+
expect(result).to.have.property('age');
|
|
80
|
+
expect(result).to.have.property('occupation');
|
|
81
|
+
expect(result).to.have.property('skills');
|
|
82
|
+
expect(result.skills).to.be.an('array');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return structured JSON with Sonnet Think', async function () {
|
|
86
|
+
const model = ModelMix.new(setup).sonnet4think();
|
|
87
|
+
|
|
88
|
+
model.addText('Generate information about a fictional city.');
|
|
89
|
+
|
|
90
|
+
const result = await model.json({
|
|
91
|
+
name: "Springfield",
|
|
92
|
+
population: 50000,
|
|
93
|
+
country: "USA",
|
|
94
|
+
attractions: ["Museum", "Park"]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.log(`Anthropic JSON result:`, result);
|
|
98
|
+
|
|
99
|
+
expect(result).to.be.an('object');
|
|
100
|
+
expect(result).to.have.property('name');
|
|
101
|
+
expect(result).to.have.property('population');
|
|
102
|
+
expect(result).to.have.property('country');
|
|
103
|
+
expect(result).to.have.property('attractions');
|
|
104
|
+
expect(result.attractions).to.be.an('array');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return structured JSON with Google Gemini', async function () {
|
|
108
|
+
const model = ModelMix.new(setup).gemini25flash();
|
|
109
|
+
|
|
110
|
+
model.addText('Generate information about a fictional city.');
|
|
111
|
+
|
|
112
|
+
const result = await model.json({
|
|
113
|
+
name: "Springfield",
|
|
114
|
+
population: 50000,
|
|
115
|
+
country: "USA",
|
|
116
|
+
attractions: ["Museum", "Park"]
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
console.log(`Google Gemini JSON result:`, result);
|
|
120
|
+
|
|
121
|
+
expect(result).to.be.an('object');
|
|
122
|
+
expect(result).to.have.property('name');
|
|
123
|
+
expect(result).to.have.property('population');
|
|
124
|
+
expect(result).to.have.property('country');
|
|
125
|
+
expect(result).to.have.property('attractions');
|
|
126
|
+
expect(result.attractions).to.be.an('array');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('Model Fallback', function () {
|
|
133
|
+
|
|
134
|
+
it('should fallback from OpenAI to Anthropic', async function () {
|
|
135
|
+
// Create a model chain: non-existent model -> Claude
|
|
136
|
+
const model = ModelMix.new(setup)
|
|
137
|
+
.attach('non-existent-model', new MixOpenAI())
|
|
138
|
+
.sonnet4();
|
|
139
|
+
|
|
140
|
+
model.addText('Say "fallback test successful" and nothing else.');
|
|
141
|
+
|
|
142
|
+
const response = await model.message();
|
|
143
|
+
console.log(`Fallback test result: ${response}`);
|
|
144
|
+
|
|
145
|
+
expect(response).to.be.a('string');
|
|
146
|
+
expect(response.toLowerCase()).to.include('fallback test successful');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should fallback from Anthropic to OpenAI', async function () {
|
|
150
|
+
// Create a model chain: non-existent model -> Claude
|
|
151
|
+
const model = ModelMix.new(setup)
|
|
152
|
+
.attach('non-existent-model', new MixAnthropic())
|
|
153
|
+
.gpt41nano();
|
|
154
|
+
|
|
155
|
+
model.addText('Say "fallback test successful" and nothing else.');
|
|
156
|
+
|
|
157
|
+
const response = await model.message();
|
|
158
|
+
console.log(`Fallback test result: ${response}`);
|
|
159
|
+
|
|
160
|
+
expect(response).to.be.a('string');
|
|
161
|
+
expect(response.toLowerCase()).to.include('fallback test successful');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('Additional Model Tests', function () {
|
|
167
|
+
|
|
168
|
+
it('should work with Scout model', async function () {
|
|
169
|
+
const model = ModelMix.new(setup).scout();
|
|
170
|
+
|
|
171
|
+
model.addText('Say "scout test successful" and nothing else.');
|
|
172
|
+
|
|
173
|
+
const response = await model.message();
|
|
174
|
+
console.log(`Scout response: ${response}`);
|
|
175
|
+
|
|
176
|
+
expect(response).to.be.a('string');
|
|
177
|
+
expect(response.toLowerCase()).to.include('scout test successful');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should work with KimiK2 model', async function () {
|
|
181
|
+
const model = ModelMix.new(setup).kimiK2();
|
|
182
|
+
|
|
183
|
+
model.addText('Say "kimik2 test successful" and nothing else.');
|
|
184
|
+
|
|
185
|
+
const response = await model.message();
|
|
186
|
+
console.log(`KimiK2 response: ${response}`);
|
|
187
|
+
|
|
188
|
+
expect(response).to.be.a('string');
|
|
189
|
+
expect(response.toLowerCase()).to.include('kimik2 test successful');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should work with GPT-OSS model', async function () {
|
|
193
|
+
const model = ModelMix.new(setup).gptOss();
|
|
194
|
+
|
|
195
|
+
model.addText('Say "gptoss test successful" and nothing else.');
|
|
196
|
+
|
|
197
|
+
const response = await model.message();
|
|
198
|
+
console.log(`GPT-OSS response: ${response}`);
|
|
199
|
+
|
|
200
|
+
expect(response).to.be.a('string');
|
|
201
|
+
expect(response.toLowerCase()).to.include('gptoss test successful');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should work with Grok3Mini model', async function () {
|
|
205
|
+
const model = ModelMix.new(setup).grok3mini();
|
|
206
|
+
|
|
207
|
+
model.addText('Say "grok3mini test successful" and nothing else.');
|
|
208
|
+
|
|
209
|
+
const response = await model.message();
|
|
210
|
+
console.log(`Grok3Mini response: ${response}`);
|
|
211
|
+
|
|
212
|
+
expect(response).to.be.a('string');
|
|
213
|
+
expect(response.toLowerCase()).to.include('grok3mini test successful');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('Image Processing with JSON Output', function () {
|
|
219
|
+
|
|
220
|
+
it('should process images and return JSON with Maverick', async function () {
|
|
221
|
+
const model = ModelMix.new(setup).maverick();
|
|
222
|
+
model.addImage(path.join(fixturesPath, 'img.png'))
|
|
223
|
+
.addText('Analyze this image and provide details in JSON format.');
|
|
224
|
+
|
|
225
|
+
const result = await model.json({
|
|
226
|
+
color: "string",
|
|
227
|
+
shape: "string",
|
|
228
|
+
description: "string"
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
console.log(`Scout image JSON result:`, result);
|
|
232
|
+
|
|
233
|
+
expect(result).to.be.an('object');
|
|
234
|
+
expect(result).to.have.property('color');
|
|
235
|
+
expect(result).to.have.property('shape');
|
|
236
|
+
expect(result).to.have.property('description');
|
|
237
|
+
expect(result.color.toLowerCase()).to.include('blue');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should process images and return JSON with Grok 4', async function () {
|
|
241
|
+
const model = ModelMix.new(setup).grok4();
|
|
242
|
+
|
|
243
|
+
model.addImageFromUrl(blueSquareBase64)
|
|
244
|
+
.addText('Analyze this image and provide details in JSON format.');
|
|
245
|
+
|
|
246
|
+
const result = await model.json({
|
|
247
|
+
color: "string",
|
|
248
|
+
shape: "string",
|
|
249
|
+
description: "string"
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
console.log(`Grok3Mini image JSON result:`, result);
|
|
253
|
+
|
|
254
|
+
expect(result).to.be.an('object');
|
|
255
|
+
expect(result).to.have.property('color');
|
|
256
|
+
expect(result).to.have.property('shape');
|
|
257
|
+
expect(result).to.have.property('description');
|
|
258
|
+
expect(result.color.toLowerCase()).to.include('blue');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('JSON Structured Output for New Models', function () {
|
|
264
|
+
|
|
265
|
+
it('should return structured JSON with Scout', async function () {
|
|
266
|
+
const model = ModelMix.new(setup).scout();
|
|
267
|
+
|
|
268
|
+
model.addText('Generate information about a fictional animal.');
|
|
269
|
+
|
|
270
|
+
const result = await model.json({
|
|
271
|
+
name: "Dragon",
|
|
272
|
+
type: "Mythical",
|
|
273
|
+
abilities: ["Fire breathing", "Flight"],
|
|
274
|
+
habitat: "Mountains"
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
console.log(`Scout JSON result:`, result);
|
|
278
|
+
|
|
279
|
+
expect(result).to.be.an('object');
|
|
280
|
+
expect(result).to.have.property('name');
|
|
281
|
+
expect(result).to.have.property('type');
|
|
282
|
+
expect(result).to.have.property('abilities');
|
|
283
|
+
expect(result).to.have.property('habitat');
|
|
284
|
+
expect(result.abilities).to.be.an('array');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should return structured JSON with KimiK2', async function () {
|
|
288
|
+
const model = ModelMix.new(setup).kimiK2();
|
|
289
|
+
|
|
290
|
+
model.addText('Generate information about a fictional vehicle.');
|
|
291
|
+
|
|
292
|
+
const result = await model.json({
|
|
293
|
+
name: "Flying Car",
|
|
294
|
+
type: "Transportation",
|
|
295
|
+
features: ["Anti-gravity", "Auto-pilot"],
|
|
296
|
+
manufacturer: "Future Motors"
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
console.log(`KimiK2 JSON result:`, result);
|
|
300
|
+
|
|
301
|
+
expect(result).to.be.an('object');
|
|
302
|
+
expect(result).to.have.property('name');
|
|
303
|
+
expect(result).to.have.property('type');
|
|
304
|
+
expect(result).to.have.property('features');
|
|
305
|
+
expect(result).to.have.property('manufacturer');
|
|
306
|
+
expect(result.features).to.be.an('array');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should return structured JSON with GPT-OSS', async function () {
|
|
310
|
+
const model = ModelMix.new(setup).gptOss();
|
|
311
|
+
|
|
312
|
+
model.addText('Generate information about a fictional planet.');
|
|
313
|
+
|
|
314
|
+
const result = await model.json({
|
|
315
|
+
name: "Nova Prime",
|
|
316
|
+
type: "Gas Giant",
|
|
317
|
+
moons: ["Alpha", "Beta", "Gamma"],
|
|
318
|
+
atmosphere: "Hydrogen and Helium"
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
console.log(`GPT-OSS JSON result:`, result);
|
|
322
|
+
|
|
323
|
+
expect(result).to.be.an('object');
|
|
324
|
+
expect(result).to.have.property('name');
|
|
325
|
+
expect(result).to.have.property('type');
|
|
326
|
+
expect(result).to.have.property('moons');
|
|
327
|
+
expect(result).to.have.property('atmosphere');
|
|
328
|
+
expect(result.moons).to.be.an('array');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should return structured JSON with Grok3Mini', async function () {
|
|
332
|
+
const model = ModelMix.new(setup).grok3mini();
|
|
333
|
+
|
|
334
|
+
model.addText('Generate information about a fictional technology.');
|
|
335
|
+
|
|
336
|
+
const result = await model.json({
|
|
337
|
+
name: "Quantum Computer",
|
|
338
|
+
type: "Computing",
|
|
339
|
+
applications: ["Cryptography", "Simulation"],
|
|
340
|
+
power: "1000 qubits"
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
console.log(`Grok3Mini JSON result:`, result);
|
|
344
|
+
|
|
345
|
+
expect(result).to.be.an('object');
|
|
346
|
+
expect(result).to.have.property('name');
|
|
347
|
+
expect(result).to.have.property('type');
|
|
348
|
+
expect(result).to.have.property('applications');
|
|
349
|
+
expect(result).to.have.property('power');
|
|
350
|
+
expect(result.applications).to.be.an('array');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
});
|
package/test/mocha.opts
ADDED
package/test/setup.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global test setup for ModelMix
|
|
3
|
+
*
|
|
4
|
+
* This file contains shared configuration and utilities for all test files.
|
|
5
|
+
* It runs before each test suite and sets up the testing environment.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Set dummy environment variables for testing to prevent library errors
|
|
9
|
+
|
|
10
|
+
const chai = require('chai');
|
|
11
|
+
const sinon = require('sinon');
|
|
12
|
+
const nock = require('nock');
|
|
13
|
+
|
|
14
|
+
// Set up chai
|
|
15
|
+
chai.config.includeStack = true;
|
|
16
|
+
chai.config.showDiff = true;
|
|
17
|
+
|
|
18
|
+
// Global test timeout
|
|
19
|
+
const TIMEOUT = 10000;
|
|
20
|
+
|
|
21
|
+
// Set dummy environment variables for testing to prevent library initialization errors
|
|
22
|
+
process.env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || 'sk-ant-test-dummy-key-for-testing-purposes';
|
|
23
|
+
process.env.OPENAI_API_KEY = process.env.OPENAI_API_KEY || 'sk-proj-test-dummy-key-for-testing-purposes';
|
|
24
|
+
process.env.PPLX_API_KEY = process.env.PPLX_API_KEY || 'pplx-test-dummy-key-for-testing-purposes';
|
|
25
|
+
process.env.GROQ_API_KEY = process.env.GROQ_API_KEY || 'gsk_test-dummy-key-for-testing-purposes';
|
|
26
|
+
process.env.TOGETHER_API_KEY = process.env.TOGETHER_API_KEY || '49a96test-dummy-key-for-testing-purposes';
|
|
27
|
+
process.env.XAI_API_KEY = process.env.XAI_API_KEY || 'xai-test-dummy-key-for-testing-purposes';
|
|
28
|
+
process.env.CEREBRAS_API_KEY = process.env.CEREBRAS_API_KEY || 'csk-test-dummy-key-for-testing-purposes';
|
|
29
|
+
process.env.GOOGLE_API_KEY = process.env.GOOGLE_API_KEY || 'AIzatest-dummy-key-for-testing-purposes';
|
|
30
|
+
process.env.LAMBDA_API_KEY = process.env.LAMBDA_API_KEY || 'secret_test-dummy-key-for-testing-purposes';
|
|
31
|
+
process.env.BRAVE_API_KEY = process.env.BRAVE_API_KEY || 'BSA0test-dummy-key-for-testing-purposes_fm';
|
|
32
|
+
|
|
33
|
+
// Global test configuration
|
|
34
|
+
global.TEST_CONFIG = {
|
|
35
|
+
TIMEOUT,
|
|
36
|
+
MOCK_APIS: true,
|
|
37
|
+
DEBUG: false
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Global cleanup function
|
|
41
|
+
global.cleanup = function() {
|
|
42
|
+
nock.cleanAll();
|
|
43
|
+
sinon.restore();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Console override for testing (suppress debug logs unless needed)
|
|
47
|
+
if (!process.env.DEBUG_TESTS) {
|
|
48
|
+
const originalConsole = {
|
|
49
|
+
log: console.log,
|
|
50
|
+
warn: console.warn,
|
|
51
|
+
error: console.error,
|
|
52
|
+
info: console.info,
|
|
53
|
+
debug: console.debug
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Suppress most console output during tests
|
|
57
|
+
console.log = () => {};
|
|
58
|
+
console.info = () => {};
|
|
59
|
+
console.debug = () => {};
|
|
60
|
+
|
|
61
|
+
// Keep warnings and errors visible
|
|
62
|
+
console.warn = originalConsole.warn;
|
|
63
|
+
console.error = originalConsole.error;
|
|
64
|
+
|
|
65
|
+
// Restore original console for specific test debugging
|
|
66
|
+
global.restoreConsole = () => {
|
|
67
|
+
Object.assign(console, originalConsole);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Utility functions for tests
|
|
72
|
+
global.testUtils = {
|
|
73
|
+
/**
|
|
74
|
+
* Create a mock API response for different providers
|
|
75
|
+
*/
|
|
76
|
+
createMockResponse: (provider, content, options = {}) => {
|
|
77
|
+
switch (provider) {
|
|
78
|
+
case 'openai':
|
|
79
|
+
return {
|
|
80
|
+
choices: [{
|
|
81
|
+
message: {
|
|
82
|
+
role: 'assistant',
|
|
83
|
+
content: content,
|
|
84
|
+
...options
|
|
85
|
+
}
|
|
86
|
+
}]
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
case 'anthropic':
|
|
90
|
+
return {
|
|
91
|
+
content: [{
|
|
92
|
+
type: 'text',
|
|
93
|
+
text: content,
|
|
94
|
+
...options
|
|
95
|
+
}]
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
case 'google':
|
|
99
|
+
return {
|
|
100
|
+
candidates: [{
|
|
101
|
+
content: {
|
|
102
|
+
parts: [{
|
|
103
|
+
text: content,
|
|
104
|
+
...options
|
|
105
|
+
}]
|
|
106
|
+
}
|
|
107
|
+
}]
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a mock error response
|
|
117
|
+
*/
|
|
118
|
+
createMockError: (statusCode, message) => ({
|
|
119
|
+
error: {
|
|
120
|
+
message: message,
|
|
121
|
+
type: 'test_error',
|
|
122
|
+
code: statusCode
|
|
123
|
+
}
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Wait for a specified amount of time
|
|
128
|
+
*/
|
|
129
|
+
wait: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generate test image data
|
|
133
|
+
*/
|
|
134
|
+
generateTestImage: (format = 'jpeg') => {
|
|
135
|
+
const formats = {
|
|
136
|
+
jpeg: '/9j/4AAQSkZJRgABAQAAAQABAAD//2Q==',
|
|
137
|
+
png: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
|
|
138
|
+
gif: 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const mimeTypes = {
|
|
142
|
+
jpeg: 'image/jpeg',
|
|
143
|
+
png: 'image/png',
|
|
144
|
+
gif: 'image/gif'
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return `data:${mimeTypes[format]};base64,${formats[format]}`;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Export hooks for tests to use
|
|
152
|
+
global.setupTestHooks = function() {
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
// Disable real HTTP requests
|
|
155
|
+
if (global.TEST_CONFIG.MOCK_APIS) {
|
|
156
|
+
nock.disableNetConnect();
|
|
157
|
+
// Allow localhost connections for local servers if needed
|
|
158
|
+
nock.enableNetConnect('localhost');
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
afterEach(() => {
|
|
163
|
+
global.cleanup();
|
|
164
|
+
|
|
165
|
+
if (global.TEST_CONFIG.MOCK_APIS) {
|
|
166
|
+
nock.enableNetConnect();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Handle unhandled promise rejections in tests
|
|
172
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
173
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
console.log('✅ ModelMix test setup complete');
|