Spaces:
Sleeping
Sleeping
| import { describe, it, expect } from 'vitest'; | |
| import Database from 'better-sqlite3'; | |
| import { initDb } from '../../db/index.js'; | |
| /** | |
| * All migrations must be idempotent: running initDb twice on the same | |
| * physical database file should produce identical state. | |
| */ | |
| describe('Migration idempotency', () => { | |
| it('initDb on a fresh in-memory DB then re-run produces identical row counts', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| // Use a single shared file so both inits hit the same DB. | |
| const tmpPath = `/tmp/freeapi-idempotency-${Date.now()}.db`; | |
| const db1 = initDb(tmpPath); | |
| const before = { | |
| models: (db1.prepare('SELECT COUNT(*) AS c FROM models').get() as { c: number }).c, | |
| fallback: (db1.prepare('SELECT COUNT(*) AS c FROM fallback_config').get() as { c: number }).c, | |
| enabledModels: (db1.prepare('SELECT COUNT(*) AS c FROM models WHERE enabled = 1').get() as { c: number }).c, | |
| disabledModels: (db1.prepare('SELECT COUNT(*) AS c FROM models WHERE enabled = 0').get() as { c: number }).c, | |
| orphanFallbacks: (db1.prepare(` | |
| SELECT COUNT(*) AS c FROM fallback_config f | |
| LEFT JOIN models m ON f.model_db_id = m.id | |
| WHERE m.id IS NULL | |
| `).get() as { c: number }).c, | |
| }; | |
| db1.close(); | |
| // Re-init the same DB file — V1..V9 should all no-op idempotently. | |
| const db2 = initDb(tmpPath); | |
| const after = { | |
| models: (db2.prepare('SELECT COUNT(*) AS c FROM models').get() as { c: number }).c, | |
| fallback: (db2.prepare('SELECT COUNT(*) AS c FROM fallback_config').get() as { c: number }).c, | |
| enabledModels: (db2.prepare('SELECT COUNT(*) AS c FROM models WHERE enabled = 1').get() as { c: number }).c, | |
| disabledModels: (db2.prepare('SELECT COUNT(*) AS c FROM models WHERE enabled = 0').get() as { c: number }).c, | |
| orphanFallbacks: (db2.prepare(` | |
| SELECT COUNT(*) AS c FROM fallback_config f | |
| LEFT JOIN models m ON f.model_db_id = m.id | |
| WHERE m.id IS NULL | |
| `).get() as { c: number }).c, | |
| }; | |
| db2.close(); | |
| expect(after).toEqual(before); | |
| expect(after.orphanFallbacks).toBe(0); | |
| }); | |
| it('every catalog row has exactly one fallback_config entry', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| const rows = db.prepare(` | |
| SELECT m.id, COUNT(f.id) AS fb_count | |
| FROM models m | |
| LEFT JOIN fallback_config f ON m.id = f.model_db_id | |
| GROUP BY m.id | |
| HAVING COUNT(f.id) <> 1 | |
| `).all() as { id: number; fb_count: number }[]; | |
| expect(rows).toEqual([]); | |
| }); | |
| it('UNIQUE(platform, model_id) constraint holds — no duplicate catalog rows', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| const dups = db.prepare(` | |
| SELECT platform, model_id, COUNT(*) AS c FROM models | |
| GROUP BY platform, model_id | |
| HAVING COUNT(*) > 1 | |
| `).all(); | |
| expect(dups).toEqual([]); | |
| }); | |
| it('V12: dead OR :free rows are absent and the four new rows are present', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| const dead = db.prepare(` | |
| SELECT model_id FROM models | |
| WHERE platform = 'openrouter' | |
| AND model_id IN ('inclusionai/ling-2.6-1t:free', 'tencent/hy3-preview:free') | |
| `).all(); | |
| expect(dead).toEqual([]); | |
| // V21 pruned these three after live probing returned 404 "no endpoints found". | |
| const pruned = db.prepare(` | |
| SELECT model_id FROM models | |
| WHERE platform = 'openrouter' | |
| AND model_id IN ( | |
| 'arcee-ai/trinity-large-thinking:free', | |
| 'minimax/minimax-m2.5:free', | |
| 'baidu/cobuddy:free' | |
| ) | |
| `).all(); | |
| expect(pruned).toEqual([]); | |
| const live = db.prepare(` | |
| SELECT model_id FROM models | |
| WHERE platform = 'openrouter' | |
| AND model_id IN ( | |
| 'openrouter/owl-alpha', | |
| 'nousresearch/hermes-3-llama-3.1-405b:free' | |
| ) | |
| ORDER BY model_id | |
| `).all() as { model_id: string }[]; | |
| expect(live.map(r => r.model_id)).toEqual([ | |
| 'nousresearch/hermes-3-llama-3.1-405b:free', | |
| 'openrouter/owl-alpha', | |
| ]); | |
| const widened = db.prepare(` | |
| SELECT model_id, context_window FROM models | |
| WHERE platform = 'openrouter' | |
| AND model_id IN ('nvidia/nemotron-3-super-120b-a12b:free', 'qwen/qwen3-coder:free') | |
| ORDER BY model_id | |
| `).all() as { model_id: string; context_window: number }[]; | |
| expect(widened).toEqual([ | |
| { model_id: 'nvidia/nemotron-3-super-120b-a12b:free', context_window: 1000000 }, | |
| { model_id: 'qwen/qwen3-coder:free', context_window: 1048576 }, | |
| ]); | |
| }); | |
| it('V13: cross-provider catalog refresh applies cleanly', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| // Disables — row kept but enabled=0. | |
| const disabled = db.prepare(` | |
| SELECT platform, model_id, enabled FROM models | |
| WHERE (platform = 'google' AND model_id = 'gemini-3.1-pro-preview') | |
| OR (platform = 'ollama' AND model_id IN ('kimi-k2-thinking', 'mistral-large-3:675b', 'deepseek-v3.2')) | |
| ORDER BY platform, model_id | |
| `).all() as { platform: string; model_id: string; enabled: number }[]; | |
| expect(disabled).toHaveLength(4); | |
| for (const row of disabled) expect(row.enabled).toBe(0); | |
| // Hard removals — row is gone entirely. | |
| const removed = db.prepare(` | |
| SELECT model_id FROM models | |
| WHERE (platform = 'sambanova' AND model_id = 'DeepSeek-V3.1-cb') | |
| OR (platform = 'cloudflare' AND model_id = '@cf/moonshotai/kimi-k2.5') | |
| `).all(); | |
| expect(removed).toEqual([]); | |
| // New rows present across providers (incl. new huggingface platform). | |
| const additions = db.prepare(` | |
| SELECT platform, model_id FROM models | |
| WHERE (platform, model_id) IN (VALUES | |
| ('groq', 'openai/gpt-oss-safeguard-20b'), | |
| ('cloudflare', '@cf/nvidia/nemotron-3-120b-a12b'), | |
| ('cloudflare', '@cf/google/gemma-4-26b-a4b-it'), | |
| ('google', 'gemini-3.5-flash'), | |
| ('nvidia', 'deepseek-ai/deepseek-v4-flash'), | |
| ('nvidia', 'z-ai/glm-5.1'), | |
| ('nvidia', 'qwen/qwen3-coder-480b-a35b-instruct'), | |
| ('mistral', 'mistral-small-latest'), | |
| ('mistral', 'ministral-8b-latest'), | |
| ('cohere', 'command-a-reasoning-08-2025'), | |
| ('cohere', 'command-r-08-2024'), | |
| ('ollama', 'qwen3-coder-next'), | |
| ('huggingface', 'deepseek-ai/DeepSeek-V4-Flash'), | |
| ('huggingface', 'moonshotai/Kimi-K2.6'), | |
| ('huggingface', 'Qwen/Qwen3-Coder-Next') | |
| ) | |
| `).all(); | |
| expect(additions).toHaveLength(15); | |
| // Spot-check critical limit/context updates. | |
| const cerebrasLimits = db.prepare(` | |
| SELECT rpm_limit, rpd_limit, tpm_limit, tpd_limit FROM models | |
| WHERE platform = 'cerebras' AND model_id = 'qwen-3-235b-a22b-instruct-2507' | |
| `).get() as { rpm_limit: number; rpd_limit: number; tpm_limit: number; tpd_limit: number }; | |
| expect(cerebrasLimits).toEqual({ rpm_limit: 5, rpd_limit: 2400, tpm_limit: 30000, tpd_limit: 1000000 }); | |
| const cfFp8Ctx = (db.prepare(` | |
| SELECT context_window FROM models WHERE platform = 'cloudflare' AND model_id = '@cf/meta/llama-3.3-70b-instruct-fp8-fast' | |
| `).get() as { context_window: number }).context_window; | |
| expect(cfFp8Ctx).toBe(24000); | |
| const mistralCtx = db.prepare(` | |
| SELECT model_id, context_window FROM models | |
| WHERE platform = 'mistral' | |
| AND model_id IN ('codestral-latest', 'devstral-latest', 'magistral-medium-latest', 'mistral-large-latest') | |
| ORDER BY model_id | |
| `).all() as { model_id: string; context_window: number }[]; | |
| expect(mistralCtx).toEqual([ | |
| { model_id: 'codestral-latest', context_window: 256000 }, | |
| { model_id: 'devstral-latest', context_window: 262144 }, | |
| { model_id: 'magistral-medium-latest', context_window: 131072 }, | |
| { model_id: 'mistral-large-latest', context_window: 262144 }, | |
| ]); | |
| }); | |
| it('V14: cerebras deprecation disables qwen-3-235b and llama3.1-8b but keeps gpt-oss-120b enabled', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| const rows = db.prepare(` | |
| SELECT model_id, enabled FROM models | |
| WHERE platform = 'cerebras' | |
| AND model_id IN ('qwen-3-235b-a22b-instruct-2507', 'llama3.1-8b', 'gpt-oss-120b') | |
| ORDER BY model_id | |
| `).all() as { model_id: string; enabled: number }[]; | |
| expect(rows).toEqual([ | |
| { model_id: 'gpt-oss-120b', enabled: 1 }, | |
| { model_id: 'llama3.1-8b', enabled: 0 }, | |
| { model_id: 'qwen-3-235b-a22b-instruct-2507', enabled: 0 }, | |
| ]); | |
| }); | |
| it('V23: sambanova/chutes are gone; live-verified free additions are present with the right flags', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| // Platform drops — no model, fallback, or key rows survive. | |
| const deadRows = db.prepare( | |
| `SELECT COUNT(*) AS n FROM models WHERE platform IN ('sambanova', 'chutes')` | |
| ).get() as { n: number }; | |
| expect(deadRows.n).toBe(0); | |
| const deadKeys = db.prepare( | |
| `SELECT COUNT(*) AS n FROM api_keys WHERE platform IN ('sambanova', 'chutes')` | |
| ).get() as { n: number }; | |
| expect(deadKeys.n).toBe(0); | |
| // Additions, with the flags the live probe verified (vision/tools come | |
| // from the V16/V22 rules, so they must hold on a fresh seed too). | |
| const added = db.prepare(` | |
| SELECT platform, model_id, enabled, supports_vision, supports_tools FROM models | |
| WHERE (platform = 'openrouter' AND model_id IN ( | |
| 'moonshotai/kimi-k2.6:free', | |
| 'nvidia/nemotron-3-ultra-550b-a55b:free', | |
| 'nvidia/nemotron-nano-12b-v2-vl:free', | |
| 'meta-llama/llama-3.2-3b-instruct:free', | |
| 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free')) | |
| OR (platform = 'zhipu' AND model_id = 'glm-4.6v-flash') | |
| ORDER BY platform, model_id | |
| `).all() as { model_id: string; enabled: number; supports_vision: number; supports_tools: number }[]; | |
| expect(added.map(r => [r.model_id, r.enabled, r.supports_vision, r.supports_tools])).toEqual([ | |
| ['cognitivecomputations/dolphin-mistral-24b-venice-edition:free', 1, 0, 0], | |
| ['meta-llama/llama-3.2-3b-instruct:free', 1, 0, 0], | |
| ['moonshotai/kimi-k2.6:free', 1, 0, 1], | |
| ['nvidia/nemotron-3-ultra-550b-a55b:free', 0, 0, 1], // hangs 180s+; seeded disabled (tools verified via Zen in V24) | |
| ['nvidia/nemotron-nano-12b-v2-vl:free', 1, 1, 1], | |
| ['glm-4.6v-flash', 1, 1, 1], | |
| ]); | |
| }); | |
| it('V24: Zen roster refresh lands and the hung NIM gemma is paused', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| const zen = db.prepare(` | |
| SELECT model_id, enabled, supports_tools FROM models | |
| WHERE platform = 'opencode' AND model_id IN ('nemotron-3-ultra-free', 'minimax-m3-free') | |
| ORDER BY model_id | |
| `).all() as { model_id: string; enabled: number; supports_tools: number }[]; | |
| expect(zen.map(r => [r.model_id, r.enabled, r.supports_tools])).toEqual([ | |
| // minimax-m3-free was seeded enabled here in V24, then retired in V25 when | |
| // its free promo ended (now enabled=0). nemotron-3-ultra-free is still live. | |
| ['minimax-m3-free', 0, 1], | |
| ['nemotron-3-ultra-free', 1, 1], | |
| ]); | |
| // The hung NIM gemma route is paused (row kept, enabled=0, re-asserted | |
| // each boot like the V13 disables). | |
| const gemma = db.prepare(` | |
| SELECT enabled FROM models WHERE platform = 'nvidia' AND model_id = 'google/gemma-4-31b-it' | |
| `).get() as { enabled: number }; | |
| expect(gemma.enabled).toBe(0); | |
| }); | |
| it('V25: dead OpenCode Zen free promos (nemotron-3-super-free, minimax-m3-free) are disabled', () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| const dead = db.prepare(` | |
| SELECT model_id, enabled FROM models | |
| WHERE platform = 'opencode' AND model_id IN ('nemotron-3-super-free', 'minimax-m3-free') | |
| ORDER BY model_id | |
| `).all() as { model_id: string; enabled: number }[]; | |
| expect(dead.map(r => [r.model_id, r.enabled])).toEqual([ | |
| ['minimax-m3-free', 0], | |
| ['nemotron-3-super-free', 0], | |
| ]); | |
| }); | |
| it('all enabled catalog platforms have a registered provider', async () => { | |
| process.env.ENCRYPTION_KEY = '0'.repeat(64); | |
| const db = initDb(':memory:'); | |
| const { hasProvider } = await import('../../providers/index.js'); | |
| const platforms = (db.prepare( | |
| `SELECT DISTINCT platform FROM models WHERE enabled = 1` | |
| ).all() as { platform: any }[]).map(r => r.platform); | |
| const missing = platforms.filter(p => !hasProvider(p)); | |
| expect(missing).toEqual([]); | |
| }); | |
| }); | |