claude-code-provider-switch 1.1.4 โ†’ 1.1.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.
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Comprehensive tests for validation, error handling, and constants
5
+ */
6
+
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+
10
+ // Import modules to test
11
+ const {
12
+ OLLAMA,
13
+ OPENROUTER,
14
+ ANTHROPIC,
15
+ CACHE,
16
+ ENV_VARS,
17
+ DEFAULT_MODELS,
18
+ HTTP_STATUS
19
+ } = require("../lib/constants");
20
+
21
+ const {
22
+ ProviderError,
23
+ NetworkError,
24
+ AuthenticationError,
25
+ ValidationError,
26
+ CacheError
27
+ } = require("../lib/errors");
28
+
29
+ const {
30
+ validateModelName,
31
+ validateAuthToken,
32
+ validateModelResponse,
33
+ validateHostname,
34
+ validatePort
35
+ } = require("../lib/validation");
36
+
37
+ const { loadEnvFile } = require("../lib/config");
38
+
39
+ // Test configuration
40
+ const testConfig = {
41
+ testDir: __dirname,
42
+ };
43
+
44
+ // Utility functions
45
+ async function test(description, testFn) {
46
+ try {
47
+ console.log(`๐Ÿงช ${description}`);
48
+ await testFn();
49
+ console.log(`โœ… ${description} - PASSED`);
50
+ } catch (error) {
51
+ console.log(`โŒ ${description} - FAILED: ${error.message}`);
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ function assert(condition, message) {
57
+ if (!condition) {
58
+ throw new Error(message);
59
+ }
60
+ }
61
+
62
+ function assertThrows(fn, expectedErrorType, message) {
63
+ try {
64
+ fn();
65
+ throw new Error(`Expected ${expectedErrorType.name} to be thrown`);
66
+ } catch (error) {
67
+ if (error.constructor !== expectedErrorType) {
68
+ throw new Error(`Expected ${expectedErrorType.name}, got ${error.constructor.name}: ${error.message}`);
69
+ }
70
+ }
71
+ }
72
+
73
+ // Test suite
74
+ async function runTests() {
75
+ console.log("๐Ÿš€ Starting validation, errors, and constants test suite...\n");
76
+
77
+ // ========== CONSTANTS TESTS ==========
78
+
79
+ await test("Constants module exports all required objects", () => {
80
+ assert(OLLAMA, "OLLAMA constants not exported");
81
+ assert(OPENROUTER, "OPENROUTER constants not exported");
82
+ assert(ANTHROPIC, "ANTHROPIC constants not exported");
83
+ assert(CACHE, "CACHE constants not exported");
84
+ assert(ENV_VARS, "ENV_VARS constants not exported");
85
+ assert(DEFAULT_MODELS, "DEFAULT_MODELS constants not exported");
86
+ assert(HTTP_STATUS, "HTTP_STATUS constants not exported");
87
+ });
88
+
89
+ await test("OLLAMA constants have correct values", () => {
90
+ assert(OLLAMA.DEFAULT_HOST === 'localhost', "OLLAMA.DEFAULT_HOST incorrect");
91
+ assert(OLLAMA.DEFAULT_PORT === 11434, "OLLAMA.DEFAULT_PORT incorrect");
92
+ assert(OLLAMA.API_PATH === '/api/tags', "OLLAMA.API_PATH incorrect");
93
+ assert(OLLAMA.TIMEOUT === 5000, "OLLAMA.TIMEOUT incorrect");
94
+ assert(OLLAMA.AUTH_HEADER === 'Bearer', "OLLAMA.AUTH_HEADER incorrect");
95
+ });
96
+
97
+ await test("Cache constants are properly defined", () => {
98
+ assert(CACHE.TTL === 5 * 60 * 1000, "CACHE.TTL incorrect");
99
+ assert(CACHE.MAX_SIZE === 50, "CACHE.MAX_SIZE incorrect");
100
+ assert(CACHE.MAX_MEMORY === 10 * 1024 * 1024, "CACHE.MAX_MEMORY incorrect");
101
+ assert(CACHE.KEYS.OLLAMA_MODELS === 'ollama-models', "CACHE.KEYS.OLLAMA_MODELS incorrect");
102
+ assert(CACHE.KEYS.OPENROUTER_MODELS === 'openrouter-models', "CACHE.KEYS.OPENROUTER_MODELS incorrect");
103
+ assert(CACHE.KEYS.ANTHROPIC_MODELS === 'anthropic-models', "CACHE.KEYS.ANTHROPIC_MODELS incorrect");
104
+ });
105
+
106
+ await test("HTTP status constants are correct", () => {
107
+ assert(HTTP_STATUS.OK === 200, "HTTP_STATUS.OK incorrect");
108
+ assert(HTTP_STATUS.UNAUTHORIZED === 401, "HTTP_STATUS.UNAUTHORIZED incorrect");
109
+ assert(HTTP_STATUS.FORBIDDEN === 403, "HTTP_STATUS.FORBIDDEN incorrect");
110
+ assert(HTTP_STATUS.NOT_FOUND === 404, "HTTP_STATUS.NOT_FOUND incorrect");
111
+ assert(HTTP_STATUS.SERVER_ERROR === 500, "HTTP_STATUS.SERVER_ERROR incorrect");
112
+ });
113
+
114
+ // ========== ERROR CLASSES TESTS ==========
115
+
116
+ await test("ProviderError creates correct error", () => {
117
+ const cause = new Error("Original error");
118
+ const error = new ProviderError("Test message", "test-provider", cause);
119
+
120
+ assert(error instanceof Error, "ProviderError should extend Error");
121
+ assert(error.name === "ProviderError", "ProviderError name incorrect");
122
+ assert(error.message === "Test message", "ProviderError message incorrect");
123
+ assert(error.provider === "test-provider", "ProviderError provider incorrect");
124
+ assert(error.cause === cause, "ProviderError cause incorrect");
125
+ });
126
+
127
+ await test("NetworkError creates correct error", () => {
128
+ const error = new NetworkError("Network failed", "ollama", 500);
129
+
130
+ assert(error instanceof ProviderError, "NetworkError should extend ProviderError");
131
+ assert(error.name === "NetworkError", "NetworkError name incorrect");
132
+ assert(error.statusCode === 500, "NetworkError statusCode incorrect");
133
+ });
134
+
135
+ await test("ValidationError creates correct error", () => {
136
+ const error = new ValidationError("Invalid input", "model");
137
+
138
+ assert(error instanceof Error, "ValidationError should extend Error");
139
+ assert(error.name === "ValidationError", "ValidationError name incorrect");
140
+ assert(error.field === "model", "ValidationError field incorrect");
141
+ });
142
+
143
+ await test("AuthenticationError creates correct error", () => {
144
+ const error = new AuthenticationError("Auth failed", "openrouter");
145
+
146
+ assert(error instanceof ProviderError, "AuthenticationError should extend ProviderError");
147
+ assert(error.name === "AuthenticationError", "AuthenticationError name incorrect");
148
+ assert(error.provider === "openrouter", "AuthenticationError provider incorrect");
149
+ });
150
+
151
+ await test("CacheError creates correct error", () => {
152
+ const error = new CacheError("Cache operation failed", "set");
153
+
154
+ assert(error instanceof Error, "CacheError should extend Error");
155
+ assert(error.name === "CacheError", "CacheError name incorrect");
156
+ assert(error.operation === "set", "CacheError operation incorrect");
157
+ });
158
+
159
+ // ========== VALIDATION TESTS ==========
160
+
161
+ await test("validateModelName accepts valid model names", () => {
162
+ const validModels = [
163
+ "gpt-4",
164
+ "claude-3-5-sonnet-latest",
165
+ "llama-2-7b-chat",
166
+ "minimax-m2.5:cloud"
167
+ ];
168
+
169
+ validModels.forEach(model => {
170
+ const result = validateModelName(model);
171
+ assert(result === model, `validateModelName should return ${model}`);
172
+ });
173
+ });
174
+
175
+ await test("validateModelName rejects invalid model names", () => {
176
+ assertThrows(() => validateModelName(null), ValidationError, "Should reject null");
177
+ assertThrows(() => validateModelName(123), ValidationError, "Should reject number");
178
+ assertThrows(() => validateModelName(""), ValidationError, "Should reject empty string");
179
+ assertThrows(() => validateModelName(" "), ValidationError, "Should reject whitespace");
180
+ assertThrows(() => validateModelName("a".repeat(101)), ValidationError, "Should reject too long string");
181
+ });
182
+
183
+ await test("validateAuthToken accepts valid tokens", () => {
184
+ const validTokens = [
185
+ "sk-1234567890abcdef",
186
+ "Bearer token123456789",
187
+ "12345678901234567890"
188
+ ];
189
+
190
+ validTokens.forEach(token => {
191
+ const result = validateAuthToken(token);
192
+ assert(result === token, `validateAuthToken should return ${token}`);
193
+ });
194
+ });
195
+
196
+ await test("validateAuthToken handles null/undefined", () => {
197
+ assert(validateAuthToken(null) === null, "Should return null for null");
198
+ assert(validateAuthToken(undefined) === null, "Should return null for undefined");
199
+ assert(validateAuthToken("") === null, "Should return null for empty string");
200
+ assert(validateAuthToken(" ") === null, "Should return null for whitespace");
201
+ });
202
+
203
+ await test("validateAuthToken rejects invalid tokens", () => {
204
+ assertThrows(() => validateAuthToken(123), ValidationError, "Should reject number");
205
+ assertThrows(() => validateAuthToken("short"), ValidationError, "Should reject too short token");
206
+ assertThrows(() => validateAuthToken("a".repeat(1001)), ValidationError, "Should reject too long token");
207
+ });
208
+
209
+ await test("validateModelResponse accepts valid responses", () => {
210
+ const validResponses = [
211
+ { models: [{ name: "gpt-4" }, { name: "claude-3" }] },
212
+ { data: [{ name: "gpt-4" }, { name: "claude-3" }] },
213
+ [{ name: "gpt-4" }, { name: "claude-3" }]
214
+ ];
215
+
216
+ validResponses.forEach(response => {
217
+ const result = validateModelResponse(response);
218
+ assert(Array.isArray(result), "Should return array");
219
+ assert(result.length === 2, "Should return correct number of models");
220
+ });
221
+ });
222
+
223
+ await test("validateModelResponse rejects invalid responses", () => {
224
+ assertThrows(() => validateModelResponse(null), ValidationError, "Should reject null");
225
+ assertThrows(() => validateModelResponse("string"), ValidationError, "Should reject string");
226
+ assertThrows(() => validateModelResponse({}), ValidationError, "Should reject empty object");
227
+ assertThrows(() => validateModelResponse({ models: "not array" }), ValidationError, "Should reject non-array models");
228
+ assertThrows(() => validateModelResponse({ models: [] }), ValidationError, "Should reject empty array");
229
+ assertThrows(() => validateModelResponse([{ name: 123 }]), ValidationError, "Should reject model without name string");
230
+ assertThrows(() => validateModelResponse([{ name: "" }]), ValidationError, "Should reject model with empty name");
231
+ });
232
+
233
+ await test("validateHostname accepts valid hostnames", () => {
234
+ const validHostnames = [
235
+ "localhost",
236
+ "api.openrouter.ai",
237
+ "192.168.1.1",
238
+ "example.com",
239
+ "sub.domain.example.com"
240
+ ];
241
+
242
+ validHostnames.forEach(hostname => {
243
+ const result = validateHostname(hostname);
244
+ assert(result === hostname, `validateHostname should return ${hostname}`);
245
+ });
246
+ });
247
+
248
+ await test("validateHostname rejects invalid hostnames", () => {
249
+ assertThrows(() => validateHostname(null), ValidationError, "Should reject null");
250
+ assertThrows(() => validateHostname(123), ValidationError, "Should reject number");
251
+ assertThrows(() => validateHostname(""), ValidationError, "Should reject empty string");
252
+ assertThrows(() => validateHostname(" "), ValidationError, "Should reject whitespace");
253
+ assertThrows(() => validateHostname("a".repeat(254)), ValidationError, "Should reject too long hostname");
254
+ assertThrows(() => validateHostname("invalid hostname"), ValidationError, "Should reject hostname with space");
255
+ assertThrows(() => validateHostname("invalid@hostname"), ValidationError, "Should reject hostname with @");
256
+ });
257
+
258
+ await test("validatePort accepts valid ports", () => {
259
+ const validPorts = [1, 80, 443, 11434, 65535];
260
+
261
+ validPorts.forEach(port => {
262
+ const result = validatePort(port);
263
+ assert(result === port, `validatePort should return ${port}`);
264
+ });
265
+ });
266
+
267
+ await test("validatePort rejects invalid ports", () => {
268
+ assertThrows(() => validatePort(null), ValidationError, "Should reject null");
269
+ assertThrows(() => validatePort("80"), ValidationError, "Should reject string");
270
+ assertThrows(() => validatePort(80.5), ValidationError, "Should reject decimal");
271
+ assertThrows(() => validatePort(0), ValidationError, "Should reject 0");
272
+ assertThrows(() => validatePort(-1), ValidationError, "Should reject negative");
273
+ assertThrows(() => validatePort(65536), ValidationError, "Should reject too high");
274
+ });
275
+
276
+ // ========== INTEGRATION TESTS ==========
277
+
278
+ await test("Environment file contains all required variables", () => {
279
+ const envPath = path.join(__dirname, "..", ".env");
280
+ assert(fs.existsSync(envPath), ".env file should exist");
281
+
282
+ const envContent = fs.readFileSync(envPath, "utf8");
283
+
284
+ Object.values(ENV_VARS).forEach(varName => {
285
+ assert(envContent.includes(varName), `.env should contain ${varName}`);
286
+ });
287
+ });
288
+
289
+ await test("loadEnvFile returns envFile path", () => {
290
+ const envVars = loadEnvFile();
291
+ assert(envVars, "loadEnvFile should return object");
292
+ assert(envVars.envFile, "loadEnvFile should include envFile path");
293
+ assert(typeof envVars.envFile === "string", "envFile should be string");
294
+ });
295
+
296
+ await test("Error stack traces are preserved", () => {
297
+ const cause = new Error("Original cause");
298
+ const error = new ProviderError("Test error", "test", cause);
299
+
300
+ assert(error.stack, "Error should have stack trace");
301
+ assert(error.stack.includes("ProviderError"), "Stack should mention error type");
302
+ });
303
+
304
+ await test("Constants are properly defined", () => {
305
+ // Test that constants have expected values (they're objects, not frozen by default)
306
+ const originalHost = OLLAMA.DEFAULT_HOST;
307
+ assert(typeof originalHost === 'string', "OLLAMA.DEFAULT_HOST should be string");
308
+ assert(OLLAMA.DEFAULT_HOST === 'localhost', "OLLAMA.DEFAULT_HOST should be localhost");
309
+
310
+ // Test that constants objects exist and have expected structure
311
+ assert(typeof OLLAMA === 'object', "OLLAMA should be object");
312
+ assert(typeof CACHE === 'object', "CACHE should be object");
313
+ assert(typeof HTTP_STATUS === 'object', "HTTP_STATUS should be object");
314
+ });
315
+
316
+ console.log("\n๐ŸŽ‰ All validation, errors, and constants tests passed!");
317
+ }
318
+
319
+ // Run tests
320
+ runTests().catch((error) => {
321
+ console.error("๐Ÿ’ฅ Test suite failed:", error.message);
322
+ console.error(error.stack);
323
+ process.exit(1);
324
+ });