From 19f4b3b0bc888a14034b9a61f2008aedfae345c1 Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@keylocation.sg> Date: Sun, 25 Jun 2017 07:36:13 +0200 Subject: [PATCH] Refactor repository worker (#344) * Move to subdir * Downgrade eslint to 3 * Refactor api and config usage * Refactor mergeRenovateJson * Test mergeRenovateJson * getOnboardingStatus tests * Refactor repository structure * Refactor config.logger * Revert "Refactor config.logger" This reverts commit 6d7f81af6ee284d01aab811dab7eb05c2274edf3. * Refactor repository logging * Refactor try/catch * Refactor platform and onboarding * Refactor setNpmrc * Fix github logger * npm api use config.logger * Refactor repo worker logger * Refactor repo worker * Refactor branched upgrades * Repository refactoring * Move some debug logging to trace * Deprecate fileName * Refactor upgrades * Refactor repository logs * More repository log refactoring * Refactor repository location * Revert "Refactor repository location" This reverts commit faecbf29516737a2752de54103c0228b9112a51c. * Fix tests * mergeRenovateJson * Recombine repository worker * Add initApis tests * add detectPackageFiles tests * Add determineRepoUpgrades tests * start groupUpgradesByBranch tests * add test * add test * Finish groupUpgradesByBranch coverage * Test updateBranchesSequentially * Finish repo coverage * Finish branch worker coverage * Finish workers coverage * Fix isPin * Complete workers coverage * Finish helpers coverage * Add gitlab api tests * getBranchStatus tests * test createPr * start getPr testing * getPr * update and merge PR tests * getFile * getFileContent tests * getFileJson tests * createFile * updateFile * createBranch * commitFilesToBranch * update yarn * Update yarn --- docs/configuration.md | 2 +- lib/api/github.js | 1 + lib/api/npm.js | 5 +- lib/config/index.js | 6 +- lib/helpers/platform.js | 16 + lib/helpers/versions.js | 6 + lib/workers/branch.js | 26 +- lib/workers/index.js | 7 +- lib/workers/package-file.js | 12 +- lib/workers/pr.js | 2 +- lib/workers/repository.js | 311 ++++++++-------- test/_fixtures/config/file.js | 2 +- test/api/npm.spec.js | 11 +- .../__snapshots__/package-json.spec.js.snap | 3 + .../__snapshots__/versions.spec.js.snap | 31 ++ test/helpers/changelog.spec.js | 8 +- test/helpers/package-json.spec.js | 110 +----- test/helpers/platform.spec.js | 95 +++++ .../workers/__snapshots__/branch.spec.js.snap | 37 ++ test/workers/__snapshots__/index.spec.js.snap | 26 ++ .../repository-functions.spec.js.snap | 95 +++++ test/workers/branch.spec.js | 64 +++- test/workers/index.spec.js | 38 +- test/workers/package-file.spec.js | 8 +- test/workers/pr.spec.js | 8 +- test/workers/repository-functions.spec.js | 343 ++++++++++++++++++ test/workers/repository.spec.js | 43 ++- yarn.lock | 24 +- 28 files changed, 995 insertions(+), 345 deletions(-) create mode 100644 lib/helpers/platform.js create mode 100644 test/helpers/__snapshots__/package-json.spec.js.snap create mode 100644 test/helpers/platform.spec.js create mode 100644 test/workers/__snapshots__/index.spec.js.snap create mode 100644 test/workers/__snapshots__/repository-functions.spec.js.snap create mode 100644 test/workers/repository-functions.spec.js diff --git a/docs/configuration.md b/docs/configuration.md index 2eee94e6c7..0bb9905165 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -32,7 +32,7 @@ module.exports = { packageFiles: [ 'package.json', { - fileName: 'frontend/package.json', + packageFile: 'frontend/package.json', labels: ['upgrade', 'frontend'] }, ], diff --git a/lib/api/github.js b/lib/api/github.js index 29118debb8..7fba3274cf 100644 --- a/lib/api/github.js +++ b/lib/api/github.js @@ -124,6 +124,7 @@ async function getRepos(token, endpoint) { // Initialize GitHub by getting base branch and SHA async function initRepo(repoName, token, endpoint, repoLogger) { + logger = repoLogger || logger; logger.debug(`initRepo(${JSON.stringify(repoName)})`); if (repoLogger) { logger = repoLogger; diff --git a/lib/api/npm.js b/lib/api/npm.js index e4d476a435..409108b5c6 100644 --- a/lib/api/npm.js +++ b/lib/api/npm.js @@ -4,7 +4,6 @@ const got = require('got'); const url = require('url'); const registryUrl = require('registry-url'); const registryAuthToken = require('registry-auth-token'); -const logger = require('../helpers/logger'); module.exports = { setNpmrc, @@ -23,7 +22,7 @@ async function setNpmrc(input) { npmrc = input; } -async function getDependency(name) { +async function getDependency(name, logger) { logger.debug(`getDependency(${name})`); const scope = name.split('/')[0]; const regUrl = registryUrl(scope, { npmrc }); @@ -66,7 +65,7 @@ async function getDependency(name) { dep.versions[version] = {}; }); npmCache[cacheKey] = dep; - logger.debug(JSON.stringify(dep)); + logger.trace({ dependency: dep }, JSON.stringify(dep)); return dep; } catch (err) { logger.warn(`Dependency not found: ${name}`); diff --git a/lib/config/index.js b/lib/config/index.js index 341091161d..5dff1c6cc0 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -46,13 +46,13 @@ async function parseConfigs(env, argv) { }); } - logger.debug({ config: defaultConfig }, 'Default config'); + logger.trace({ config: defaultConfig }, 'Default config'); logger.debug({ config: fileConfig }, 'File config'); logger.debug({ config: cliConfig }, 'CLI config'); logger.debug({ config: envConfig }, 'Env config'); // Get global config - logger.debug({ config }, 'Raw config'); + logger.trace({ config }, 'Raw config'); // Check platforms and tokens if (config.platform === 'github') { @@ -107,7 +107,7 @@ async function parseConfigs(env, argv) { } // Print config - logger.debug({ config }, 'Global config'); + logger.trace({ config }, 'Global config'); // Remove log file entries delete config.logFile; delete config.logFileLevel; diff --git a/lib/helpers/platform.js b/lib/helpers/platform.js new file mode 100644 index 0000000000..8255006a48 --- /dev/null +++ b/lib/helpers/platform.js @@ -0,0 +1,16 @@ +const githubApi = require('../../lib/api/github'); +const gitlabApi = require('../../lib/api/gitlab'); + +module.exports = { + // TODO: Centralise platform-specific functions here (e.g. wording) + getApi, +}; + +function getApi(platform) { + if (platform === 'github') { + return githubApi; + } else if (platform === 'gitlab') { + return gitlabApi; + } + throw new Error(`Unknown platform: ${platform}`); +} diff --git a/lib/helpers/versions.js b/lib/helpers/versions.js index 357ed95123..8c684fe17f 100644 --- a/lib/helpers/versions.js +++ b/lib/helpers/versions.js @@ -31,6 +31,7 @@ function determineUpgrades(dep, currentVersion, config) { const maxSatisfying = semver.maxSatisfying(versionList, currentVersion); allUpgrades.pin = { upgradeType: 'pin', + isPin: true, newVersion: maxSatisfying, newVersionMajor: semver.major(maxSatisfying), }; @@ -90,6 +91,11 @@ function determineUpgrades(dep, currentVersion, config) { changeLogToVersion, automergeEnabled, }; + if (upgradeType === 'major') { + allUpgrades[upgradeKey].isMajor = true; + } else if (upgradeType === 'minor') { + allUpgrades[upgradeKey].isMinor = true; + } } }); if (allUpgrades.pin && Object.keys(allUpgrades).length > 1) { diff --git a/lib/workers/branch.js b/lib/workers/branch.js index 01df868f2b..8e75b18301 100644 --- a/lib/workers/branch.js +++ b/lib/workers/branch.js @@ -9,6 +9,7 @@ module.exports = { getParentBranch, ensureBranch, updateBranch, + removeStandaloneBranches, }; async function getParentBranch(branchName, config) { @@ -60,7 +61,7 @@ async function getParentBranch(branchName, config) { // Ensure branch exists with appropriate content async function ensureBranch(upgrades) { - logger.debug({ config: upgrades }, 'ensureBranch'); + logger.trace({ config: upgrades }, 'ensureBranch'); // Use the first upgrade for all the templates const branchName = handlebars.compile(upgrades[0].branchName)(upgrades[0]); // parentBranch is the branch we will base off @@ -197,13 +198,14 @@ async function ensureBranch(upgrades) { return true; } -async function updateBranch(upgrades, parentLogger) { +async function updateBranch(upgrades) { + await removeStandaloneBranches(upgrades); const upgrade0 = upgrades[0]; // Use templates to generate strings const branchName = handlebars.compile(upgrade0.branchName)(upgrade0); const prTitle = handlebars.compile(upgrade0.prTitle)(upgrade0); - logger = parentLogger.child({ + logger = upgrade0.logger.child({ repository: upgrade0.repository, branch: branchName, }); @@ -241,3 +243,21 @@ async function updateBranch(upgrades, parentLogger) { // Don't throw here - we don't want to stop the other renovations } } + +async function removeStandaloneBranches(upgrades) { + if (upgrades.length > 1) { + for (const upgrade of upgrades) { + const standaloneBranchName = handlebars.compile(upgrade.branchName)( + upgrade + ); + upgrade.logger.debug(`Need to delete branch ${standaloneBranchName}`); + try { + await upgrade.api.deleteBranch(standaloneBranchName); + } catch (err) { + upgrade.logger.debug(`Couldn't delete branch ${standaloneBranchName}`); + } + // Rename to group branchName + upgrade.branchName = upgrade.groupBranchName; + } + } +} diff --git a/lib/workers/index.js b/lib/workers/index.js index 6c3a3e8b27..b1d7fe3929 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -7,15 +7,16 @@ module.exports = { }; async function start() { - // Parse config + logger.info('Renovate starting'); try { - logger.info('Renovate starting'); const config = await configParser.parseConfigs(process.env, process.argv); // Iterate through repositories sequentially for (let index = 0; index < config.repositories.length; index += 1) { const repoConfig = configParser.getRepoConfig(config, index); - repoConfig.logger = logger; + repoConfig.logger = logger.child({ repository: repoConfig.repository }); + repoConfig.logger.info('Renovating repository'); await repositoryWorker.processRepo(repoConfig); + repoConfig.logger.info('Finished repository'); } logger.info('Renovate finished'); } catch (err) { diff --git a/lib/workers/package-file.js b/lib/workers/package-file.js index 712ecb028d..ae1bd98b74 100644 --- a/lib/workers/package-file.js +++ b/lib/workers/package-file.js @@ -36,7 +36,9 @@ async function processPackageFile(config) { { config: packageContent.renovate }, 'package.json>renovate config' ); - Object.assign(config, packageContent.renovate, { repoConfigured: true }); + Object.assign(config, packageContent.renovate, { + renovateJsonPresent: true, + }); } // Now check if config is disabled if (config.enabled === false) { @@ -118,7 +120,7 @@ function assignDepConfigs(inputConfig, deps) { delete returnDep.config.githubAppKey; delete returnDep.config.packageFiles; delete returnDep.config.logLevel; - delete returnDep.config.repoConfigured; + delete returnDep.config.renovateJsonPresent; delete returnDep.config.ignoreDeps; delete returnDep.config.packages; delete returnDep.config.maintainYarnLock; @@ -134,7 +136,7 @@ async function findUpgrades(dependencies) { const allUpgrades = []; // findDepUpgrades can add more than one upgrade to allUpgrades async function findDepUpgrades(dep) { - const npmDependency = await npmApi.getDependency(dep.depName); + const npmDependency = await npmApi.getDependency(dep.depName, logger); if (!npmDependency) { // If dependency lookup fails then ignore it and keep going return; @@ -154,7 +156,9 @@ async function findUpgrades(dependencies) { )}` ); upgrades.forEach(upgrade => { - allUpgrades.push(Object.assign({}, dep, upgrade)); + const upgradeObj = Object.assign({}, dep, dep.config, upgrade); + delete upgradeObj.config; + allUpgrades.push(upgradeObj); }); } else { logger.debug(`${dep.depName}: No upgrades required`); diff --git a/lib/workers/pr.js b/lib/workers/pr.js index 6e91bf32e1..d03d72f443 100644 --- a/lib/workers/pr.js +++ b/lib/workers/pr.js @@ -12,7 +12,7 @@ module.exports = { // Ensures that PR exists with matching title/body async function ensurePr(upgrades, logger) { - logger.debug({ config: upgrades }, 'ensurePr'); + logger.trace({ config: upgrades }, 'ensurePr'); // If there is a group, it will use the config of the first upgrade in the array const config = Object.assign({}, upgrades[0]); config.upgrades = []; diff --git a/lib/workers/repository.js b/lib/workers/repository.js index b4e80398df..ec96f9581b 100644 --- a/lib/workers/repository.js +++ b/lib/workers/repository.js @@ -1,132 +1,77 @@ -// Global requires +// Third party requires const handlebars = require('handlebars'); const ini = require('ini'); -let logger = require('../helpers/logger'); const stringify = require('json-stringify-pretty-compact'); -// API -const githubApi = require('../api/github'); -const gitlabApi = require('../api/gitlab'); -const npmApi = require('../api/npm'); // Config const defaultsParser = require('../config/defaults'); +// API +const githubApi = require('../../lib/api/github'); +const gitlabApi = require('../../lib/api/gitlab'); +const npmApi = require('../api/npm'); // Workers -const packageFileWorker = require('./package-file'); const branchWorker = require('./branch'); +const packageFileWorker = require('./package-file'); module.exports = { - processRepo, - processUpgrades, - removeStandaloneBranches, - mergeRenovateJson, - checkIfOnboarded, setNpmrc, + initApis, + mergeRenovateJson, + onboardRepository, + getOnboardingStatus, detectPackageFiles, - getAllRepoUpgrades, + determineRepoUpgrades, + groupUpgradesByBranch, + updateBranchesSequentially, + processRepo, }; -// This will be github or others -let api; - -// Queue package files in sequence within a repo -async function processRepo(config) { - logger = config.logger.child({ repository: config.repository }); - config.logger = logger; // eslint-disable-line no-param-reassign - logger.info('Renovating repository'); - logger.debug({ config }, 'processRepo'); - if (config.platform === 'github') { - api = githubApi; - } else if (config.platform === 'gitlab') { - api = gitlabApi; - } else { - // TODO: throw this? - logger.error( - `Unknown platform ${config.platform} for repository ${config.repository}` - ); - return; - } - try { - // Initialize repo - await api.initRepo( - config.repository, - config.token, - config.endpoint, - logger - ); - // Override settings with renovate.json if present - await module.exports.mergeRenovateJson(config); - // Check that the repository is onboarded - const isOnboarded = await module.exports.checkIfOnboarded(config); - if (isOnboarded === false) { - return; - } - // Check for presence of .npmrc in repository - await module.exports.setNpmrc(config); - // Detect package files if none already configured - await module.exports.detectPackageFiles(config); - const upgrades = await module.exports.getAllRepoUpgrades(config); - await module.exports.processUpgrades(upgrades); - } catch (error) { - throw error; - } - logger.info('Finished repository'); -} - -// Check for config in `renovate.json` -async function mergeRenovateJson(config) { - const renovateJson = await api.getFileJson('renovate.json'); - if (renovateJson) { - logger.debug({ config: renovateJson }, 'renovate.json config'); - Object.assign(config, renovateJson, { repoConfigured: true }); - } else { - logger.debug('No renovate.json found'); - } -} - // Check for .npmrc in repository and pass it to npm api if found -async function setNpmrc() { +async function setNpmrc(config) { try { let npmrc = null; - const npmrcContent = await api.getFileContent('.npmrc'); + const npmrcContent = await config.api.getFileContent('.npmrc'); if (npmrcContent) { - logger.debug('Found .npmrc file in repository'); + config.logger.debug('Found .npmrc file in repository'); npmrc = ini.parse(npmrcContent); } npmApi.setNpmrc(npmrc); } catch (err) { - logger.error('Failed to set .npmrc'); + config.logger.error('Failed to set .npmrc'); } } -async function checkIfOnboarded(config) { - logger.debug('Checking if repo is configured'); - // Check if repository is configured - if (config.repoConfigured || config.onboarding === false) { - logger.debug('Repo is configured or onboarding disabled'); - return true; - } - const pr = await api.findPr('renovate/configure', 'Configure Renovate'); - if (pr) { - if (pr.isClosed) { - logger.debug('Closed Configure Renovate PR found - continuing'); - return true; +async function initApis(inputConfig) { + function getPlatformApi(platform) { + if (platform === 'github') { + return githubApi; + } else if (platform === 'gitlab') { + return gitlabApi; } - // PR exists but hasn't been closed yet - logger.error(`Close PR #${pr.displayNumber} before continuing`); - return false; + throw new Error(`Unknown platform: ${platform}`); } - await onboardRepository(config); - return false; + + const config = Object.assign({}, inputConfig); + config.api = getPlatformApi(config.platform); + await config.api.initRepo( + config.repository, + config.token, + config.endpoint, + config.logger + ); + // Check for presence of .npmrc in repository + await module.exports.setNpmrc(config); + return config; } -// Ensure config contains packageFiles -async function detectPackageFiles(config) { - if (config.packageFiles.length === 0) { - // autodiscover filenames if none manually configured - const fileNames = await api.findFilePaths('package.json'); - // Map to config structure - const packageFiles = fileNames.map(fileName => ({ fileName })); - Object.assign(config, { packageFiles }); +// Check for config in `renovate.json` +async function mergeRenovateJson(config) { + const renovateJson = await config.api.getFileJson('renovate.json'); + if (!renovateJson) { + config.logger.debug('No renovate.json found'); + return config; } + config.logger.debug({ config: renovateJson }, 'renovate.json config'); + return Object.assign({}, config, renovateJson, { renovateJsonPresent: true }); } async function onboardRepository(config) { @@ -155,7 +100,7 @@ If the default settings are all suitable for you, simply close this Pull Request prBody = prBody.replace(/Pull Request/g, 'Merge Request'); } const defaultConfigString = `${stringify(defaultConfig)}\n`; - await api.commitFilesToBranch( + await config.api.commitFilesToBranch( 'renovate/configure', [ { @@ -165,97 +110,139 @@ If the default settings are all suitable for you, simply close this Pull Request ], 'Add renovate.json' ); - const pr = await api.createPr( + const pr = await config.api.createPr( 'renovate/configure', 'Configure Renovate', prBody ); - logger.info(`Created ${pr.displayNumber} for configuration`); + config.logger.debug(`Created ${pr.displayNumber} for configuration`); } -async function getAllRepoUpgrades(config) { - logger.info('getAllRepoUpgrades'); +async function getOnboardingStatus(config) { + config.logger.debug('Checking if repo is configured'); + // Check if repository is configured + if (config.onboarding === false) { + config.logger.debug('Repo onboarding is disabled'); + return true; + } + if (config.renovateJsonPresent) { + config.logger.debug('Repo onboarded'); + return true; + } + const pr = await config.api.findPr( + 'renovate/configure', + 'Configure Renovate' + ); + if (pr) { + if (pr.isClosed) { + config.logger.debug('Found closed Configure Renovate PR'); + return true; + } + // PR exists but hasn't been closed yet + config.logger.debug( + `PR #${pr.displayNumber} needs to be closed to enable renovate to continue` + ); + return false; + } + await module.exports.onboardRepository(config); + return false; +} + +async function detectPackageFiles(config) { + config.logger.trace({ config }, 'detectPackageFiles'); + const packageFiles = await config.api.findFilePaths('package.json'); + config.logger.debug(`Found ${packageFiles.length} package file(s)`); + return Object.assign({}, config, { packageFiles }); +} + +async function determineRepoUpgrades(config) { + config.logger.trace({ config }, 'determineRepoUpgrades'); + if (config.packageFiles.length === 0) { + config.logger.warn('No package files found'); + } let upgrades = []; for (let packageFile of config.packageFiles) { if (typeof packageFile === 'string') { - packageFile = { fileName: packageFile }; + packageFile = { packageFile }; + } else if (packageFile.fileName) { + // Retained deprecated 'fileName' for backwards compatibility + // TODO: Remove in renovate 9 + packageFile.packageFile = packageFile.fileName; + delete packageFile.fileName; } - const cascadedConfig = Object.assign({}, config, packageFile); - // Remove unnecessary fields - cascadedConfig.packageFile = cascadedConfig.fileName; - delete cascadedConfig.fileName; + const packageFileConfig = Object.assign({}, config, packageFile); + delete packageFileConfig.packageFiles; upgrades = upgrades.concat( - await packageFileWorker.processPackageFile(cascadedConfig) + await packageFileWorker.processPackageFile(packageFileConfig) ); } return upgrades; } -async function processUpgrades(upgrades) { - if (upgrades.length) { - const upgradeCount = upgrades.length === 1 - ? '1 dependency upgrade' - : `${upgrades.length} dependency upgrades`; - logger.info(`Processing ${upgradeCount}`); - } else { - logger.info('No upgrades to process'); - } - logger.debug({ config: upgrades }, 'All upgrades'); +async function groupUpgradesByBranch(upgrades, logger) { + logger.trace({ config: upgrades }, 'groupUpgradesByBranch'); + logger.info(`Processing ${upgrades.length} dependency upgrade(s)`); const branchUpgrades = {}; - for (const upgrade of upgrades) { - const flattened = Object.assign({}, upgrade.config, upgrade); - delete flattened.config; - if (flattened.upgradeType === 'pin') { - flattened.isPin = true; - } else if (flattened.upgradeType === 'major') { - flattened.isMajor = true; - } else if (flattened.upgradeType === 'minor') { - flattened.isMinor = true; - } + for (const upg of upgrades) { + const upgrade = Object.assign({}, upg); // Check whether to use a group name let branchName; - if (flattened.groupName) { + if (upgrade.groupName) { + upgrade.groupSlug = + upgrade.groupSlug || + upgrade.groupName.toLowerCase().replace(/[^a-z0-9+]+/g, '-'); + branchName = handlebars.compile(upgrade.groupBranchName)(upgrade); logger.debug( - `Dependency ${flattened.depName} is part of group '${flattened.groupName}'` + { branchName }, + `Dependency ${upgrade.depName} is part of group '${upgrade.groupName}'` ); - flattened.groupSlug = - flattened.groupSlug || - flattened.groupName.toLowerCase().replace(/[^a-z0-9+]+/g, '-'); - branchName = handlebars.compile(flattened.groupBranchName)(flattened); - logger.debug(`branchName=${branchName}`); if (branchUpgrades[branchName]) { - // flattened.branchName = flattened.groupBranchName; - flattened.commitMessage = flattened.groupCommitMessage; - flattened.prTitle = flattened.groupPrTitle; - flattened.prBody = flattened.groupPrBody; + upgrade.commitMessage = upgrade.groupCommitMessage; + upgrade.prTitle = upgrade.groupPrTitle; + upgrade.prBody = upgrade.groupPrBody; } } else { - branchName = handlebars.compile(flattened.branchName)(flattened); + branchName = handlebars.compile(upgrade.branchName)(upgrade); } branchUpgrades[branchName] = branchUpgrades[branchName] || []; - branchUpgrades[branchName] = [flattened].concat(branchUpgrades[branchName]); + branchUpgrades[branchName] = [upgrade].concat(branchUpgrades[branchName]); } - logger.debug({ config: branchUpgrades }, 'Branched upgrades'); - for (const branch of Object.keys(branchUpgrades)) { - await module.exports.removeStandaloneBranches(branchUpgrades[branch]); - await branchWorker.updateBranch(branchUpgrades[branch], logger); + logger.debug(`Returning ${Object.keys(branchUpgrades).length} branch(es)`); + return branchUpgrades; +} + +async function updateBranchesSequentially(branchUpgrades, logger) { + logger.trace({ config: branchUpgrades }, 'updateBranchesSequentially'); + logger.debug(`Updating ${Object.keys(branchUpgrades).length} branch(es)`); + for (const branchName of Object.keys(branchUpgrades)) { + await branchWorker.updateBranch(branchUpgrades[branchName]); } } -async function removeStandaloneBranches(upgrades) { - if (upgrades.length > 1) { - for (const upgrade of upgrades) { - const standaloneBranchName = handlebars.compile(upgrade.branchName)( - upgrade - ); - logger.debug(`Need to delete branch ${standaloneBranchName}`); - try { - await upgrade.api.deleteBranch(standaloneBranchName); - } catch (err) { - logger.debug(`Couldn't delete branch ${standaloneBranchName}`); - } - // Rename to group branchName - upgrade.branchName = upgrade.groupBranchName; +async function processRepo(repoConfig) { + let config = Object.assign({}, repoConfig); + config.logger.trace({ config }, 'processRepo'); + try { + config = await module.exports.initApis(config); + config = await module.exports.mergeRenovateJson(config); + const repoIsOnboarded = await module.exports.getOnboardingStatus(config); + if (!repoIsOnboarded) { + config.logger.info('"Configure Renovate" PR needs to be closed first'); + return; + } + const hasConfiguredPackageFiles = config.packageFiles.length > 0; + if (!hasConfiguredPackageFiles) { + config = await module.exports.detectPackageFiles(config); } + const allUpgrades = await module.exports.determineRepoUpgrades(config); + const branchUpgrades = await module.exports.groupUpgradesByBranch( + allUpgrades, + config.logger + ); + await updateBranchesSequentially(branchUpgrades, config.logger); + } catch (error) { + // Swallow this error so that other repositories can be processed + config.logger.error(`Failed to process repository: ${error.message}`); + config.logger.debug({ error }); } } diff --git a/test/_fixtures/config/file.js b/test/_fixtures/config/file.js index 7ccccf04de..61e930fe6b 100644 --- a/test/_fixtures/config/file.js +++ b/test/_fixtures/config/file.js @@ -11,7 +11,7 @@ module.exports = { repository: 'singapore/renovate', packageFiles: [ { - fileName: 'package.json', + packageFile: 'package.json', labels: ['a'], }, ], diff --git a/test/api/npm.spec.js b/test/api/npm.spec.js index 0989b12b6d..9f1ed8804f 100644 --- a/test/api/npm.spec.js +++ b/test/api/npm.spec.js @@ -2,6 +2,7 @@ const npm = require('../../lib/api/npm'); const got = require('got'); const registryUrl = require('registry-url'); const registryAuthToken = require('registry-auth-token'); +const logger = require('../_fixtures/logger'); jest.mock('registry-url'); jest.mock('registry-auth-token'); @@ -25,7 +26,7 @@ describe('api/npm', () => { it('should fetch package info from npm', async () => { registryUrl.mockImplementation(() => 'https://npm.mycustomregistry.com/'); got.mockImplementation(() => Promise.resolve(npmResponse)); - const res = await npm.getDependency('foobar'); + const res = await npm.getDependency('foobar', logger); expect(res).toMatchSnapshot(); const call = got.mock.calls[0]; expect(call).toMatchSnapshot(); @@ -33,8 +34,8 @@ describe('api/npm', () => { it('should cache package info from npm', async () => { registryUrl.mockImplementation(() => 'https://npm.mycustomregistry.com/'); got.mockImplementation(() => Promise.resolve(npmResponse)); - const res1 = await npm.getDependency('foobar'); - const res2 = await npm.getDependency('foobar'); + const res1 = await npm.getDependency('foobar', logger); + const res2 = await npm.getDependency('foobar', logger); expect(res1).toEqual(res2); expect(got.mock.calls.length).toEqual(1); }); @@ -43,7 +44,7 @@ describe('api/npm', () => { got.mockImplementation(() => { throw new Error('not found'); }); - const res = await npm.getDependency('foobar'); + const res = await npm.getDependency('foobar', logger); expect(res).toBeNull(); }); it('should send an authorization header if provided', async () => { @@ -53,7 +54,7 @@ describe('api/npm', () => { token: '1234', })); got.mockImplementation(() => Promise.resolve(npmResponse)); - const res = await npm.getDependency('foobar'); + const res = await npm.getDependency('foobar', logger); expect(res).toMatchSnapshot(); const call = got.mock.calls[0]; expect(call).toMatchSnapshot(); diff --git a/test/helpers/__snapshots__/package-json.spec.js.snap b/test/helpers/__snapshots__/package-json.spec.js.snap new file mode 100644 index 0000000000..856503e452 --- /dev/null +++ b/test/helpers/__snapshots__/package-json.spec.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`helpers/platform getApi(platform) throws error 1`] = `[Error: Unknown platform: foo]`; diff --git a/test/helpers/__snapshots__/versions.spec.js.snap b/test/helpers/__snapshots__/versions.spec.js.snap index d9b94b59bc..82ec942873 100644 --- a/test/helpers/__snapshots__/versions.spec.js.snap +++ b/test/helpers/__snapshots__/versions.spec.js.snap @@ -6,6 +6,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "1.4.1", + "isMajor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "major", @@ -19,6 +20,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.0.0", "changeLogToVersion": "1.4.1", + "isMinor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "minor", @@ -32,6 +34,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.9.7", "changeLogToVersion": "1.4.1", + "isMajor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "major", @@ -42,6 +45,7 @@ Array [ exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) pins minor ranged versions 1`] = ` Array [ Object { + "isPin": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "pin", @@ -55,6 +59,7 @@ Array [ "automergeEnabled": true, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "0.9.7", + "isMinor": true, "newVersion": "0.9.7", "newVersionMajor": 0, "upgradeType": "minor", @@ -63,6 +68,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "1.4.1", + "isMajor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "major", @@ -76,6 +82,7 @@ Array [ "automergeEnabled": true, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "1.4.1", + "isMajor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "major", @@ -89,6 +96,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "1.4.1", + "isMajor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "major", @@ -102,6 +110,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.0.0-beta", "changeLogToVersion": "1.1.0-beta", + "isMinor": true, "newVersion": "1.1.0-beta", "newVersionMajor": 1, "upgradeType": "minor", @@ -115,6 +124,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.0.34", "changeLogToVersion": "0.0.35", + "isMinor": true, "isRange": true, "newVersion": "^0.0.35", "newVersionMajor": 0, @@ -129,6 +139,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.4.1", "changeLogToVersion": "2.0.1", + "isMajor": true, "newVersion": "2.0.1", "newVersionMajor": 2, "upgradeType": "major", @@ -139,6 +150,7 @@ Array [ exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) supports future versions if already future 1`] = ` Array [ Object { + "isPin": true, "newVersion": "2.0.3", "newVersionMajor": 2, "upgradeType": "pin", @@ -152,6 +164,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.4.1", "changeLogToVersion": "2.0.3", + "isMajor": true, "newVersion": "2.0.3", "newVersionMajor": 2, "upgradeType": "major", @@ -165,6 +178,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "0.9.7", + "isMinor": true, "newVersion": "0.9.7", "newVersionMajor": 0, "upgradeType": "minor", @@ -173,6 +187,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "1.4.1", + "isMajor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "major", @@ -186,6 +201,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "0.9.7", + "isMinor": true, "newVersion": "0.9.7", "newVersionMajor": 0, "upgradeType": "minor", @@ -194,6 +210,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.4.4", "changeLogToVersion": "1.4.1", + "isMajor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "major", @@ -207,6 +224,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.9.7", "changeLogToVersion": "1.4.1", + "isMajor": true, "isRange": true, "newVersion": "1.x", "newVersionMajor": 1, @@ -221,6 +239,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.3.0", "changeLogToVersion": "1.4.1", + "isMinor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "minor", @@ -234,6 +253,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.3.0", "changeLogToVersion": "1.4.1", + "isMinor": true, "isRange": true, "newVersion": "1.4.x", "newVersionMajor": 1, @@ -248,6 +268,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.7.2", "changeLogToVersion": "0.9.7", + "isMinor": true, "isRange": true, "newVersion": "<= 0.9.7", "newVersionMajor": 0, @@ -257,6 +278,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.7.2", "changeLogToVersion": "1.4.1", + "isMajor": true, "isRange": true, "newVersion": "<= 1.4.1", "newVersionMajor": 1, @@ -271,6 +293,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.0.1", "changeLogToVersion": "1.4.1", + "isMinor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "minor", @@ -284,6 +307,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.7.2", "changeLogToVersion": "0.9.7", + "isMinor": true, "isRange": true, "newVersion": "^0.9.0", "newVersionMajor": 0, @@ -293,6 +317,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.7.2", "changeLogToVersion": "1.4.1", + "isMajor": true, "isRange": true, "newVersion": "^1.0.0", "newVersionMajor": 1, @@ -307,6 +332,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.7.2", "changeLogToVersion": "0.9.7", + "isMinor": true, "isRange": true, "newVersion": "~0.9.0", "newVersionMajor": 0, @@ -316,6 +342,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.7.2", "changeLogToVersion": "1.4.1", + "isMajor": true, "isRange": true, "newVersion": "~1.4.0", "newVersionMajor": 1, @@ -330,6 +357,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "0.9.7", "changeLogToVersion": "1.4.1", + "isMajor": true, "isRange": true, "newVersion": "1", "newVersionMajor": 1, @@ -344,6 +372,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.3.0", "changeLogToVersion": "1.4.1", + "isMinor": true, "isRange": true, "newVersion": "1.4", "newVersionMajor": 1, @@ -358,6 +387,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.3.0", "changeLogToVersion": "1.4.1", + "isMinor": true, "newVersion": "1.4.1", "newVersionMajor": 1, "upgradeType": "minor", @@ -371,6 +401,7 @@ Array [ "automergeEnabled": false, "changeLogFromVersion": "1.3.0", "changeLogToVersion": "1.4.1", + "isMinor": true, "isRange": true, "newVersion": "~1.4.0", "newVersionMajor": 1, diff --git a/test/helpers/changelog.spec.js b/test/helpers/changelog.spec.js index 81f3a7d3e8..2c25e0dbba 100644 --- a/test/helpers/changelog.spec.js +++ b/test/helpers/changelog.spec.js @@ -1,12 +1,6 @@ const changelog = require('changelog'); const changelogHelper = require('../../lib/helpers/changelog'); -const bunyan = require('bunyan'); - -const logger = bunyan.createLogger({ - name: 'test', - stream: process.stdout, - level: 'fatal', -}); +const logger = require('../_fixtures/logger'); jest.mock('changelog'); diff --git a/test/helpers/package-json.spec.js b/test/helpers/package-json.spec.js index a973c30091..60fd09ddc5 100644 --- a/test/helpers/package-json.spec.js +++ b/test/helpers/package-json.spec.js @@ -1,101 +1,21 @@ -const fs = require('fs'); -const path = require('path'); -const packageJson = require('../../lib/helpers/package-json'); -const bunyan = require('bunyan'); +const platformHelper = require('../../lib/helpers/platform'); -const logger = bunyan.createLogger({ - name: 'test', - stream: process.stdout, - level: 'fatal', -}); - -const defaultTypes = [ - 'dependencies', - 'devDependencies', - 'optionalDependencies', -]; - -function readFixture(fixture) { - return fs.readFileSync( - path.resolve(__dirname, `../_fixtures/package-json/${fixture}`), - 'utf8' - ); -} - -const input01Content = readFixture('inputs/01.json'); -const input02Content = readFixture('inputs/02.json'); - -describe('helpers/package-json', () => { - describe('.extractDependencies(packageJson, sections)', () => { - it('returns an array of correct length', () => { - const extractedDependencies = packageJson.extractDependencies( - JSON.parse(input01Content), - defaultTypes - ); - extractedDependencies.should.be.instanceof(Array); - extractedDependencies.should.have.length(10); - }); - it('each element contains non-null depType, depName, currentVersion', () => { - const extractedDependencies = packageJson.extractDependencies( - JSON.parse(input01Content), - defaultTypes - ); - extractedDependencies - .every(dep => dep.depType && dep.depName && dep.currentVersion) - .should.eql(true); - }); - it('supports null devDependencies', () => { - const extractedDependencies = packageJson.extractDependencies( - JSON.parse(input02Content), - defaultTypes - ); - extractedDependencies.should.be.instanceof(Array); - extractedDependencies.should.have.length(6); - }); - }); - describe('.setNewValue(currentFileContent, depType, depName, newVersion, logger)', () => { - it('replaces a dependency value', () => { - const outputContent = readFixture('outputs/011.json'); - const testContent = packageJson.setNewValue( - input01Content, - 'dependencies', - 'cheerio', - '0.22.1', - logger - ); - testContent.should.equal(outputContent); - }); - it('replaces only the first instance of a value', () => { - const outputContent = readFixture('outputs/012.json'); - const testContent = packageJson.setNewValue( - input01Content, - 'devDependencies', - 'angular-touch', - '1.6.1', - logger - ); - testContent.should.equal(outputContent); +describe('helpers/platform', () => { + describe('getApi(platform)', () => { + it('returns github', () => { + platformHelper.getApi('github'); }); - it('replaces only the second instance of a value', () => { - const outputContent = readFixture('outputs/013.json'); - const testContent = packageJson.setNewValue( - input01Content, - 'devDependencies', - 'angular-sanitize', - '1.6.1', - logger - ); - testContent.should.equal(outputContent); + it('returns gitlab', () => { + platformHelper.getApi('gitlab'); }); - it('handles the case where the desired version is already supported', () => { - const testContent = packageJson.setNewValue( - input01Content, - 'devDependencies', - 'angular-touch', - '1.5.8', - logger - ); - testContent.should.equal(input01Content); + it('throws error', () => { + let e; + try { + platformHelper.getApi('foo'); + } catch (err) { + e = err; + } + expect(e).toMatchSnapshot(); }); }); }); diff --git a/test/helpers/platform.spec.js b/test/helpers/platform.spec.js new file mode 100644 index 0000000000..8b71735a81 --- /dev/null +++ b/test/helpers/platform.spec.js @@ -0,0 +1,95 @@ +const fs = require('fs'); +const path = require('path'); +const packageJson = require('../../lib/helpers/package-json'); +const logger = require('../_fixtures/logger'); + +const defaultTypes = [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', +]; + +function readFixture(fixture) { + return fs.readFileSync( + path.resolve(__dirname, `../_fixtures/package-json/${fixture}`), + 'utf8' + ); +} + +const input01Content = readFixture('inputs/01.json'); +const input02Content = readFixture('inputs/02.json'); + +describe('helpers/package-json', () => { + describe('.extractDependencies(packageJson, sections)', () => { + it('returns an array of correct length', () => { + const extractedDependencies = packageJson.extractDependencies( + JSON.parse(input01Content), + defaultTypes + ); + extractedDependencies.should.be.instanceof(Array); + extractedDependencies.should.have.length(10); + }); + it('each element contains non-null depType, depName, currentVersion', () => { + const extractedDependencies = packageJson.extractDependencies( + JSON.parse(input01Content), + defaultTypes + ); + extractedDependencies + .every(dep => dep.depType && dep.depName && dep.currentVersion) + .should.eql(true); + }); + it('supports null devDependencies', () => { + const extractedDependencies = packageJson.extractDependencies( + JSON.parse(input02Content), + defaultTypes + ); + extractedDependencies.should.be.instanceof(Array); + extractedDependencies.should.have.length(6); + }); + }); + describe('.setNewValue(currentFileContent, depType, depName, newVersion, logger)', () => { + it('replaces a dependency value', () => { + const outputContent = readFixture('outputs/011.json'); + const testContent = packageJson.setNewValue( + input01Content, + 'dependencies', + 'cheerio', + '0.22.1', + logger + ); + testContent.should.equal(outputContent); + }); + it('replaces only the first instance of a value', () => { + const outputContent = readFixture('outputs/012.json'); + const testContent = packageJson.setNewValue( + input01Content, + 'devDependencies', + 'angular-touch', + '1.6.1', + logger + ); + testContent.should.equal(outputContent); + }); + it('replaces only the second instance of a value', () => { + const outputContent = readFixture('outputs/013.json'); + const testContent = packageJson.setNewValue( + input01Content, + 'devDependencies', + 'angular-sanitize', + '1.6.1', + logger + ); + testContent.should.equal(outputContent); + }); + it('handles the case where the desired version is already supported', () => { + const testContent = packageJson.setNewValue( + input01Content, + 'devDependencies', + 'angular-touch', + '1.5.8', + logger + ); + testContent.should.equal(input01Content); + }); + }); +}); diff --git a/test/workers/__snapshots__/branch.spec.js.snap b/test/workers/__snapshots__/branch.spec.js.snap index 61d37a092e..30e0b33d8f 100644 --- a/test/workers/__snapshots__/branch.spec.js.snap +++ b/test/workers/__snapshots__/branch.spec.js.snap @@ -41,3 +41,40 @@ Object { ], } `; + +exports[`workers/branch removeStandaloneBranches(upgrades) deletes standalone branch names 1`] = ` +Array [ + Object { + "api": Object { + "deleteBranch": [Function], + }, + "branchName": "what", + "groupBranchName": "what", + "logger": Object { + "child": [Function], + "debug": [Function], + "error": [Function], + "fatal": [Function], + "info": [Function], + "trace": [Function], + "warn": [Function], + }, + }, + Object { + "api": Object { + "deleteBranch": [Function], + }, + "branchName": "what", + "groupBranchName": "what", + "logger": Object { + "child": [Function], + "debug": [Function], + "error": [Function], + "fatal": [Function], + "info": [Function], + "trace": [Function], + "warn": [Function], + }, + }, +] +`; diff --git a/test/workers/__snapshots__/index.spec.js.snap b/test/workers/__snapshots__/index.spec.js.snap new file mode 100644 index 0000000000..c4d0b2a0df --- /dev/null +++ b/test/workers/__snapshots__/index.spec.js.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/workers/index processes repositories 1`] = ` +Array [ + Array [ + Object { + "foo": 1, + "repositories": Array [ + "a", + "b", + ], + }, + 0, + ], + Array [ + Object { + "foo": 1, + "repositories": Array [ + "a", + "b", + ], + }, + 1, + ], +] +`; diff --git a/test/workers/__snapshots__/repository-functions.spec.js.snap b/test/workers/__snapshots__/repository-functions.spec.js.snap new file mode 100644 index 0000000000..1c9ad4be9d --- /dev/null +++ b/test/workers/__snapshots__/repository-functions.spec.js.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`workers/repository detectPackageFiles(config) adds package files to object 1`] = ` +Array [ + "package.json", + "backend/package.json", +] +`; + +exports[`workers/repository groupUpgradesByBranch(upgrades, logger) does not group if different compiled branch names 1`] = ` +Object { + "bar-1.1.0": Array [ + Object { + "branchName": "bar-{{version}}", + "version": "1.1.0", + }, + ], + "foo-1.1.0": Array [ + Object { + "branchName": "foo-{{version}}", + "version": "1.1.0", + }, + ], + "foo-2.0.0": Array [ + Object { + "branchName": "foo-{{version}}", + "version": "2.0.0", + }, + ], +} +`; + +exports[`workers/repository groupUpgradesByBranch(upgrades, logger) groups if same compiled branch names 1`] = ` +Object { + "bar-1.1.0": Array [ + Object { + "branchName": "bar-{{version}}", + "version": "1.1.0", + }, + ], + "foo": Array [ + Object { + "branchName": "foo", + "version": "2.0.0", + }, + Object { + "branchName": "foo", + "version": "1.1.0", + }, + ], +} +`; + +exports[`workers/repository groupUpgradesByBranch(upgrades, logger) groups if same compiled group name 1`] = ` +Object { + "foo": Array [ + Object { + "branchName": "foo", + "version": "2.0.0", + }, + ], + "renovate/my-group": Array [ + Object { + "branchName": "bar-{{version}}", + "commitMessage": undefined, + "groupBranchName": "renovate/my-group", + "groupName": "My Group", + "groupSlug": "my-group", + "prBody": undefined, + "prTitle": undefined, + "version": "1.1.0", + }, + Object { + "branchName": "foo", + "groupBranchName": "renovate/{{groupSlug}}", + "groupName": "My Group", + "groupSlug": "my-group", + "version": "1.1.0", + }, + ], +} +`; + +exports[`workers/repository groupUpgradesByBranch(upgrades, logger) returns one branch if one input 1`] = ` +Object { + "foo-1.1.0": Array [ + Object { + "branchName": "foo-{{version}}", + "version": "1.1.0", + }, + ], +} +`; + +exports[`workers/repository initApis(config) throws if unknown platform 1`] = `"Unknown platform: foo"`; diff --git a/test/workers/branch.spec.js b/test/workers/branch.spec.js index a740fb4438..0174244038 100644 --- a/test/workers/branch.spec.js +++ b/test/workers/branch.spec.js @@ -5,13 +5,7 @@ const yarnHelper = require('../../lib/helpers/yarn'); const defaultConfig = require('../../lib/config/defaults').getConfig(); const packageJsonHelper = require('../../lib/helpers/package-json'); -const bunyan = require('bunyan'); - -const logger = bunyan.createLogger({ - name: 'test', - stream: process.stdout, - level: 'fatal', -}); +const logger = require('../_fixtures/logger'); jest.mock('../../lib/helpers/yarn'); jest.mock('../../lib/helpers/package-json'); @@ -333,41 +327,85 @@ describe('workers/branch', () => { config.api = { checkForClosedPr: jest.fn(), }; + config.logger = logger; branchWorker.ensureBranch = jest.fn(() => true); prWorker.ensurePr = jest.fn(() => true); }); it('returns immediately if closed PR found', async () => { config.api.checkForClosedPr.mockReturnValue(true); - await branchWorker.updateBranch([config], logger); + await branchWorker.updateBranch([config]); expect(branchWorker.ensureBranch.mock.calls.length).toBe(0); }); it('does not return immediately if recreateClosed true', async () => { config.api.checkForClosedPr.mockReturnValue(true); config.recreateClosed = true; - await branchWorker.updateBranch([config], logger); + await branchWorker.updateBranch([config]); expect(branchWorker.ensureBranch.mock.calls.length).toBe(1); }); it('pins', async () => { config.upgradeType = 'pin'; - await branchWorker.updateBranch([config], logger); + await branchWorker.updateBranch([config]); expect(branchWorker.ensureBranch.mock.calls.length).toBe(1); }); it('majors', async () => { config.upgradeType = 'major'; - await branchWorker.updateBranch([config], logger); + await branchWorker.updateBranch([config]); expect(branchWorker.ensureBranch.mock.calls.length).toBe(1); }); it('minors', async () => { config.upgradeType = 'minor'; - await branchWorker.updateBranch([config], logger); + await branchWorker.updateBranch([config]); expect(branchWorker.ensureBranch.mock.calls.length).toBe(1); }); it('handles errors', async () => { config.api.checkForClosedPr = jest.fn(() => { throw new Error('oops'); }); - await branchWorker.updateBranch([config], logger); + await branchWorker.updateBranch([config]); expect(branchWorker.ensureBranch.mock.calls.length).toBe(0); }); }); + describe('removeStandaloneBranches(upgrades)', () => { + it('deletes standalone branch names', async () => { + const api = { + deleteBranch: jest.fn(), + }; + const upgrades = [ + { + branchName: 'foo', + groupBranchName: 'what', + api, + logger, + }, + { + branchName: 'bar', + groupBranchName: 'what', + api, + logger, + }, + ]; + await branchWorker.removeStandaloneBranches(upgrades); + expect(upgrades).toMatchSnapshot(); + }); + it('handles errors', async () => { + const upgrades = [ + { + branchName: 'foo', + api: { + deleteBranch: jest.fn(() => { + throw new Error('deletion error'); + }), + }, + logger, + }, + { + branchName: 'bar', + groupBranchName: 'what', + api: null, + logger, + }, + ]; + await branchWorker.removeStandaloneBranches(upgrades); + }); + }); }); diff --git a/test/workers/index.spec.js b/test/workers/index.spec.js index 5cacbbdab3..e0a5512a3d 100644 --- a/test/workers/index.spec.js +++ b/test/workers/index.spec.js @@ -1,5 +1,37 @@ -require('../../lib/workers/index'); +const indexWorker = require('../../lib/workers/index'); +const repositoryWorker = require('../../lib/workers/repository'); +const configParser = require('../../lib/config'); -it('placeholder', () => { - // TODO: write tests for this module - this is here so the file shows up in coverage +describe('lib/workers/index', () => { + beforeEach(() => { + jest.resetAllMocks(); + configParser.parseConfigs = jest.fn(); + configParser.getRepoConfig = jest.fn(); + repositoryWorker.processRepo = jest.fn(); + }); + it('handles zero repos', async () => { + configParser.parseConfigs.mockReturnValueOnce({ + repositories: [], + }); + await indexWorker.start(); + }); + it('processes repositories', async () => { + configParser.parseConfigs.mockReturnValueOnce({ + foo: 1, + repositories: ['a', 'b'], + }); + configParser.getRepoConfig.mockReturnValue({ + repository: 'foo', + }); + await indexWorker.start(); + expect(configParser.parseConfigs.mock.calls.length).toBe(1); + expect(configParser.getRepoConfig.mock.calls).toMatchSnapshot(); + expect(repositoryWorker.processRepo.mock.calls.length).toBe(2); + }); + it('catches errors', async () => { + configParser.parseConfigs.mockImplementationOnce(() => { + throw new Error('a'); + }); + await indexWorker.start(); + }); }); diff --git a/test/workers/package-file.spec.js b/test/workers/package-file.spec.js index 0fc62553f0..44d2f05920 100644 --- a/test/workers/package-file.spec.js +++ b/test/workers/package-file.spec.js @@ -2,13 +2,7 @@ const packageFileWorker = require('../../lib/workers/package-file'); const npmApi = require('../../lib/api/npm'); const versionsHelper = require('../../lib/helpers/versions'); const packageJsonHelper = require('../../lib/helpers/package-json'); -const bunyan = require('bunyan'); - -const logger = bunyan.createLogger({ - name: 'test', - stream: process.stdout, - level: 'fatal', -}); +const logger = require('../_fixtures/logger'); jest.mock('../../lib/workers/branch'); jest.mock('../../lib/workers/pr'); diff --git a/test/workers/pr.spec.js b/test/workers/pr.spec.js index deff4e1fbc..7656ac354c 100644 --- a/test/workers/pr.spec.js +++ b/test/workers/pr.spec.js @@ -2,13 +2,7 @@ const prWorker = require('../../lib/workers/pr'); const changelogHelper = require('../../lib/helpers/changelog'); const defaultConfig = require('../../lib/config/defaults').getConfig(); -const bunyan = require('bunyan'); - -const logger = bunyan.createLogger({ - name: 'test', - stream: process.stdout, - level: 'fatal', -}); +const logger = require('../_fixtures/logger'); jest.mock('../../lib/helpers/changelog'); changelogHelper.getChangeLog = jest.fn(); diff --git a/test/workers/repository-functions.spec.js b/test/workers/repository-functions.spec.js new file mode 100644 index 0000000000..a948846789 --- /dev/null +++ b/test/workers/repository-functions.spec.js @@ -0,0 +1,343 @@ +const repositoryWorker = require('../../lib/workers/repository'); +const packageFileWorker = require('../../lib/workers/package-file'); +const branchWorker = require('../../lib/workers/branch'); +const logger = require('../_fixtures/logger'); + +const githubApi = require('../../lib/api/github'); +const gitlabApi = require('../../lib/api/gitlab'); +const npmApi = require('../../lib/api/npm'); + +jest.mock('../../lib/api/github'); +jest.mock('../../lib/api/gitlab'); +jest.mock('../../lib/api/npm'); +jest.mock('../../lib/workers/branch'); +jest.mock('../../lib/workers/package-file'); + +describe('workers/repository', () => { + describe('setNpmrc(config)', () => { + it('Skips if npmrc not found', async () => { + const config = { + api: { + getFileContent: jest.fn(), + }, + }; + await repositoryWorker.setNpmrc(config); + }); + it('Parses if npmrc found', async () => { + const config = { + api: { + getFileContent: jest.fn(() => 'a = b'), + }, + logger, + }; + await repositoryWorker.setNpmrc(config); + }); + it('Catches errors', async () => { + const config = { + api: { + getFileContent: jest.fn(() => { + throw new Error('file error'); + }), + }, + logger, + }; + await repositoryWorker.setNpmrc(config); + }); + }); + describe('initApis(config)', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + it('returns github api', async () => { + const config = { platform: 'github' }; + const res = await repositoryWorker.initApis(config); + expect(res.platform).toEqual('github'); + expect(githubApi.initRepo.mock.calls.length).toBe(1); + expect(gitlabApi.initRepo.mock.calls.length).toBe(0); + expect(npmApi.setNpmrc.mock.calls.length).toBe(1); + }); + it('returns gitlab api', async () => { + const config = { platform: 'gitlab' }; + const res = await repositoryWorker.initApis(config); + expect(res.platform).toEqual('gitlab'); + expect(githubApi.initRepo.mock.calls.length).toBe(0); + expect(gitlabApi.initRepo.mock.calls.length).toBe(1); + expect(npmApi.setNpmrc.mock.calls.length).toBe(1); + }); + it('throws if unknown platform', async () => { + const config = { platform: 'foo' }; + let e; + try { + await repositoryWorker.initApis(config); + } catch (err) { + e = err; + } + expect(e.message).toMatchSnapshot(); + expect(githubApi.initRepo.mock.calls.length).toBe(0); + expect(gitlabApi.initRepo.mock.calls.length).toBe(0); + expect(npmApi.setNpmrc.mock.calls.length).toBe(0); + }); + }); + describe('mergeRenovateJson(config)', () => { + let config; + beforeEach(() => { + config = { + api: { + getFileJson: jest.fn(), + }, + logger, + }; + }); + it('returns same config if no renovate.json found', async () => { + expect(await repositoryWorker.mergeRenovateJson(config)).toEqual(config); + }); + it('returns extended config if renovate.json found', async () => { + config.api.getFileJson.mockReturnValueOnce({ foo: 1 }); + const returnConfig = await repositoryWorker.mergeRenovateJson(config); + expect(returnConfig.foo).toBe(1); + expect(returnConfig.renovateJsonPresent).toBe(true); + }); + }); + describe('onboardRepository(config)', () => { + let config; + beforeEach(() => { + config = { + api: { + commitFilesToBranch: jest.fn(), + createPr: jest.fn(() => ({ displayNumber: 1 })), + }, + logger, + }; + }); + it('should commit files and create PR', async () => { + config.platform = 'github'; + await repositoryWorker.onboardRepository(config); + expect(config.api.commitFilesToBranch.mock.calls.length).toBe(1); + expect(config.api.createPr.mock.calls.length).toBe(1); + expect( + config.api.createPr.mock.calls[0][2].indexOf('Pull Request') + ).not.toBe(-1); + expect( + config.api.createPr.mock.calls[0][2].indexOf('Merge Request') + ).toBe(-1); + }); + it('should adapt for gitlab phrasing', async () => { + config.platform = 'gitlab'; + await repositoryWorker.onboardRepository(config); + expect(config.api.createPr.mock.calls[0][2].indexOf('Pull Request')).toBe( + -1 + ); + expect( + config.api.createPr.mock.calls[0][2].indexOf('Merge Request') + ).not.toBe(-1); + }); + }); + describe('getOnboardingStatus(config)', () => { + let config; + beforeEach(() => { + config = { + api: { + commitFilesToBranch: jest.fn(), + createPr: jest.fn(() => ({ displayNumber: 1 })), + findPr: jest.fn(), + }, + logger, + }; + }); + it('returns true if onboarding is false', async () => { + config.onboarding = false; + const res = await repositoryWorker.getOnboardingStatus(config); + expect(res).toEqual(true); + expect(config.api.findPr.mock.calls.length).toBe(0); + }); + it('returns complete if renovate onboarded', async () => { + config.renovateJsonPresent = true; + const res = await repositoryWorker.getOnboardingStatus(config); + expect(res).toEqual(true); + expect(config.api.findPr.mock.calls.length).toBe(0); + }); + it('returns complete if pr and pr is closed', async () => { + config.api.findPr.mockReturnValueOnce({ isClosed: true }); + const res = await repositoryWorker.getOnboardingStatus(config); + expect(res).toEqual(true); + expect(config.api.findPr.mock.calls.length).toBe(1); + }); + it('returns in progres if pr and pr is not closed', async () => { + config.api.findPr.mockReturnValueOnce({}); + const res = await repositoryWorker.getOnboardingStatus(config); + expect(res).toEqual(false); + expect(config.api.findPr.mock.calls.length).toBe(1); + }); + it('returns none if no pr', async () => { + const res = await repositoryWorker.getOnboardingStatus(config); + expect(res).toEqual(false); + expect(config.api.findPr.mock.calls.length).toBe(1); + }); + }); + describe('detectPackageFiles(config)', () => { + it('adds package files to object', async () => { + const config = { + api: { + findFilePaths: jest.fn(() => [ + 'package.json', + 'backend/package.json', + ]), + }, + logger, + }; + const res = await repositoryWorker.detectPackageFiles(config); + expect(res).toMatchObject(config); + expect(res.packageFiles).toMatchSnapshot(); + }); + }); + describe('determineRepoUpgrades(config)', () => { + let config; + beforeEach(() => { + config = { + logger, + }; + }); + it('returns empty array if no packageFiles', async () => { + config.packageFiles = []; + const upgrades = await repositoryWorker.determineRepoUpgrades(config); + expect(upgrades.length).toBe(0); + }); + it('returns empty array if none found', async () => { + config.packageFiles = [ + 'package.json', + { + packageFile: 'backend/package.json', + }, + ]; + packageFileWorker.processPackageFile.mockReturnValue([]); + const upgrades = await repositoryWorker.determineRepoUpgrades(config); + expect(upgrades.length).toBe(0); + }); + it('returns array if upgrades found', async () => { + config.packageFiles = [ + 'package.json', + { + packageFile: 'backend/package.json', + }, + { + fileName: 'frontend/package.json', + }, + ]; + packageFileWorker.processPackageFile.mockReturnValueOnce(['a']); + packageFileWorker.processPackageFile.mockReturnValueOnce(['b', 'c']); + const upgrades = await repositoryWorker.determineRepoUpgrades(config); + expect(upgrades.length).toBe(3); + }); + }); + describe('groupUpgradesByBranch(upgrades, logger)', () => { + it('returns empty object if no input array', async () => { + const res = await repositoryWorker.groupUpgradesByBranch([], logger); + expect(res).toEqual({}); + }); + it('returns one branch if one input', async () => { + const upgrades = [ + { + branchName: 'foo-{{version}}', + version: '1.1.0', + }, + ]; + const res = await repositoryWorker.groupUpgradesByBranch( + upgrades, + logger + ); + expect(Object.keys(res).length).toBe(1); + expect(res).toMatchSnapshot(); + }); + it('does not group if different compiled branch names', async () => { + const upgrades = [ + { + branchName: 'foo-{{version}}', + version: '1.1.0', + }, + { + branchName: 'foo-{{version}}', + version: '2.0.0', + }, + { + branchName: 'bar-{{version}}', + version: '1.1.0', + }, + ]; + const res = await repositoryWorker.groupUpgradesByBranch( + upgrades, + logger + ); + expect(Object.keys(res).length).toBe(3); + expect(res).toMatchSnapshot(); + }); + it('groups if same compiled branch names', async () => { + const upgrades = [ + { + branchName: 'foo', + version: '1.1.0', + }, + { + branchName: 'foo', + version: '2.0.0', + }, + { + branchName: 'bar-{{version}}', + version: '1.1.0', + }, + ]; + const res = await repositoryWorker.groupUpgradesByBranch( + upgrades, + logger + ); + expect(Object.keys(res).length).toBe(2); + expect(res).toMatchSnapshot(); + }); + it('groups if same compiled group name', async () => { + const upgrades = [ + { + branchName: 'foo', + version: '1.1.0', + groupName: 'My Group', + groupBranchName: 'renovate/{{groupSlug}}', + }, + { + branchName: 'foo', + version: '2.0.0', + }, + { + branchName: 'bar-{{version}}', + version: '1.1.0', + groupName: 'My Group', + groupBranchName: 'renovate/my-group', + }, + ]; + const res = await repositoryWorker.groupUpgradesByBranch( + upgrades, + logger + ); + expect(Object.keys(res).length).toBe(2); + expect(res).toMatchSnapshot(); + }); + }); + describe('updateBranchesSequentially(branchUpgrades, logger)', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + it('handles empty case', async () => { + await repositoryWorker.updateBranchesSequentially({}, logger); + expect(branchWorker.updateBranch.mock.calls.length).toBe(0); + }); + it('updates branches', async () => { + const branchUpgrades = { + foo: {}, + bar: {}, + baz: {}, + }; + await repositoryWorker.updateBranchesSequentially(branchUpgrades, logger); + expect(branchWorker.updateBranch.mock.calls.length).toBe(3); + }); + }); + describe('processRepo(repoConfig)', () => { + // TODO + }); +}); diff --git a/test/workers/repository.spec.js b/test/workers/repository.spec.js index 2babfd8c83..b79da639bb 100644 --- a/test/workers/repository.spec.js +++ b/test/workers/repository.spec.js @@ -1,28 +1,33 @@ const repositoryWorker = require('../../lib/workers/repository'); -const branchWorker = require('../../lib/workers/branch'); +const logger = require('../_fixtures/logger'); -jest.mock('../../lib/workers/branch'); -jest.mock('../../lib/workers/pr'); -jest.mock('../../lib/api/npm'); -jest.mock('../../lib/api/github'); -jest.mock('../../lib/api/gitlab'); -jest.mock('../../lib/helpers/versions'); +repositoryWorker.initApis = jest.fn(input => input); +repositoryWorker.mergeRenovateJson = jest.fn(input => input); +repositoryWorker.getOnboardingStatus = jest.fn(() => true); +repositoryWorker.detectPackageFiles = jest.fn(input => input); -describe('repositoryWorker', () => { - describe('processUpgrades(upgrades)', () => { +describe('workers/repository', () => { + describe('processRepo', () => { + let config; beforeEach(() => { - repositoryWorker.updateBranch = jest.fn(); + config = { + logger, + packageFiles: [], + }; }); - it('handles zero upgrades', async () => { - // await repositoryWorker.processUpgrades([]); - expect(branchWorker.updateBranch.mock.calls.length).toBe(0); + it('returns early if repo is not onboarded', async () => { + repositoryWorker.getOnboardingStatus.mockReturnValueOnce(false); + await repositoryWorker.processRepo(config); }); - it('handles non-zero upgrades', async () => { - await repositoryWorker.processUpgrades([ - { branchName: 'a' }, - { branchName: 'b' }, - ]); - expect(branchWorker.updateBranch.mock.calls.length).toBe(2); + it('detects package files if none configured', async () => { + repositoryWorker.getOnboardingStatus.mockReturnValueOnce(true); + await repositoryWorker.processRepo(config); + }); + it('swallows errors', async () => { + repositoryWorker.initApis.mockImplementationOnce(() => { + throw new Error('bad init'); + }); + await repositoryWorker.processRepo(config); }); }); }); diff --git a/yarn.lock b/yarn.lock index a7d4a43e99..290e577391 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2593,10 +2593,14 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" -moment@2.17.1, moment@2.x.x, moment@^2.10.6: +moment@2.17.1: version "2.17.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" +moment@2.x.x, moment@^2.10.6: + version "2.18.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + ms@2.0.0, ms@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3058,8 +3062,8 @@ read-pkg@^2.0.0: path-type "^2.0.0" readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.2.tgz#5a04df05e4f57fe3f0dc68fdd11dc5c97c7e6f4d" + version "2.3.1" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.1.tgz#84e26965bb9e785535ed256e8d38e92c69f09d10" dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -3290,6 +3294,10 @@ safe-buffer@^5.0.1, safe-buffer@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" + safe-json-stringify@~1: version "1.0.4" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911" @@ -3307,8 +3315,8 @@ sane@~1.6.0: watch "~0.10.0" sax@^1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + version "1.2.2" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" semver-diff@^2.0.0: version "2.1.0" @@ -3480,10 +3488,10 @@ string-width@^2.0.0: strip-ansi "^3.0.0" string_decoder@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + version "1.0.2" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.2.tgz#b29e1f4e1125fa97a10382b8a533737b7491e179" dependencies: - safe-buffer "~5.1.0" + safe-buffer "~5.0.1" stringstream@~0.0.4: version "0.0.5" -- GitLab