import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'; import type { NextRequest } from 'next/server'; const mocks = vi.hoisted(() => ({ validateUrlForSSRF: vi.fn() })); vi.mock('@/lib/server/ssrf-guard', () => ({ validateUrlForSSRF: mocks.validateUrlForSSRF, })); vi.mock('@/lib/logger', () => ({ createLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }), })); async function postProbeModels(body: Record) { const { POST } = await import('@/app/api/provider/probe-models/route'); const request = new Request('http://localhost/api/provider/probe-models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); return POST(request as unknown as NextRequest); } describe('POST /api/provider/probe-models', () => { beforeEach(() => { vi.resetModules(); mocks.validateUrlForSSRF.mockReset(); mocks.validateUrlForSSRF.mockResolvedValue(null); }); afterEach(() => { vi.unstubAllGlobals(); }); it('maps an upstream redirect to the exact redirect-not-allowed contract without reading it', async () => { const text = vi.fn().mockResolvedValue('redirect response body'); const fetchMock = vi.fn().mockResolvedValue({ ok: false, status: 302, text, } as unknown as Response); vi.stubGlobal('fetch', fetchMock); const res = await postProbeModels({ baseUrl: 'https://api.example.com', apiKey: 'test-key', }); const json = await res.json(); expect(res.status).toBe(403); expect(json).toEqual({ success: false, errorCode: 'REDIRECT_NOT_ALLOWED', error: 'Redirects are not allowed', }); expect(fetchMock).toHaveBeenCalledTimes(1); expect(text).not.toHaveBeenCalled(); }); it('preserves successful model filtering and response metadata', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue( new Response( JSON.stringify({ data: [ { id: 'chat-model', owned_by: 'provider' }, { id: 'text-embedding-3-small', owned_by: 'provider' }, ], }), { status: 200, headers: { 'Content-Type': 'application/json' } }, ), ), ); const res = await postProbeModels({ baseUrl: 'https://api.example.com', apiKey: 'test-key', }); const json = await res.json(); expect(res.status).toBe(200); expect(json).toEqual({ success: true, models: [{ id: 'chat-model', ownedBy: 'provider' }], total: 2, filtered: 1, }); }); it.each([401, 403])('preserves the API-key error contract for upstream %i', async (status) => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, status, text: vi.fn().mockResolvedValue('invalid key'), } as unknown as Response), ); const res = await postProbeModels({ baseUrl: 'https://api.example.com', apiKey: 'bad-key', }); const json = await res.json(); expect(res.status).toBe(401); expect(json).toEqual({ success: false, errorCode: 'INVALID_REQUEST', error: 'API key is invalid or expired', }); }); it('preserves the manual-entry response when no model endpoint exists', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, status: 404, text: vi.fn(), } as unknown as Response), ); const res = await postProbeModels({ baseUrl: 'https://api.example.com', apiKey: 'test-key', }); const json = await res.json(); expect(res.status).toBe(404); expect(json).toEqual({ success: false, errorCode: 'INVALID_REQUEST', error: 'This provider does not expose a model list', }); }); });