claude-code-provider-switch 1.1.4 → 1.1.5
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/bin/claude-switch.js +13 -0
- package/index.js +19 -13
- package/lib/config.js +220 -0
- package/lib/menu.js +10 -4
- package/package.json +10 -8
- package/test/run-tests.js +56 -0
- package/test/test-comprehensive.js +282 -0
- package/test/test-provider-integration.js +392 -0
- package/test/test-validation-errors.js +324 -0
package/bin/claude-switch.js
CHANGED
|
@@ -187,6 +187,12 @@ async function main(forceMenu = false) {
|
|
|
187
187
|
return;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
if (args[0] === "api-keys") {
|
|
191
|
+
const { showApiKeyMenu } = require("../lib/config");
|
|
192
|
+
await showApiKeyMenu();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
190
196
|
// Handle --version flag
|
|
191
197
|
if (args[0] === "--version" || args[0] === "-v") {
|
|
192
198
|
try {
|
|
@@ -249,6 +255,13 @@ async function main(forceMenu = false) {
|
|
|
249
255
|
case "show-defaults":
|
|
250
256
|
showDefaults();
|
|
251
257
|
return;
|
|
258
|
+
case "api-keys":
|
|
259
|
+
const { showApiKeyMenu } = require("../lib/config");
|
|
260
|
+
await showApiKeyMenu();
|
|
261
|
+
// Show menu again after API key management
|
|
262
|
+
const newSelectedProvider = await showProviderMenu();
|
|
263
|
+
// Handle the new selection recursively
|
|
264
|
+
return main(true);
|
|
252
265
|
}
|
|
253
266
|
|
|
254
267
|
// For provider selection, show model selection
|
package/index.js
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Claude Code Provider Switcher - Main Entry Point
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This is the main entry point for importing the package as a module.
|
|
5
5
|
* For CLI usage, use the bin/claude-switch.js file.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
module.exports = {
|
|
9
9
|
// Configuration utilities
|
|
10
|
-
...require(
|
|
11
|
-
|
|
10
|
+
...require("./lib/config"),
|
|
11
|
+
|
|
12
12
|
// Provider launchers
|
|
13
|
-
launchOpenRouter: require(
|
|
14
|
-
launchAnthropic: require(
|
|
15
|
-
launchOllama: require(
|
|
16
|
-
launchDefault: require(
|
|
17
|
-
|
|
13
|
+
launchOpenRouter: require("./lib/openrouter").launchOpenRouter,
|
|
14
|
+
launchAnthropic: require("./lib/anthropic").launchAnthropic,
|
|
15
|
+
launchOllama: require("./lib/ollama").launchOllama,
|
|
16
|
+
launchDefault: require("./lib/default").launchDefault,
|
|
17
|
+
|
|
18
18
|
// Menu functions
|
|
19
|
-
showProviderMenu: require(
|
|
20
|
-
showUsage: require(
|
|
21
|
-
showDefaults: require(
|
|
22
|
-
setupDefaults: require(
|
|
23
|
-
showModelSelectionForProvider:
|
|
19
|
+
showProviderMenu: require("./lib/menu").showProviderMenu,
|
|
20
|
+
showUsage: require("./lib/menu").showUsage,
|
|
21
|
+
showDefaults: require("./lib/menu").showDefaults,
|
|
22
|
+
setupDefaults: require("./lib/menu").setupDefaults,
|
|
23
|
+
showModelSelectionForProvider:
|
|
24
|
+
require("./lib/menu").showModelSelectionForProvider,
|
|
25
|
+
|
|
26
|
+
// API key management
|
|
27
|
+
showApiKeyMenu: require("./lib/config").showApiKeyMenu,
|
|
28
|
+
updateApiKey: require("./lib/config").updateApiKey,
|
|
29
|
+
maskApiKey: require("./lib/config").maskApiKey,
|
|
24
30
|
};
|
package/lib/config.js
CHANGED
|
@@ -261,6 +261,223 @@ function getProviderDefaultModel(provider, envVars = null) {
|
|
|
261
261
|
return modelMap[provider] || "";
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Show API key management menu
|
|
266
|
+
*/
|
|
267
|
+
async function showApiKeyMenu() {
|
|
268
|
+
const readline = require("readline");
|
|
269
|
+
const rl = readline.createInterface({
|
|
270
|
+
input: process.stdin,
|
|
271
|
+
output: process.stdout,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const providers = [
|
|
275
|
+
{ id: "openrouter", name: "OpenRouter", envVar: "OPENROUTER_AUTH_TOKEN" },
|
|
276
|
+
{ id: "anthropic", name: "Anthropic", envVar: "ANTHROPIC_API_KEY" },
|
|
277
|
+
{ id: "ollama", name: "Ollama", envVar: "OLLAMA_AUTH_TOKEN" },
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
let selectedIndex = 0;
|
|
281
|
+
|
|
282
|
+
function displayMenu() {
|
|
283
|
+
console.clear();
|
|
284
|
+
log("API Key Management", "cyan");
|
|
285
|
+
log("", "reset");
|
|
286
|
+
log("Select a provider to update its API key:", "yellow");
|
|
287
|
+
log("", "reset");
|
|
288
|
+
|
|
289
|
+
providers.forEach((provider, index) => {
|
|
290
|
+
const marker = index === selectedIndex ? "❯" : " ";
|
|
291
|
+
const envVars = loadEnvFile();
|
|
292
|
+
const hasKey = envVars[provider.envVar] ? "✅" : "❌";
|
|
293
|
+
log(
|
|
294
|
+
`${marker} ${index + 1}) ${provider.name} - ${hasKey}`,
|
|
295
|
+
index === selectedIndex ? "green" : "reset",
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
log("", "reset");
|
|
300
|
+
log("Controls:", "yellow");
|
|
301
|
+
log("↑/↓ - Navigate", "reset");
|
|
302
|
+
log("Enter - Update API key", "reset");
|
|
303
|
+
log("1-3 - Quick select", "reset");
|
|
304
|
+
log("ESC - Return to main menu", "reset");
|
|
305
|
+
log("", "reset");
|
|
306
|
+
log("💡 Tips:", "cyan");
|
|
307
|
+
log("• ✅ = API key is set", "reset");
|
|
308
|
+
log("• ❌ = API key is missing", "reset");
|
|
309
|
+
log("• Press Enter to set or update the API key", "reset");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
displayMenu();
|
|
313
|
+
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
const handleKeyPress = (str, key) => {
|
|
316
|
+
if (key.name === "up") {
|
|
317
|
+
selectedIndex =
|
|
318
|
+
(selectedIndex - 1 + providers.length) % providers.length;
|
|
319
|
+
displayMenu();
|
|
320
|
+
} else if (key.name === "down") {
|
|
321
|
+
selectedIndex = (selectedIndex + 1) % providers.length;
|
|
322
|
+
displayMenu();
|
|
323
|
+
} else if (key.name === "escape") {
|
|
324
|
+
process.stdin.removeAllListeners("keypress");
|
|
325
|
+
rl.close();
|
|
326
|
+
resolve();
|
|
327
|
+
} else if (key.name === "return") {
|
|
328
|
+
process.stdin.removeAllListeners("keypress");
|
|
329
|
+
rl.close();
|
|
330
|
+
updateApiKey(providers[selectedIndex]).then(resolve);
|
|
331
|
+
} else if (key.name >= "1" && key.name <= "3") {
|
|
332
|
+
const index = parseInt(key.name) - 1;
|
|
333
|
+
if (index < providers.length) {
|
|
334
|
+
selectedIndex = index;
|
|
335
|
+
process.stdin.removeAllListeners("keypress");
|
|
336
|
+
rl.close();
|
|
337
|
+
updateApiKey(providers[selectedIndex]).then(resolve);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
process.stdin.on("keypress", handleKeyPress);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Update API key for a specific provider
|
|
348
|
+
*/
|
|
349
|
+
async function updateApiKey(provider) {
|
|
350
|
+
const envVars = loadEnvFile();
|
|
351
|
+
const currentKey = envVars[provider.envVar] || "";
|
|
352
|
+
|
|
353
|
+
log(`\n=== ${provider.name} API Key Management ===`, "cyan");
|
|
354
|
+
|
|
355
|
+
if (currentKey) {
|
|
356
|
+
log(`Current API key: ${maskApiKey(currentKey)}`, "yellow");
|
|
357
|
+
log("", "reset");
|
|
358
|
+
|
|
359
|
+
const options = [
|
|
360
|
+
{ id: "update", name: "Update API key" },
|
|
361
|
+
{ id: "remove", name: "Remove API key" },
|
|
362
|
+
{ id: "cancel", name: "Cancel" },
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
let selectedIndex = 0;
|
|
366
|
+
|
|
367
|
+
function displayOptions() {
|
|
368
|
+
log("Options:", "yellow");
|
|
369
|
+
options.forEach((option, index) => {
|
|
370
|
+
const marker = index === selectedIndex ? "❯" : " ";
|
|
371
|
+
log(
|
|
372
|
+
`${marker} ${option.name}`,
|
|
373
|
+
index === selectedIndex ? "green" : "reset",
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
log("", "reset");
|
|
377
|
+
log("Controls:", "yellow");
|
|
378
|
+
log("↑/↓ - Navigate", "reset");
|
|
379
|
+
log("Enter - Select option", "reset");
|
|
380
|
+
log("ESC - Cancel", "reset");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
displayOptions();
|
|
384
|
+
|
|
385
|
+
const readline = require("readline");
|
|
386
|
+
const rl = readline.createInterface({
|
|
387
|
+
input: process.stdin,
|
|
388
|
+
output: process.stdout,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const choice = await new Promise((resolve) => {
|
|
392
|
+
const handleKeyPress = (str, key) => {
|
|
393
|
+
if (key.name === "up") {
|
|
394
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
395
|
+
// Clear and redraw
|
|
396
|
+
console.clear();
|
|
397
|
+
log(`=== ${provider.name} API Key Management ===`, "cyan");
|
|
398
|
+
log(`Current API key: ${maskApiKey(currentKey)}`, "yellow");
|
|
399
|
+
log("", "reset");
|
|
400
|
+
displayOptions();
|
|
401
|
+
} else if (key.name === "down") {
|
|
402
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
403
|
+
// Clear and redraw
|
|
404
|
+
console.clear();
|
|
405
|
+
log(`=== ${provider.name} API Key Management ===`, "cyan");
|
|
406
|
+
log(`Current API key: ${maskApiKey(currentKey)}`, "yellow");
|
|
407
|
+
log("", "reset");
|
|
408
|
+
displayOptions();
|
|
409
|
+
} else if (key.name === "escape") {
|
|
410
|
+
process.stdin.removeAllListeners("keypress");
|
|
411
|
+
rl.close();
|
|
412
|
+
resolve("cancel");
|
|
413
|
+
} else if (key.name === "return") {
|
|
414
|
+
process.stdin.removeAllListeners("keypress");
|
|
415
|
+
rl.close();
|
|
416
|
+
resolve(options[selectedIndex].id);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
process.stdin.on("keypress", handleKeyPress);
|
|
421
|
+
readline.emitKeypressEvents(process.stdin);
|
|
422
|
+
if (process.stdin.setRawMode) {
|
|
423
|
+
process.stdin.setRawMode(true);
|
|
424
|
+
}
|
|
425
|
+
rl.question("", () => {});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (choice === "update") {
|
|
429
|
+
const newKey = await promptForApiKey(provider.name, provider.envVar);
|
|
430
|
+
if (newKey) {
|
|
431
|
+
updateEnvFile(provider.envVar, newKey);
|
|
432
|
+
log(`${provider.name} API key updated successfully!`, "green");
|
|
433
|
+
}
|
|
434
|
+
} else if (choice === "remove") {
|
|
435
|
+
updateEnvFile(provider.envVar, "");
|
|
436
|
+
log(`${provider.name} API key removed!`, "yellow");
|
|
437
|
+
} else {
|
|
438
|
+
log("Operation cancelled.", "yellow");
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
log(`No ${provider.name} API key is currently set.`, "yellow");
|
|
442
|
+
log("", "reset");
|
|
443
|
+
const newKey = await promptForApiKey(provider.name, provider.envVar);
|
|
444
|
+
if (newKey) {
|
|
445
|
+
updateEnvFile(provider.envVar, newKey);
|
|
446
|
+
log(`${provider.name} API key set successfully!`, "green");
|
|
447
|
+
} else {
|
|
448
|
+
log("Operation cancelled.", "yellow");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
log("", "reset");
|
|
453
|
+
log("Press Enter to continue...", "cyan");
|
|
454
|
+
|
|
455
|
+
const rl = require("readline").createInterface({
|
|
456
|
+
input: process.stdin,
|
|
457
|
+
output: process.stdout,
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
await new Promise((resolve) => {
|
|
461
|
+
rl.question("", () => {
|
|
462
|
+
rl.close();
|
|
463
|
+
resolve();
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Mask API key for display
|
|
470
|
+
*/
|
|
471
|
+
function maskApiKey(apiKey) {
|
|
472
|
+
if (!apiKey) return "Not set";
|
|
473
|
+
if (apiKey.length <= 8) return "*".repeat(apiKey.length);
|
|
474
|
+
return (
|
|
475
|
+
apiKey.substring(0, 4) +
|
|
476
|
+
"*".repeat(apiKey.length - 8) +
|
|
477
|
+
apiKey.substring(apiKey.length - 4)
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
264
481
|
module.exports = {
|
|
265
482
|
colors,
|
|
266
483
|
log,
|
|
@@ -275,4 +492,7 @@ module.exports = {
|
|
|
275
492
|
setDefaultProvider,
|
|
276
493
|
setDefaultModel,
|
|
277
494
|
getProviderDefaultModel,
|
|
495
|
+
showApiKeyMenu,
|
|
496
|
+
updateApiKey,
|
|
497
|
+
maskApiKey,
|
|
278
498
|
};
|
package/lib/menu.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
getProviderDefaultModel,
|
|
10
10
|
setDefaultProvider,
|
|
11
11
|
setDefaultModel,
|
|
12
|
+
showApiKeyMenu,
|
|
12
13
|
} = require("./config");
|
|
13
14
|
const {
|
|
14
15
|
showModelSelection: showOpenRouterModelSelection,
|
|
@@ -77,6 +78,7 @@ function showProviderMenu() {
|
|
|
77
78
|
aliases: ["original", "orig", "def", "d"],
|
|
78
79
|
},
|
|
79
80
|
{ id: "set-default", name: "Set as Default", aliases: ["set-default"] },
|
|
81
|
+
{ id: "api-keys", name: "Manage API Keys", aliases: ["api-keys", "keys"] },
|
|
80
82
|
{ id: "help", name: "Help", aliases: ["help", "-h", "--help"] },
|
|
81
83
|
];
|
|
82
84
|
|
|
@@ -111,18 +113,21 @@ function showProviderMenu() {
|
|
|
111
113
|
const isDefault = provider.id === defaultProvider;
|
|
112
114
|
const defaultIndicator = isDefault ? " [DEFAULT]" : "";
|
|
113
115
|
const isSetDefaultOption = provider.id === "set-default";
|
|
116
|
+
const isApiKeysOption = provider.id === "api-keys";
|
|
114
117
|
const aliases =
|
|
115
118
|
provider.aliases.length > 0
|
|
116
119
|
? ` Aliases: (${provider.aliases.join(", ")})`
|
|
117
120
|
: "";
|
|
118
121
|
|
|
119
|
-
// Highlight
|
|
122
|
+
// Highlight special options with different colors
|
|
120
123
|
const color =
|
|
121
124
|
index === selectedIndex
|
|
122
125
|
? "green"
|
|
123
126
|
: isSetDefaultOption
|
|
124
127
|
? "orange"
|
|
125
|
-
:
|
|
128
|
+
: isApiKeysOption
|
|
129
|
+
? "cyan"
|
|
130
|
+
: "reset";
|
|
126
131
|
|
|
127
132
|
log(
|
|
128
133
|
`${marker} ${index + 1}) ${provider.name}${defaultIndicator}${aliases}`,
|
|
@@ -134,7 +139,7 @@ function showProviderMenu() {
|
|
|
134
139
|
log("Controls:", "yellow");
|
|
135
140
|
log("↑/↓ - Navigate", "reset");
|
|
136
141
|
log("Enter - Select provider", "reset");
|
|
137
|
-
log("1-
|
|
142
|
+
log("1-7 - Quick select", "reset");
|
|
138
143
|
log("ESC - Exit", "reset");
|
|
139
144
|
log("", "reset");
|
|
140
145
|
|
|
@@ -197,7 +202,7 @@ function showProviderMenu() {
|
|
|
197
202
|
process.stdin.removeAllListeners("keypress");
|
|
198
203
|
rl.close();
|
|
199
204
|
resolve(providers[selectedIndex]);
|
|
200
|
-
} else if (str && /^[1-
|
|
205
|
+
} else if (str && /^[1-7]$/.test(str)) {
|
|
201
206
|
const index = parseInt(str) - 1;
|
|
202
207
|
if (index >= 0 && index < providers.length) {
|
|
203
208
|
selectedIndex = index;
|
|
@@ -483,6 +488,7 @@ function showUsage() {
|
|
|
483
488
|
);
|
|
484
489
|
log(" show-defaults - Display current default settings", "reset");
|
|
485
490
|
log(" clear-defaults - Reset all default settings", "reset");
|
|
491
|
+
log(" api-keys - Manage API keys for providers", "reset");
|
|
486
492
|
log("", "reset");
|
|
487
493
|
log("Options:", "reset");
|
|
488
494
|
log(" --model - Show interactive model selection menu", "reset");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-provider-switch",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Cross-platform Claude Code provider switcher (OpenRouter, Ollama, Anthropic)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"keywords": [
|
|
25
25
|
"claude",
|
|
26
26
|
"claude-code",
|
|
27
|
-
"openrouter",
|
|
28
|
-
"llm switcher",
|
|
29
|
-
"llm provider switcher",
|
|
30
|
-
"ai provider switcher",
|
|
31
|
-
"ai model switcher",
|
|
32
|
-
"ai model provider switcher",
|
|
33
27
|
"claude code provider switcher",
|
|
28
|
+
"claude code llm switcher",
|
|
29
|
+
"claude code llm provider switcher",
|
|
30
|
+
"ai provider switcher",
|
|
31
|
+
"claude code ai model switcher",
|
|
32
|
+
"claude code ai model provider switcher",
|
|
33
|
+
"claude code OpenRouter Ollama Anthropic",
|
|
34
34
|
"cli"
|
|
35
35
|
],
|
|
36
36
|
"author": "Adrian R",
|
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
"files": [
|
|
42
42
|
"bin/",
|
|
43
43
|
"lib/",
|
|
44
|
-
"
|
|
44
|
+
"test/",
|
|
45
|
+
"README.md",
|
|
46
|
+
"index.js"
|
|
45
47
|
],
|
|
46
48
|
"preferGlobal": true
|
|
47
49
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
console.log("🧪 Running complete claude-switch test suite...\n");
|
|
7
|
+
|
|
8
|
+
// Test files in order
|
|
9
|
+
const testFiles = [
|
|
10
|
+
{ name: "Validation & Error Tests", file: "test-validation-errors.js" },
|
|
11
|
+
{ name: "Provider Integration Tests", file: "test-provider-integration.js" },
|
|
12
|
+
{ name: "Comprehensive Integration Tests", file: "test-comprehensive.js" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
let currentTest = 0;
|
|
16
|
+
|
|
17
|
+
function runNextTest() {
|
|
18
|
+
if (currentTest >= testFiles.length) {
|
|
19
|
+
console.log("\n🎉 All test suites passed!");
|
|
20
|
+
console.log("\n📊 Test Coverage Summary:");
|
|
21
|
+
console.log(" ✅ Constants and configuration");
|
|
22
|
+
console.log(" ✅ Error handling and validation");
|
|
23
|
+
console.log(" ✅ Provider-specific functionality");
|
|
24
|
+
console.log(" ✅ Integration and end-to-end tests");
|
|
25
|
+
console.log(" ✅ Cache and performance tests");
|
|
26
|
+
console.log(" ✅ Environment and configuration tests");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const test = testFiles[currentTest];
|
|
32
|
+
console.log(`📋 Running ${test.name}...`);
|
|
33
|
+
|
|
34
|
+
const testProcess = spawn("node", [path.join(__dirname, test.file)], {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
testProcess.on("close", (code) => {
|
|
39
|
+
if (code === 0) {
|
|
40
|
+
console.log(`✅ ${test.name} passed!\n`);
|
|
41
|
+
currentTest++;
|
|
42
|
+
runNextTest();
|
|
43
|
+
} else {
|
|
44
|
+
console.log(`\n❌ ${test.name} failed!`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
testProcess.on("error", (error) => {
|
|
50
|
+
console.log(`\n💥 Failed to run ${test.name}: ${error.message}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Start running tests
|
|
56
|
+
runNextTest();
|