🛠️ feat: Add Config Validation Bypass & Improve Error Handling (#10733)

* fix: exit on invalid configuration and add bypass validation warning

* test: enhance loadCustomConfig tests for exit behavior on invalid config
This commit is contained in:
Marco Beretta 2025-12-03 23:23:36 +01:00 committed by Danny Avila
parent 5b3cef6d86
commit cea4f57a73
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
2 changed files with 54 additions and 13 deletions

View file

@ -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:');

View file

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