diff --git a/packages/server/lib/util/validation.js b/packages/server/lib/util/validation.js index 72f25304797d..1e4e2736b0da 100644 --- a/packages/server/lib/util/validation.js +++ b/packages/server/lib/util/validation.js @@ -104,18 +104,19 @@ const isValidBrowserList = (key, browsers) => { } const isValidRetriesConfig = (key, value) => { - const isNullOrNumber = isOneOf([_.isNumber, _.isNull]) - - if ( - isNullOrNumber(value) - || (_.isEqual(_.keys(value), ['runMode', 'openMode'])) - && isNullOrNumber(value.runMode) - && isNullOrNumber(value.openMode) - ) { + const optionalKeys = ['runMode', 'openMode'] + const isValidRetryValue = (val) => _.isNull(val) || (Number.isInteger(val) && val >= 0) + const optionalKeysAreValid = (val, k) => optionalKeys.includes(k) && isValidRetryValue(val) + + if (isValidRetryValue(value)) { + return true + } + + if (_.isObject(value) && _.every(value, optionalKeysAreValid)) { return true } - return errMsg(key, value, 'a number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls') + return errMsg(key, value, 'a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls') } const isValidFirefoxGcInterval = (key, value) => { diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index 941bbdf75417..58bf50345bbb 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -690,6 +690,34 @@ describe('lib/config', () => { }) }) + context('retries', () => { + const retriesError = 'a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls' + + // need to keep the const here or it'll get stripped by the build + // eslint-disable-next-line no-unused-vars + const cases = [ + [{ retries: null }, 'with null', true], + [{ retries: 3 }, 'when a number', true], + [{ retries: 3.2 }, 'when a float', false], + [{ retries: -1 }, 'with a negative number', false], + [{ retries: true }, 'when true', false], + [{ retries: false }, 'when false', false], + [{ retries: {} }, 'with an empty object', true], + [{ retries: { runMode: 3 } }, 'when runMode is a positive number', true], + [{ retries: { runMode: -1 } }, 'when runMode is a negative number', false], + [{ retries: { openMode: 3 } }, 'when openMode is a positive number', true], + [{ retries: { openMode: -1 } }, 'when openMode is a negative number', false], + [{ retries: { openMode: 3, TypoRunMode: 3 } }, 'when there is an additional unknown key', false], + [{ retries: { openMode: 3, runMode: 3 } }, 'when both runMode and openMode are positive numbers', true], + ].forEach(([config, expectation, shouldPass]) => { + it(`${shouldPass ? 'passes' : 'fails'} ${expectation}`, function () { + this.setup(config) + + return shouldPass ? this.expectValidationPasses() : this.expectValidationFails(retriesError) + }) + }) + }) + context('firefoxGcInterval', () => { it('passes if a number', function () { this.setup({ firefoxGcInterval: 1 })