diff --git a/lib/config/migrate-validate.js b/lib/config/migrate-validate.js index 54b6c7cc41f021b942248dcb0849eafce0e84a20..d29592e6e8432c3ec7cc002ccb1046ba2950aca5 100644 --- a/lib/config/migrate-validate.js +++ b/lib/config/migrate-validate.js @@ -7,6 +7,7 @@ module.exports = { }; function migrateAndValidate(config, input) { + logger.debug('migrateAndValidate()'); const { isMigrated, migratedConfig } = configMigration.migrateConfig(input); if (isMigrated) { logger.info( @@ -18,15 +19,15 @@ function migrateAndValidate(config, input) { const { warnings, errors } = configValidation.validateConfig(massagedConfig); // istanbul ignore if if (warnings.length) { - logger.debug({ warnings }, 'Found renovate config warnings'); + logger.info({ warnings }, 'Found renovate config warnings'); } if (errors.length) { - logger.warn({ errors }, 'Found renovate config errors'); + logger.info({ errors }, 'Found renovate config errors'); } + massagedConfig.errors = (config.errors || []).concat(errors); if (!config.repoIsOnboarded) { // TODO #556 - enable warnings in real PRs massagedConfig.warnings = (config.warnings || []).concat(warnings); - massagedConfig.errors = (config.errors || []).concat(errors); } return massagedConfig; } diff --git a/lib/config/presets.js b/lib/config/presets.js index 0ad3d850da6283ac1c988877832fa610a4340806..019350a9e0e0df2b5409980e902896debd4561b6 100644 --- a/lib/config/presets.js +++ b/lib/config/presets.js @@ -27,8 +27,30 @@ async function resolveConfigPresets(inputConfig, existingPresets = []) { logger.warn(`Already seen preset ${preset} in ${existingPresets}`); } else { logger.trace(`Resolving preset "${preset}"`); + let fetchedPreset; + try { + fetchedPreset = await getPreset(preset); + } catch (err) { + // istanbul ignore else + if (existingPresets.length === 0) { + const error = new Error('config-validation'); + if (err.message === 'dep not found') { + error.validationError = `Cannot find preset's package (${preset})`; + } else if (err.message === 'preset renovate-config not found') { + // istanbul ignore next + error.validationError = `Preset package is missing a renovate-config entry (${preset})`; + } else if (err.message === 'preset not found') { + error.validationError = `Preset name not found within published preset config (${preset})`; + } + logger.info('Throwing preset error'); + throw error; + } else { + logger.warn({ preset }, `Cannot find nested preset`); + fetchedPreset = {}; + } + } const presetConfig = await resolveConfigPresets( - await getPreset(preset), + fetchedPreset, existingPresets.concat([preset]) ); config = configParser.mergeChildConfig(config, presetConfig); @@ -137,23 +159,16 @@ async function getPreset(preset) { logger.trace(`getPreset(${preset})`); const { packageName, presetName, params } = parsePreset(preset); let presetConfig; - try { - const dep = await npm.getDependency(packageName); - if (!dep) { - logger.warn(`Failed to look up preset packageName ${packageName}`); - return {}; - } - if (!dep['renovate-config']) { - logger.warn(`Package ${packageName} has no renovate-config`); - return {}; - } - presetConfig = dep['renovate-config'][presetName]; - } catch (err) { - logger.warn({ err }, `Failed to look up package ${packageName}`); + const dep = await npm.getDependency(packageName); + if (!dep) { + throw Error('dep not found'); + } + if (!dep['renovate-config']) { + throw Error('preset renovate-config not found'); } + presetConfig = dep['renovate-config'][presetName]; if (!presetConfig) { - logger.warn(`Cannot find preset ${preset}`); - return {}; + throw Error('preset not found'); } logger.debug(`Found preset ${preset}`); logger.trace({ presetConfig }); diff --git a/lib/manager/resolve.js b/lib/manager/resolve.js index 16baf370ba98ef6587b15e0ca31c212ca2695e5e..45b3ffc4ef23ad048e435e667869f9d133425833 100644 --- a/lib/manager/resolve.js +++ b/lib/manager/resolve.js @@ -87,6 +87,17 @@ async function resolvePackageFiles(config) { { config: migratedConfig }, 'package.json migrated config' ); + // istanbul ignore if + if (migratedConfig.errors.length) { + const error = new Error('config-validation'); + error.configFile = packageFile.packageFile; + error.validationError = + 'The `renovate` config inside `package.json` failed validation'; + error.validationMessage = migratedConfig.errors + .map(e => e.message) + .join(', '); + throw error; + } const resolvedConfig = await presets.resolveConfigPresets( migratedConfig ); @@ -144,5 +155,6 @@ async function resolvePackageFiles(config) { logger.debug('queue'); const queue = allPackageFiles.map(p => resolvePackageFile(p)); const packageFiles = (await Promise.all(queue)).filter(p => p !== null); + platform.ensureIssueClosing('Action Required: Fix Renovate Configuration'); return checkMonorepos({ ...config, packageFiles }); } diff --git a/lib/platform/github/index.js b/lib/platform/github/index.js index 390d9ea09aa8aff72a4fa16247fc411f12d8c065..dc60e1b97d99f437308d92bed9f3146f630f77bf 100644 --- a/lib/platform/github/index.js +++ b/lib/platform/github/index.js @@ -27,6 +27,8 @@ module.exports = { mergeBranch, getBranchLastCommitTime, // issue + ensureIssue, + ensureIssueClosing, addAssignees, addReviewers, // Comments @@ -106,6 +108,7 @@ async function initRepo(repoName, token, endpoint, forkMode, forkToken) { logger.info({ err, res }, 'Unknown GitHub initRepo error'); throw err; } + delete config.issueList; delete config.prList; delete config.fileList; await Promise.all([getPrList(), getFileList()]); @@ -422,6 +425,57 @@ async function getBranchLastCommitTime(branchName) { // Issue +async function getIssueList() { + if (!config.issueList) { + config.issueList = (await get( + `repos/${config.repoName}/issues?filter=created&state=open` + )).body.map(i => ({ + number: i.number, + title: i.title, + })); + } + return config.issueList; +} + +async function ensureIssue(title, body) { + logger.debug(`ensureIssue()`); + const issueList = await getIssueList(); + const issue = issueList.find(i => i.title === title); + if (issue) { + const issueBody = (await get( + `repos/${config.repoName}/issues/${issue.number}` + )).body.body; + if (issueBody !== body) { + logger.debug('Updating issue body'); + await get.patch(`repos/${config.repoName}/issues/${issue.number}`, { + body: { body }, + }); + return 'updated'; + } + } else { + await get.post(`repos/${config.repoName}/issues`, { + body: { + title, + body, + }, + }); + return 'created'; + } + return null; +} + +async function ensureIssueClosing(title) { + logger.debug(`ensureIssueClosing()`); + const issueList = await getIssueList(); + for (const issue of issueList) { + if (issue.title === title) { + await get.patch(`repos/${config.repoName}/issues/${issue.id}`, { + body: { state: 'closed' }, + }); + } + } +} + async function addAssignees(issueNo, assignees) { logger.debug(`Adding assignees ${assignees} to #${issueNo}`); await get.post(`repos/${config.repoName}/issues/${issueNo}/assignees`, { diff --git a/lib/platform/gitlab/index.js b/lib/platform/gitlab/index.js index 29d26faead32564c12d715df7bde0f71c37be246..732db8abea86e161ef8f07af4aba3513862ebeaf 100644 --- a/lib/platform/gitlab/index.js +++ b/lib/platform/gitlab/index.js @@ -23,6 +23,8 @@ module.exports = { mergeBranch, getBranchLastCommitTime, // issue + ensureIssue, + ensureIssueClosing, addAssignees, addReviewers, // Comments @@ -304,6 +306,13 @@ async function getBranchLastCommitTime(branchName) { // Issue +function ensureIssue() { + // istanbul ignore next + logger.warn(`ensureIssue() is not implemented`); +} + +function ensureIssueClosing() {} + async function addAssignees(iid, assignees) { logger.debug(`Adding assignees ${assignees} to #${iid}`); if (assignees.length > 1) { diff --git a/lib/platform/vsts/index.js b/lib/platform/vsts/index.js index f5ef87c8bb16fbbb1b706ebcf9a13f1f25cdb3ec..3f5220efcee8f180c1e457cf5f5fcd0aceb7f417 100644 --- a/lib/platform/vsts/index.js +++ b/lib/platform/vsts/index.js @@ -24,6 +24,8 @@ module.exports = { mergeBranch, getBranchLastCommitTime, // issue + ensureIssue, + ensureIssueClosing, addAssignees, addReviewers, // Comments @@ -470,6 +472,13 @@ async function mergePr(pr) { await null; } +function ensureIssue() { + // istanbul ignore next + logger.warn(`ensureIssue() is not implemented`); +} + +function ensureIssueClosing() {} + /** * * @param {number} issueNo diff --git a/lib/workers/repository/error-config.js b/lib/workers/repository/error-config.js new file mode 100644 index 0000000000000000000000000000000000000000..ca65c6477a1dc75222877b88fa75d489ac9039cc --- /dev/null +++ b/lib/workers/repository/error-config.js @@ -0,0 +1,31 @@ +module.exports = { + raiseConfigWarningIssue, +}; + +async function raiseConfigWarningIssue(config, error) { + logger.debug('raiseConfigWarningIssue()'); + let body = `There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop renovations until it is fixed.\n\n`; + if (error.configFile) { + body += `Configuration file: \`${error.configFile}\`\n`; + } + body += `Error type: ${error.validationError}\n`; + if (error.validationMessage) { + body += `Message: ${error.validationMessage}\n`; + } + if (config.repoIsOnboarded) { + const res = await platform.ensureIssue( + 'Action Required: Fix Renovate Configuration', + body + ); + if (res) { + logger.warn({ configError: error, res }, 'Config Warning'); + } + } else { + // update onboarding Pr + logger.info('Updating onboarding PR'); + const pr = await platform.getBranchPr('renovate/configure'); + body = `## Action Required: Fix Renovate Configuration\n\n${body}`; + body += `\n\nOnce you have resolved this problem (in this onboarding branch), Renovate will return to providing you with a preview of your repository's configuration.`; + await platform.updatePr(pr.number, 'Configure Renovate', body); + } +} diff --git a/lib/workers/repository/error.js b/lib/workers/repository/error.js index d6abcbf91bc573a09ad272f9d0733448f6610a68..8a6a04d2f0c746e133700e2da26e78ae993d334d 100644 --- a/lib/workers/repository/error.js +++ b/lib/workers/repository/error.js @@ -1,8 +1,10 @@ +const { raiseConfigWarningIssue } = require('./error-config'); + module.exports = { handleError, }; -function handleError(config, err) { +async function handleError(config, err) { if (err.message === 'uninitiated') { logger.info('Repository is uninitiated - skipping'); delete config.branchList; // eslint-disable-line no-param-reassign @@ -19,6 +21,11 @@ function handleError(config, err) { } else if (err.message === 'loops>5') { logger.error('Repository has looped 5 times already'); return err.message; + } else if (err.message === 'config-validation') { + delete config.branchList; // eslint-disable-line no-param-reassign + logger.info({ error: err }, 'Repository has invalid config'); + await raiseConfigWarningIssue(config, err); + return err.message; } // Swallow this error so that other repositories can be processed logger.error({ err }, `Repository has unknown error`); diff --git a/lib/workers/repository/init/config.js b/lib/workers/repository/init/config.js index 6bdcd8f9dde165de54aad0c77bd2bc36e3db4fd4..c6ac025c43bc3da75feed16241905c2b65a061bc 100644 --- a/lib/workers/repository/init/config.js +++ b/lib/workers/repository/init/config.js @@ -29,14 +29,11 @@ async function mergeRenovateConfig(config) { allowDuplicateKeys ); if (jsonValidationError) { - const error = { - depName: configFile, - message: jsonValidationError, - }; - logger.warn({ renovateConfig }, error.message); - returnConfig.errors.push(error); - // Return unless error can be ignored - return returnConfig; + const error = new Error('config-validation'); + error.configFile = configFile; + error.validationError = 'Invalid JSON (parsing failed)'; + error.validationMessage = jsonValidationError; + throw error; } allowDuplicateKeys = false; jsonValidationError = jsonValidator.validate( @@ -44,17 +41,25 @@ async function mergeRenovateConfig(config) { allowDuplicateKeys ); if (jsonValidationError) { - const error = { - depName: configFile, - message: jsonValidationError, - }; - logger.warn({ renovateConfig }, error.message); - returnConfig.errors.push(error); - // Return unless error can be ignored + const error = new Error('config-validation'); + error.configFile = configFile; + error.validationError = 'Duplicate keys in JSON'; + error.validationMessage = JSON.stringify(jsonValidationError); + throw error; } const renovateJson = JSON.parse(renovateConfig); logger.debug({ config: renovateJson }, 'renovate.json config'); const migratedConfig = migrateAndValidate(config, renovateJson); + if (migratedConfig.errors.length) { + const error = new Error('config-validation'); + error.configFile = configFile; + error.validationError = + 'The renovate configuration file contains some invalid settings'; + error.validationMessage = migratedConfig.errors + .map(e => e.message) + .join(', '); + throw error; + } logger.debug({ config: migratedConfig }, 'renovate.json migrated config'); const decryptedConfig = decryptConfig(migratedConfig, config.privateKey); const resolvedConfig = await presets.resolveConfigPresets(decryptedConfig); diff --git a/test/config/__snapshots__/presets.spec.js.snap b/test/config/__snapshots__/presets.spec.js.snap index 32a21dc9244ef162b1a619d2f1de1c90d17405ad..4eb4714d24a41776b1a68333b5092cbd49c37ff6 100644 --- a/test/config/__snapshots__/presets.spec.js.snap +++ b/test/config/__snapshots__/presets.spec.js.snap @@ -31,7 +31,11 @@ Object { } `; -exports[`config/presets getPreset handles 404 packages 1`] = `Object {}`; +exports[`config/presets getPreset handles 404 packages 1`] = `undefined`; + +exports[`config/presets getPreset handles 404 packages 2`] = `undefined`; + +exports[`config/presets getPreset handles 404 packages 3`] = `undefined`; exports[`config/presets getPreset handles missing params 1`] = ` Object { @@ -49,11 +53,23 @@ Object { } `; -exports[`config/presets getPreset handles no config 1`] = `Object {}`; +exports[`config/presets getPreset handles no config 1`] = `undefined`; + +exports[`config/presets getPreset handles no config 2`] = `undefined`; + +exports[`config/presets getPreset handles no config 3`] = `undefined`; + +exports[`config/presets getPreset handles preset not found 1`] = `undefined`; + +exports[`config/presets getPreset handles preset not found 2`] = `undefined`; + +exports[`config/presets getPreset handles preset not found 3`] = `undefined`; + +exports[`config/presets getPreset handles throw errors 1`] = `undefined`; -exports[`config/presets getPreset handles preset not found 1`] = `Object {}`; +exports[`config/presets getPreset handles throw errors 2`] = `undefined`; -exports[`config/presets getPreset handles throw errors 1`] = `Object {}`; +exports[`config/presets getPreset handles throw errors 3`] = `undefined`; exports[`config/presets getPreset ignores irrelevant params 1`] = ` Object { @@ -688,29 +704,31 @@ Object { } `; -exports[`config/presets resolvePreset returns same if invalid preset 1`] = ` -Object { - "foo": 1, -} -`; - exports[`config/presets resolvePreset returns same if no presets 1`] = ` Object { "foo": 1, } `; -exports[`config/presets resolvePreset works with valid 1`] = ` -Object { - "description": Array [ - "Use version pinning (maintain a single version only and not semver ranges)", - ], - "foo": 1, - "pinVersions": true, -} -`; +exports[`config/presets resolvePreset throws if invalid preset 1`] = `undefined`; + +exports[`config/presets resolvePreset throws if invalid preset 2`] = `"Preset name not found within published preset config (:invalid-preset)"`; + +exports[`config/presets resolvePreset throws if invalid preset 3`] = `undefined`; -exports[`config/presets resolvePreset works with valid and invalid 1`] = ` +exports[`config/presets resolvePreset throws if invalid preset file 1`] = `undefined`; + +exports[`config/presets resolvePreset throws if invalid preset file 2`] = `"Cannot find preset's package (notfoundaaaaaaaa)"`; + +exports[`config/presets resolvePreset throws if invalid preset file 3`] = `undefined`; + +exports[`config/presets resolvePreset throws if valid and invalid 1`] = `undefined`; + +exports[`config/presets resolvePreset throws if valid and invalid 2`] = `"Preset name not found within published preset config (:invalid-preset)"`; + +exports[`config/presets resolvePreset throws if valid and invalid 3`] = `undefined`; + +exports[`config/presets resolvePreset works with valid 1`] = ` Object { "description": Array [ "Use version pinning (maintain a single version only and not semver ranges)", diff --git a/test/config/presets.spec.js b/test/config/presets.spec.js index 7b4daa5bf60eff9795e71d5dde70e32058934f1f..6a910c3dfb8f0162feef0595cda982cf89906275 100644 --- a/test/config/presets.spec.js +++ b/test/config/presets.spec.js @@ -74,11 +74,33 @@ describe('config/presets', () => { expect(config).toMatchObject(res); expect(res).toMatchSnapshot(); }); - it('returns same if invalid preset', async () => { + it('throws if invalid preset file', async () => { + config.foo = 1; + config.extends = ['notfoundaaaaaaaa']; + let e; + try { + await presets.resolveConfigPresets(config); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); + }); + it('throws if invalid preset', async () => { config.foo = 1; config.extends = [':invalid-preset']; - const res = await presets.resolveConfigPresets(config); - expect(res).toMatchSnapshot(); + let e; + try { + await presets.resolveConfigPresets(config); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); }); it('works with valid', async () => { config.foo = 1; @@ -87,12 +109,19 @@ describe('config/presets', () => { expect(res).toMatchSnapshot(); expect(res.pinVersions).toBe(true); }); - it('works with valid and invalid', async () => { + it('throws if valid and invalid', async () => { config.foo = 1; config.extends = [':invalid-preset', ':pinVersions']; - const res = await presets.resolveConfigPresets(config); - expect(res).toMatchSnapshot(); - expect(res.pinVersions).toBe(true); + let e; + try { + await presets.resolveConfigPresets(config); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); }); it('resolves app preset', async () => { config.extends = [':app']; @@ -281,20 +310,52 @@ describe('config/presets', () => { expect(res).toMatchSnapshot(); }); it('handles 404 packages', async () => { - const res = await presets.getPreset('notfound:foo'); - expect(res).toMatchSnapshot(); + let e; + try { + await presets.getPreset('notfound:foo'); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); }); it('handles no config', async () => { - const res = await presets.getPreset('noconfig:foo'); - expect(res).toMatchSnapshot(); + let e; + try { + await presets.getPreset('noconfig:foo'); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); }); it('handles throw errors', async () => { - const res = await presets.getPreset('throw:foo'); - expect(res).toMatchSnapshot(); + let e; + try { + await presets.getPreset('throw:foo'); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); }); it('handles preset not found', async () => { - const res = await presets.getPreset('wrongpreset:foo'); - expect(res).toMatchSnapshot(); + let e; + try { + await presets.getPreset('wrongpreset:foo'); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); }); }); }); diff --git a/test/manager/__snapshots__/resolve.spec.js.snap b/test/manager/__snapshots__/resolve.spec.js.snap index 565914a26e089601daef8d978f71ca7ab9a5238e..a30be6019650714c6d9bf29db502938794d5ca23 100644 --- a/test/manager/__snapshots__/resolve.spec.js.snap +++ b/test/manager/__snapshots__/resolve.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`manager/resolve resolvePackageFiles() deetect package.json and warns if cannot parse 1`] = ` +exports[`manager/resolve resolvePackageFiles() detect package.json and warns if cannot parse 1`] = ` Object { "assignees": Array [], "autodiscover": false, diff --git a/test/manager/resolve.spec.js b/test/manager/resolve.spec.js index 29198257559605c95c56e69d3abf44ca3f743186..4198dea4181fb67c7de425d16aedd028f894b715 100644 --- a/test/manager/resolve.spec.js +++ b/test/manager/resolve.spec.js @@ -25,7 +25,7 @@ describe('manager/resolve', () => { expect(res).toMatchSnapshot(); expect(res.errors).toHaveLength(2); }); - it('deetect package.json and warns if cannot parse', async () => { + it('detect package.json and warns if cannot parse', async () => { manager.detectPackageFiles = jest.fn(() => [ { packageFile: 'package.json' }, ]); diff --git a/test/platform/__snapshots__/index.spec.js.snap b/test/platform/__snapshots__/index.spec.js.snap index 789c8486ea692b3b0229e0c76b4f47f5f65c9e9c..d9e87ca47ad2f22cfbb07fb5a15ac4888f15da10 100644 --- a/test/platform/__snapshots__/index.spec.js.snap +++ b/test/platform/__snapshots__/index.spec.js.snap @@ -17,6 +17,8 @@ Array [ "deleteBranch", "mergeBranch", "getBranchLastCommitTime", + "ensureIssue", + "ensureIssueClosing", "addAssignees", "addReviewers", "ensureComment", @@ -50,6 +52,8 @@ Array [ "deleteBranch", "mergeBranch", "getBranchLastCommitTime", + "ensureIssue", + "ensureIssueClosing", "addAssignees", "addReviewers", "ensureComment", @@ -83,6 +87,8 @@ Array [ "deleteBranch", "mergeBranch", "getBranchLastCommitTime", + "ensureIssue", + "ensureIssueClosing", "addAssignees", "addReviewers", "ensureComment", diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js index 0421e7dc880e7866f03c3c2dd86a0ad6e5314555..f9fb1a97a0570e984fd99a571ee5b73cce616060 100644 --- a/test/platform/github/index.spec.js +++ b/test/platform/github/index.spec.js @@ -821,6 +821,75 @@ describe('platform/github', () => { expect(res).toBeDefined(); }); }); + describe('ensureIssue()', () => { + it('creates issue', async () => { + get.mockImplementationOnce(() => ({ + body: [ + { + number: 1, + title: 'title-1', + }, + { + number: 2, + title: 'title-2', + }, + ], + })); + const res = await github.ensureIssue('new-title', 'new-content'); + expect(res).toEqual('created'); + }); + it('updates issue', async () => { + get.mockReturnValueOnce({ + body: [ + { + number: 1, + title: 'title-1', + }, + { + number: 2, + title: 'title-2', + }, + ], + }); + get.mockReturnValueOnce({ body: { body: 'new-content' } }); + const res = await github.ensureIssue('title-2', 'newer-content'); + expect(res).toEqual('updated'); + }); + it('skips update if unchanged', async () => { + get.mockReturnValueOnce({ + body: [ + { + number: 1, + title: 'title-1', + }, + { + number: 2, + title: 'title-2', + }, + ], + }); + get.mockReturnValueOnce({ body: { body: 'newer-content' } }); + const res = await github.ensureIssue('title-2', 'newer-content'); + expect(res).toBe(null); + }); + }); + describe('ensureIssueClosing()', () => { + it('closes issue', async () => { + get.mockImplementationOnce(() => ({ + body: [ + { + number: 1, + title: 'title-1', + }, + { + number: 2, + title: 'title-2', + }, + ], + })); + await github.ensureIssueClosing('title-2'); + }); + }); describe('addAssignees(issueNo, assignees)', () => { it('should add the given assignees to the issue', async () => { await initRepo('some/repo', 'token'); diff --git a/test/workers/repository/error-config.spec.js b/test/workers/repository/error-config.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..b07b791d58e07b1da75d7e954cec94bfdcd7d9ec --- /dev/null +++ b/test/workers/repository/error-config.spec.js @@ -0,0 +1,32 @@ +const { + raiseConfigWarningIssue, +} = require('../../../lib/workers/repository/error-config'); + +let config; +beforeEach(() => { + jest.resetAllMocks(); + config = require('../../_fixtures/config'); +}); + +describe('workers/repository/error-config', () => { + describe('raiseConfigWarningIssue()', () => { + it('creates issues', async () => { + const error = new Error('config-validation'); + error.configFile = 'package.json'; + error.validationMessage = 'some-message'; + config.repoIsOnboarded = true; + platform.ensureIssue.mockReturnValue('created'); + const res = await raiseConfigWarningIssue(config, error); + expect(res).toBeUndefined(); + }); + it('handles onboarding', async () => { + const error = new Error('config-validation'); + error.configFile = 'package.json'; + error.validationMessage = 'some-message'; + config.repoIsOnboarded = false; + platform.getBranchPr.mockReturnValueOnce({ number: 1 }); + const res = await raiseConfigWarningIssue(config, error); + expect(res).toBeUndefined(); + }); + }); +}); diff --git a/test/workers/repository/error.spec.js b/test/workers/repository/error.spec.js index 1846e76a6f332efbef195bf39a06fcd334516437..7ded2443f277ad4c7649b327eb85ce0ca24a0a34 100644 --- a/test/workers/repository/error.spec.js +++ b/test/workers/repository/error.spec.js @@ -1,5 +1,7 @@ const { handleError } = require('../../../lib/workers/repository/error'); +jest.mock('../../../lib/workers/repository/error-config'); + let config; beforeEach(() => { jest.resetAllMocks(); @@ -14,15 +16,16 @@ describe('workers/repository/error', () => { 'fork', 'no-package-files', 'loops>5', + 'config-validation', ]; errors.forEach(err => { - it(`errors ${err}`, () => { - const res = handleError(config, new Error(err)); + it(`errors ${err}`, async () => { + const res = await handleError(config, new Error(err)); expect(res).toEqual(err); }); }); - it('handles unknown error', () => { - const res = handleError(config, new Error('abcdefg')); + it('handles unknown error', async () => { + const res = await handleError(config, new Error('abcdefg')); expect(res).toEqual('unknown-error'); }); }); diff --git a/test/workers/repository/init/__snapshots__/config.spec.js.snap b/test/workers/repository/init/__snapshots__/config.spec.js.snap index 612e2d5005d1bc0aef9e31fa176a003d9487c07a..8fc9ed4d12ef63749ea8c6503c432692c6d25600 100644 --- a/test/workers/repository/init/__snapshots__/config.spec.js.snap +++ b/test/workers/repository/init/__snapshots__/config.spec.js.snap @@ -1,15 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`workers/repository/init/config mergeRenovateConfig() returns error if cannot parse 1`] = ` -Object { - "depName": "renovate.json", - "message": "Syntax error near cannot par", -} -`; - -exports[`workers/repository/init/config mergeRenovateConfig() returns error if duplicate keys 1`] = ` -Object { - "depName": ".renovaterc", - "message": "Syntax error: duplicated keys \\"enabled\\" near \\": false }", -} -`; +exports[`workers/repository/init/config mergeRenovateConfig() returns error if cannot parse 1`] = `"renovate.json"`; + +exports[`workers/repository/init/config mergeRenovateConfig() returns error if cannot parse 2`] = `"Invalid JSON (parsing failed)"`; + +exports[`workers/repository/init/config mergeRenovateConfig() returns error if cannot parse 3`] = `"Syntax error near cannot par"`; + +exports[`workers/repository/init/config mergeRenovateConfig() throws error if duplicate keys 1`] = `".renovaterc"`; + +exports[`workers/repository/init/config mergeRenovateConfig() throws error if duplicate keys 2`] = `"Duplicate keys in JSON"`; + +exports[`workers/repository/init/config mergeRenovateConfig() throws error if duplicate keys 3`] = `"\\"Syntax error: duplicated keys \\\\\\"enabled\\\\\\" near \\\\\\": false }\\""`; + +exports[`workers/repository/init/config mergeRenovateConfig() throws error if misconfigured 1`] = `[Error: config-validation]`; diff --git a/test/workers/repository/init/config.spec.js b/test/workers/repository/init/config.spec.js index 90d9f97917b23e07cbf7f164e6650ee13936dfb1..593cd1f9bcbb38a9b2be19fd9814086ce86aa3d3 100644 --- a/test/workers/repository/init/config.spec.js +++ b/test/workers/repository/init/config.spec.js @@ -9,9 +9,18 @@ beforeEach(() => { const { mergeRenovateConfig, } = require('../../../../lib/workers/repository/init/config'); +const migrateValidate = require('../../../../lib/config/migrate-validate'); + +jest.mock('../../../../lib/config/migrate-validate'); describe('workers/repository/init/config', () => { describe('mergeRenovateConfig()', () => { + beforeEach(() => { + migrateValidate.migrateAndValidate.mockReturnValue({ + warnings: [], + errors: [], + }); + }); it('returns config if not found', async () => { platform.getFileList.mockReturnValue(['package.json']); const res = await mergeRenovateConfig(config); @@ -20,16 +29,30 @@ describe('workers/repository/init/config', () => { it('returns error if cannot parse', async () => { platform.getFileList.mockReturnValue(['package.json', 'renovate.json']); platform.getFile.mockReturnValue('cannot parse'); - const res = await mergeRenovateConfig(config); - expect(res.errors).toHaveLength(1); - expect(res.errors[0]).toMatchSnapshot(); + let e; + try { + await mergeRenovateConfig(config); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); }); - it('returns error if duplicate keys', async () => { + it('throws error if duplicate keys', async () => { platform.getFileList.mockReturnValue(['package.json', '.renovaterc']); platform.getFile.mockReturnValue('{ "enabled": true, "enabled": false }'); - const res = await mergeRenovateConfig(config); - expect(res.errors).toHaveLength(1); - expect(res.errors[0]).toMatchSnapshot(); + let e; + try { + await mergeRenovateConfig(config); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e.configFile).toMatchSnapshot(); + expect(e.validationError).toMatchSnapshot(); + expect(e.validationMessage).toMatchSnapshot(); }); it('finds .renovaterc.json', async () => { platform.getFileList.mockReturnValue([ @@ -39,5 +62,23 @@ describe('workers/repository/init/config', () => { platform.getFile.mockReturnValue('{}'); await mergeRenovateConfig(config); }); + it('throws error if misconfigured', async () => { + platform.getFileList.mockReturnValue([ + 'package.json', + '.renovaterc.json', + ]); + platform.getFile.mockReturnValue('{}'); + migrateValidate.migrateAndValidate.mockReturnValueOnce({ + errors: [{}], + }); + let e; + try { + await mergeRenovateConfig(config); + } catch (err) { + e = err; + } + expect(e).toBeDefined(); + expect(e).toMatchSnapshot(); + }); }); });