diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index 485d4678e56ac1..67943b9aa6b52a 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -1251,6 +1251,39 @@ describe('config/validation', () => { }, ]); }); + + it('validates options with different type but defaultValue=null', async () => { + const config = { + minimumReleaseAge: null, + groupName: null, + groupSlug: null, + dependencyDashboardLabels: null, + defaultRegistryUrls: null, + registryUrls: null, + hostRules: [ + { + artifactAuth: null, + concurrentRequestLimit: null, + httpsCertificate: null, + httpsPrivateKey: null, + httpsCertificateAuthority: null, + }, + ], + encrypted: null, + milestone: null, + branchConcurrentLimit: null, + hashedBranchLength: null, + assigneesSampleSize: null, + reviewersSampleSize: null, + }; + const { warnings, errors } = await configValidation.validateConfig( + false, + // @ts-expect-error: contains invalid values + config, + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(0); + }); }); describe('validate globalOptions()', () => { @@ -1540,5 +1573,25 @@ describe('config/validation', () => { expect(warnings).toHaveLength(0); expect(errors).toHaveLength(0); }); + + it('validates options with different type but defaultValue=null', async () => { + const config = { + onboardingCommitMessage: null, + dryRun: null, + logContext: null, + endpoint: null, + skipInstalls: null, + autodiscoverFilter: null, + autodiscoverNamespaces: null, + autodiscoverTopics: null, + }; + const { warnings, errors } = await configValidation.validateConfig( + true, + // @ts-expect-error: contains invalid values + config, + ); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(0); + }); }); }); diff --git a/lib/config/validation.ts b/lib/config/validation.ts index 61621457b64e83..1a5b269cda6671 100644 --- a/lib/config/validation.ts +++ b/lib/config/validation.ts @@ -828,139 +828,141 @@ async function validateGlobalConfig( warnings: ValidationMessage[], currentPath: string | undefined, ): Promise { - if (type === 'string') { - if (is.string(val)) { - if ( - key === 'onboardingConfigFileName' && - !configFileNames.includes(val) - ) { - warnings.push({ - topic: 'Configuration Error', - message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${configFileNames.join(', ')}.`, - }); - } else if ( - key === 'repositoryCache' && - !['enabled', 'disabled', 'reset'].includes(val) - ) { - warnings.push({ - topic: 'Configuration Error', - message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['enabled', 'disabled', 'reset'].join(', ')}.`, - }); - } else if ( - key === 'dryRun' && - !['extract', 'lookup', 'full'].includes(val) - ) { + if (val !== null) { + if (type === 'string') { + if (is.string(val)) { + if ( + key === 'onboardingConfigFileName' && + !configFileNames.includes(val) + ) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${configFileNames.join(', ')}.`, + }); + } else if ( + key === 'repositoryCache' && + !['enabled', 'disabled', 'reset'].includes(val) + ) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['enabled', 'disabled', 'reset'].join(', ')}.`, + }); + } else if ( + key === 'dryRun' && + !['extract', 'lookup', 'full'].includes(val) + ) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['extract', 'lookup', 'full'].join(', ')}.`, + }); + } else if ( + key === 'binarySource' && + !['docker', 'global', 'install', 'hermit'].includes(val) + ) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['docker', 'global', 'install', 'hermit'].join(', ')}.`, + }); + } else if ( + key === 'requireConfig' && + !['required', 'optional', 'ignored'].includes(val) + ) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['required', 'optional', 'ignored'].join(', ')}.`, + }); + } else if ( + key === 'gitUrl' && + !['default', 'ssh', 'endpoint'].includes(val) + ) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['default', 'ssh', 'endpoint'].join(', ')}.`, + }); + } + } else { warnings.push({ topic: 'Configuration Error', - message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['extract', 'lookup', 'full'].join(', ')}.`, + message: `Configuration option \`${currentPath}\` should be a string.`, }); - } else if ( - key === 'binarySource' && - !['docker', 'global', 'install', 'hermit'].includes(val) - ) { + } + } else if (type === 'integer') { + if (!is.number(val)) { warnings.push({ topic: 'Configuration Error', - message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['docker', 'global', 'install', 'hermit'].join(', ')}.`, + message: `Configuration option \`${currentPath}\` should be an integer. Found: ${JSON.stringify( + val, + )} (${typeof val}).`, }); - } else if ( - key === 'requireConfig' && - !['required', 'optional', 'ignored'].includes(val) - ) { + } + } else if (type === 'boolean') { + if (val !== true && val !== false) { warnings.push({ topic: 'Configuration Error', - message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['required', 'optional', 'ignored'].join(', ')}.`, + message: `Configuration option \`${currentPath}\` should be a boolean. Found: ${JSON.stringify( + val, + )} (${typeof val}).`, }); - } else if ( - key === 'gitUrl' && - !['default', 'ssh', 'endpoint'].includes(val) - ) { + } + } else if (type === 'array') { + if (is.array(val)) { + if (key === 'gitNoVerify') { + const allowedValues = ['commit', 'push']; + for (const value of val as string[]) { + if (!allowedValues.includes(value)) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid value for \`${currentPath}\`. The allowed values are ${allowedValues.join(', ')}.`, + }); + } + } + } + } else { warnings.push({ topic: 'Configuration Error', - message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['default', 'ssh', 'endpoint'].join(', ')}.`, + message: `Configuration option \`${currentPath}\` should be a list (Array).`, }); } - } else { - warnings.push({ - topic: 'Configuration Error', - message: `Configuration option \`${currentPath}\` should be a string.`, - }); - } - } else if (type === 'integer') { - if (!is.number(val)) { - warnings.push({ - topic: 'Configuration Error', - message: `Configuration option \`${currentPath}\` should be an integer. Found: ${JSON.stringify( - val, - )} (${typeof val}).`, - }); - } - } else if (type === 'boolean') { - if (val !== true && val !== false) { - warnings.push({ - topic: 'Configuration Error', - message: `Configuration option \`${currentPath}\` should be a boolean. Found: ${JSON.stringify( - val, - )} (${typeof val}).`, - }); - } - } else if (type === 'array') { - if (is.array(val)) { - if (key === 'gitNoVerify') { - const allowedValues = ['commit', 'push']; - for (const value of val as string[]) { - if (!allowedValues.includes(value)) { - warnings.push({ - topic: 'Configuration Error', - message: `Invalid value for \`${currentPath}\`. The allowed values are ${allowedValues.join(', ')}.`, - }); + } else if (type === 'object') { + if (is.plainObject(val)) { + if (key === 'onboardingConfig') { + const subValidation = await validateConfig(false, val); + for (const warning of subValidation.warnings.concat( + subValidation.errors, + )) { + warnings.push(warning); } - } - } - } else { - warnings.push({ - topic: 'Configuration Error', - message: `Configuration option \`${currentPath}\` should be a list (Array).`, - }); - } - } else if (type === 'object') { - if (is.plainObject(val)) { - if (key === 'onboardingConfig') { - const subValidation = await validateConfig(false, val); - for (const warning of subValidation.warnings.concat( - subValidation.errors, - )) { - warnings.push(warning); - } - } else if (key === 'force') { - const subValidation = await validateConfig(true, val); - for (const warning of subValidation.warnings.concat( - subValidation.errors, - )) { - warnings.push(warning); - } - } else if (key === 'cacheTtlOverride') { - for (const [subKey, subValue] of Object.entries(val)) { - if (!is.number(subValue)) { + } else if (key === 'force') { + const subValidation = await validateConfig(true, val); + for (const warning of subValidation.warnings.concat( + subValidation.errors, + )) { + warnings.push(warning); + } + } else if (key === 'cacheTtlOverride') { + for (const [subKey, subValue] of Object.entries(val)) { + if (!is.number(subValue)) { + warnings.push({ + topic: 'Configuration Error', + message: `Invalid \`${currentPath}.${subKey}\` configuration: value must be an integer.`, + }); + } + } + } else { + const res = validatePlainObject(val); + if (res !== true) { warnings.push({ topic: 'Configuration Error', - message: `Invalid \`${currentPath}.${subKey}\` configuration: value must be an integer.`, + message: `Invalid \`${currentPath}.${res}\` configuration: value must be a string.`, }); } } } else { - const res = validatePlainObject(val); - if (res !== true) { - warnings.push({ - topic: 'Configuration Error', - message: `Invalid \`${currentPath}.${res}\` configuration: value must be a string.`, - }); - } + warnings.push({ + topic: 'Configuration Error', + message: `Configuration option \`${currentPath}\` should be a JSON object.`, + }); } - } else { - warnings.push({ - topic: 'Configuration Error', - message: `Configuration option \`${currentPath}\` should be a JSON object.`, - }); } } }