From 768425f35e2296e3d70ce33b978d67be4cba4abf Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Thu, 17 Jan 2019 18:56:53 +0300 Subject: [PATCH 01/11] refactor entrypoint in purpose to be able to test release process --- .eslintrc.json | 5 +- README.md | 15 +- index.js | 417 +++++--------------------------------------- package-lock.json | 2 +- package.json | 3 +- src/release.js | 427 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 490 insertions(+), 379 deletions(-) mode change 100755 => 100644 index.js create mode 100644 src/release.js diff --git a/.eslintrc.json b/.eslintrc.json index 68eaced..6cfce3b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,5 +21,8 @@ "error", "always" ] + }, + "parserOptions": { + "ecmaVersion": 2017 } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 1ab0ee6..6f7f439 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This tool automate TAO extension release ## Installation -Please verify installation [prerequisite](#prerequisite). And run : +Please verify installation [prerequisite](#prerequisite). And run : ```sh npm i -g @oat-sa/tao-extension-release @@ -20,6 +20,15 @@ taoRelease and follow the instructions +## Commandline arguments + +Commandline arguments: + +`--base-branch` - branch to release from. 'develop' by default +`--branch-prefix` - releasing branch prefix. 'release' by default +`--origin` - git repository origin. 'origin' by default +`--release-branch` - branch to release to. 'master' by default +`--www-userz` - www user. 'www-data' by default ## Development @@ -40,7 +49,7 @@ npm link So the command `taoRelease` will use the sources. -Useful commands : +Useful commands : - `npm test` runs the test suite - `npm run test:cov` runs the test suite with code coverage @@ -76,7 +85,7 @@ You also need the `php` command available in your `PATH`. ### `Task foosass not found` -Everything looks ok but you don't know why the `grunt` task is not found. If you have updated `node` or `npm` recently, you can fix this by : +Everything looks ok but you don't know why the `grunt` task is not found. If you have updated `node` or `npm` recently, you can fix this by : ```sh cd tao/views/build diff --git a/index.js b/index.js old mode 100755 new mode 100644 index 3b11b11..4e09d92 --- a/index.js +++ b/index.js @@ -1,373 +1,44 @@ -#!/usr/bin/env node - -/** - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; under version 2 - * of the License (non-upgradable). - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * Copyright (c) 2017 Open Assessment Technologies SA; - */ - -/** - * CLI script entry point - * - * Long but linear process. - * - * @author Bertrand Chevrier - */ - -const path = require('path'); -const inquirer = require('inquirer'); -const opn = require('opn'); -const updateNotifier = require('update-notifier'); -const pkg = require('./package.json'); -const log = require('./src/log.js'); -const config = require('./src/config.js')(); -const gitClientFactory = require('./src/git.js'); -const github = require('./src/github.js'); -const taoInstanceFactory = require('./src/taoInstance.js'); - -const data = {}; - -//TODO CLI params -const origin = 'origin'; -const baseBranch = 'develop'; -const releaseBranch = 'master'; -const branchPrefix = 'release'; -const wwwUser = 'www-data'; - -var taoInstance; -var gitClient; -var githubClient; - - -log.title('TAO Extension Release'); - -// check for updates - -updateNotifier({pkg}).notify(); - - -// Load local config - -config.load() - .then( result => Object.assign(data, result) ) - - -// Github Token - .then( () => { - if (!data.token) { - - setTimeout(() => opn('https://github.com/settings/tokens'), 2000); - - return inquirer.prompt({ - type: 'input', - name: 'token', - message: 'I need a Github token, with "repo" rights (check your browser) : ', - validate : token => /[a-z0-9]{32,48}/i.test(token), - filter : token => token.trim() - }).then(result => { - if (result.token) { - data.token = result.token; - return config.write(data); - } else { - log.exit('No token, no script. Sorry.'); - } - }); - } - }) - - -// Select TAO instance - .then( () => inquirer.prompt({ - type: 'input', - name: 'taoRoot', - message: 'Path to the TAO instance : ', - default: data.taoRoot || process.cwd() - }) ) - .then( result => { - taoInstance = taoInstanceFactory(path.resolve(result.taoRoot), false, wwwUser); - return taoInstance.isRoot(); - }) - .then( result => { - if (!result.dir) { - log.exit(`${result.dir} is not a TAO instance`); - } - data.taoRoot = result.dir; - }) - .then( () => taoInstance.isInstalled() ) - .then( result => { - if (!result) { - log.exit('It looks like the given TAO instance is not installed.'); - } - }) - -// Select the extension to release - .then( () => taoInstance.getExtensions()) - .then( extensions => inquirer.prompt({ - type: 'list', - name: 'extension', - message: 'Which extension you want to release ? ', - pageSize: 12, - choices: extensions, - default : data.extension && data.extension.name - }) ) - .then( result => { - if (!result.extension) { - log.exit('No extension.'); - } - data.extension = { - name: result.extension, - path: `${data.taoRoot}/${result.extension}`, - }; - - gitClient = gitClientFactory(data.extension.path, origin); - - return config.write(data); - }) - - -// Verify local changes and current branch - .then( () => log.doing('Checking extension status')) - .then( () => gitClient.hasLocalChanges() ) - .then( result => { - if (result) { - log.exit(`The extension ${data.extension.name} has local changes, please clean or stash them before releasing`); - } - }) - .then( () => inquirer.prompt({ - type: 'confirm', - name: 'pull', - message: `Can I checkout and pull ${baseBranch} and ${releaseBranch} ?` - }) ) - .then( result => { - if (!result.pull) { - log.exit(); - } - - log.done(`${data.extension.name} is clean`); - }) - - -// Sign tags (todo, not yet implemented) - .then( () => gitClient.hasSignKey() ) - .then( result => data.signtags = result) - - -// Fetch and pull branches, extract manifests and repo name - .then( () => log.doing(`Updating ${data.extension.name}`) ) - - .then( () => gitClient.pull(releaseBranch) ) - .then( () => taoInstance.parseManifest(`${data.extension.path}/manifest.php`) ) - .then( manifest => { - data.lastVersion = manifest.version; - data.lastTag = `v${manifest.version}`; - }) - - .then( () => gitClient.pull(baseBranch) ) - .then( () => taoInstance.parseManifest(`${data.extension.path}/manifest.php`) ) - .then( manifest => { - data.extension.manifest = manifest; - data.version = manifest.version; - data.tag = `v${manifest.version}`; - data.releasingBranch = `${branchPrefix}-${manifest.version}`; - }) - - .then( () => taoInstance.getRepoName(data.extension.name) ) - .then( result => { - if(result){ - githubClient = github(data.token, result); - } else { - log.exit('Unable to find the gitbuh repository name'); - } - }) - - -//Release exists ? - .then( () => log.doing(`Check if tag ${data.tag} exists`)) - .then( () => gitClient.hasTag(data.tag)) - .then( result => { - if (result) { - log.exit(`The tag ${data.tag} already exists`); - } - }) - .then( () => log.done() ) - - -// Needs a release (diff) ? - .then( () => log.doing(`Diff ${baseBranch}..${releaseBranch}`) ) - .then( () => gitClient.hasDiff(baseBranch, releaseBranch) ) - .then( result => { - if (!result) { - return inquirer.prompt({ - type: 'confirm', - name: 'diff', - message: `It seems there is no changes between ${baseBranch} and ${releaseBranch}'. Do you want to rrelease anyway ?` - }).then(result => { - if (!result.diff) { - log.exit(); - } - }); - } else { - log.done(); - } - }) - - -// Last confirmation - .then( () => inquirer.prompt({ - type: 'confirm', - name: 'go', - message: `Let's release version ${data.extension.name}@${data.version} 🚀 ?` - }) ) - .then( result => { - if (!result.go) { - log.exit(); - } - }) - - -// Create the release branch - .then( () => log.doing('Create release branch') ) - .then( () => gitClient.localBranch(data.releasingBranch) ) - .then( () => log.done(`${branchPrefix}-${data.version} created`) ) - - -// Compile assets - .then( () => log.doing('Bundling') ) - .then( () => log.info('Asset build started, this may take a while') ) - .then( () => { - return taoInstance.buildAssets(data.extension.name, false) - .catch( err => log.error(`Unable to bundle assets. ${err.message}. Continue.`) ); - }) - .then( () => { - return gitClient.commitAndPush(data.releasingBranch, 'bundle assets').catch( err => log.error(`Unable to bundle assets. ${err.message}. Continue.`) ); - }) - .then( changes => { - if(changes && changes.length){ - log.info(`Commit : [bundle assets - ${changes.length} files]`); - changes.forEach( file => log.info(` - ${file}`) ); - } - }) - .then( () => log.done() ) - - -// Update translations - .then( () => log.doing('Translations') ) - .then( () => log.warn('Update translations during a release only if you know what you are doing') ) - .then( () => inquirer.prompt({ - type: 'confirm', - name: 'translation', - message: `${data.extension.name} needs updated translations ? `, - default : false - }) ) - .then( result => { - if(result.translation){ - return taoInstance.updateTranslations(data.extension.name) - .catch( err => log.error(`Unable to update translations. ${err.message}. Continue.`)) - .then(() => gitClient.commitAndPush(data.releasingBranch, 'update translations')) - .then( changes => { - if(changes && changes.length){ - log.info(`Commit : [update translations - ${changes.length} files]`); - changes.forEach( file => log.info(` - ${file}`) ); - } - }); - } - }) - - -// Create PR - .then( () => log.doing('Create the pull request') ) - .then( () => githubClient.createReleasePR(data.releasingBranch, releaseBranch, data.version, data.lastVersion) ) - .then( result => { - if(result && result.state === 'open'){ - data.pr = { - url : result.html_url, - apiUrl : result.url, - number : result.number, - id : result.id - }; - log.info(`${data.pr.url} created`); - log.done(); - } else { - log.exit('Unable to create the release pull request'); - } - }) - -// Extract release notes - .then( () => log.doing('Extract release notes') ) - .then( () => githubClient.extractReleaseNotesFromReleasePR(data.pr.number) ) - .then( result => { - if(result){ - data.pr.notes = result; - console.log(data.pr.notes ); - log.done(); - } else { - log.exit('Unable to create the release notes'); - } - }) - -// Merge PR - .then( () => setTimeout(() => opn(data.pr.url), 2000) ) - .then( () => inquirer.prompt({ - type: 'confirm', - name: 'pr', - message: 'Please review the release PR (you can make the last changes now). Can I merge it now ?', - })) - .then( result => { - if (!result.pr) { - log.exit(); - } - }) - .then( () => log.doing('Merging the pull request') ) - .then( () => gitClient.mergePr(releaseBranch, data.releasingBranch) ) - .then( () => log.done('PR merged') ) - - -// Create and push the tag - .then( () => log.doing(`Add and push tag ${data.tag}`) ) - .then( () => gitClient.tag(releaseBranch, data.tag, `version ${data.version}`) ) - .then( () => log.done() ) - - -// GH release - .then( () => log.doing(`Creating github release ${data.version}`) ) - .then( () => inquirer.prompt({ - type: 'input', - name: 'comment', - message: 'Any comment on the release ?', - }) ) - .then( result => { - const releaseComment = `${result.comment}\n\n**Release notes :**\n${data.pr.notes}`; - return githubClient.release(data.tag, releaseComment); - }) - .then( () => log.done() ) - - -// Merge Back - .then( () => log.doing('Merging back master into develop') ) - .then( () => gitClient.mergeBack(baseBranch, releaseBranch) ) - .then( () => log.done()) - - -// Clean up - .then( () => log.doing('Clean up the place') ) - .then( () => gitClient.deleteBranch(data.releasingBranch) ) - .then( () => log.done()) - -// End - .then( () => log.done('Good job!') ) - - -// Errors - .catch(err => log.error(err) ); +const log = require('./src/log.js'); + +const argv = require('minimist')(process.argv.slice(2)); + +const baseBranch = argv['base-branch'] || 'develop'; +const branchPrefix = argv['branch-prefix'] || 'release'; +const origin = argv['origin'] || 'origin'; +const releaseBranch = argv['release-branch'] || 'master'; +const wwwUser = argv['www-user'] || 'www-data'; + +const release = require('./src/release')(baseBranch, branchPrefix, origin, releaseBranch, wwwUser); + +async function releaseExtension() { + try { + log.title('TAO Extension Release'); + + await release.loadConfig(); + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyLocalChanges(); + await release.signTags(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.isReleaseExists(); + await release.isReleaseRequired(); + await release.confirmRelease(); + await release.createReleasingBranch(); + await release.compileAssets(); + await release.updateTranslations(); + await release.createPullRequest(); + await release.extractReleaseNotes(); + await release.mergePullRequest(); + await release.createReleaseTag(); + await release.createGithubRelease(); + await release.mergeBack(); + await release.removeReleasingBranch(); + + log.done('Good job!'); + } catch (error) { + log.error(error); + } +} + +releaseExtension(); diff --git a/package-lock.json b/package-lock.json index add323f..0fd9fb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-extension-release", - "version": "0.2.1", + "version": "0.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 54a87e4..f3d23b6 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "cross-spawn": "^6.0.3", "fs-extra": "^5.0.0", "inquirer": "^3.3.0", + "minimist": "^1.2.0", "octonode": "^0.9.4", "opn": "^5.1.0", "php-parser": "^2.0.7", @@ -40,7 +41,7 @@ "update-notifier": "^2.3.0" }, "engine": { - "node": ">=6.5.0" + "node": ">=8.6.0" }, "devDependencies": { "eslint": "^4.18.2", diff --git a/src/release.js b/src/release.js new file mode 100644 index 0000000..bb4f9c0 --- /dev/null +++ b/src/release.js @@ -0,0 +1,427 @@ +/** + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; under version 2 + * of the License (non-upgradable). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2019 Open Assessment Technologies SA; + */ + +/** + * This module contains methods to release a TAO extension. + * + * @author Anton Tsymuk + */ + +const inquirer = require('inquirer'); +const opn = require('opn'); +const path = require('path'); + +const config = require('./config.js')(); +const gitClientFactory = require('./git.js'); +const github = require('./github.js'); +const log = require('./log.js'); +const taoInstanceFactory = require('./taoInstance.js'); + +/** + * Get the taoExtensionRelease + * + * @param {String} baseBranch - branch to release from + * @param {String} branchPrefix - releasing branch prefix + * @param {String} origin - git repository origin + * @param {String} releaseBranch - branch to release to + * @param {String} wwwUser - name of the www user + * @return {Object} - instance of taoExtensionRelease + */ +module.exports = function taoExtensionReleaseFactory(baseBranch, branchPrefix, origin, releaseBranch, wwwUser) { + let data = {}; + let gitClient; + let githubClient; + let taoInstance; + + return { + /** + * Compile and publish extension assets + */ + async compileAssets() { + log.doing('Bundling'); + log.info('Asset build started, this may take a while'); + + try { + await taoInstance.buildAssets(data.extension.name, false); + + const changes = await gitClient.commitAndPush(data.releasingBranch, 'bundle assets'); + + if (changes && changes.length) { + log.info(`Commit : [bundle assets - ${changes.length} files]`); + changes.forEach(file => log.info(` - ${file}`)); + } + } catch (error) { + log.error(`Unable to bundle assets. ${error.message}. Continue.`); + } + + log.done(); + }, + + /** + * Prompt user to confrim release + */ + async confirmRelease() { + const { go } = await inquirer.prompt({ + type: 'confirm', + name: 'go', + message: `Let's release version ${data.extension.name}@${data.version} 🚀 ?` + }); + + if (!go) { + log.exit(); + } + }, + + /** + * Create release on github + */ + async createGithubRelease() { + log.doing(`Creating github release ${data.version}`); + + const { comment } = await inquirer.prompt({ + type: 'input', + name: 'comment', + message: 'Any comment on the release ?', + }); + + const releaseComment = `${comment}\n\n**Release notes :**\n${data.pr.notes}`; + + await githubClient.release(data.tag, releaseComment); + + log.done(); + }, + + /** + * Create relase pull request from releasing branch + */ + async createPullRequest() { + log.doing('Create the pull request'); + + const pullRequest = await githubClient.createReleasePR( + data.releasingBranch, + releaseBranch, + data.version, + data.lastVersion + ); + + if (pullRequest && pullRequest.state === 'open') { + data.pr = { + url: pullRequest.html_url, + apiUrl: pullRequest.url, + number: pullRequest.number, + id: pullRequest.id + }; + + log.info(`${data.pr.url} created`); + log.done(); + } else { + log.exit('Unable to create the release pull request'); + } + }, + + /** + * Create and publish release tag + */ + async createReleaseTag() { + log.doing(`Add and push tag ${data.tag}`); + + await gitClient.tag(releaseBranch, data.tag, `version ${data.version}`); + + log.done(); + }, + + /** + * Create releasing branch + */ + async createReleasingBranch() { + log.doing('Create release branch'); + + await gitClient.localBranch(data.releasingBranch); + + log.done(`${data.releasingBranch} created`); + }, + + /** + * Extract release notes from release pull request + */ + async extractReleaseNotes() { + log.doing('Extract release notes'); + + const releaseNotes = await githubClient + .extractReleaseNotesFromReleasePR(data.pr.number); + + if (releaseNotes) { + data.pr.notes = releaseNotes; + + log.info(data.pr.notes); + log.done(); + } else { + log.exit('Unable to create the release notes'); + } + }, + + /** + * Initialise github client for the extension to release repository + */ + async initialiseGithubClient() { + const repoName = await taoInstance.getRepoName(data.extension.name); + + if (repoName) { + githubClient = github(data.token, repoName); + } else { + log.exit('Unable to find the gitbuh repository name'); + } + }, + + /** + * Check if release exists + */ + async isReleaseExists() { + log.doing(`Check if tag ${data.tag} exists`); + + if (await gitClient.hasTag(data.tag)) { + log.exit(`The tag ${data.tag} already exists`); + } + + log.done(); + }, + + /** + * Check if there is any diffs between base and release branches and prompt to confirm release user if there is no diffs + */ + async isReleaseRequired() { + log.doing(`Diff ${baseBranch}..${releaseBranch}`); + const hasDiff = await gitClient.hasDiff(baseBranch, releaseBranch); + if (!hasDiff) { + const { diff } = await inquirer.prompt({ + type: 'confirm', + name: 'diff', + message: `It seems there is no changes between ${baseBranch} and ${releaseBranch}. Do you want to release anyway?` + }); + + if (!diff) { + log.exit(); + } + } + + log.done(); + }, + + /** + * Load and initialise release extension config + */ + async loadConfig() { + data = Object.assign({}, await config.load()); + + // Request github token if necessary + if (!data.token) { + setTimeout(() => opn('https://github.com/settings/tokens'), 2000); + + const { token } = await inquirer.prompt({ + type: 'input', + name: 'token', + message: 'I need a Github token, with "repo" rights (check your browser) : ', + validate: token => /[a-z0-9]{32,48}/i.test(token), + filter: token => token.trim() + }); + + data.token = token; + + await config.write(data); + } + }, + + /** + * Merge release branch back into base branch + */ + async mergeBack() { + log.doing(`Merging back ${releaseBranch} into ${baseBranch}`); + + await gitClient.mergeBack(baseBranch, releaseBranch); + + log.done(); + }, + + /** + * Merge release pull request + */ + async mergePullRequest() { + setTimeout(() => opn(data.pr.url), 2000); + + const { pr } = await inquirer.prompt({ + type: 'confirm', + name: 'pr', + message: 'Please review the release PR (you can make the last changes now). Can I merge it now ?', + }); + + if (!pr) { + log.exit(); + } + + log.doing('Merging the pull request'); + + await gitClient.mergePr(releaseBranch, data.releasingBranch); + + log.done('PR merged'); + }, + + /** + * Remove releasing branch + */ + async removeReleasingBranch() { + log.doing('Clean up the place'); + + await gitClient.deleteBranch(data.releasingBranch); + + log.done(); + }, + + /** + * Select and initialise the extension to release + */ + async selectExtension() { + const availableExtensions = await taoInstance.getExtensions(); + + const { extension } = await inquirer.prompt({ + type: 'list', + name: 'extension', + message: 'Which extension you want to release ? ', + pageSize: 12, + choices: availableExtensions, + default: data.extension && data.extension.name, + }); + + gitClient = gitClientFactory(`${data.taoRoot}/${extension}`, origin, extension); + + data.extension = { + name: extension, + path: `${data.taoRoot}/${extension}`, + }; + + await config.write(data); + }, + + /** + * Select and initialise tao instance + */ + async selectTaoInstance() { + const { taoRoot } = await inquirer.prompt({ + type: 'input', + name: 'taoRoot', + message: 'Path to the TAO instance : ', + default: data.taoRoot || process.cwd() + }); + + taoInstance = taoInstanceFactory(path.resolve(taoRoot), false, wwwUser); + + const { dir, root } = await taoInstance.isRoot(); + + if (!root) { + log.exit(`${dir} is not a TAO instance`); + } + + if (!await taoInstance.isInstalled()) { + log.exit('It looks like the given TAO instance is not installed.'); + } + + data.taoRoot = dir; + }, + + /** + * Sign tags (todo, not yet implemented) + */ + async signTags() { + data.signtags = await gitClient.hasSignKey(); + }, + + /** + * Update and publish translations + */ + async updateTranslations() { + log.doing('Translations'); + log.warn('Update translations during a release only if you know what you are doing'); + + const { translation } = await inquirer.prompt({ + type: 'confirm', + name: 'translation', + message: `${data.extension.name} needs updated translations ? `, + default: false + }); + + if (translation) { + try { + await taoInstance.updateTranslations(data.extension.name); + + const changes = await gitClient.commitAndPush(data.releasingBranch, 'update translations'); + + if (changes && changes.length) { + log.info(`Commit : [update translations - ${changes.length} files]`); + changes.forEach(file => log.info(` - ${file}`)); + } + } catch (error) { + log.error(`Unable to update translations. ${error.message}. Continue.`); + } + } + }, + + /** + * Fetch and pull branches, extract manifests and repo name + */ + async verifyBranches() { + const { pull } = await inquirer.prompt({ + type: 'confirm', + name: 'pull', + message: `Can I checkout and pull ${baseBranch} and ${releaseBranch} ?` + }); + + if (!pull) { + log.exit(); + } + + log.doing(`Updating ${data.extension.name}`); + + await gitClient.pull(releaseBranch); + + const { version: lastVersion } = await taoInstance.parseManifest(`${data.extension.path}/manifest.php`); + data.lastVersion = lastVersion; + data.lastTag = `v${lastVersion}`; + + await gitClient.pull(baseBranch); + + const manifest = await taoInstance.parseManifest(`${data.extension.path}/manifest.php`); + + data.extension = manifest; + data.version = manifest.version; + data.tag = `v${manifest.version}`; + data.releasingBranch = `${branchPrefix}-${manifest.version}`; + }, + + /** + * Verify if local branch has no uncommied changes + */ + async verifyLocalChanges() { + log.doing('Checking extension status'); + + if (await gitClient.hasLocalChanges()) { + log.exit(`The extension ${data.extension.name} has local changes, please clean or stash them before releasing`); + } + + log.done(`${data.extension.name} is clean`); + }, + }; +}; From 8eb6f9eb1f508032bd9d610586ccaabb37b678e4 Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Fri, 18 Jan 2019 17:04:33 +0300 Subject: [PATCH 02/11] request pr commits using github v4 api; limit commits by 28 in query for issues search --- README.md | 10 +- package-lock.json | 27 +++++ package.json | 1 + src/github.js | 284 ++++++++++++++++++++++++++-------------------- 4 files changed, 192 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index 6f7f439..02229ec 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ and follow the instructions Commandline arguments: -`--base-branch` - branch to release from. 'develop' by default -`--branch-prefix` - releasing branch prefix. 'release' by default -`--origin` - git repository origin. 'origin' by default -`--release-branch` - branch to release to. 'master' by default -`--www-userz` - www user. 'www-data' by default + - `--base-branch` - branch to release from. 'develop' by default + - `--branch-prefix` - releasing branch prefix. 'release' by default + - `--origin` - git repository origin. 'origin' by default + - `--release-branch` - branch to release to. 'master' by default + - `--www-userz` - www user. 'www-data' by default ## Development diff --git a/package-lock.json b/package-lock.json index 0fd9fb2..95121cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -609,6 +609,15 @@ "capture-stack-trace": "^1.0.0" } }, + "cross-fetch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.2.tgz", + "integrity": "sha1-pH/09/xxLauo9qaVoRyUhEDUVyM=", + "requires": { + "node-fetch": "2.1.2", + "whatwg-fetch": "2.0.4" + } + }, "cross-spawn": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.4.tgz", @@ -1879,6 +1888,14 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, + "graphql-request": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.8.2.tgz", + "integrity": "sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg==", + "requires": { + "cross-fetch": "2.2.2" + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -2637,6 +2654,11 @@ } } }, + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" + }, "nodemon": { "version": "1.18.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.7.tgz", @@ -6688,6 +6710,11 @@ "extsprintf": "^1.2.0" } }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + }, "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", diff --git a/package.json b/package.json index f3d23b6..00b9948 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "commander": "^2.11.0", "cross-spawn": "^6.0.3", "fs-extra": "^5.0.0", + "graphql-request": "^1.8.2", "inquirer": "^3.3.0", "minimist": "^1.2.0", "octonode": "^0.9.4", diff --git a/src/github.js b/src/github.js index 23e0a0c..5cfc0d5 100644 --- a/src/github.js +++ b/src/github.js @@ -22,6 +22,9 @@ * @author Bertrand Chevrier */ +const util = require('util'); +const { GraphQLClient } = require('graphql-request'); + const validate = require('./validate.js'); /** @@ -39,13 +42,21 @@ module.exports = function githubFactory(token, repository) { const client = require('octonode').client(token); const ghrepo = client.repo(repository); + const graphQLClient = new GraphQLClient( + 'https://api.github.com/graphql', + { + headers: { + Authorization: `token ${token}` + } + } + ); /** * Add the checks to display in a release PR for a given repository */ const extensionPRChecks = { - 'oat-sa/tao-core' : 'Increase TAO-VERSION in `manifest.php`', - 'oat-sa/extension-tao-delivery-rdf' : 'Do not forget to update the dependency and release oat-sa/extension-tao-community' + 'oat-sa/tao-core': 'Increase TAO-VERSION in `manifest.php`', + 'oat-sa/extension-tao-delivery-rdf': 'Do not forget to update the dependency and release oat-sa/extension-tao-community' }; /** @@ -62,17 +73,17 @@ module.exports = function githubFactory(token, repository) { * @returns {Promise} resolves with the pull request data */ createReleasePR(releasingBranch, releaseBranch, version = '?.?.?', fromVersion = '?.?.?') { - if(!releasingBranch || !releaseBranch) { + if (!releasingBranch || !releaseBranch) { return Promise.reject(new TypeError('Unable to create a release pull request when the branches are not defined')); } - return new Promise( (resolve, reject) => { + return new Promise((resolve, reject) => { ghrepo.pr({ title: `Release ${version}`, - body : this.getReleasePRComment(version, fromVersion), + body: this.getReleasePRComment(version, fromVersion), head: releasingBranch, base: releaseBranch - }, (err, data) => { - if(err){ + }, (err, data) => { + if (err) { return reject(err); } return resolve(data); @@ -93,10 +104,10 @@ module.exports = function githubFactory(token, repository) { 'CSS and JavaScript bundles' ]; - if(typeof extensionPRChecks[repository] !== 'undefined'){ + if (typeof extensionPRChecks[repository] !== 'undefined') { checks.push(extensionPRChecks[repository]); } - return `Please verify the following points :\n${checks.map( c => '\n- [ ] ' + c)}`; + return `Please verify the following points :\n${checks.map(c => '\n- [ ] ' + c)}`; }, /** @@ -105,28 +116,28 @@ module.exports = function githubFactory(token, repository) { * @param {Boolean} [forceMerge = false] - do we merge the PR if not yet done ? * @returns {Promise} */ - closePR(prNumber, forceMerge = false){ - return new Promise( (resolve, reject) => { + closePR(prNumber, forceMerge = false) { + return new Promise((resolve, reject) => { validate.prNumber(prNumber); const ghpr = client.pr(repository, prNumber); const doClose = () => { - ghpr.close( closeErr => { - if(closeErr){ + ghpr.close(closeErr => { + if (closeErr) { return reject(closeErr); } return resolve(true); }); }; - ghpr.merged( (err, merged) => { - if(err){ + ghpr.merged((err, merged) => { + if (err) { return reject(err); } - if(!merged){ - if(forceMerge){ + if (!merged) { + if (forceMerge) { return ghpr.merge('Forced merged', mergeErr => { - if(mergeErr){ + if (mergeErr) { return reject(mergeErr); } return doClose(); @@ -146,14 +157,14 @@ module.exports = function githubFactory(token, repository) { * @param {String} [comment] - comment the release * @returns {Promise} */ - release(tag, comment = ''){ - return new Promise( (resolve, reject) => { + release(tag, comment = '') { + return new Promise((resolve, reject) => { ghrepo.release({ - tag_name : tag, - name : tag, - body : comment + tag_name: tag, + name: tag, + body: comment }, (err, released) => { - if(err){ + if (err) { return reject(err); } return resolve(released); @@ -166,20 +177,54 @@ module.exports = function githubFactory(token, repository) { * @param {String|Number} prNumber - the pull request number * @returns {Promise} resolves with the list of SHAs */ - getPRCommitShas(prNumber){ - return new Promise( (resolve, reject) => { + async getPRCommitShas(prNumber) { + const commits = []; + const [owner, name] = repository.split('/'); + + let hasNextPage = true; + let nextPageCursor = ''; + + while (hasNextPage) { + const query = ` + { + repository(owner: "${owner}", name: "${name}") { + pullRequest(number: ${prNumber}) { + commits(first: 250, after: "${nextPageCursor}") { + pageInfo { + endCursor, + hasNextPage, + } + nodes { + commit { + oid + } + } + } + } + } + } + `; + + const { + repository: { + pullRequest: { + commits: { + nodes, + pageInfo + } + } + } + } = await graphQLClient.request(query); - validate.prNumber(prNumber); + commits.push(...(await nodes) + .map(({ commit: { oid } }) => oid.slice(0, 8)) + ); - client - .pr(repository, prNumber) - .commits( (err, commits) => { - if(err){ - return reject(err); - } - return resolve(commits.map( commit => commit.sha.slice(0, 8) )); - }); - }); + hasNextPage = pageInfo.hasNextPage; + nextPageCursor = pageInfo.endCursor; + } + + return commits; }, /** @@ -190,19 +235,17 @@ module.exports = function githubFactory(token, repository) { * @param {String} [searchOptions.order = asc] - the sort order * @returns {Promise} resolves with the list issues */ - searchIssues(searchOptions = {q : '', sort : 'created', order : 'asc'}){ - return new Promise( (resolve, reject) => { - const ghsearch = client.search(); - ghsearch.issues(searchOptions, (searchErr, results) => { - if(searchErr){ - return reject(searchErr); - } - if(results && results.items && results.items.length){ - return resolve(results.items); - } - return resolve([]); - }); - }); + async searchIssues(searchOptions = { q: '', sort: 'created', order: 'asc' }) { + const ghsearch = client.search(); + const promisifiedIssuesSearch = util.promisify(ghsearch.issues); + + const issues = await promisifiedIssuesSearch.call(ghsearch, searchOptions); + + if (issues && issues.items && issues.items.length) { + return issues.items; + } + + return []; }, /** @@ -210,15 +253,15 @@ module.exports = function githubFactory(token, repository) { * @param {String|Number} prNumber - the pull request number * @returns {Promise} resolves with the pull request data */ - getPRData(prNumber){ - return new Promise( (resolve, reject) => { + getPRData(prNumber) { + return new Promise((resolve, reject) => { validate.prNumber(prNumber); client .pr(repository, prNumber) - .info( (err, data) => { - if(err){ + .info((err, data) => { + if (err) { return reject(err); } return resolve(data); @@ -239,31 +282,31 @@ module.exports = function githubFactory(token, repository) { * @param {String} [noteData.branch] - the name of the merged branch * @returns {String} the release note description */ - formatReleaseNote(noteData){ - const note = []; - const typeExp = /(fix|feature|breaking)/i; + formatReleaseNote(noteData) { + const note = []; + const typeExp = /(fix|feature|breaking)/i; const jiraIdExp = /[A-Z]{2,6}[- ]{1}[0-9]{1,6}/i; //internal extraction helper const extract = (string = '', exp) => { let match = string.match(exp); - if(match !== null && match.index > -1){ + if (match !== null && match.index > -1) { return match[0]; } return false; }; //extract the type of change - const extractType = () => { + const extractType = () => { var type; - if(noteData.branch){ + if (noteData.branch) { type = extract(noteData.branch, typeExp); } - if(!type && noteData.title){ + if (!type && noteData.title) { type = extract(noteData.title, typeExp); } - if(type){ + if (type) { type = type.trim(); type = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); } @@ -274,16 +317,16 @@ module.exports = function githubFactory(token, repository) { const extractJiraId = () => { var jiraId; - if(noteData.branch){ + if (noteData.branch) { jiraId = extract(noteData.branch, jiraIdExp); } - if(!jiraId && noteData.title){ + if (!jiraId && noteData.title) { jiraId = extract(noteData.title, jiraIdExp); } - if(!jiraId && noteData.body){ + if (!jiraId && noteData.body) { jiraId = extract(noteData.body, jiraIdExp); } - if(jiraId){ + if (jiraId) { jiraId = jiraId .trim() .replace(/\s/, '-') @@ -292,20 +335,20 @@ module.exports = function githubFactory(token, repository) { return jiraId; }; - if(noteData){ + if (noteData) { const type = extractType(); const jiraId = extractJiraId(); - if(type){ + if (type) { note.push(`_${type}_`); } - if(jiraId){ + if (jiraId) { note.push(`[${jiraId}](https://oat-sa.atlassian.net/browse/${jiraId})`); } - if(note.length){ + if (note.length) { note.push(':'); } - if(noteData.title){ + if (noteData.title) { note.push( noteData.title .replace(typeExp, '') @@ -315,11 +358,11 @@ module.exports = function githubFactory(token, repository) { .trim() ); } - if(noteData.number && noteData.url){ + if (noteData.number && noteData.url) { note.push(`[#${noteData.number}](${noteData.url})`); } - if(noteData.user && noteData.assignee){ + if (noteData.user && noteData.assignee) { note.push('('); note.push(`by [${noteData.user}](https://github.com/${noteData.user})`); note.push('-'); @@ -339,61 +382,52 @@ module.exports = function githubFactory(token, repository) { * @param {String|Number} prNumber - the number of the release pull request * @returns {Promise} resolves with the release note description */ - extractReleaseNotesFromReleasePR(prNumber) { - - //1. Get all commits from the release PR - return this.getPRCommitShas(prNumber) - .then( commits => { - if(commits && commits.length){ - // we filter out PR inside those commits - // (github considers pr as issues) - return this.searchIssues({ - q : `${commits.join('+')}+repo:${repository}+type:pr+base:develop+is:merged`, - sort: 'closed', - order: 'asc' - }); - } - }) - .then( issues => { - if(issues && issues.length) { - //we load the full description from all of them (for the head branch mostly) - return Promise.all( - issues.map( issue => this.getPRData(issue.number) ) - ); - } - return []; - }) - .then( mergedPrResults => { - //extract only useful data - if(mergedPrResults && mergedPrResults.length){ - return mergedPrResults.map( result => { - return { - title : result.title, - number : result.number, - url : result.html_url, - user : result.user && result.user.login, - assignee : result.assignee && result.assignee.login, - commit: result.merge_commit_sha, - body : result.body, - branch : result.head && result.head.ref - }; - }); - } - return []; + async extractReleaseNotesFromReleasePR(prNumber) { + const commits = await this.getPRCommitShas(prNumber) || []; + + const issues = []; + while (commits.length) { + issues.push(...( + await this.searchIssues({ + q: `${commits.splice(0, 28).join('+')}+repo:${repository}+type:pr+base:develop+is:merged`, + sort: 'closed', + order: 'asc' + }) + )); + } + + // Remove dublicates + const uniqIssue = issues.filter((issue, index, self) => + index === self.findIndex((d) => ( + d.id === issue.id + )) + ); + + const issuesDetails = []; + + for (const issue of uniqIssue) { + issuesDetails.push(await this.getPRData(issue.number)); + } + + return issuesDetails + .map(result => { + return this.formatReleaseNote({ + title: result.title, + number: result.number, + url: result.html_url, + user: result.user && result.user.login, + assignee: result.assignee && result.assignee.login, + commit: result.merge_commit_sha, + body: result.body, + branch: result.head && result.head.ref + }); }) - .then( notesData => { - //extract the IDs and format the notes - if(notesData && notesData.length){ - return notesData - .map( noteData => this.formatReleaseNote(noteData) ) - .reduce( (acc, note) => { - if(note){ - acc += `- ${note}\n`; - } - return acc; - }, ''); + .reduce((acc, note) => { + if (note) { + acc += `- ${note}\n`; } - }); + return acc; + }, ''); } }; }; From 60c8921ccedf7dc6216d64271b0191c95e9ed081 Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Mon, 21 Jan 2019 16:23:36 +0300 Subject: [PATCH 03/11] introduce githubApiClientFactory to work with github v4 api; create a method to search pull request --- .eslintrc.json | 2 +- src/github.js | 125 +++++++---------------------------------- src/githubApiClient.js | 114 +++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 106 deletions(-) create mode 100644 src/githubApiClient.js diff --git a/.eslintrc.json b/.eslintrc.json index 6cfce3b..853e2cc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,6 +23,6 @@ ] }, "parserOptions": { - "ecmaVersion": 2017 + "ecmaVersion": 2018 } } diff --git a/src/github.js b/src/github.js index 5cfc0d5..fc9485a 100644 --- a/src/github.js +++ b/src/github.js @@ -23,8 +23,8 @@ */ const util = require('util'); -const { GraphQLClient } = require('graphql-request'); +const githubApiClientFactory = require('./githubApiClient'); const validate = require('./validate.js'); /** @@ -42,14 +42,7 @@ module.exports = function githubFactory(token, repository) { const client = require('octonode').client(token); const ghrepo = client.repo(repository); - const graphQLClient = new GraphQLClient( - 'https://api.github.com/graphql', - { - headers: { - Authorization: `token ${token}` - } - } - ); + const githubApiClient = githubApiClientFactory(token); /** * Add the checks to display in a release PR for a given repository @@ -185,26 +178,6 @@ module.exports = function githubFactory(token, repository) { let nextPageCursor = ''; while (hasNextPage) { - const query = ` - { - repository(owner: "${owner}", name: "${name}") { - pullRequest(number: ${prNumber}) { - commits(first: 250, after: "${nextPageCursor}") { - pageInfo { - endCursor, - hasNextPage, - } - nodes { - commit { - oid - } - } - } - } - } - } - `; - const { repository: { pullRequest: { @@ -214,9 +187,9 @@ module.exports = function githubFactory(token, repository) { } } } - } = await graphQLClient.request(query); + } = await githubApiClient.getPRCommits(prNumber, name, owner, nextPageCursor); - commits.push(...(await nodes) + commits.push(...nodes .map(({ commit: { oid } }) => oid.slice(0, 8)) ); @@ -227,48 +200,6 @@ module.exports = function githubFactory(token, repository) { return commits; }, - /** - * Search issues - * @param {Object} [searchOptions] - search parameters - * @param {String} [searchOptions.q] - the github search query - * @param {String} [searchOptions.sort = created] - how to sort the issues - * @param {String} [searchOptions.order = asc] - the sort order - * @returns {Promise} resolves with the list issues - */ - async searchIssues(searchOptions = { q: '', sort: 'created', order: 'asc' }) { - const ghsearch = client.search(); - const promisifiedIssuesSearch = util.promisify(ghsearch.issues); - - const issues = await promisifiedIssuesSearch.call(ghsearch, searchOptions); - - if (issues && issues.items && issues.items.length) { - return issues.items; - } - - return []; - }, - - /** - * Load all info about a Pull Request - * @param {String|Number} prNumber - the pull request number - * @returns {Promise} resolves with the pull request data - */ - getPRData(prNumber) { - return new Promise((resolve, reject) => { - - validate.prNumber(prNumber); - - client - .pr(repository, prNumber) - .info((err, data) => { - if (err) { - return reject(err); - } - return resolve(data); - }); - }); - }, - /** * Format a release note string from release note data * @param {Object} noteData - the @@ -387,47 +318,31 @@ module.exports = function githubFactory(token, repository) { const issues = []; while (commits.length) { - issues.push(...( - await this.searchIssues({ - q: `${commits.splice(0, 28).join('+')}+repo:${repository}+type:pr+base:develop+is:merged`, - sort: 'closed', - order: 'asc' - }) - )); + issues.push(...(await githubApiClient.searchPullRequests( + `${commits.splice(0, 28).join(' ')} repo:${repository} type:pr base:develop is:merged`, + )).search.nodes); } // Remove dublicates const uniqIssue = issues.filter((issue, index, self) => index === self.findIndex((d) => ( - d.id === issue.id + d.number === issue.number )) ); - const issuesDetails = []; - - for (const issue of uniqIssue) { - issuesDetails.push(await this.getPRData(issue.number)); - } - - return issuesDetails - .map(result => { - return this.formatReleaseNote({ - title: result.title, - number: result.number, - url: result.html_url, - user: result.user && result.user.login, - assignee: result.assignee && result.assignee.login, - commit: result.merge_commit_sha, - body: result.body, - branch: result.head && result.head.ref - }); + return uniqIssue + .map(issue => ({ + ...issue, + assignee: issue.assignee.nodes[0].login, + commit: issue.commit.oid, + user: issue.user.login, + closedAt: new Date(issue.closedAt).getTime(), + })) + .sort(({ closedAt: a }, { closedAt: b }) => { + return a - b; }) - .reduce((acc, note) => { - if (note) { - acc += `- ${note}\n`; - } - return acc; - }, ''); + .map(this.formatReleaseNote) + .reduce((acc, note) => note ? `${acc} - ${note}\n` : acc, ''); } }; }; diff --git a/src/githubApiClient.js b/src/githubApiClient.js new file mode 100644 index 0000000..8befae2 --- /dev/null +++ b/src/githubApiClient.js @@ -0,0 +1,114 @@ +/** + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; under version 2 + * of the License (non-upgradable). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2019 Open Assessment Technologies SA; + */ + +/** + * This module contains methods to comunicate with GitHub v4 Api + * + * @author Anton Tsymuk + */ + +const { GraphQLClient } = require('graphql-request'); + +/** + * Creates a github api client + * @param {String} token - the github token, with permissions to manage the repo + * @returns {Object} the client + */ +module.exports = function githubApiClientFactory(token) { + const graphQLClient = new GraphQLClient( + 'https://api.github.com/graphql', + { + headers: { + Authorization: `token ${token}` + } + } + ); + + return { + /** + * Fetch commits of a PR by PR number + * + * @param {Number|String} prNumber - number of PR + * @param {String} repositoryName - repository name + * @param {String} repositoryOwner - repository owner + * @param {String} nextPageCursor - cursor to item from which to start fetching + * @returns {Object} + */ + getPRCommits(prNumber, repositoryName, repositoryOwner, nextPageCursor = '') { + const query = ` + { + repository(owner: "${repositoryOwner}", name: "${repositoryName}") { + pullRequest(number: ${prNumber}) { + commits(first: 250, after: "${nextPageCursor}") { + pageInfo { + endCursor, + hasNextPage, + } + nodes { + commit { + oid + } + } + } + } + } + } + `; + + return graphQLClient.request(query); + }, + + /** + * Search github pull requests by query + * + * @param {String} searchQuery - query for github search api + * @returns {Object} + */ + searchPullRequests(searchQuery) { + const query = ` + { + search(first: 100, query: "${searchQuery}", type: ISSUE) { + nodes { + ... on PullRequest { + assignee: assignees(first: 1) { + nodes { + login + } + }, + body, + branch: headRefName, + closedAt, + commit: mergeCommit { + oid + }, + number, + title, + url, + user: author { + login + }, + } + } + } + } + `; + + return graphQLClient.request(query); + } + }; +}; From da9672e8dcf1d829f9c176b5346412cd29684e8f Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Mon, 21 Jan 2019 16:25:12 +0300 Subject: [PATCH 04/11] fix lint issue --- src/github.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/github.js b/src/github.js index fc9485a..ef06526 100644 --- a/src/github.js +++ b/src/github.js @@ -22,8 +22,6 @@ * @author Bertrand Chevrier */ -const util = require('util'); - const githubApiClientFactory = require('./githubApiClient'); const validate = require('./validate.js'); From 7cb29963a5c895681fc876d6d0f1eb356026b859 Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Tue, 22 Jan 2019 12:00:56 +0300 Subject: [PATCH 05/11] add tests for loadConfig method of release module --- tests/unit/release/loadConfig/test.js | 139 ++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tests/unit/release/loadConfig/test.js diff --git a/tests/unit/release/loadConfig/test.js b/tests/unit/release/loadConfig/test.js new file mode 100644 index 0000000..85f38e3 --- /dev/null +++ b/tests/unit/release/loadConfig/test.js @@ -0,0 +1,139 @@ +/** + * + * Unit test the loadConfig method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Bertrand Chevrier + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +//load the tested module and mock dependencies +const config = { + load: () => ({}), + write: () => { }, +}; +const inquirer = { + prompt: () => ({}), +}; +const opn = sandbox.spy(); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + inquirer, + opn: opn, +})(); + +test('should define loadConfig method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.loadConfig === 'function', 'The release instance has loadConfig method'); + + t.end(); +}); + +test('should load config', async (t) => { + t.plan(1); + + const data = { token: 'testToken' }; + + sandbox.stub(config, 'load').returns(data); + + await release.loadConfig(); + + t.ok(config.load.callCount === 1, 'Config has been loaded'); + + sandbox.restore(); + t.end(); +}); + +test('should open github token settings if there is no token in the config', async (t) => { + t.plan(2); + + const clock = sandbox.useFakeTimers(); + + await release.loadConfig(); + + clock.tick(2000); + + t.ok(opn.callCount === 1, 'Config has been loaded'); + t.ok(opn.calledWith('https://github.com/settings/tokens')); + + clock.restore(); + sandbox.restore(); + t.end(); +}); + +test('should prompt user to provide a token if there is no token in the config', async (t) => { + t.plan(4); + + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message }) => { + t.ok(type === 'input', 'The type should be "input"'); + t.ok(name === 'token', 'The param name should be token'); + t.ok(message === 'I need a Github token, with "repo" rights (check your browser) : ', 'Should disaplay appropriate message'); + + return {}; + }); + + await release.loadConfig(); + + t.ok(inquirer.prompt.callCount === 1, 'Prompt has been initialised'); + + sandbox.restore(); + t.end(); +}); + +test('should validate provided token', async (t) => { + t.plan(2); + + const validToken = 'hsajdf234jhsaj234dfhh234asj32dfh'; + const invalidToken = 'invalidToken'; + + sandbox.stub(inquirer, 'prompt').callsFake(({ validate }) => { + t.ok(validate(validToken), 'Validate valid token'); + t.notOk(validate(invalidToken), 'Validate invalid token'); + + return {}; + }); + + await release.loadConfig(); + + sandbox.restore(); + t.end(); +}); + +test('should trim token', async (t) => { + t.plan(1); + + sandbox.stub(inquirer, 'prompt').callsFake(({ filter }) => { + t.ok(filter(' testToken ') === 'testToken', 'Validate valid token'); + + return {}; + }); + + await release.loadConfig(); + + sandbox.restore(); + t.end(); +}); + +test('should save provided token', async (t) => { + t.plan(2); + + const token = 'testToken'; + + sandbox.stub(config, 'write'); + + sandbox.stub(inquirer, 'prompt').returns({ token }); + + await release.loadConfig(); + + t.ok(config.write.callCount === 1, 'The config has been saved'); + t.ok(config.write.calledWith({ token }), 'The token has been saved in the config'); + + sandbox.restore(); + t.end(); +}); From 9cd90d04df1cd8325ba995172e8fccc3f5f033fd Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Wed, 23 Jan 2019 14:05:30 +0300 Subject: [PATCH 06/11] add tests for release extension --- index.js | 2 +- src/release.js | 4 +- tests/unit/release/compileAssets/test.js | 193 +++++++++++++++++ tests/unit/release/confirmRelease/test.js | 95 +++++++++ .../unit/release/createGithubRelease/test.js | 150 +++++++++++++ tests/unit/release/createPullRequest/test.js | 174 +++++++++++++++ tests/unit/release/createReleaseTag/test.js | 116 ++++++++++ .../release/createReleasingBranch/test.js | 110 ++++++++++ tests/unit/release/doesReleaseExists/test.js | 127 +++++++++++ .../unit/release/extractReleaseNotes/test.js | 170 +++++++++++++++ .../release/initialiseGithubClient/test.js | 104 +++++++++ tests/unit/release/isReleaseRequired/test.js | 145 +++++++++++++ tests/unit/release/loadConfig/test.js | 23 +- tests/unit/release/mergeBack/test.js | 103 +++++++++ tests/unit/release/mergePullRequest/test.js | 201 ++++++++++++++++++ .../release/removeReleasingBranch/test.js | 109 ++++++++++ tests/unit/release/selectExtension/test.js | 131 ++++++++++++ tests/unit/release/selectTaoInstance/test.js | 148 +++++++++++++ tests/unit/release/signTags/test.js | 62 ++++++ tests/unit/release/updateTranslations/test.js | 194 +++++++++++++++++ tests/unit/release/verifyBranches/test.js | 161 ++++++++++++++ tests/unit/release/verifyLocalChanges/test.js | 120 +++++++++++ 22 files changed, 2628 insertions(+), 14 deletions(-) create mode 100644 tests/unit/release/compileAssets/test.js create mode 100644 tests/unit/release/confirmRelease/test.js create mode 100644 tests/unit/release/createGithubRelease/test.js create mode 100644 tests/unit/release/createPullRequest/test.js create mode 100644 tests/unit/release/createReleaseTag/test.js create mode 100644 tests/unit/release/createReleasingBranch/test.js create mode 100644 tests/unit/release/doesReleaseExists/test.js create mode 100644 tests/unit/release/extractReleaseNotes/test.js create mode 100644 tests/unit/release/initialiseGithubClient/test.js create mode 100644 tests/unit/release/isReleaseRequired/test.js create mode 100644 tests/unit/release/mergeBack/test.js create mode 100644 tests/unit/release/mergePullRequest/test.js create mode 100644 tests/unit/release/removeReleasingBranch/test.js create mode 100644 tests/unit/release/selectExtension/test.js create mode 100644 tests/unit/release/selectTaoInstance/test.js create mode 100644 tests/unit/release/signTags/test.js create mode 100644 tests/unit/release/updateTranslations/test.js create mode 100644 tests/unit/release/verifyBranches/test.js create mode 100644 tests/unit/release/verifyLocalChanges/test.js diff --git a/index.js b/index.js index 4e09d92..c9d1363 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,7 @@ async function releaseExtension() { await release.signTags(); await release.verifyBranches(); await release.initialiseGithubClient(); - await release.isReleaseExists(); + await release.doesReleaseExists(); await release.isReleaseRequired(); await release.confirmRelease(); await release.createReleasingBranch(); diff --git a/src/release.js b/src/release.js index bb4f9c0..9abf52d 100644 --- a/src/release.js +++ b/src/release.js @@ -191,7 +191,7 @@ module.exports = function taoExtensionReleaseFactory(baseBranch, branchPrefix, o /** * Check if release exists */ - async isReleaseExists() { + async doesReleaseExists() { log.doing(`Check if tag ${data.tag} exists`); if (await gitClient.hasTag(data.tag)) { @@ -377,6 +377,8 @@ module.exports = function taoExtensionReleaseFactory(baseBranch, branchPrefix, o log.error(`Unable to update translations. ${error.message}. Continue.`); } } + + log.done(); }, /** diff --git a/tests/unit/release/compileAssets/test.js b/tests/unit/release/compileAssets/test.js new file mode 100644 index 0000000..ba919ff --- /dev/null +++ b/tests/unit/release/compileAssets/test.js @@ -0,0 +1,193 @@ +/** + * + * Unit test the compileAssets method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const branchPrefix = 'release'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + commitAndPush: () => { }, + pull: () => { } +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, + error: () => { }, + info: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const version = '1.1.1'; +const taoInstance = { + buildAssets: () => { }, + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version, name: extension }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, branchPrefix); + +test('should define compileAssets method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.compileAssets === 'function', 'The release instance has compileAssets method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'doing'); + + await release.compileAssets(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith('Bundling'), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should log info message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'info'); + + await release.compileAssets(); + + t.equal(log.info.callCount, 1, 'Info has been logged'); + t.ok(log.info.calledWith('Asset build started, this may take a while'), 'Info has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should build assets', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(taoInstance, 'buildAssets'); + + await release.compileAssets(); + + t.equal(taoInstance.buildAssets.callCount, 1, 'Assets has been builded'); + t.ok(taoInstance.buildAssets.calledWith(extension, false), 'Assets of apropriate extension has been builded'); + + sandbox.restore(); + t.end(); +}); + +test('should publish assets', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(gitClientInstance, 'commitAndPush'); + + await release.compileAssets(); + + t.equal(gitClientInstance.commitAndPush.callCount, 1, 'Assets has been published'); + t.ok(gitClientInstance.commitAndPush.calledWith(`${branchPrefix}-${version}`, 'bundle assets'), 'Assets of apropriate extension has been published'); + + sandbox.restore(); + t.end(); +}); + +test('should log error message if compilation failed', async (t) => { + t.plan(2); + + const errorMessage = 'testError'; + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'error'); + sandbox.stub(taoInstance, 'buildAssets').throws(new Error(errorMessage)); + + await release.compileAssets(); + + t.equal(log.error.callCount, 1, 'Error has been logged'); + t.ok(log.error.calledWith(`Unable to bundle assets. ${errorMessage}. Continue.`), 'Error has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should log info message after compilation of assets', async (t) => { + t.plan(4); + + const changes = ['change1', 'change2']; + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'info'); + sandbox.stub(gitClientInstance, 'commitAndPush').returns(changes); + + await release.compileAssets(); + + t.equal(log.info.callCount, 4, 'Info has been logged'); + t.ok(log.info.calledWith(`Commit : [bundle assets - ${changes.length} files]`), 'Info has been logged with apropriate message'); + changes.forEach(change => + t.ok(log.info.calledWith(` - ${change}`), 'Info has been logged with apropriate message') + ); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'done'); + + await release.compileAssets(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/confirmRelease/test.js b/tests/unit/release/confirmRelease/test.js new file mode 100644 index 0000000..5ae0e0b --- /dev/null +++ b/tests/unit/release/confirmRelease/test.js @@ -0,0 +1,95 @@ +/** + * + * Unit test the confirmRelease method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + pull: () => { } +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const version = '1.1.1'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version, name: extension }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(); + +test('should define confirmRelease method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.confirmRelease === 'function', 'The release instance has confirmRelease method'); + + t.end(); +}); + +test('should prompt to confirm release', async (t) => { + t.plan(4); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message }) => { + t.equal(type, 'confirm', 'The type should be "confirm"'); + t.equal(name, 'go', 'The param name should be go'); + t.equal(message, `Let's release version ${extension}@${version} 🚀 ?`, 'Should disaplay appropriate message'); + + return { go: true }; + }); + + await release.confirmRelease(); + + t.equal(inquirer.prompt.callCount, 1, 'Prompt has been initialised'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit if release was not confirmed', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(inquirer, 'prompt').returns({ go: false }); + sandbox.stub(log, 'exit'); + + await release.confirmRelease(); + + t.equal(log.exit.callCount, 1, 'Exit has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/createGithubRelease/test.js b/tests/unit/release/createGithubRelease/test.js new file mode 100644 index 0000000..77798ed --- /dev/null +++ b/tests/unit/release/createGithubRelease/test.js @@ -0,0 +1,150 @@ +/** + * + * Unit test the createGithubRelease method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const branchPrefix = 'release'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const githubInstance = { + createReleasePR: () => ({ state: 'open' }), + release: () => { }, +}; +const githubFactory = sandbox.stub().callsFake(() => githubInstance); +const gitClientInstance = { + pull: () => { } +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, + info: () => { }, +}; +const taoRoot = 'testRoot'; +const releaseComment = 'testComment'; +const inquirer = { + prompt: () => ({ extension, taoRoot, comment: releaseComment }), +}; +const version = '1.1.1'; +const releaseBranch = 'testReleaseBranch'; +const taoInstance = { + getExtensions: () => [], + getRepoName: () => 'testRepo', + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './github.js': githubFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, branchPrefix, null, releaseBranch); + +test('should define createGithubRelease method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.createGithubRelease === 'function', 'The release instance has createGithubRelease method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(log, 'doing'); + + await release.createGithubRelease(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith(`Creating github release ${version}`), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should prompt about release comment', async (t) => { + t.plan(4); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message }) => { + t.equal(type, 'input', 'The type should be "input"'); + t.equal(name, 'comment', 'The param name should be comment'); + t.equal(message, 'Any comment on the release ?', 'Should disaplay appropriate message'); + + + return { comment: releaseComment }; + }); + + await release.createGithubRelease(); + + t.equal(inquirer.prompt.callCount, 1, 'Prompt has been initialised'); + + sandbox.restore(); + t.end(); +}); + +test('should create release', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(githubInstance, 'release'); + + await release.createGithubRelease(); + + t.equal(githubInstance.release.callCount, 1, 'Github release has been created'); + t.ok(githubInstance.release.calledWith(`v${version}`), 'Github release has been created from apropriate tag'); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(log, 'done'); + + await release.createGithubRelease(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/createPullRequest/test.js b/tests/unit/release/createPullRequest/test.js new file mode 100644 index 0000000..0acb5f8 --- /dev/null +++ b/tests/unit/release/createPullRequest/test.js @@ -0,0 +1,174 @@ +/** + * + * Unit test the createPullRequest method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const branchPrefix = 'release'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const githubInstance = { + createReleasePR: () => { }, +}; +const githubFactory = sandbox.stub().callsFake(() => githubInstance); +const gitClientInstance = { + pull: () => { } +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, + info: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const version = '1.1.1'; +const releaseBranch = 'testReleaseBranch'; +const taoInstance = { + getExtensions: () => [], + getRepoName: () => 'testRepo', + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './github.js': githubFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, branchPrefix, null, releaseBranch); + +test('should define createPullRequest method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.createPullRequest === 'function', 'The release instance has createPullRequest method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + + sandbox.stub(log, 'doing'); + + await release.createPullRequest(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith('Create the pull request'), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should create pull request', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + + sandbox.stub(githubInstance, 'createReleasePR'); + + await release.createPullRequest(); + + t.equal(githubInstance.createReleasePR.callCount, 1, 'Release pull request has been created'); + t.ok( + githubInstance.createReleasePR.calledWith( + `${branchPrefix}-${version}`, + releaseBranch, + version, + version, + ), + 'Release pull request has been created from releasing branch', + ); + + sandbox.restore(); + t.end(); +}); + +test('should log info message', async (t) => { + t.plan(2); + + const url = 'testUrl'; + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + + sandbox.stub(githubInstance, 'createReleasePR').returns({ + state: 'open', + html_url: url, + }); + sandbox.stub(log, 'info'); + + await release.createPullRequest(); + + t.equal(log.info.callCount, 1, 'Info has been logged'); + t.ok(log.info.calledWith(`${url} created`), 'Info has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + + sandbox.stub(githubInstance, 'createReleasePR').returns({ + state: 'open', + }); + sandbox.stub(log, 'done'); + + await release.createPullRequest(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit message if pull request is not created', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + + sandbox.stub(log, 'exit'); + + await release.createPullRequest(); + + t.equal(log.exit.callCount, 1, 'Exit has been logged'); + t.ok(log.exit.calledWith('Unable to create the release pull request'), 'Exit has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/createReleaseTag/test.js b/tests/unit/release/createReleaseTag/test.js new file mode 100644 index 0000000..e4146af --- /dev/null +++ b/tests/unit/release/createReleaseTag/test.js @@ -0,0 +1,116 @@ +/** + * + * Unit test the createReleaseTag method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + pull: () => { }, + tag: () => { }, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const releaseBranch = 'testReleaseBranch'; +const version = '1.1.1'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, null, null, releaseBranch); + +test('should define createReleaseTag method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.createReleaseTag === 'function', 'The release instance has createReleaseTag method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'doing'); + + await release.createReleaseTag(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith(`Add and push tag v${version}`), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should create release tag', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(gitClientInstance, 'tag'); + + await release.createReleaseTag(); + + t.equal(gitClientInstance.tag.callCount, 1, 'Tag has been created'); + t.ok( + gitClientInstance.tag.calledWith( + releaseBranch, + `v${version}`, + `version ${version}` + ), + 'Tag has been created with apropriate version' + ); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'done'); + + await release.createReleaseTag(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/createReleasingBranch/test.js b/tests/unit/release/createReleasingBranch/test.js new file mode 100644 index 0000000..d466b58 --- /dev/null +++ b/tests/unit/release/createReleasingBranch/test.js @@ -0,0 +1,110 @@ +/** + * + * Unit test the createReleasingBranch method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const branchPrefix = 'release'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + pull: () => { }, + localBranch: () => { }, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const version = '1.1.1'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, branchPrefix); + +test('should define createReleasingBranch method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.createReleasingBranch === 'function', 'The release instance has createReleasingBranch method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'doing'); + + await release.createReleasingBranch(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith('Create release branch'), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should create releasing branch', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(gitClientInstance, 'localBranch'); + + await release.createReleasingBranch(); + + t.equal(gitClientInstance.localBranch.callCount, 1, 'Branch has been created'); + t.ok(gitClientInstance.localBranch.calledWith(`${branchPrefix}-${version}`), 'Apropriated branch has been created'); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'done'); + + await release.createReleasingBranch(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + t.ok(log.done.calledWith(`${branchPrefix}-${version} created`), 'Done has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/doesReleaseExists/test.js b/tests/unit/release/doesReleaseExists/test.js new file mode 100644 index 0000000..1a9dadb --- /dev/null +++ b/tests/unit/release/doesReleaseExists/test.js @@ -0,0 +1,127 @@ +/** + * + * Unit test the doesReleaseExists method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + pull: () => { }, + hasTag: () => false +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const version = '1.1.1'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(); + +test('should define doesReleaseExists method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.doesReleaseExists === 'function', 'The release instance has doesReleaseExists method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'doing'); + + await release.doesReleaseExists(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith(`Check if tag v${version} exists`), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should check if release exists', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(gitClientInstance, 'hasTag'); + + await release.doesReleaseExists(); + + t.equal(gitClientInstance.hasTag.callCount, 1, 'Release has been checked'); + t.ok(gitClientInstance.hasTag.calledWith(`v${version}`), 'Apropriated release has been checked'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit if release exists', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'exit'); + sandbox.stub(gitClientInstance, 'hasTag').returns(true); + + await release.doesReleaseExists(); + + t.equal(log.exit.callCount, 1, 'Exit has been logged'); + t.ok(log.exit.calledWith(`The tag v${version} already exists`), 'Exit has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'done'); + + await release.doesReleaseExists(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/extractReleaseNotes/test.js b/tests/unit/release/extractReleaseNotes/test.js new file mode 100644 index 0000000..ae84e82 --- /dev/null +++ b/tests/unit/release/extractReleaseNotes/test.js @@ -0,0 +1,170 @@ +/** + * + * Unit test the extractReleaseNotes method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const branchPrefix = 'release'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const prNumber = '123'; +const githubInstance = { + extractReleaseNotesFromReleasePR: () => { }, + createReleasePR: () => ({ state: 'open', number: prNumber }), +}; +const githubFactory = sandbox.stub().callsFake(() => githubInstance); +const gitClientInstance = { + pull: () => { } +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, + info: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const version = '1.1.1'; +const releaseBranch = 'testReleaseBranch'; +const taoInstance = { + getExtensions: () => [], + getRepoName: () => 'testRepo', + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './github.js': githubFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, branchPrefix, null, releaseBranch); + +test('should define extractReleaseNotes method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.extractReleaseNotes === 'function', 'The release instance has extractReleaseNotes method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(log, 'doing'); + + await release.extractReleaseNotes(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith('Extract release notes'), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should extract release notes', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(githubInstance, 'extractReleaseNotesFromReleasePR'); + + await release.extractReleaseNotes(); + + t.equal(githubInstance.extractReleaseNotesFromReleasePR.callCount, 1, 'Release notes has been extracted'); + t.ok(githubInstance.extractReleaseNotesFromReleasePR.calledWith(prNumber), 'Release notes has been extracted from apropriate pull request'); + + sandbox.restore(); + t.end(); +}); + +test('should log info message', async (t) => { + t.plan(2); + + const releaseNotes = 'testRleaseNotes'; + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(githubInstance, 'extractReleaseNotesFromReleasePR').returns(releaseNotes); + sandbox.stub(log, 'info'); + + await release.extractReleaseNotes(); + + t.equal(log.info.callCount, 1, 'Info has been logged'); + t.ok(log.info.calledWith(releaseNotes), 'Info has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + const releaseNotes = 'testRleaseNotes'; + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(githubInstance, 'extractReleaseNotesFromReleasePR').returns(releaseNotes); + sandbox.stub(log, 'done'); + + await release.extractReleaseNotes(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit message if can not extract release notes', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(log, 'exit'); + + await release.extractReleaseNotes(); + + t.equal(log.exit.callCount, 1, 'Exit has been logged'); + t.ok(log.exit.calledWith('Unable to create the release notes'), 'Exit has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/initialiseGithubClient/test.js b/tests/unit/release/initialiseGithubClient/test.js new file mode 100644 index 0000000..7ce8deb --- /dev/null +++ b/tests/unit/release/initialiseGithubClient/test.js @@ -0,0 +1,104 @@ +/** + * + * Unit test the initialiseGithubClient method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const githubFactory = sandbox.stub(); +const gitClientInstance = {}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const repositoryName = 'testRepository'; +const taoInstance = { + getExtensions: () => [], + getRepoName: () => repositoryName, + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './github.js': githubFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(); + +test('should define initialiseGithubClient method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.initialiseGithubClient === 'function', 'The release instance has initialiseGithubClient method'); + + t.end(); +}); + +test('should get repository name', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(taoInstance, 'getRepoName').returns(repositoryName); + + await release.initialiseGithubClient(); + + t.equal(taoInstance.getRepoName.callCount, 1, 'Getting of repository name'); + t.ok(taoInstance.getRepoName.calledWith(extension), 'Getting of repository name of apropriate extension'); + + sandbox.restore(); + t.end(); +}); + +test('should create github instance', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + githubFactory.resetHistory(); + + await release.initialiseGithubClient(); + + t.equal(githubFactory.callCount, 1, 'Github client has been initialised'); + t.ok(githubFactory.calledWith(sinon.match.any, repositoryName), 'Github client has been initialised with apropriate repository'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit message if can not get repository name', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(log, 'exit'); + sandbox.stub(taoInstance, 'getRepoName').returns(null); + + await release.initialiseGithubClient(); + + t.equal(log.exit.callCount, 1, 'Exit has been logged'); + t.ok(log.exit.calledWith('Unable to find the gitbuh repository name'), 'Exit has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/isReleaseRequired/test.js b/tests/unit/release/isReleaseRequired/test.js new file mode 100644 index 0000000..ac601cf --- /dev/null +++ b/tests/unit/release/isReleaseRequired/test.js @@ -0,0 +1,145 @@ +/** + * + * Unit test the isReleaseRequired method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const baseBranch = 'testBaseBranch'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + hasDiff: () => true, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const log = { + doing: () => { }, + done: () => { }, + exit: () => { }, +}; +const releaseBranch = 'testReleaseBranch'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(baseBranch, null, null, releaseBranch); + +test('should define isReleaseRequired method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.isReleaseRequired === 'function', 'The release instance has isReleaseRequired method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(log, 'doing'); + + await release.isReleaseRequired(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith(`Diff ${baseBranch}..${releaseBranch}`), 1, 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should check for diffs between base and release branches', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'hasDiff').returns(true); + + await release.isReleaseRequired(); + + t.equal(gitClientInstance.hasDiff.callCount, 1, 'Diffs has been checked'); + t.ok(gitClientInstance.hasDiff.calledWith(baseBranch, releaseBranch), 1, 'Diffs has been checked between apropriate branches'); + + sandbox.restore(); + t.end(); +}); + +test('should prompt about release if there is no diffs', async (t) => { + t.plan(4); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'hasDiff').returns(false); + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message }) => { + t.equal(type, 'confirm', 'The type should be "confirm"'); + t.equal(name, 'diff', 'The param name should be diff'); + t.equal(message, `It seems there is no changes between ${baseBranch} and ${releaseBranch}. Do you want to release anyway?`, 'Should disaplay appropriate message'); + + return { diff: true }; + }); + + await release.isReleaseRequired(); + + t.equal(inquirer.prompt.callCount, 1, 'Prompt has been initialised'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'hasDiff').returns(false); + sandbox.stub(inquirer, 'prompt').returns({ diff: false }); + sandbox.stub(log, 'exit'); + + await release.isReleaseRequired(); + + t.equal(log.exit.callCount, 1, 'Exit has been logged'); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(log, 'done'); + + await release.isReleaseRequired(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/loadConfig/test.js b/tests/unit/release/loadConfig/test.js index 85f38e3..325621a 100644 --- a/tests/unit/release/loadConfig/test.js +++ b/tests/unit/release/loadConfig/test.js @@ -3,7 +3,7 @@ * Unit test the loadConfig method of module src/release.js * * @copyright 2019 Open Assessment Technologies SA; - * @author Bertrand Chevrier + * @author Anton Tsymuk */ const proxyquire = require('proxyquire'); @@ -12,7 +12,6 @@ const test = require('tape'); const sandbox = sinon.sandbox.create(); -//load the tested module and mock dependencies const config = { load: () => ({}), write: () => { }, @@ -24,13 +23,13 @@ const opn = sandbox.spy(); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, inquirer, - opn: opn, + opn, })(); test('should define loadConfig method on release instance', (t) => { t.plan(1); - t.ok(typeof release.loadConfig === 'function', 'The release instance has loadConfig method'); + t.equal(typeof release.loadConfig, 'function', 'The release instance has loadConfig method'); t.end(); }); @@ -44,7 +43,7 @@ test('should load config', async (t) => { await release.loadConfig(); - t.ok(config.load.callCount === 1, 'Config has been loaded'); + t.equal(config.load.callCount, 1, 'Config has been loaded'); sandbox.restore(); t.end(); @@ -59,7 +58,7 @@ test('should open github token settings if there is no token in the config', asy clock.tick(2000); - t.ok(opn.callCount === 1, 'Config has been loaded'); + t.equal(opn.callCount, 1, 'Config has been loaded'); t.ok(opn.calledWith('https://github.com/settings/tokens')); clock.restore(); @@ -71,16 +70,16 @@ test('should prompt user to provide a token if there is no token in the config', t.plan(4); sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message }) => { - t.ok(type === 'input', 'The type should be "input"'); - t.ok(name === 'token', 'The param name should be token'); - t.ok(message === 'I need a Github token, with "repo" rights (check your browser) : ', 'Should disaplay appropriate message'); + t.equal(type, 'input', 'The type should be "input"'); + t.equal(name, 'token', 'The param name should be token'); + t.equal(message, 'I need a Github token, with "repo" rights (check your browser) : ', 'Should disaplay appropriate message'); return {}; }); await release.loadConfig(); - t.ok(inquirer.prompt.callCount === 1, 'Prompt has been initialised'); + t.equal(inquirer.prompt.callCount, 1, 'Prompt has been initialised'); sandbox.restore(); t.end(); @@ -109,7 +108,7 @@ test('should trim token', async (t) => { t.plan(1); sandbox.stub(inquirer, 'prompt').callsFake(({ filter }) => { - t.ok(filter(' testToken ') === 'testToken', 'Validate valid token'); + t.equal(filter(' testToken '), 'testToken', 'Validate valid token'); return {}; }); @@ -131,7 +130,7 @@ test('should save provided token', async (t) => { await release.loadConfig(); - t.ok(config.write.callCount === 1, 'The config has been saved'); + t.equal(config.write.callCount, 1, 'The config has been saved'); t.ok(config.write.calledWith({ token }), 'The token has been saved in the config'); sandbox.restore(); diff --git a/tests/unit/release/mergeBack/test.js b/tests/unit/release/mergeBack/test.js new file mode 100644 index 0000000..8afbd14 --- /dev/null +++ b/tests/unit/release/mergeBack/test.js @@ -0,0 +1,103 @@ +/** + * + * Unit test the mergeBack method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const baseBranch = 'testBaseBranch'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + mergeBack: () => { }, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const log = { + doing: () => { }, + done: () => { }, +}; +const releaseBranch = 'testReleaseBranch'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './taoInstance.js': taoINstanceFactory, + './log.js': log, + inquirer, +})(baseBranch, null, null, releaseBranch); + +test('should define mergeBack method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.mergeBack === 'function', 'The release instance has mergeBack method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(log, 'doing'); + + await release.mergeBack(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith(`Merging back ${releaseBranch} into ${baseBranch}`), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should merge release branch into base branch', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'mergeBack'); + + await release.mergeBack(); + + t.equal(gitClientInstance.mergeBack.callCount, 1, 'Branch has been merged'); + t.ok(gitClientInstance.mergeBack.calledWith(baseBranch, releaseBranch), 'Release branch has been merged into base branch'); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(log, 'done'); + + await release.mergeBack(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/mergePullRequest/test.js b/tests/unit/release/mergePullRequest/test.js new file mode 100644 index 0000000..15faa21 --- /dev/null +++ b/tests/unit/release/mergePullRequest/test.js @@ -0,0 +1,201 @@ +/** + * + * Unit test the mergePullRequest method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const branchPrefix = 'release'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const githubInstance = { + createReleasePR: () => ({ state: 'open' }) +}; +const githubFactory = sandbox.stub().callsFake(() => githubInstance); +const gitClientInstance = { + pull: () => { }, + mergePr: () => { }, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, + info: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot, pr: true }), +}; +const version = '1.1.1'; +const releaseBranch = 'testReleaseBranch'; +const taoInstance = { + getExtensions: () => [], + getRepoName: () => 'testRepo', + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const opn = sandbox.spy(); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './github.js': githubFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, + opn, +})(null, branchPrefix, null, releaseBranch); + +test('should define mergePullRequest method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.mergePullRequest === 'function', 'The release instance has mergePullRequest method'); + + t.end(); +}); + +test('should open pull request in browser', async (t) => { + t.plan(1); + + const clock = sandbox.useFakeTimers(); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + opn.resetHistory(); + + await release.mergePullRequest(); + + clock.tick(2000); + + t.equal(opn.callCount, 1, 'Browser has been opened'); + + clock.restore(); + t.end(); +}); + +test('should prompt about merging pull request', async (t) => { + t.plan(4); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message }) => { + t.equal(type, 'confirm', 'The type should be "confrim"'); + t.equal(name, 'pr', 'The param name should be pr'); + t.equal(message, 'Please review the release PR (you can make the last changes now). Can I merge it now ?', 'Should disaplay appropriate message'); + + + return { pr: true }; + }); + + await release.mergePullRequest(); + + t.equal(inquirer.prompt.callCount, 1, 'Prompt has been initialised'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit if pr is not confirmed', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(inquirer, 'prompt').returns({ pr: false }); + sandbox.stub(log, 'exit'); + + await release.mergePullRequest(); + + t.equal(log.exit.callCount, 1, 'Exit has been logged'); + + sandbox.restore(); + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(log, 'doing'); + + await release.mergePullRequest(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith('Merging the pull request'), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should merge pull request', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(gitClientInstance, 'mergePr'); + + await release.mergePullRequest(); + + t.equal(gitClientInstance.mergePr.callCount, 1, 'PR has been merged'); + t.ok( + gitClientInstance.mergePr.calledWith( + releaseBranch, + `${branchPrefix}-${version}` + ), + 'Apropriated PR has been merged', + ); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + await release.initialiseGithubClient(); + await release.createPullRequest(); + + sandbox.stub(log, 'done'); + + await release.mergePullRequest(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + t.ok(log.done.calledWith('PR merged'), 'Done has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/removeReleasingBranch/test.js b/tests/unit/release/removeReleasingBranch/test.js new file mode 100644 index 0000000..017134d --- /dev/null +++ b/tests/unit/release/removeReleasingBranch/test.js @@ -0,0 +1,109 @@ +/** + * + * Unit test the removeReleasingBranch method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const branchPrefix = 'release'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + pull: () => { }, + deleteBranch: () => { }, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const version = '1.1.1'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version }) +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, branchPrefix); + +test('should define removeReleasingBranch method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.removeReleasingBranch === 'function', 'The release instance has removeReleasingBranch method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'doing'); + + await release.removeReleasingBranch(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith('Clean up the place'), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should delete releasing branch', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(gitClientInstance, 'deleteBranch'); + + await release.removeReleasingBranch(); + + t.equal(gitClientInstance.deleteBranch.callCount, 1, 'Branch has been deleted'); + t.ok(gitClientInstance.deleteBranch.calledWith(`${branchPrefix}-${version}`), 'Releasing branch has been deleted'); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'done'); + + await release.removeReleasingBranch(); + + t.equal(log.done.callCount, 1, 'done has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/selectExtension/test.js b/tests/unit/release/selectExtension/test.js new file mode 100644 index 0000000..1c9d7ec --- /dev/null +++ b/tests/unit/release/selectExtension/test.js @@ -0,0 +1,131 @@ +/** + * + * Unit test the selectTaoInstance method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const config = { + write: () => { }, +}; +const gitClientFactory = sandbox.stub(); +const origin = 'testOrigin'; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ taoRoot }), +}; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, null, origin); + +test('should define selectExtension method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.selectExtension === 'function', 'The release instance has selectExtension method'); + + t.end(); +}); + +test('should get available extensions', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + + sandbox.stub(inquirer, 'prompt').returns({ extension: '' }); + sandbox.stub(taoInstance, 'getExtensions').returns([]); + + await release.selectExtension(); + + t.equal(taoInstance.getExtensions.callCount, 1, 'Avaliable extension has been received'); + + sandbox.restore(); + t.end(); +}); + +test('should prompt to select tao extension', async (t) => { + t.plan(6); + + const availableExtensions = ['testExtensionFoo', 'testExtensionBar']; + + await release.selectTaoInstance(); + + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message, pageSize, choices }) => { + t.equal(type, 'list', 'The type should be "list"'); + t.equal(name, 'extension', 'The param name should be extension'); + t.equal(message, 'Which extension you want to release ? ', 'Should disaplay appropriate message'); + t.equal(pageSize, 12, 'The page size should be 12'); + t.equal(choices, availableExtensions, 'Should use avaiable extensions as choices'); + + return { extension: '' }; + }); + sandbox.stub(taoInstance, 'getExtensions').returns(availableExtensions); + + await release.selectExtension(); + + t.equal(inquirer.prompt.callCount, 1, 'Extension has been prompted'); + + sandbox.restore(); + t.end(); +}); + +test('should create gitClient instance', async (t) => { + t.plan(2); + + const extension = 'testExtension'; + + await release.selectTaoInstance(); + + sandbox.stub(inquirer, 'prompt').returns({ extension }); + sandbox.stub(taoInstance, 'getExtensions').returns([]); + gitClientFactory.resetHistory(); + + await release.selectExtension(); + + t.equal(gitClientFactory.callCount, 1, 'gitClient instance has been created'); + t.ok(gitClientFactory.calledWith(`${taoRoot}/${extension}`, origin, extension), 'gitClient instance has been created with appropriate args'); + + sandbox.restore(); + t.end(); +}); + +test('should save selected extension to config', async (t) => { + t.plan(2); + + const extension = 'testExtension'; + + await release.selectTaoInstance(); + + sandbox.stub(inquirer, 'prompt').returns({ extension }); + sandbox.stub(taoInstance, 'getExtensions').returns([]); + sandbox.stub(config, 'write'); + + await release.selectExtension(); + + t.equal(config.write.callCount, 1, 'Config has been saved'); + t.ok(config.write.calledWith({ + extension: { + path: `${taoRoot}/${extension}`, + name: extension, + }, + taoRoot, + }), 'Extesion has been saved to config'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/selectTaoInstance/test.js b/tests/unit/release/selectTaoInstance/test.js new file mode 100644 index 0000000..a29b77f --- /dev/null +++ b/tests/unit/release/selectTaoInstance/test.js @@ -0,0 +1,148 @@ +/** + * + * Unit test the selectTaoInstance method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const path = require('path'); +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const inquirer = { + prompt: () => ({}), +}; +const log = { + exit: () => { }, +}; +const taoInstance = { + isInstalled: () => ({}), + isRoot: () => ({}), +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const wwwUser = 'testwwwUser'; +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, null, null, null, wwwUser); + +test('should define selectTaoInstance method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.selectTaoInstance === 'function', 'The release instance has selectTaoInstance method'); + + t.end(); +}); + +test('should prompt to provide path to tao instance', async (t) => { + t.plan(5); + + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message, default: defaultValue }) => { + t.equal(type, 'input', 'The type should be "input"'); + t.equal(name, 'taoRoot', 'The param name should be taoRoot'); + t.equal(message, 'Path to the TAO instance : ', 'Should disaplay appropriate message'); + t.equal(defaultValue, path.resolve(''), 'Should provide default value'); + + return { taoRoot: '' }; + }); + + await release.selectTaoInstance(); + + t.equal(inquirer.prompt.callCount, 1, 'Prompt has been initialised'); + + sandbox.restore(); + t.end(); +}); + +test('should initialise taoInstance', async (t) => { + t.plan(2); + + const taoRoot = 'testRoot'; + + sandbox.stub(inquirer, 'prompt').returns({ taoRoot }); + taoINstanceFactory.resetHistory(); + + await release.selectTaoInstance(); + + t.equal(taoINstanceFactory.callCount, 1, 'Instance has been initialised'); + t.ok(taoINstanceFactory.calledWith(path.resolve(taoRoot), false, wwwUser), 'Instance has been initialised with proper args'); + + sandbox.restore(); + t.end(); +}); + +test('should check if under provided path there is a real tao instance', async (t) => { + t.plan(1); + + const taoRoot = 'testRoot'; + + sandbox.stub(inquirer, 'prompt').returns({ taoRoot }); + sandbox.stub(taoInstance, 'isRoot').returns({}); + + await release.selectTaoInstance(); + + t.equal(taoInstance.isRoot.callCount, 1, 'Path has been checked'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit if provided path is not a tao root', async (t) => { + t.plan(2); + + const taoRoot = 'testRoot'; + const dir = 'testDir'; + + sandbox.stub(log, 'exit'); + sandbox.stub(inquirer, 'prompt').returns({ taoRoot }); + sandbox.stub(taoInstance, 'isRoot').returns({ dir }); + + await release.selectTaoInstance(); + + t.equal(log.exit.callCount, 1, 'Exit message has been logged'); + t.ok(log.exit.calledWith(`${dir} is not a TAO instance`), 'Exit message has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should check if tao instance is installed', async (t) => { + t.plan(1); + + const taoRoot = 'testRoot'; + + sandbox.stub(inquirer, 'prompt').returns({ taoRoot }); + sandbox.stub(taoInstance, 'isInstalled').returns(true); + sandbox.stub(taoInstance, 'isRoot').returns({ root: true }); + + await release.selectTaoInstance(); + + t.equal(taoInstance.isInstalled.callCount, 1, 'Path has been checked'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit if tao instance is not installed', async (t) => { + t.plan(2); + + const taoRoot = 'testRoot'; + + sandbox.stub(log, 'exit'); + sandbox.stub(inquirer, 'prompt').returns({ taoRoot }); + sandbox.stub(taoInstance, 'isInstalled').returns(false); + sandbox.stub(taoInstance, 'isRoot').returns({ root: true }); + + await release.selectTaoInstance(); + + t.equal(log.exit.callCount, 1, 'Exit message has been logged'); + t.ok(log.exit.calledWith('It looks like the given TAO instance is not installed.'), 'Exit message has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/signTags/test.js b/tests/unit/release/signTags/test.js new file mode 100644 index 0000000..b04c0a6 --- /dev/null +++ b/tests/unit/release/signTags/test.js @@ -0,0 +1,62 @@ +/** + * + * Unit test the signTags method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + hasSignKey: () => { }, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(); + +test('should define signTags method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.signTags === 'function', 'The release instance has signTags method'); + + t.end(); +}); + +test('should check if there is any sign tags', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'hasSignKey'); + + await release.signTags(); + + t.equal(gitClientInstance.hasSignKey.callCount, 1, 'Sign tags has been checked'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/updateTranslations/test.js b/tests/unit/release/updateTranslations/test.js new file mode 100644 index 0000000..ca61937 --- /dev/null +++ b/tests/unit/release/updateTranslations/test.js @@ -0,0 +1,194 @@ +/** + * + * Unit test the updateTranslations method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const branchPrefix = 'release'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + commitAndPush: () => { }, + pull: () => { } +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + exit: () => { }, + doing: () => { }, + done: () => { }, + error: () => { }, + info: () => { }, + warn: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot, translation: true }), +}; +const version = '1.1.1'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({ version, name: extension }), + updateTranslations: () => { }, +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(null, branchPrefix); + +test('should define updateTranslations method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.updateTranslations === 'function', 'The release instance has updateTranslations method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'doing'); + + await release.updateTranslations(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith('Translations'), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should log warn message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'warn'); + + await release.updateTranslations(); + + t.equal(log.warn.callCount, 1, 'Warn has been logged'); + t.ok(log.warn.calledWith('Update translations during a release only if you know what you are doing'), 'Warn has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should update translitions', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(taoInstance, 'updateTranslations'); + + await release.updateTranslations(); + + t.equal(taoInstance.updateTranslations.callCount, 1, 'Translations has been updated'); + t.ok(taoInstance.updateTranslations.calledWith(extension), 'Translations of apropriate extension has been updated'); + + sandbox.restore(); + t.end(); +}); + +test('should publish translations', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(gitClientInstance, 'commitAndPush'); + + await release.updateTranslations(); + + t.equal(gitClientInstance.commitAndPush.callCount, 1, 'Translations has been published'); + t.ok(gitClientInstance.commitAndPush.calledWith(`${branchPrefix}-${version}`, 'update translations'), 'Translations of apropriate extension has been published'); + + sandbox.restore(); + t.end(); +}); + +test('should log error message if update failed', async (t) => { + t.plan(2); + + const errorMessage = 'testError'; + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'error'); + sandbox.stub(taoInstance, 'updateTranslations').throws(new Error(errorMessage)); + + await release.updateTranslations(); + + t.equal(log.error.callCount, 1, 'Error has been logged'); + t.ok(log.error.calledWith(`Unable to update translations. ${errorMessage}. Continue.`), 'Error has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should log info message after update of translations', async (t) => { + t.plan(4); + + const changes = ['change1', 'change2']; + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'info'); + sandbox.stub(gitClientInstance, 'commitAndPush').returns(changes); + + await release.updateTranslations(); + + t.equal(log.info.callCount, 3, 'Info has been logged'); + t.ok(log.info.calledWith(`Commit : [update translations - ${changes.length} files]`), 'Info has been logged with apropriate message'); + changes.forEach(change => + t.ok(log.info.calledWith(` - ${change}`), 'Info has been logged with apropriate message') + ); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(log, 'done'); + + await release.updateTranslations(); + + t.equal(log.done.callCount, 1, 'Done has been logged'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/verifyBranches/test.js b/tests/unit/release/verifyBranches/test.js new file mode 100644 index 0000000..16be94b --- /dev/null +++ b/tests/unit/release/verifyBranches/test.js @@ -0,0 +1,161 @@ +/** + * + * Unit test the verifyBranches method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const baseBranch = 'testBaseBranch'; +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + pull: () => { }, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + doing: () => { }, + exit: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, pull: true, taoRoot }), +}; +const releaseBranch = 'testReleaseBranch'; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), + parseManifest: () => ({}), +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(baseBranch, null, null, releaseBranch); + +test('should define verifyBranches method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.verifyBranches === 'function', 'The release instance has verifyBranches method'); + + t.end(); +}); + +test('should prompt to pull branches', async (t) => { + t.plan(4); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message }) => { + t.equal(type, 'confirm', 'The type should be "confirm"'); + t.equal(name, 'pull', 'The param name should be pull'); + t.equal(message, `Can I checkout and pull ${baseBranch} and ${releaseBranch} ?`, 'Should disaplay appropriate message'); + + return { pull: true }; + }); + + await release.verifyBranches(); + + t.equal(inquirer.prompt.callCount, 1, 'Prompt has been initialised'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit if pull not confirmed', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(inquirer, 'prompt').returns({ pull: false }); + sandbox.stub(log, 'exit'); + + await release.verifyBranches(); + + t.equal(log.exit.callCount, 1, 'Exit has been logged'); + + sandbox.restore(); + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(log, 'doing'); + + await release.verifyBranches(); + + t.equal(log.doing.callCount, 1, 'Doing has been logged'); + t.ok(log.doing.calledWith(`Updating ${extension}`), 'Doing has been logged with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should pull release branch', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'pull'); + + await release.verifyBranches(); + + t.equal(gitClientInstance.pull.callCount, 2, 'Branches have been pulled'); + t.ok(gitClientInstance.pull.calledWith(releaseBranch), 'Release branch have been pulled'); + + sandbox.restore(); + t.end(); +}); + +test('should parse extension manifes', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(taoInstance, 'parseManifest').returns({}); + + await release.verifyBranches(); + + t.equal(taoInstance.parseManifest.callCount, 2, 'Extension manifest has been parsed'); + t.ok(taoInstance.parseManifest.calledWith(`${taoRoot}/${extension}/manifest.php`), 'Extension manifest has been parsed from appropriated extension'); + + sandbox.restore(); + t.end(); +}); + +test('should pull base branch', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'pull'); + + await release.verifyBranches(); + + t.equal(gitClientInstance.pull.callCount, 2, 'Branches have been pulled'); + t.ok(gitClientInstance.pull.calledWith(releaseBranch), 'Base branch have been pulled'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/verifyLocalChanges/test.js b/tests/unit/release/verifyLocalChanges/test.js new file mode 100644 index 0000000..57c07e9 --- /dev/null +++ b/tests/unit/release/verifyLocalChanges/test.js @@ -0,0 +1,120 @@ +/** + * + * Unit test the verifyLocalChanges method of module src/release.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const config = { + write: () => { }, +}; +const extension = 'testExtension'; +const gitClientInstance = { + hasLocalChanges: () => { }, +}; +const gitClientFactory = sandbox.stub().callsFake(() => gitClientInstance); +const log = { + doing: () => { }, + done: () => { }, + exit: () => { }, +}; +const taoRoot = 'testRoot'; +const inquirer = { + prompt: () => ({ extension, taoRoot }), +}; +const taoInstance = { + getExtensions: () => [], + isInstalled: () => true, + isRoot: () => ({ root: true, dir: taoRoot }), +}; +const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const release = proxyquire.noCallThru().load('../../../../src/release.js', { + './config.js': () => config, + './git.js': gitClientFactory, + './log.js': log, + './taoInstance.js': taoINstanceFactory, + inquirer, +})(); + +test('should define verifyLocalChanges method on release instance', (t) => { + t.plan(1); + + t.ok(typeof release.verifyLocalChanges === 'function', 'The release instance has verifyLocalChanges method'); + + t.end(); +}); + +test('should log doing message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(log, 'doing'); + + await release.verifyLocalChanges(); + + t.equal(log.doing.callCount, 1, 'Notify about starting verification of local changes'); + t.ok(log.doing.calledWith('Checking extension status'), 'Notify about starting verification of local changes with apropriate message'); + + sandbox.restore(); + t.end(); +}); + +test('should check if there is any local changes', async (t) => { + t.plan(1); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'hasLocalChanges'); + + await release.verifyLocalChanges(); + + t.equal(gitClientInstance.hasLocalChanges.callCount, 1, 'Local changes has been checked'); + + sandbox.restore(); + t.end(); +}); + +test('should log exit message if there is local changes', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(gitClientInstance, 'hasLocalChanges').returns(false); + sandbox.stub(log, 'done'); + + await release.verifyLocalChanges(); + + t.equal(log.done.callCount, 1, 'Notify about local changes'); + t.ok(log.done.calledWith(`${extension} is clean`), `The extension ${extension} has local changes, please clean or stash them before releasing`); + + sandbox.restore(); + t.end(); +}); + +test('should log done message', async (t) => { + t.plan(2); + + await release.selectTaoInstance(); + await release.selectExtension(); + + sandbox.stub(log, 'done'); + + await release.verifyLocalChanges(); + + t.equal(log.done.callCount, 1, 'Notify about finishing verification of local changes'); + t.ok(log.done.calledWith(`${extension} is clean`), 'Notify about finishing verification of local changes with apropriate message'); + + sandbox.restore(); + t.end(); +}); From 91981dab7144e1494cdc4229375ad1d103b263ca Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Wed, 23 Jan 2019 17:11:44 +0300 Subject: [PATCH 07/11] add tests for github api related changes --- tests/unit/github/test.js | 107 ++++++++++++++- .../unit/githubApiClient/getPRCommits/test.js | 125 ++++++++++++++++++ .../githubApiClientFactory/test.js | 56 ++++++++ .../searchPullRequests/test.js | 82 ++++++++++++ tests/unit/release/updateTranslations/test.js | 24 ++++ tests/unit/release/verifyLocalChanges/test.js | 8 +- 6 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 tests/unit/githubApiClient/getPRCommits/test.js create mode 100644 tests/unit/githubApiClient/githubApiClientFactory/test.js create mode 100644 tests/unit/githubApiClient/searchPullRequests/test.js diff --git a/tests/unit/github/test.js b/tests/unit/github/test.js index 8971a59..845a365 100644 --- a/tests/unit/github/test.js +++ b/tests/unit/github/test.js @@ -8,12 +8,21 @@ const test = require('tape'); const proxyquire = require('proxyquire'); +const sinon = require('sinon'); const token = 'ffaaffe5a8'; const repo = 'foo-sa/bar-project'; +const sandbox = sinon.sandbox.create(); + +const githubApiClientInstance = { + getPRCommits: () => {}, + searchPullRequests: () => {}, +}; +const githubApiClientFactory = sandbox.stub().callsFake(() => githubApiClientInstance); + //load the tested module and mock octonode -const github = proxyquire('../../../src/github.js', { +const github = proxyquire.noCallThru().load('../../../src/github.js', { octonode : { client(){ return { @@ -25,9 +34,9 @@ const github = proxyquire('../../../src/github.js', { }; } }; - }, - '@noCallThru': true - } + } + }, + './githubApiClient': githubApiClientFactory, }); test('the module api', t => { @@ -154,4 +163,94 @@ test('the method formatReleaseNote', t => { t.end(); }); +test('the getPRCommitShas method', async (t) => { + t.plan(4); + + const [owner, name] = repo.split('/'); + const prNumber = 1234; + + const ghclient = github(token, repo); + + t.equal(typeof ghclient.getPRCommitShas, 'function', 'The client exposes the method formatReleaseNote'); + + const commits = { + repository: { + pullRequest: { + commits: { + nodes: [ + { commit: { oid: '1' } }, + { commit: { oid: '2' } }, + ], + pageInfo: { + hasNextPage: false, + }, + }, + }, + }, + }; + + sandbox.stub(githubApiClientInstance, 'getPRCommits').returns(commits); + + const actual = await ghclient.getPRCommitShas(prNumber); + + t.equal(githubApiClientInstance.getPRCommits.callCount, 1, 'Commits have been requested'); + t.ok( + githubApiClientInstance.getPRCommits.calledWith( + prNumber, + name, + owner, + '' + ), + 'Commits have been requested with apropriate arguments' + ); + t.deepEqual( + actual, + commits.repository.pullRequest.commits.nodes.map(({ commit: { oid } }) => oid.slice(0, 8)), + 'Commits have been returned' + ); + + sandbox.restore(); + t.end(); +}); + +test('the extractReleaseNotesFromReleasePR method', async (t) => { + t.plan(3); + const prNumber = 1234; + + const ghclient = github(token, repo); + + t.equal(typeof ghclient.extractReleaseNotesFromReleasePR, 'function', 'The client exposes the method formatReleaseNote'); + + const commits = { + repository: { + pullRequest: { + commits: { + nodes: [ + { commit: { oid: '1' } }, + { commit: { oid: '2' } }, + ], + pageInfo: { + hasNextPage: false, + }, + }, + }, + }, + }; + + sandbox.stub(githubApiClientInstance, 'getPRCommits').returns(commits); + sandbox.stub(githubApiClientInstance, 'searchPullRequests').returns({ search: { nodes: [] } }); + + await ghclient.extractReleaseNotesFromReleasePR(prNumber); + + t.equal(githubApiClientInstance.searchPullRequests.callCount, 1, 'Commits have been requested'); + t.ok( + githubApiClientInstance.searchPullRequests.calledWith( + '1 2 repo:foo-sa/bar-project type:pr base:develop is:merged', + ), + 'Commits have been requested with apropriate arguments' + ); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/githubApiClient/getPRCommits/test.js b/tests/unit/githubApiClient/getPRCommits/test.js new file mode 100644 index 0000000..d1fb697 --- /dev/null +++ b/tests/unit/githubApiClient/getPRCommits/test.js @@ -0,0 +1,125 @@ +/** + * + * Unit test the getPRCommits method of module src/githubApiClient.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const graphqlRequestInstance = { + request: () => { }, +}; +const graphqlRequest = sandbox.stub().callsFake(() => graphqlRequestInstance); +const githubApiClient = proxyquire.noCallThru().load('../../../../src/githubApiClient.js', { + 'graphql-request': { + GraphQLClient: graphqlRequest, + }, +})(); + +test('should request commits', async (t) => { + t.plan(2); + + const prNumber = '1234'; + const repositoryName = 'testRepository'; + const repositoryOwner = 'testOwner'; + const nextPageCursor = 'testCursor'; + + const query = ` + { + repository(owner: "${repositoryOwner}", name: "${repositoryName}") { + pullRequest(number: ${prNumber}) { + commits(first: 250, after: "${nextPageCursor}") { + pageInfo { + endCursor, + hasNextPage, + } + nodes { + commit { + oid + } + } + } + } + } + } + `; + + sandbox.stub(graphqlRequestInstance, 'request'); + + await githubApiClient.getPRCommits( + prNumber, + repositoryName, + repositoryOwner, + nextPageCursor, + ); + + t.equal(graphqlRequestInstance.request.callCount, 1, 'Commits have been requeted'); + t.ok(graphqlRequestInstance.request.calledWith(query), 'Commits have been requeted with apropriate query'); + + sandbox.restore(); + t.end(); +}); + +test('should use empty nextCursor by default', async (t) => { + t.plan(2); + + const prNumber = '1234'; + const repositoryName = 'testRepository'; + const repositoryOwner = 'testOwner'; + const nextPageCursor = ''; + + const query = ` + { + repository(owner: "${repositoryOwner}", name: "${repositoryName}") { + pullRequest(number: ${prNumber}) { + commits(first: 250, after: "${nextPageCursor}") { + pageInfo { + endCursor, + hasNextPage, + } + nodes { + commit { + oid + } + } + } + } + } + } + `; + + sandbox.stub(graphqlRequestInstance, 'request'); + + await githubApiClient.getPRCommits( + prNumber, + repositoryName, + repositoryOwner, + ); + + t.equal(graphqlRequestInstance.request.callCount, 1, 'Commits have been requeted'); + t.ok(graphqlRequestInstance.request.calledWith(query), 'Commits have been requeted with apropriate query'); + + sandbox.restore(); + t.end(); +}); + +test('should return requested commits', async (t) => { + t.plan(1); + + const commits = ['commit1', 'commit2']; + + sandbox.stub(graphqlRequestInstance, 'request').returns(commits); + + const actual = await githubApiClient.getPRCommits(); + + t.equal(actual, commits, 'Commits have been returned'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/githubApiClient/githubApiClientFactory/test.js b/tests/unit/githubApiClient/githubApiClientFactory/test.js new file mode 100644 index 0000000..ae8f107 --- /dev/null +++ b/tests/unit/githubApiClient/githubApiClientFactory/test.js @@ -0,0 +1,56 @@ +/** + * + * Unit test the githubApiClientFactory from module src/githubApiClient.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const graphqlRequest = sandbox.stub(); +const githubApiClientFactory = proxyquire.noCallThru().load('../../../../src/githubApiClient.js', { + 'graphql-request': { + GraphQLClient: graphqlRequest, + }, +}); + +test('should create instance with exposed public methods', (t) => { + t.plan(2); + + const githubApiClient = githubApiClientFactory(); + + t.ok(typeof githubApiClient.getPRCommits === 'function', 'The githubApiClient instance has getPRCommits method'); + t.ok(typeof githubApiClient.searchPullRequests === 'function', 'The githubApiClient instance has searchPullRequests method'); + + t.end(); +}); + +test('should create instance of GraphQLClient', (t) => { + t.plan(2); + + const token = 'testToken'; + + graphqlRequest.resetHistory(); + + githubApiClientFactory(token); + + t.equal(graphqlRequest.callCount, 1, 'GraphQLClient instance created'); + t.ok( + graphqlRequest.calledWith( + 'https://api.github.com/graphql', + { + headers: { + Authorization: `token ${token}`, + }, + }, + ), + 'GraphQLClient initialised with right args' + ); + + t.end(); +}); diff --git a/tests/unit/githubApiClient/searchPullRequests/test.js b/tests/unit/githubApiClient/searchPullRequests/test.js new file mode 100644 index 0000000..e77aea3 --- /dev/null +++ b/tests/unit/githubApiClient/searchPullRequests/test.js @@ -0,0 +1,82 @@ +/** + * + * Unit test the searchPullRequests method of module src/githubApiClient.js + * + * @copyright 2019 Open Assessment Technologies SA; + * @author Anton Tsymuk + */ + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const test = require('tape'); + +const sandbox = sinon.sandbox.create(); + +const graphqlRequestInstance = { + request: () => { }, +}; +const graphqlRequest = sandbox.stub().callsFake(() => graphqlRequestInstance); +const githubApiClient = proxyquire.noCallThru().load('../../../../src/githubApiClient.js', { + 'graphql-request': { + GraphQLClient: graphqlRequest, + }, +})(); + +test('should search pull requests', async (t) => { + t.plan(2); + + const searchQuery = 'testSearchQuery'; + + const query = ` + { + search(first: 100, query: "${searchQuery}", type: ISSUE) { + nodes { + ... on PullRequest { + assignee: assignees(first: 1) { + nodes { + login + } + }, + body, + branch: headRefName, + closedAt, + commit: mergeCommit { + oid + }, + number, + title, + url, + user: author { + login + }, + } + } + } + } + `; + + sandbox.stub(graphqlRequestInstance, 'request'); + + await githubApiClient.searchPullRequests(searchQuery); + + t.equal(graphqlRequestInstance.request.callCount, 1, 'Pull requests search has been requeted'); + t.ok(graphqlRequestInstance.request.calledWith(query), 'Pull requests search has been requeted with apropriate query'); + + sandbox.restore(); + t.end(); +}); + +test('should return found pull requests', async (t) => { + t.plan(1); + + const pullRequests = ['pullRequests1', 'pullRequests2']; + + sandbox.stub(graphqlRequestInstance, 'request').returns(pullRequests); + + const actual = await githubApiClient.searchPullRequests(); + + t.equal(actual, pullRequests, 'Pull requests have been returned'); + + sandbox.restore(); + t.end(); +}); diff --git a/tests/unit/release/updateTranslations/test.js b/tests/unit/release/updateTranslations/test.js index ca61937..4a475ee 100644 --- a/tests/unit/release/updateTranslations/test.js +++ b/tests/unit/release/updateTranslations/test.js @@ -95,6 +95,30 @@ test('should log warn message', async (t) => { t.end(); }); +test('should prompt to update translations', async (t) => { + t.plan(5); + + await release.selectTaoInstance(); + await release.selectExtension(); + await release.verifyBranches(); + + sandbox.stub(inquirer, 'prompt').callsFake(({ type, name, message, default: defaultValue }) => { + t.equal(type, 'confirm', 'The type should be "confirm"'); + t.equal(name, 'translation', 'The param name should be translation'); + t.equal(message, `${extension} needs updated translations ? `, 'Should disaplay appropriate message'); + t.equal(defaultValue, false, 'Default value should be false'); + + return { translation: false }; + }); + + await release.updateTranslations(); + + t.equal(inquirer.prompt.callCount, 1, 'Prompt has been initialised'); + + sandbox.restore(); + t.end(); +}); + test('should update translitions', async (t) => { t.plan(2); diff --git a/tests/unit/release/verifyLocalChanges/test.js b/tests/unit/release/verifyLocalChanges/test.js index 57c07e9..a3541c2 100644 --- a/tests/unit/release/verifyLocalChanges/test.js +++ b/tests/unit/release/verifyLocalChanges/test.js @@ -90,13 +90,13 @@ test('should log exit message if there is local changes', async (t) => { await release.selectTaoInstance(); await release.selectExtension(); - sandbox.stub(gitClientInstance, 'hasLocalChanges').returns(false); - sandbox.stub(log, 'done'); + sandbox.stub(gitClientInstance, 'hasLocalChanges').returns(true); + sandbox.stub(log, 'exit'); await release.verifyLocalChanges(); - t.equal(log.done.callCount, 1, 'Notify about local changes'); - t.ok(log.done.calledWith(`${extension} is clean`), `The extension ${extension} has local changes, please clean or stash them before releasing`); + t.equal(log.exit.callCount, 1, 'Notify about local changes'); + t.ok(log.exit.calledWith(`The extension ${extension} has local changes, please clean or stash them before releasing`), `The extension ${extension} has local changes, please clean or stash them before releasing`); sandbox.restore(); t.end(); From 84eaa4e6bd785f2cc746690f8e6224b9788939a3 Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Wed, 23 Jan 2019 17:52:12 +0300 Subject: [PATCH 08/11] update version --- index.js | 0 package-lock.json | 2 +- package.json | 2 +- src/github.js | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) mode change 100644 => 100755 index.js diff --git a/index.js b/index.js old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json index 95121cd..df0f83e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-extension-release", - "version": "0.3.0", + "version": "0.3.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a78c433..a813277 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-extension-release", - "version": "0.3.1", + "version": "0.3.2", "description": "Helps you to release TAO extensions", "main": "index.js", "scripts": { diff --git a/src/github.js b/src/github.js index ef06526..619a22d 100644 --- a/src/github.js +++ b/src/github.js @@ -331,7 +331,7 @@ module.exports = function githubFactory(token, repository) { return uniqIssue .map(issue => ({ ...issue, - assignee: issue.assignee.nodes[0].login, + assignee: issue.assignee.nodes[0] && issue.assignee.nodes[0].login, commit: issue.commit.oid, user: issue.user.login, closedAt: new Date(issue.closedAt).getTime(), From 2bc32a8e5e14e7f1156059cedafd453757f776e6 Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Mon, 28 Jan 2019 12:22:56 +0300 Subject: [PATCH 09/11] update HISTORY.md --- HISTORY.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 83cebe8..1ccd1d3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -25,3 +25,10 @@ - Translations are false by default - `wwwUser` can be defined in the config file + ## [Version 0.4.0](https://github.com/oat-sa/tao-extension-release/releases/tag/0.4.0) + + - Refactor release functionality into separate module + - Partially migrate application to use github v4 api + - Extract release notes from github pull requests + - Introduce `base-branch`, `branch-prefix`, `origin`, `release-branch`, `www-user` command line arguments + - Cover changes with unit tests From 1ea1d03c993671c9c025e0f65f1f1f01410f6ee1 Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Mon, 28 Jan 2019 12:24:55 +0300 Subject: [PATCH 10/11] resolve PR comments --- README.md | 2 +- index.js | 5 +++++ src/github.js | 11 +++++++++-- src/release.js | 2 +- tests/unit/release/compileAssets/test.js | 6 +++--- tests/unit/release/confirmRelease/test.js | 4 ++-- tests/unit/release/createGithubRelease/test.js | 4 ++-- tests/unit/release/createPullRequest/test.js | 4 ++-- tests/unit/release/createReleaseTag/test.js | 4 ++-- tests/unit/release/createReleasingBranch/test.js | 4 ++-- tests/unit/release/doesReleaseExists/test.js | 4 ++-- tests/unit/release/extractReleaseNotes/test.js | 4 ++-- tests/unit/release/initialiseGithubClient/test.js | 6 +++--- tests/unit/release/isReleaseRequired/test.js | 4 ++-- tests/unit/release/mergeBack/test.js | 4 ++-- tests/unit/release/mergePullRequest/test.js | 4 ++-- tests/unit/release/removeReleasingBranch/test.js | 4 ++-- tests/unit/release/selectExtension/test.js | 4 ++-- tests/unit/release/selectTaoInstance/test.js | 10 +++++----- tests/unit/release/signTags/test.js | 4 ++-- tests/unit/release/updateTranslations/test.js | 4 ++-- tests/unit/release/verifyBranches/test.js | 4 ++-- tests/unit/release/verifyLocalChanges/test.js | 4 ++-- 23 files changed, 59 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index a9b1e72..15bbcd5 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Commandline arguments: - `--branch-prefix` - releasing branch prefix. 'release' by default - `--origin` - git repository origin. 'origin' by default - `--release-branch` - branch to release to. 'master' by default - - `--www-userz` - www user. 'www-data' by default + - `--www-user` - www user. 'www-data' by default ## Development diff --git a/index.js b/index.js index 96fef0c..6952105 100755 --- a/index.js +++ b/index.js @@ -26,7 +26,12 @@ * @author Bertrand Chevrier */ +const updateNotifier = require('update-notifier'); + const log = require('./src/log.js'); +const pkg = require('./package.json'); + +updateNotifier({pkg}).notify(); const argv = require('minimist')(process.argv.slice(2)); diff --git a/src/github.js b/src/github.js index 619a22d..1bfdb27 100644 --- a/src/github.js +++ b/src/github.js @@ -38,6 +38,12 @@ module.exports = function githubFactory(token, repository) { .githubToken(token) .githubRepository(repository); + /* TODO: Since github v4 api does not support all required functionality at the moment of integration, + currently mixed approach is used: + - Github v4 api is used to fetch data. + - octonode package is used for creating pull request and release. + Once github v4 api add support for missing functionality, the application should be fully migrated to the v4 api + */ const client = require('octonode').client(token); const ghrepo = client.repo(repository); const githubApiClient = githubApiClientFactory(token); @@ -313,11 +319,12 @@ module.exports = function githubFactory(token, repository) { */ async extractReleaseNotesFromReleasePR(prNumber) { const commits = await this.getPRCommitShas(prNumber) || []; + const chunkSize = 28; const issues = []; - while (commits.length) { + for (let i = 0; i < commits.length; i += chunkSize) { issues.push(...(await githubApiClient.searchPullRequests( - `${commits.splice(0, 28).join(' ')} repo:${repository} type:pr base:develop is:merged`, + `${commits.slice(i, i + chunkSize).join(' ')} repo:${repository} type:pr base:develop is:merged`, )).search.nodes); } diff --git a/src/release.js b/src/release.js index 9abf52d..da1b2b4 100644 --- a/src/release.js +++ b/src/release.js @@ -184,7 +184,7 @@ module.exports = function taoExtensionReleaseFactory(baseBranch, branchPrefix, o if (repoName) { githubClient = github(data.token, repoName); } else { - log.exit('Unable to find the gitbuh repository name'); + log.exit('Unable to find the github repository name'); } }, diff --git a/tests/unit/release/compileAssets/test.js b/tests/unit/release/compileAssets/test.js index ba919ff..b1af0d8 100644 --- a/tests/unit/release/compileAssets/test.js +++ b/tests/unit/release/compileAssets/test.js @@ -41,12 +41,12 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version, name: extension }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, branchPrefix); @@ -105,7 +105,7 @@ test('should build assets', async (t) => { await release.compileAssets(); - t.equal(taoInstance.buildAssets.callCount, 1, 'Assets has been builded'); + t.equal(taoInstance.buildAssets.callCount, 1, 'Assets has been built'); t.ok(taoInstance.buildAssets.calledWith(extension, false), 'Assets of apropriate extension has been builded'); sandbox.restore(); diff --git a/tests/unit/release/confirmRelease/test.js b/tests/unit/release/confirmRelease/test.js index 5ae0e0b..6408c0e 100644 --- a/tests/unit/release/confirmRelease/test.js +++ b/tests/unit/release/confirmRelease/test.js @@ -36,12 +36,12 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version, name: extension }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(); diff --git a/tests/unit/release/createGithubRelease/test.js b/tests/unit/release/createGithubRelease/test.js index 77798ed..d7ce225 100644 --- a/tests/unit/release/createGithubRelease/test.js +++ b/tests/unit/release/createGithubRelease/test.js @@ -46,13 +46,13 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './github.js': githubFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, branchPrefix, null, releaseBranch); diff --git a/tests/unit/release/createPullRequest/test.js b/tests/unit/release/createPullRequest/test.js index 0acb5f8..84fc51f 100644 --- a/tests/unit/release/createPullRequest/test.js +++ b/tests/unit/release/createPullRequest/test.js @@ -44,13 +44,13 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './github.js': githubFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, branchPrefix, null, releaseBranch); diff --git a/tests/unit/release/createReleaseTag/test.js b/tests/unit/release/createReleaseTag/test.js index e4146af..bf78898 100644 --- a/tests/unit/release/createReleaseTag/test.js +++ b/tests/unit/release/createReleaseTag/test.js @@ -38,12 +38,12 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, null, null, releaseBranch); diff --git a/tests/unit/release/createReleasingBranch/test.js b/tests/unit/release/createReleasingBranch/test.js index d466b58..f8bb5c3 100644 --- a/tests/unit/release/createReleasingBranch/test.js +++ b/tests/unit/release/createReleasingBranch/test.js @@ -38,12 +38,12 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, branchPrefix); diff --git a/tests/unit/release/doesReleaseExists/test.js b/tests/unit/release/doesReleaseExists/test.js index 1a9dadb..e6c64a0 100644 --- a/tests/unit/release/doesReleaseExists/test.js +++ b/tests/unit/release/doesReleaseExists/test.js @@ -37,12 +37,12 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(); diff --git a/tests/unit/release/extractReleaseNotes/test.js b/tests/unit/release/extractReleaseNotes/test.js index ae84e82..79bd808 100644 --- a/tests/unit/release/extractReleaseNotes/test.js +++ b/tests/unit/release/extractReleaseNotes/test.js @@ -46,13 +46,13 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './github.js': githubFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, branchPrefix, null, releaseBranch); diff --git a/tests/unit/release/initialiseGithubClient/test.js b/tests/unit/release/initialiseGithubClient/test.js index 7ce8deb..45073a2 100644 --- a/tests/unit/release/initialiseGithubClient/test.js +++ b/tests/unit/release/initialiseGithubClient/test.js @@ -33,13 +33,13 @@ const taoInstance = { isInstalled: () => true, isRoot: () => ({ root: true, dir: taoRoot }), }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './github.js': githubFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(); @@ -97,7 +97,7 @@ test('should log exit message if can not get repository name', async (t) => { await release.initialiseGithubClient(); t.equal(log.exit.callCount, 1, 'Exit has been logged'); - t.ok(log.exit.calledWith('Unable to find the gitbuh repository name'), 'Exit has been logged with apropriate message'); + t.ok(log.exit.calledWith('Unable to find the github repository name'), 'Exit has been logged with apropriate message'); sandbox.restore(); t.end(); diff --git a/tests/unit/release/isReleaseRequired/test.js b/tests/unit/release/isReleaseRequired/test.js index ac601cf..ffe9f89 100644 --- a/tests/unit/release/isReleaseRequired/test.js +++ b/tests/unit/release/isReleaseRequired/test.js @@ -36,12 +36,12 @@ const taoInstance = { isInstalled: () => true, isRoot: () => ({ root: true, dir: taoRoot }), }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(baseBranch, null, null, releaseBranch); diff --git a/tests/unit/release/mergeBack/test.js b/tests/unit/release/mergeBack/test.js index 8afbd14..bc2466f 100644 --- a/tests/unit/release/mergeBack/test.js +++ b/tests/unit/release/mergeBack/test.js @@ -35,11 +35,11 @@ const taoInstance = { isInstalled: () => true, isRoot: () => ({ root: true, dir: taoRoot }), }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, './log.js': log, inquirer, })(baseBranch, null, null, releaseBranch); diff --git a/tests/unit/release/mergePullRequest/test.js b/tests/unit/release/mergePullRequest/test.js index 15faa21..5b02124 100644 --- a/tests/unit/release/mergePullRequest/test.js +++ b/tests/unit/release/mergePullRequest/test.js @@ -45,14 +45,14 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const opn = sandbox.spy(); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './github.js': githubFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, opn, })(null, branchPrefix, null, releaseBranch); diff --git a/tests/unit/release/removeReleasingBranch/test.js b/tests/unit/release/removeReleasingBranch/test.js index 017134d..80ac86c 100644 --- a/tests/unit/release/removeReleasingBranch/test.js +++ b/tests/unit/release/removeReleasingBranch/test.js @@ -38,12 +38,12 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({ version }) }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, branchPrefix); diff --git a/tests/unit/release/selectExtension/test.js b/tests/unit/release/selectExtension/test.js index 1c9d7ec..3d19d98 100644 --- a/tests/unit/release/selectExtension/test.js +++ b/tests/unit/release/selectExtension/test.js @@ -26,11 +26,11 @@ const taoInstance = { isInstalled: () => true, isRoot: () => ({ root: true, dir: taoRoot }), }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, null, origin); diff --git a/tests/unit/release/selectTaoInstance/test.js b/tests/unit/release/selectTaoInstance/test.js index a29b77f..4823558 100644 --- a/tests/unit/release/selectTaoInstance/test.js +++ b/tests/unit/release/selectTaoInstance/test.js @@ -23,11 +23,11 @@ const taoInstance = { isInstalled: () => ({}), isRoot: () => ({}), }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const wwwUser = 'testwwwUser'; const release = proxyquire.noCallThru().load('../../../../src/release.js', { './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, null, null, null, wwwUser); @@ -65,12 +65,12 @@ test('should initialise taoInstance', async (t) => { const taoRoot = 'testRoot'; sandbox.stub(inquirer, 'prompt').returns({ taoRoot }); - taoINstanceFactory.resetHistory(); + taoInstanceFactory.resetHistory(); await release.selectTaoInstance(); - t.equal(taoINstanceFactory.callCount, 1, 'Instance has been initialised'); - t.ok(taoINstanceFactory.calledWith(path.resolve(taoRoot), false, wwwUser), 'Instance has been initialised with proper args'); + t.equal(taoInstanceFactory.callCount, 1, 'Instance has been initialised'); + t.ok(taoInstanceFactory.calledWith(path.resolve(taoRoot), false, wwwUser), 'Instance has been initialised with proper args'); sandbox.restore(); t.end(); diff --git a/tests/unit/release/signTags/test.js b/tests/unit/release/signTags/test.js index b04c0a6..f9e543b 100644 --- a/tests/unit/release/signTags/test.js +++ b/tests/unit/release/signTags/test.js @@ -29,11 +29,11 @@ const taoInstance = { isInstalled: () => true, isRoot: () => ({ root: true, dir: taoRoot }), }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(); diff --git a/tests/unit/release/updateTranslations/test.js b/tests/unit/release/updateTranslations/test.js index 4a475ee..852d919 100644 --- a/tests/unit/release/updateTranslations/test.js +++ b/tests/unit/release/updateTranslations/test.js @@ -42,12 +42,12 @@ const taoInstance = { parseManifest: () => ({ version, name: extension }), updateTranslations: () => { }, }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(null, branchPrefix); diff --git a/tests/unit/release/verifyBranches/test.js b/tests/unit/release/verifyBranches/test.js index 16be94b..c1b8c60 100644 --- a/tests/unit/release/verifyBranches/test.js +++ b/tests/unit/release/verifyBranches/test.js @@ -36,12 +36,12 @@ const taoInstance = { isRoot: () => ({ root: true, dir: taoRoot }), parseManifest: () => ({}), }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(baseBranch, null, null, releaseBranch); diff --git a/tests/unit/release/verifyLocalChanges/test.js b/tests/unit/release/verifyLocalChanges/test.js index a3541c2..24f4f60 100644 --- a/tests/unit/release/verifyLocalChanges/test.js +++ b/tests/unit/release/verifyLocalChanges/test.js @@ -34,12 +34,12 @@ const taoInstance = { isInstalled: () => true, isRoot: () => ({ root: true, dir: taoRoot }), }; -const taoINstanceFactory = sandbox.stub().callsFake(() => taoInstance); +const taoInstanceFactory = sandbox.stub().callsFake(() => taoInstance); const release = proxyquire.noCallThru().load('../../../../src/release.js', { './config.js': () => config, './git.js': gitClientFactory, './log.js': log, - './taoInstance.js': taoINstanceFactory, + './taoInstance.js': taoInstanceFactory, inquirer, })(); From dcc5e8f423f24d9a159473a89c4494d3f40ee38f Mon Sep 17 00:00:00 2001 From: Anton Tsymuk Date: Mon, 28 Jan 2019 12:25:06 +0300 Subject: [PATCH 11/11] update version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index df0f83e..fb63daa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-extension-release", - "version": "0.3.2", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a813277..82db1fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-extension-release", - "version": "0.3.2", + "version": "0.4.0", "description": "Helps you to release TAO extensions", "main": "index.js", "scripts": {