From cea4f57a73b4a3dedcbb4a7157541302b69934aa Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 3 Dec 2025 23:23:36 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20feat:=20Add=20Config=20?= =?UTF-8?q?Validation=20Bypass=20&=20Improve=20Error=20Handling=20(#10733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: exit on invalid configuration and add bypass validation warning * test: enhance loadCustomConfig tests for exit behavior on invalid config --- .../services/Config/loadCustomConfig.js | 32 ++++++++++------- .../services/Config/loadCustomConfig.spec.js | 35 +++++++++++++++++++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/api/server/services/Config/loadCustomConfig.js b/api/server/services/Config/loadCustomConfig.js index c0415674b9..db25049957 100644 --- a/api/server/services/Config/loadCustomConfig.js +++ b/api/server/services/Config/loadCustomConfig.js @@ -85,26 +85,32 @@ Please specify a correct \`imageOutputType\` value (case-sensitive). let errorMessage = `Invalid custom config file at ${configPath}: ${JSON.stringify(result.error, null, 2)}`; - if (i === 0) { - logger.error(errorMessage); - const speechError = result.error.errors.find( - (err) => - err.code === 'unrecognized_keys' && - (err.message?.includes('stt') || err.message?.includes('tts')), - ); + logger.error(errorMessage); + const speechError = result.error.errors.find( + (err) => + err.code === 'unrecognized_keys' && + (err.message?.includes('stt') || err.message?.includes('tts')), + ); - if (speechError) { - logger.warn(` + if (speechError) { + logger.warn(` The Speech-to-text and Text-to-speech configuration format has recently changed. If you're getting this error, please refer to the latest documentation: https://www.librechat.ai/docs/configuration/stt_tts`); - } - - i++; } - return null; + if (process.env.CONFIG_BYPASS_VALIDATION === 'true') { + logger.warn( + 'CONFIG_BYPASS_VALIDATION is enabled. Continuing with default configuration despite validation errors.', + ); + return null; + } + + logger.error( + 'Exiting due to invalid configuration. Set CONFIG_BYPASS_VALIDATION=true to bypass this check.', + ); + process.exit(1); } else { if (printConfig) { logger.info('Custom config file loaded:'); diff --git a/api/server/services/Config/loadCustomConfig.spec.js b/api/server/services/Config/loadCustomConfig.spec.js index 4f2006a053..f7f11dc8f6 100644 --- a/api/server/services/Config/loadCustomConfig.spec.js +++ b/api/server/services/Config/loadCustomConfig.spec.js @@ -50,8 +50,25 @@ const { logger } = require('@librechat/data-schemas'); const loadCustomConfig = require('./loadCustomConfig'); describe('loadCustomConfig', () => { + const originalExit = process.exit; + const mockExit = jest.fn((code) => { + throw new Error(`process.exit called with "${code}"`); + }); + + beforeAll(() => { + process.exit = mockExit; + }); + + afterAll(() => { + process.exit = originalExit; + }); + beforeEach(() => { jest.resetAllMocks(); + // Re-apply the exit mock implementation after resetAllMocks + mockExit.mockImplementation((code) => { + throw new Error(`process.exit called with "${code}"`); + }); delete process.env.CONFIG_PATH; }); @@ -94,20 +111,38 @@ describe('loadCustomConfig', () => { it('should return null and log if config schema validation fails', async () => { const invalidConfig = { invalidField: true }; process.env.CONFIG_PATH = 'invalidConfig.yaml'; + process.env.CONFIG_BYPASS_VALIDATION = 'true'; loadYaml.mockReturnValueOnce(invalidConfig); const result = await loadCustomConfig(); expect(result).toBeNull(); + expect(logger.warn).toHaveBeenCalledWith( + 'CONFIG_BYPASS_VALIDATION is enabled. Continuing with default configuration despite validation errors.', + ); + delete process.env.CONFIG_BYPASS_VALIDATION; + }); + + it('should call process.exit(1) when config validation fails without bypass', async () => { + const invalidConfig = { invalidField: true }; + process.env.CONFIG_PATH = 'invalidConfig.yaml'; + loadYaml.mockReturnValueOnce(invalidConfig); + + await expect(loadCustomConfig()).rejects.toThrow('process.exit called with "1"'); + expect(logger.error).toHaveBeenCalledWith( + 'Exiting due to invalid configuration. Set CONFIG_BYPASS_VALIDATION=true to bypass this check.', + ); }); it('should handle and return null on YAML parse error for a string response from remote', async () => { process.env.CONFIG_PATH = 'http://example.com/config.yaml'; + process.env.CONFIG_BYPASS_VALIDATION = 'true'; axios.get.mockResolvedValue({ data: 'invalidYAMLContent' }); const result = await loadCustomConfig(); expect(result).toBeNull(); + delete process.env.CONFIG_BYPASS_VALIDATION; }); it('should return the custom config object for a valid remote config file', async () => {