diff --git a/commands/pipelines/diff.js b/commands/pipelines/diff.js index 29ef820..9a8a1ac 100644 --- a/commands/pipelines/diff.js +++ b/commands/pipelines/diff.js @@ -108,6 +108,24 @@ function* diff(targetApp, downstreamApp, githubToken, herokuUserAgent) { } } +function* findDownstreamApps(heroku, coupling) { + const allApps = yield cli.action(`Fetching apps from pipeline`, + heroku.request({ + method: 'GET', + path: `/pipelines/${coupling.pipeline.id}/apps`, + headers: { Accept: PIPELINES_HEADER } + })); + + const sourceStage = coupling.stage; + const downstreamStage = PROMOTION_ORDER[PROMOTION_ORDER.indexOf(sourceStage) + 1]; + if (downstreamStage === null || PROMOTION_ORDER.indexOf(sourceStage) < 0) { + throw new Error(`Unable to infer the downstream stage for ${sourceStage}`); + } + return allApps.filter(function (app) { + return app.coupling.stage === downstreamStage; + }); +} + module.exports = { topic: 'pipelines', command: 'diff', @@ -128,26 +146,28 @@ module.exports = { } const targetAppId = coupling.app.id; - const allApps = yield cli.action(`Fetching apps from pipeline`, - heroku.request({ - method: 'GET', - path: `/pipelines/${coupling.pipeline.id}/apps`, - headers: { 'Accept': PIPELINES_HEADER } - })); - - const sourceStage = coupling.stage; - const downstreamStage = PROMOTION_ORDER[PROMOTION_ORDER.indexOf(sourceStage) + 1]; - if (downstreamStage === null || PROMOTION_ORDER.indexOf(sourceStage) < 0) { + // Find downstream apps + let downstreamApps; + try { + downstreamApps = yield findDownstreamApps(heroku, coupling); + } catch (err) { return cli.error(`Unable to diff ${targetAppName}`); } - const downstreamApps = allApps.filter(function(app) { - return app.coupling.stage === downstreamStage; - }); if (downstreamApps.length < 1) { return cli.error(`Cannot diff ${targetAppName} as there are no downstream apps configured`); } + // Fetch GitHub token for the user + let githubAccount; + try { + githubAccount = yield kolkrabbiRequest(`/account/github/token`, heroku.options.token); + } catch (err) { + cli.hush(err); + cli.error(`You need to enable the GitHub integration for your Heroku account`); + return cli.error(`See https://devcenter.heroku.com/articles/github-integration for help`); + } + // Fetch GitHub repo/latest release hash for [target, downstream[0], .., downstream[n]] apps const wrappedGetAppInfo = co.wrap(getAppInfo); const appInfoPromises = [wrappedGetAppInfo(heroku, targetAppName, targetAppId)]; @@ -165,9 +185,6 @@ module.exports = { return cli.error(`No release was found for ${targetAppName}, unable to diff`); } - // Fetch GitHub token for the user - const githubAccount = yield kolkrabbiRequest(`/account/github/token`, heroku.options.token); - // Diff [{target, downstream[0]}, {target, downstream[1]}, .., {target, downstream[n]}] const downstreamAppsInfo = appInfo.slice(1); for (let downstreamAppInfo of downstreamAppsInfo) { diff --git a/test/commands/pipelines/diff.js b/test/commands/pipelines/diff.js index d928c87..5ccd678 100644 --- a/test/commands/pipelines/diff.js +++ b/test/commands/pipelines/diff.js @@ -67,6 +67,12 @@ describe('pipelines:diff', function () { .reply(200, [targetApp, downstreamApp1, downstreamApp2]); } + function mockGithubAccount() { + nock(kolkrabbiApi) + .get(`/account/github/token`) + .reply(200, { github: { token: 'github-token' } }); + } + beforeEach(function () { cli.mockConsole(); }); @@ -75,7 +81,7 @@ describe('pipelines:diff', function () { nock.cleanAll(); }); - describe('for app without a pipeline', function () { + describe('for apps without a pipeline', function () { it('should return an error', function () { const req = nock(api) .get(`/apps/${targetApp.name}/pipeline-couplings`) @@ -89,9 +95,11 @@ describe('pipelines:diff', function () { }); }); - describe('for app with a pipeline but no downstream apps', function () { - it('should return an error', function () { + describe('for apps with a pipeline', function () { + beforeEach(function () { mockPipelineCoupling(); + }); + it('should return an error for pipelines with no downstream apps', function () { nock(api) .get(`/pipelines/${pipeline.id}/apps`) .reply(200, [targetApp]); @@ -101,12 +109,23 @@ describe('pipelines:diff', function () { expect(cli.stderr).to.contain('no downstream apps'); }); }); + it('should return an error if no GitHub account is associated', function () { + mockApps(); + nock(kolkrabbiApi) + .get(`/account/github/token`) + .reply(404); + return cmd.run({ app: targetApp.name }) + .then(function () { + expect(cli.stderr).to.contain('You need to enable the GitHub integration'); + }); + }); }); describe('for invalid apps with a pipeline', function () { beforeEach(function () { mockPipelineCoupling(); mockApps(); + mockGithubAccount(); }); it('should return an error if the app is not connected to GitHub', function () { @@ -144,6 +163,7 @@ describe('pipelines:diff', function () { beforeEach(function () { mockPipelineCoupling(); mockApps(); + mockGithubAccount(); // Mock the GitHub apps for targetApp and downstreamApp1: nock(kolkrabbiApi) @@ -152,9 +172,7 @@ describe('pipelines:diff', function () { .get(`/apps/${downstreamApp1.id}/github`) .reply(200, downstreamApp1Github) .get(`/apps/${downstreamApp2.id}/github`) - .reply(200, downstreamApp2Github) - .get(`/account/github/token`) - .reply(200, { github: { token: 'github-token' } }); + .reply(200, downstreamApp2Github); // Mock latest release/slug endpoints for two apps: nock(api)