const { ViolationTypes } = require('librechat-data-provider'); jest.mock('@librechat/api', () => ({ handleError: jest.fn(), })); jest.mock('~/server/controllers/ModelController', () => ({ getModelsConfig: jest.fn(), })); jest.mock('~/server/services/Config', () => ({ getEndpointsConfig: jest.fn(), })); jest.mock('~/cache', () => ({ logViolation: jest.fn(), })); const { handleError } = require('@librechat/api'); const { getModelsConfig } = require('~/server/controllers/ModelController'); const { getEndpointsConfig } = require('~/server/services/Config'); const { logViolation } = require('~/cache'); const validateModel = require('../validateModel'); describe('validateModel', () => { let req, res, next; beforeEach(() => { jest.clearAllMocks(); req = { body: { model: 'gpt-4o', endpoint: 'openAI' } }; res = {}; next = jest.fn(); getEndpointsConfig.mockResolvedValue({ openAI: { userProvide: false }, }); getModelsConfig.mockResolvedValue({ openAI: ['gpt-4o', 'gpt-4o-mini'], }); }); describe('format validation', () => { it('rejects missing model', async () => { req.body.model = undefined; await validateModel(req, res, next); expect(handleError).toHaveBeenCalledWith(res, { text: 'Model not provided' }); expect(next).not.toHaveBeenCalled(); }); it('rejects non-string model', async () => { req.body.model = 12345; await validateModel(req, res, next); expect(handleError).toHaveBeenCalledWith(res, { text: 'Model not provided' }); expect(next).not.toHaveBeenCalled(); }); it('rejects model exceeding 256 chars', async () => { req.body.model = 'a'.repeat(257); await validateModel(req, res, next); expect(handleError).toHaveBeenCalledWith(res, { text: 'Invalid model identifier' }); }); it('rejects model with leading special character', async () => { req.body.model = '.bad-model'; await validateModel(req, res, next); expect(handleError).toHaveBeenCalledWith(res, { text: 'Invalid model identifier' }); }); it('rejects model with script injection', async () => { req.body.model = ''; await validateModel(req, res, next); expect(handleError).toHaveBeenCalledWith(res, { text: 'Invalid model identifier' }); }); it('trims whitespace before validation', async () => { req.body.model = ' gpt-4o '; getModelsConfig.mockResolvedValue({ openAI: ['gpt-4o'] }); await validateModel(req, res, next); expect(next).toHaveBeenCalled(); expect(handleError).not.toHaveBeenCalled(); }); it('rejects model with spaces in the middle', async () => { req.body.model = 'gpt 4o'; await validateModel(req, res, next); expect(handleError).toHaveBeenCalledWith(res, { text: 'Invalid model identifier' }); }); it('accepts standard model IDs', async () => { const validModels = [ 'gpt-4o', 'claude-3-5-sonnet-20241022', 'us.amazon.nova-pro-v1:0', 'qwen/qwen3.6-plus-preview:free', 'Meta-Llama-3-8B-Instruct-4bit', ]; for (const model of validModels) { jest.clearAllMocks(); req.body.model = model; getEndpointsConfig.mockResolvedValue({ openAI: { userProvide: false } }); getModelsConfig.mockResolvedValue({ openAI: [model] }); next.mockClear(); await validateModel(req, res, next); expect(next).toHaveBeenCalled(); expect(handleError).not.toHaveBeenCalled(); } }); }); describe('userProvide early-return', () => { it('calls next() immediately for userProvide endpoints without checking model list', async () => { getEndpointsConfig.mockResolvedValue({ openAI: { userProvide: true }, }); req.body.model = 'any-model-from-user-key'; await validateModel(req, res, next); expect(next).toHaveBeenCalled(); expect(getModelsConfig).not.toHaveBeenCalled(); }); it('does not call getModelsConfig for userProvide endpoints', async () => { getEndpointsConfig.mockResolvedValue({ CustomEndpoint: { userProvide: true }, }); req.body = { model: 'custom-model', endpoint: 'CustomEndpoint' }; await validateModel(req, res, next); expect(getModelsConfig).not.toHaveBeenCalled(); expect(next).toHaveBeenCalled(); }); }); describe('system endpoint list validation', () => { it('rejects a model not in the available list', async () => { req.body.model = 'not-in-list'; await validateModel(req, res, next); expect(logViolation).toHaveBeenCalledWith( req, res, ViolationTypes.ILLEGAL_MODEL_REQUEST, expect.any(Object), expect.anything(), ); expect(handleError).toHaveBeenCalledWith(res, { text: 'Illegal model request' }); expect(next).not.toHaveBeenCalled(); }); it('accepts a model in the available list', async () => { req.body.model = 'gpt-4o'; await validateModel(req, res, next); expect(next).toHaveBeenCalled(); expect(handleError).not.toHaveBeenCalled(); }); it('rejects when endpoint has no models loaded', async () => { getModelsConfig.mockResolvedValue({ openAI: undefined }); await validateModel(req, res, next); expect(handleError).toHaveBeenCalledWith(res, { text: 'Endpoint models not loaded' }); }); it('rejects when modelsConfig is null', async () => { getModelsConfig.mockResolvedValue(null); await validateModel(req, res, next); expect(handleError).toHaveBeenCalledWith(res, { text: 'Models not loaded' }); }); }); });