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 () => {