From 5636c5bf42dcb497f638ac26fbe23e6941eb1d85 Mon Sep 17 00:00:00 2001 From: Jah-yee <166608075+Jah-yee@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:08:08 +0800 Subject: [PATCH] test: add offline providers.json validation (closes #2) --- tests/providers-validate.test.mjs | 128 ++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tests/providers-validate.test.mjs diff --git a/tests/providers-validate.test.mjs b/tests/providers-validate.test.mjs new file mode 100644 index 0000000..bdac521 --- /dev/null +++ b/tests/providers-validate.test.mjs @@ -0,0 +1,128 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const providersPath = join(__dirname, '..', 'providers.json'); +const raw = readFileSync(providersPath, 'utf-8'); +const data = JSON.parse(raw); +const providers = data.providers ?? []; + +const VALID_PROTOCOLS = new Set(['openai-chat', 'gemini', 'anthropic']); +const VALID_AUTH_TYPES = new Set([ + 'bearer', 'x-api-key', 'api-key', + 'x-goog-api-key', 'google-api-key', + 'none', 'custom', +]); +const EMBEDDED_CREDS_KEYS = ['apiKey', 'token', 'secret', 'password', 'api_key', 'api-token']; + +function isValidHttpUrl(s) { + try { + const u = new URL(s); + return u.protocol === 'http:' || u.protocol === 'https:'; + } catch { + return false; + } +} + +function hasEmbeddedCreds(obj) { + for (const key of EMBEDDED_CREDS_KEYS) { + if (key in obj) return true; + } + return false; +} + +// ─── Schema & uniqueness ──────────────────────────────────────────────────── + +test('providers.json parses to an object with a providers array', () => { + assert.ok(Array.isArray(providers)); +}); + +test('every provider has a unique id', () => { + const ids = providers.map(p => p.id); + const unique = new Set(ids); + assert.equal(ids.length, unique.size, `Duplicate ids found: ${ids.filter((id, i) => ids.indexOf(id) !== i).join(', ')}`); +}); + +test('every provider has a non-empty id', () => { + for (const p of providers) { + assert.ok(p.id && p.id.trim().length > 0, `Provider missing or empty id: ${JSON.stringify(p).slice(0, 80)}`); + } +}); + +test('every provider has a non-empty name', () => { + for (const p of providers) { + assert.ok(p.name && p.name.trim().length > 0, `Provider "${p.id}" missing or empty name`); + } +}); + +test('every provider has a non-empty description', () => { + for (const p of providers) { + assert.ok(p.description && p.description.trim().length > 0, `Provider "${p.id}" missing or empty description`); + } +}); + +test('every provider has a non-empty defaultModel', () => { + for (const p of providers) { + assert.ok(p.defaultModel && p.defaultModel.trim().length > 0, `Provider "${p.id}" missing or empty defaultModel`); + } +}); + +// ─── baseUrl ─────────────────────────────────────────────────────────────── + +test('every baseUrl is a valid http or https URL', () => { + for (const p of providers) { + assert.ok(isValidHttpUrl(p.baseUrl), `Provider "${p.id}" has invalid baseUrl: "${p.baseUrl}"`); + } +}); + +test('no baseUrl contains embedded credentials', () => { + for (const p of providers) { + try { + const u = new URL(p.baseUrl); + assert.ok(!u.username && !u.password, + `Provider "${p.id}" baseUrl contains embedded credentials: "${p.baseUrl}"`); + } catch { + // isValidHttpUrl test above will catch this + } + } +}); + +// ─── protocol ───────────────────────────────────────────────────────────── + +test('every provider has a supported protocol', () => { + for (const p of providers) { + assert.ok( + VALID_PROTOCOLS.has(p.protocol), + `Provider "${p.id}" has unsupported protocol: "${p.protocol}" (supported: ${[...VALID_PROTOCOLS].join(', ')})` + ); + } +}); + +// ─── auth ───────────────────────────────────────────────────────────────── + +test('every provider has an auth object', () => { + for (const p of providers) { + assert.ok(p.auth && typeof p.auth === 'object', `Provider "${p.id}" missing or non-object auth`); + } +}); + +test('every auth.type is supported', () => { + for (const p of providers) { + assert.ok( + VALID_AUTH_TYPES.has(p.auth.type), + `Provider "${p.id}" has unsupported auth.type: "${p.auth.type}" (supported: ${[...VALID_AUTH_TYPES].join(', ')})` + ); + } +}); + +test('no auth object contains embedded credentials', () => { + for (const p of providers) { + assert.ok( + !hasEmbeddedCreds(p.auth), + `Provider "${p.id}" auth object contains embedded credential key: ${JSON.stringify(p.auth)}` + ); + } +});