| import { describe, it, expect, beforeAll } from 'vitest';
|
| import type { Express } from 'express';
|
| import { createApp } from '../../app.js';
|
| import { initDb } from '../../db/index.js';
|
| import { mintDashboardToken, isGatedApiPath } from '../helpers/auth.js';
|
|
|
| let dashToken = '';
|
|
|
| async function request(app: Express, method: string, path: string, body?: any) {
|
| const server = app.listen(0);
|
| const addr = server.address() as any;
|
| const url = `http://127.0.0.1:${addr.port}${path}`;
|
|
|
| const res = await fetch(url, {
|
| method,
|
| headers: {
|
| ...(body ? { 'Content-Type': 'application/json' } : {}),
|
| ...(isGatedApiPath(path) ? { Authorization: `Bearer ${dashToken}` } : {}),
|
| },
|
| body: body ? JSON.stringify(body) : undefined,
|
| });
|
|
|
| const data = await res.json().catch(() => null);
|
| server.close();
|
| return { status: res.status, body: data };
|
| }
|
|
|
| describe('Fallback API', () => {
|
| let app: Express;
|
|
|
| beforeAll(() => {
|
| process.env.ENCRYPTION_KEY = '0'.repeat(64);
|
| initDb(':memory:');
|
| app = createApp();
|
| dashToken = mintDashboardToken();
|
| });
|
|
|
| it('GET /api/fallback returns fallback chain', async () => {
|
| const { status, body } = await request(app, 'GET', '/api/fallback');
|
| expect(status).toBe(200);
|
| expect(Array.isArray(body)).toBe(true);
|
| expect(body.length).toBeGreaterThan(0);
|
|
|
| for (let i = 1; i < body.length; i++) {
|
| expect(body[i].priority).toBeGreaterThanOrEqual(body[i - 1].priority);
|
| }
|
| });
|
|
|
| it('GET /api/fallback entries have expected fields', async () => {
|
| const { body } = await request(app, 'GET', '/api/fallback');
|
| const first = body[0];
|
| expect(first).toHaveProperty('modelDbId');
|
| expect(first).toHaveProperty('priority');
|
| expect(first).toHaveProperty('enabled');
|
| expect(first).toHaveProperty('platform');
|
| expect(first).toHaveProperty('displayName');
|
| expect(first).toHaveProperty('intelligenceRank');
|
| });
|
|
|
| it('PUT /api/fallback updates order', async () => {
|
| const { body: original } = await request(app, 'GET', '/api/fallback');
|
|
|
|
|
| const reversed = original.map((e: any, i: number) => ({
|
| modelDbId: e.modelDbId,
|
| priority: original.length - i,
|
| enabled: e.enabled,
|
| }));
|
|
|
| const { status } = await request(app, 'PUT', '/api/fallback', reversed);
|
| expect(status).toBe(200);
|
|
|
|
|
| const { body: after } = await request(app, 'GET', '/api/fallback');
|
| expect(after[0].modelDbId).toBe(original[original.length - 1].modelDbId);
|
|
|
|
|
| const restore = original.map((e: any, i: number) => ({
|
| modelDbId: e.modelDbId,
|
| priority: i + 1,
|
| enabled: e.enabled,
|
| }));
|
| await request(app, 'PUT', '/api/fallback', restore);
|
| });
|
|
|
| it('POST /api/fallback/sort/intelligence sorts by cross-provider tier, then rank', async () => {
|
| const { status } = await request(app, 'POST', '/api/fallback/sort/intelligence');
|
| expect(status).toBe(200);
|
|
|
| const { body } = await request(app, 'GET', '/api/fallback');
|
|
|
|
|
|
|
| const tier: Record<string, number> = { Frontier: 1, Large: 2, Medium: 3, Small: 4 };
|
| const tierOf = (label: string) => tier[label] ?? 5;
|
|
|
| for (let i = 1; i < body.length; i++) {
|
| const prevTier = tierOf(body[i - 1].sizeLabel);
|
| const curTier = tierOf(body[i].sizeLabel);
|
|
|
| expect(curTier).toBeGreaterThanOrEqual(prevTier);
|
|
|
| if (curTier === prevTier) {
|
| expect(body[i].intelligenceRank).toBeGreaterThanOrEqual(body[i - 1].intelligenceRank);
|
| }
|
| }
|
| });
|
|
|
| it('intelligence sort never places a weaker tier above a Frontier model (#135)', async () => {
|
| await request(app, 'POST', '/api/fallback/sort/intelligence');
|
| const { body } = await request(app, 'GET', '/api/fallback');
|
|
|
|
|
|
|
| const lastFrontier = body.map((m: any) => m.sizeLabel).lastIndexOf('Frontier');
|
| const firstNonFrontier = body.findIndex((m: any) => m.sizeLabel !== 'Frontier');
|
| if (lastFrontier !== -1 && firstNonFrontier !== -1) {
|
| expect(lastFrontier).toBeLessThan(firstNonFrontier);
|
| }
|
| });
|
|
|
| it('POST /api/fallback/sort/speed sorts by speed', async () => {
|
| const { status } = await request(app, 'POST', '/api/fallback/sort/speed');
|
| expect(status).toBe(200);
|
|
|
| const { body } = await request(app, 'GET', '/api/fallback');
|
|
|
| for (let i = 1; i < body.length; i++) {
|
| expect(body[i].speedRank).toBeGreaterThanOrEqual(body[i - 1].speedRank);
|
| }
|
| });
|
|
|
| it('POST /api/fallback/sort/invalid returns 400', async () => {
|
| const { status } = await request(app, 'POST', '/api/fallback/sort/invalid');
|
| expect(status).toBe(400);
|
| });
|
| });
|
|
|