From 57c72795406ac3dcea0effcedfbe773fa50320ca Mon Sep 17 00:00:00 2001 From: Patrick Streule Date: Tue, 20 Feb 2018 07:33:09 +1100 Subject: [PATCH] [Bitbucket] #1509 - Add Bitbucket Pipelines service (#1510) * [Bitbucket] #1509 - Add Bitbucket Pipelines service * Use ES6 const and error helper --- lib/all-badge-examples.js | 8 +++ server.js | 61 +++++++++++++++++++ service-tests/bitbucket.js | 120 ++++++++++++++++++++++++++++++++++++- 3 files changed, 187 insertions(+), 2 deletions(-) diff --git a/lib/all-badge-examples.js b/lib/all-badge-examples.js index cabb8c52f7..edac6b008e 100644 --- a/lib/all-badge-examples.js +++ b/lib/all-badge-examples.js @@ -212,6 +212,14 @@ const allBadgeExamples = [ title: 'Jenkins coverage', previewUri: '/jenkins/c/https/jenkins.qa.ubuntu.com/view/Utopic/view/All/job/address-book-service-utopic-i386-ci.svg' }, + { + title: 'Bitbucket Pipelines', + previewUri: '/bitbucket/pipelines/atlassian/adf-builder-javascript.svg' + }, + { + title: 'Bitbucket Pipelines branch', + previewUri: '/bitbucket/pipelines/atlassian/adf-builder-javascript/task/SECO-2168.svg' + }, { title: 'Coveralls github', previewUri: '/coveralls/github/jekyll/jekyll.svg' diff --git a/server.js b/server.js index b62801885f..5d65e92e1b 100644 --- a/server.js +++ b/server.js @@ -4458,6 +4458,67 @@ cache(function(data, match, sendBadge, request) { }); })); +// Bitbucket Pipelines integration. +camp.route(/^\/bitbucket\/pipelines\/([^/]+)\/([^/]+)(?:\/(.+))?\.(svg|png|gif|jpg|json)$/, +cache(function(data, match, sendBadge, request) { + const user = match[1]; // eg, atlassian + const repo = match[2]; // eg, adf-builder-javascript + const branch = match[3] || 'master'; // eg, development + const format = match[4]; + const apiUrl = 'https://api.bitbucket.org/2.0/repositories/' + + encodeURIComponent(user) + '/' + encodeURIComponent(repo) + + '/pipelines/?fields=values.state&page=1&pagelen=2&sort=-created_on' + + '&target.ref_type=BRANCH&target.ref_name=' + encodeURIComponent(branch); + + const badgeData = getBadgeData('build', data); + + request(apiUrl, function(err, res, buffer) { + if (checkErrorResponse(badgeData, err, res)) { + sendBadge(format, badgeData); + return; + } + try { + const data = JSON.parse(buffer); + if (!data.values) { + throw Error('Unexpected response'); + } + const values = data.values.filter(value => value.state && value.state.name === 'COMPLETED'); + if (values.length > 0) { + switch (values[0].state.result.name) { + case 'SUCCESSFUL': + badgeData.text[1] = 'passing'; + badgeData.colorscheme = 'brightgreen'; + break; + case 'FAILED': + badgeData.text[1] = 'failing'; + badgeData.colorscheme = 'red'; + break; + case 'ERROR': + badgeData.text[1] = 'error'; + badgeData.colorscheme = 'red'; + break; + case 'STOPPED': + badgeData.text[1] = 'stopped'; + badgeData.colorscheme = 'yellow'; + break; + case 'EXPIRED': + badgeData.text[1] = 'expired'; + badgeData.colorscheme = 'yellow'; + break; + default: + badgeData.text[1] = 'unknown'; + } + } else { + badgeData.text[1] = 'never built'; + } + sendBadge(format, badgeData); + } catch(e) { + badgeData.text[1] = 'invalid'; + sendBadge(format, badgeData); + } + }); +})); + // Chef cookbook integration. camp.route(/^\/cookbook\/v\/(.*)\.(svg|png|gif|jpg|json)$/, cache(function(data, match, sendBadge, request) { diff --git a/service-tests/bitbucket.js b/service-tests/bitbucket.js index 32c110e266..c10175c3f4 100644 --- a/service-tests/bitbucket.js +++ b/service-tests/bitbucket.js @@ -6,8 +6,9 @@ const { isMetric, isMetricOpenIssues } = require('./helpers/validators.js'); +const { invalidJSON } = require('./helpers/response-fixtures'); -const t = new ServiceTester({ id: 'bitbucket', title: 'BitBucket badges' }); +const t = new ServiceTester({ id: 'bitbucket', title: 'Bitbucket badges' }); module.exports = t; @@ -76,7 +77,7 @@ t.create('pr-raw (connection error)') .networkOff() .expectJSON({ name: 'pull requests', value: 'inaccessible' }); - t.create('pr (valid)') +t.create('pr (valid)') .get('/pr/atlassian/python-bitbucket.json') .expectJSONTypes(Joi.object().keys({ name: 'pull requests', @@ -95,3 +96,118 @@ t.create('pr (connection error)') .get('/pr/atlassian/python-bitbucket.json') .networkOff() .expectJSON({ name: 'pull requests', value: 'inaccessible' }); + + +// tests for Bitbucket Pipelines + +function bitbucketApiResponse(status) { + return JSON.stringify({ + "values": [ + { + "state": { + "type": "pipeline_state_completed", + "name": "COMPLETED", + "result": { + "type": "pipeline_state_completed_xyz", + "name": status + } + } + } + ] + }); +} + +t.create('master build result (valid)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .expectJSONTypes(Joi.object().keys({ + name: 'build', + value: Joi.equal('failing', 'passing', 'error', 'stopped', 'expired') + })); + +t.create('master build result (not found)') + .get('/pipelines/atlassian/not-a-repo.json') + .expectJSON({ name: 'build', value: 'not found' }) + +t.create('branch build result (valid)') + .get('/pipelines/atlassian/adf-builder-javascript/shields-test-dont-remove.json') + .expectJSONTypes(Joi.object().keys({ + name: 'build', + value: Joi.equal('failing', 'passing', 'error', 'stopped', 'expired') + })); + +t.create('branch build result (not found)') + .get('/pipelines/atlassian/not-a-repo/some-branch.json') + .expectJSON({ name: 'build', value: 'not found' }) + +t.create('branch build result (never built)') + .get('/pipelines/atlassian/adf-builder-javascript/some/new/branch.json') + .expectJSON({ name: 'build', value: 'never built' }) + +t.create('build result (passing)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .intercept(nock => nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('SUCCESSFUL')) + ) + .expectJSON({ name: 'build', value: 'passing' }) + +t.create('build result (failing)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .intercept(nock => nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('FAILED')) + ) + .expectJSON({ name: 'build', value: 'failing' }) + +t.create('build result (error)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .intercept(nock => nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('ERROR')) + ) + .expectJSON({ name: 'build', value: 'error' }) + +t.create('build result (stopped)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .intercept(nock => nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('STOPPED')) + ) + .expectJSON({ name: 'build', value: 'stopped' }) + +t.create('build result (expired)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .intercept(nock => nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('EXPIRED')) + ) + .expectJSON({ name: 'build', value: 'expired' }) + +t.create('build result (unknown)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .intercept(nock => nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, bitbucketApiResponse('NEW_AND_UNEXPECTED')) + ) + .expectJSON({ name: 'build', value: 'unknown' }) + +t.create('build result (empty json)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .intercept(nock => nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(200, '{}') + ) + .expectJSON({ name: 'build', value: 'invalid' }) + +t.create('build result (invalid json)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .intercept(nock => nock('https://api.bitbucket.org') + .get(/^\/2.0\/.*/) + .reply(invalidJSON) + ) + .expectJSON({ name: 'build', value: 'invalid' }) + +t.create('build result (network error)') + .get('/pipelines/atlassian/adf-builder-javascript.json') + .networkOff() + .expectJSON({ name: 'build', value: 'inaccessible' });