diff --git a/app/lib/createDeployer.js b/app/lib/createDeployer.js deleted file mode 100644 index 297d9e922..000000000 --- a/app/lib/createDeployer.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict'; - -/** - * Create deploy API factory fn. - */ -function createDeployer({ fetch, fs, FormData }) { - - /** - * Deploy diagram to the given endpoint URL. - */ - return async function deploy(url, { deploymentName, tenantId, file = {} }, cb) { - - try { - // callback is optional - cb = cb || noop; - - if (!deploymentName) { - throw new Error('Failed to deploy process, deployment name must be provided.'); - } - - if (!file.name || !file.path) { - throw new Error('Failed to deploy process, file name and path must be provided.'); - } - - if (!url) { - throw new Error('Failed to deploy process, endpoint url must not be empty.'); - } - - const form = new FormData(); - - form.append('deployment-name', deploymentName); - - if (tenantId) { - form.append('tenant-id', tenantId); - } - - form.append('deploy-changed-only', 'true'); - - form.append(file.name, fs.createReadStream(file.path)); - - const serverResponse = await fetch(url, { method: 'POST', body: form }); - - if (!serverResponse.ok) { - const error = await getErrorFromResponse(serverResponse); - throw error; - } - - let response; - - try { - response = await serverResponse.json(); - } catch (error) { - response = serverResponse.statusText; - } - - return cb(null, response); - } catch (error) { - error.deploymentName = deploymentName; - - return cb(error); - } - - }; -} - -module.exports = createDeployer; - - -// helpers ////// -function noop() { } - - -async function getErrorFromResponse(response) { - const error = new Error(); - - try { - const body = await response.json(); - error.message = body.message; - } catch (_) { - error.message = response.statusText; - } - - error.status = response.status; - error.statusText = response.statusText; - error.url = response.url; - - return error; -} diff --git a/app/lib/deployer.js b/app/lib/deployer.js new file mode 100644 index 000000000..33b85badd --- /dev/null +++ b/app/lib/deployer.js @@ -0,0 +1,166 @@ +'use strict'; + +class Deployer { + constructor({ fetch, fs, FormData }) { + this.fetch = fetch; + this.fs = fs; + this.formDataConstructor = FormData; + } + + /** + * Deploy diagram to the given endpoint URL. + */ + async deploy(url, options, cb = noop) { + try { + this.validateDeployParams(url, options); + + const requestParams = this.getRequestParams(options); + + const serverResponse = await this.fetch(url, requestParams); + + if (!serverResponse.ok) { + const error = await getErrorFromResponse(serverResponse); + throw error; + } + + let response; + + try { + response = await serverResponse.json(); + } catch (error) { + response = serverResponse.statusText; + } + + return cb(null, response); + } catch (error) { + error.deploymentName = options.deploymentName; + + return cb(error); + } + } + + validateDeployParams(url, { deploymentName, file }) { + if (!deploymentName) { + throw new Error('Failed to deploy process, deployment name must be provided.'); + } + + if (!file || !file.name || !file.path) { + throw new Error('Failed to deploy process, file name and path must be provided.'); + } + + if (!url) { + throw new Error('Failed to deploy process, endpoint url must not be empty.'); + } + } + + getRequestParams(options) { + const body = this.getBody(options); + const headers = this.getHeaders(options); + + return { + body, + headers, + method: 'POST' + }; + } + + getBody({ deploymentName, tenantId, file = {} }) { + const form = this.getFormData(); + + form.append('deployment-name', deploymentName); + + if (tenantId) { + form.append('tenant-id', tenantId); + } + + form.append('deploy-changed-only', 'true'); + + form.append(file.name, this.fs.createReadStream(file.path)); + + return form; + } + + getHeaders({ auth }) { + const headers = {}; + + if (auth) { + headers['Authorization'] = this.getAuthHeader(auth); + } + + return headers; + } + + getAuthHeader(auth) { + const authHeaderBuilder = new AuthHeaderBuilder(auth); + + return authHeaderBuilder.build(); + } + + getFormData() { + return new this.formDataConstructor(); + } +} + + +module.exports = Deployer; + + + +// helpers ////// +class AuthHeaderBuilder { + constructor(options) { + this.options = options; + } + + build() { + const { + bearer, + password, + username + } = this.options; + + if (bearer) { + return this.getBearerHeader(bearer); + } + + if (username && password) { + return this.getBasicHeader(username, password); + } + + throw new Error('Unknown auth options.'); + } + + getBearerHeader(bearer) { + return `Bearer ${bearer}`; + } + + getBasicHeader(username, password) { + const credentials = btoa(`${username}:${password}`); + + return `Basic ${credentials}`; + } +} + +function btoa(input) { + return Buffer.from(input, 'utf8').toString('base64'); +} + +function noop() { } + + +async function getErrorFromResponse(response) { + const error = new Error(); + + try { + const body = await response.json(); + error.message = body.message; + } catch (_) { + error.message = response.statusText; + } + + error.status = response.status; + error.statusText = response.statusText; + error.url = response.url; + + return error; +} diff --git a/app/lib/index.js b/app/lib/index.js index 665f488c4..1b75426e1 100644 --- a/app/lib/index.js +++ b/app/lib/index.js @@ -32,7 +32,7 @@ var Platform = require('./platform'), Menu = require('./menu'), Cli = require('./cli'), Plugins = require('./plugins'), - deploy = require('./createDeployer')({ fetch, fs, FormData }); + Deployer = require('./deployer'); var browserOpen = require('./util/browser-open'), renderer = require('./util/renderer'); @@ -86,6 +86,9 @@ var fileSystem = new FileSystem({ dialog: dialog }); +// bootstrap deployer +var deployer = new Deployer({ fetch, fs, FormData }); + // make app a singleton if (config.get('single-instance', true)) { @@ -409,7 +412,7 @@ app.on('ready', function() { function handleDeployment(data, done) { const { endpointUrl } = data; - deploy(endpointUrl, data, function(error, result) { + deployer.deploy(endpointUrl, data, function(error, result) { if (error) { console.error('failed to deploy', error); diff --git a/app/test/spec/deploy-spec.js b/app/test/spec/deploy-spec.js deleted file mode 100644 index b70e329f1..000000000 --- a/app/test/spec/deploy-spec.js +++ /dev/null @@ -1,237 +0,0 @@ -'use strict'; - -const sinon = require('sinon'); - -const createDeployer = require('../../lib/createDeployer'); - -const fetch = require('../helper/mock/fetch'), - fs = require('../helper/mock/fs'), - FormData = require('../helper/mock/form-data'); - - -describe('deploy', function() { - - let fetchSpy; - - beforeEach(() => { - fetchSpy = sinon.spy(fetch); - }); - - afterEach(sinon.restore); - - - it('should deploy with provided parameters', function(done) { - - // given - const deploy = createDeploy(fetchSpy); - - const data = getDeploymentData({ tenantId: 'someTenantId' }); - - const url = 'some/url'; - - const expectedForm = new FormData(); - - expectedForm.append('deployment-name', data.deploymentName); - expectedForm.append(data.file.name, fs.createReadStream(data.file.path)); - expectedForm.append('tenant-id', data.tenantId); - expectedForm.append('deploy-changed-only', 'true'); - - // when - deploy(url, data, (err, data) => { - - // then - expect(fetchSpy).to.have.been.calledWith( - url, - { body: expectedForm, method: 'POST' } - ); - - expect(err).not.to.exist; - expect(data).to.eql(fetch.RESPONSE_OK); - - done(); - }); - - }); - - - it('should deploy even without tenant id provided', function(done) { - - // given - const deploy = createDeploy(fetchSpy); - - const data = getDeploymentData(); - - const url = 'some/url'; - - const expectedForm = new FormData(); - - expectedForm.append('deployment-name', data.deploymentName); - expectedForm.append(data.file.name, fs.createReadStream(data.file.path)); - expectedForm.append('deploy-changed-only', 'true'); - - // when - deploy(url, data, (err, data) => { - - // then - expect(fetchSpy).to.have.been.calledWith( - url, - { body: expectedForm, method: 'POST' } - ); - - expect(err).not.to.exist; - expect(data).to.eql(fetch.RESPONSE_OK); - - done(); - }); - - }); - - - it('should NOT throw error when response is OK but not a JSON', function(done) { - - // given - const okResponse = 'OK'; - - function fetchResolvingToText() { - return Promise.resolve({ - ok: true, - statusText: okResponse, - json() { - return Promise.reject(new Error('fail on json parse')); - } - }); - } - - // given - const deploy = createDeploy(fetchResolvingToText); - - const data = getDeploymentData(); - - // when - deploy('some/url', data, (err, data) => { - - // then - expect(err).to.not.exist; - expect(data).to.eql(okResponse); - - done(); - }); - }); - - - it('should handle fetch error', function(done) { - - // given - const fetchError = 'FETCH_ERROR'; - - function failingFetch() { - return Promise.reject(new Error(fetchError)); - } - - // given - const deploy = createDeploy(failingFetch); - - const data = getDeploymentData(); - - // when - deploy('some/url', data, (err, data) => { - - // then - expect(err).to.exist; - expect(err.message).to.eql(fetchError); - - expect(data).not.to.exist; - - done(); - }); - }); - - - it('should return error with status, statusText and url for backend error', function(done) { - - // given - const url = 'some/url', - errorStatus = 500, - errorStatusText = 'INTERNAL SERVER ERROR'; - - function failingFetch() { - return Promise.resolve({ - url, - ok: false, - status: errorStatus, - statusText: errorStatusText, - json() { - return Promise.reject(new Error('fail on json parse')); - } - }); - } - - // given - const deploy = createDeploy(failingFetch); - - const data = getDeploymentData(); - - // when - deploy(url, data, (err, data) => { - - // then - expect(err).to.exist; - expect(err.status).to.eql(errorStatus); - expect(err.statusText).to.eql(errorStatusText); - expect(err.url).to.eql(url); - - expect(data).not.to.exist; - - done(); - }); - }); - - - it('should attach deployment name to error', function(done) { - - // given - const deploymentName = 'deploymentName'; - - function failingFetch() { - return Promise.reject(new Error()); - } - - const deploy = createDeploy(failingFetch); - - const data = getDeploymentData({ deploymentName }); - - // when - deploy('some/url', data, (err, data) => { - - // then - expect(err).to.exist; - expect(err.deploymentName).to.eql(deploymentName); - - expect(data).not.to.exist; - - done(); - }); - }); - -}); - - - -// helpers ///////// -function createDeploy(fetch) { - return createDeployer({ - fetch, - fs, - FormData - }); -} - -function getDeploymentData(options = {}) { - return Object.assign({ - deploymentName: 'some deployment name', - file: { - name: 'some name', - path: 'some/path' - } - }, options); -} diff --git a/app/test/spec/deployer-spec.js b/app/test/spec/deployer-spec.js new file mode 100644 index 000000000..bfb70b3a3 --- /dev/null +++ b/app/test/spec/deployer-spec.js @@ -0,0 +1,360 @@ +'use strict'; + +const sinon = require('sinon'); + +const Deployer = require('../../lib/deployer'); + +const fetch = require('../helper/mock/fetch'), + fs = require('../helper/mock/fs'), + FormData = require('../helper/mock/form-data'); + + +describe('Deployer', function() { + + let fetchSpy; + + beforeEach(() => { + fetchSpy = sinon.spy(fetch); + }); + + afterEach(sinon.restore); + + + it('should deploy with provided parameters', async function() { + + // given + const deployer = createDeployer(fetchSpy); + + const data = getDeploymentData({ tenantId: 'someTenantId' }); + + const url = 'some/url'; + + const expectedForm = new FormData(); + + expectedForm.append('deployment-name', data.deploymentName); + expectedForm.append(data.file.name, fs.createReadStream(data.file.path)); + expectedForm.append('deploy-changed-only', 'true'); + expectedForm.append('tenant-id', data.tenantId); + + // when + await deployer.deploy(url, data, (err, data) => { + // then + expect(err).not.to.exist; + expect(data).to.eql(fetch.RESPONSE_OK); + }); + + // then + expect(fetchSpy).to.have.been.calledOnce; + + const [ usedUrl, requestParams ] = fetchSpy.getCall(0).args; + + expect(usedUrl).to.eql(url); + expect(requestParams).to.deep.contain({ + body: expectedForm, + method: 'POST' + }); + + }); + + + it('should deploy even without tenant id provided', async function() { + + // given + const deployer = createDeployer(fetchSpy); + + const data = getDeploymentData(); + + const url = 'some/url'; + + const expectedForm = new FormData(); + + expectedForm.append('deployment-name', data.deploymentName); + expectedForm.append('deploy-changed-only', 'true'); + expectedForm.append(data.file.name, fs.createReadStream(data.file.path)); + + // when + await deployer.deploy(url, data, (err, data) => { + // then + expect(err).not.to.exist; + expect(data).to.eql(fetch.RESPONSE_OK); + }); + + // then + expect(fetchSpy).to.have.been.calledOnce; + + const [ usedUrl, requestParams ] = fetchSpy.getCall(0).args; + + expect(usedUrl).to.eql(url); + expect(requestParams).to.deep.contain({ + body: expectedForm, + method: 'POST' + }); + + }); + + + it('should NOT throw error when response is OK but not a JSON', function(done) { + + // given + const okResponse = 'OK'; + + function fetchResolvingToText() { + return Promise.resolve({ + ok: true, + statusText: okResponse, + json() { + return Promise.reject(new Error('fail on json parse')); + } + }); + } + + // given + const deployer = createDeployer(fetchResolvingToText); + + const data = getDeploymentData(); + + // when + deployer.deploy('some/url', data, (err, data) => { + + // then + expect(err).to.not.exist; + expect(data).to.eql(okResponse); + + done(); + }); + }); + + + it('should handle fetch error', function(done) { + + // given + const fetchError = 'FETCH_ERROR'; + + function failingFetch() { + return Promise.reject(new Error(fetchError)); + } + + // given + const deployer = createDeployer(failingFetch); + + const data = getDeploymentData(); + + // when + deployer.deploy('some/url', data, (err, data) => { + + // then + expect(err).to.exist; + expect(err.message).to.eql(fetchError); + + expect(data).not.to.exist; + + done(); + }); + }); + + + it('should return error with proper code for backend error', function(done) { + + // given + const errorStatus = 500, + errorStatusText = 'INTERNAL SERVER ERROR'; + + function failingFetch() { + return Promise.resolve({ + ok: false, + status: errorStatus, + statusText: errorStatusText, + json() { + return Promise.reject(new Error('fail on json parse')); + } + }); + } + + // given + const deployer = createDeployer(failingFetch); + + const data = getDeploymentData(); + + // when + deployer.deploy('some/url', data, (err, data) => { + + // then + expect(err).to.exist; + expect(err.status).to.eql(errorStatus); + + expect(data).not.to.exist; + + done(); + }); + + + it('should attach deployment name to error', function(done) { + + // given + const deploymentName = 'deploymentName'; + + function failingFetch() { + return Promise.reject(new Error()); + } + + const deploy = createDeployer(failingFetch); + + const data = getDeploymentData({ deploymentName }); + + // when + deploy('some/url', data, (err, data) => { + + // then + expect(err).to.exist; + expect(err.deploymentName).to.eql(deploymentName); + + expect(data).not.to.exist; + + done(); + }); + }); + }); + + + describe('authentication', function() { + + it('should deploy without auth', async function() { + // given + const deployer = createDeployer(fetchSpy); + + const data = getDeploymentData({ tenantId: 'someTenantId' }); + + const url = 'some/url'; + + // when + await deployer.deploy(url, data); + + // then + expect(fetchSpy).to.be.calledOnce; + + const requestParams = fetchSpy.getCall(0).args[1]; + + expect(requestParams).to.satisfy(function(params) { + return !params.headers || !params.headers.Authorization; + }); + + }); + + + it('should throw error for unknown auth', async function() { + // given + const deployer = createDeployer(fetchSpy); + + const data = getDeploymentData({ + tenantId: 'someTenantId', + auth: {} + }); + + const url = 'some/url'; + + // when + await deployer.deploy(url, data, (err, data) => { + // then + expect(err).to.exist; + expect(data).not.to.exist; + }); + + expect(fetchSpy).to.not.be.called; + + }); + + + it('should deploy with basic auth', async function() { + // given + const username = 'username', + password = 'password', + credentials = btoa(`${username}:${password}`), + basicHeader = `Basic ${credentials}`; + + const deployer = createDeployer(fetchSpy); + + const data = getDeploymentData({ + tenantId: 'someTenantId', + auth: { + username, + password + } + }); + + const url = 'some/url'; + + // when + await deployer.deploy(url, data); + + // then + expect(fetchSpy).to.be.calledOnce; + + const requestParams = fetchSpy.getCall(0).args[1]; + + expect(requestParams).to.have.property('headers') + .which.has.property('Authorization').eql(basicHeader); + + }); + + + it('should deploy with bearer token', async function() { + // given + const bearerToken = 'bearerToken', + bearerHeader = `Bearer ${bearerToken}`; + + const deployer = createDeployer(fetchSpy); + + const data = getDeploymentData({ + tenantId: 'someTenantId', + auth: { + bearer: bearerToken + } + }); + + const url = 'some/url'; + + // when + await deployer.deploy(url, data); + + // then + expect(fetchSpy).to.be.calledOnce; + + const requestParams = fetchSpy.getCall(0).args[1]; + + expect(requestParams).to.have.property('headers') + .which.has.property('Authorization').eql(bearerHeader); + + }); + + }); + +}); + + + +// helpers ///////// +function createDeployer(fetch) { + return new Deployer({ + fetch, + fs, + FormData + }); +} + +function getDeploymentData(options = {}) { + return Object.assign({ + deploymentName: 'some deployment name', + file: { + name: 'some name', + path: 'some/path' + } + }, options); +} + +/** + * @returns {string} base64 encoded content + * @param {string} text + */ +function btoa(text) { + return Buffer.from(text, 'utf8').toString('base64'); +} diff --git a/client/src/app/modals/deploy-diagram/AuthTypes.js b/client/src/app/modals/deploy-diagram/AuthTypes.js new file mode 100644 index 000000000..56d2be4d3 --- /dev/null +++ b/client/src/app/modals/deploy-diagram/AuthTypes.js @@ -0,0 +1,7 @@ +const AuthTypes = { + none: 'none', + basic: 'basic', + bearer: 'bearer' +}; + +export default AuthTypes; diff --git a/client/src/app/modals/deploy-diagram/DeployDiagramModal.js b/client/src/app/modals/deploy-diagram/DeployDiagramModal.js index db9c281ae..d3f875099 100644 --- a/client/src/app/modals/deploy-diagram/DeployDiagramModal.js +++ b/client/src/app/modals/deploy-diagram/DeployDiagramModal.js @@ -1,6 +1,7 @@ import React from 'react'; import View from './View'; +import AuthTypes from './AuthTypes'; import errorMessageFunctions from './error-messages'; @@ -15,6 +16,17 @@ const defaultState = { error: '' }; +const initialFormValues = { + endpointUrl: '', + tenantId: '', + deploymentName: '', + authType: 'none', + username: '', + password: '', + bearer: '' +}; + + class DeployDiagramModal extends React.Component { constructor(props) { super(props); @@ -69,8 +81,34 @@ class DeployDiagramModal extends React.Component { } } + validateUsername = username => { + if (!username.length) { + return 'Username must not be void.'; + } + } + + validatePassword = password => { + if (!password.length) { + return 'Password must not be void.'; + } + } + + validateBearer = bearer => { + if (!bearer.length) { + return 'Token must not be void.'; + } + } + render() { const { endpoints } = this.props; + const validators = { + endpointUrl: this.validateEndpointUrl, + deploymentName: this.validateDeploymentName, + username: this.validateUsername, + password: this.validatePassword, + bearer: this.validateBearer + }; + return ; } @@ -101,6 +137,12 @@ class DeployDiagramModal extends React.Component { tenantId: values.tenantId }; + const auth = this.getAuth(values); + + if (auth) { + payload.auth = auth; + } + return payload; } @@ -120,6 +162,21 @@ class DeployDiagramModal extends React.Component { return url; } + getAuth({ authType, username, password, bearer }) { + switch (authType) { + case AuthTypes.basic: + return { + username, + password + }; + case AuthTypes.bearer: { + return { + bearer + }; + } + } + } + getErrorMessage(error) { for (const getMessage of errorMessageFunctions) { const errorMessage = getMessage(error); diff --git a/client/src/app/modals/deploy-diagram/View.js b/client/src/app/modals/deploy-diagram/View.js index 49f3d0a0e..62448399d 100644 --- a/client/src/app/modals/deploy-diagram/View.js +++ b/client/src/app/modals/deploy-diagram/View.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import classnames from 'classnames'; @@ -13,6 +13,8 @@ import { ModalWrapper } from '../../primitives'; +import AuthTypes from './AuthTypes'; + import css from './View.less'; @@ -20,112 +22,186 @@ const SUCCESS_HEADING = 'Deployment successful'; const ERROR_HEADING = 'Deployment failed'; -const View = (props) => { - const { - error, - success, - initialValues, - onClose, - onDeploy, - validateEndpointUrl, - validateDeploymentName - } = props; +class View extends Component { - return ( - -

Deploy Diagram

+ constructor(props) { + super(props); -

- Specify deployment details and deploy this diagram to Camunda. -

+ this.state = {}; + } + + toggleDetails = () => { + this.setState({ + deployOpen: !this.state.deployOpen + }); + } + + render() { + const { + error, + success, + initialValues, + onClose, + onDeploy, + validators + } = this.props; + + const deployOpen = this.state.deployOpen; + + return ( + +

Deploy Diagram

+ +

+ Specify deployment details and deploy this diagram to Camunda. +

+ + + + {({ isSubmitting, values }) => ( + + { isSubmitting && } - - {({ errors, isSubmitting, submitCount, touched }) => ( - + { success && } - { isSubmitting && } + { error && } - { success && } +
- { error && } +
- -
- -
+ + Deployment Details + + -
- +
+ - { errors.endpointUrl && touched.endpointUrl ? ( -
{errors.endpointUrl}
- ) : null} + { (deployOpen || values['tenantId']) && } +
-
- Should point to a running Camunda Engine REST API endpoint. +
+ +
+ + Endpoint Configuration + +
+ + +
+ +
+ +
+ + + + + +
+ + { values.authType === AuthTypes.basic && } + + { values.authType === AuthTypes.bearer && } +
+
+ +
+ + +
- - -
- -
- -
- - - { errors.deploymentName && touched.deploymentName ? ( -
{errors.deploymentName}
- ) : null} -
- -
- -
- -
- -
- -
- - - -
- - -
- )} -
- -
+ + + + )} + + +
+ ); + } + +} + +function FormControl({ + field, + hint, + label, + validated, + autoFocus, + form: { touched, errors, submitCount, isSubmitting }, + ...props +}) { + const { name } = field; + + return ( + +
+ +
+ +
+ + + { errors[name] && touched[name] ? ( +
{errors[name]}
+ ) : null} + + { hint ? ( +
{ hint }
+ ) : null } +
+
); -}; +} function DeployError({ message }) { return ( @@ -153,4 +229,42 @@ function DeploySuccess({ message }) { ); } +function AuthBasic({ validators, ...props }) { + return ( + + + + + + ); +} + +function AuthBearer({ validators, ...props }) { + return ( + + ); +} + export default View; diff --git a/client/src/app/modals/deploy-diagram/View.less b/client/src/app/modals/deploy-diagram/View.less index a959ec5fd..005e2168e 100644 --- a/client/src/app/modals/deploy-diagram/View.less +++ b/client/src/app/modals/deploy-diagram/View.less @@ -51,30 +51,53 @@ } form { - font-size: 13.3333px; - margin: 15px 0; + font-size: 1.1em; + + fieldset { + border: solid 1px #E3E3E3; + border-radius: 5px; + margin: 20px auto; + padding: 15px; + + legend { + font-weight: bolder; + background: #EEEEEE; + border-radius: 5px; + padding: 5px 10px; + } + } - display: grid; - grid-template-columns: max-content auto; + .fields { - grid-column-gap: 10px; - grid-row-gap: 15px; + display: grid; + grid-template-columns: 85px auto; - align-items: baseline; + grid-column-gap: 10px; + grid-row-gap: 15px; + + align-items: baseline; + } .form-submit { - grid-column-start: 2; + text-align: right; + } + + .toggle-details { + padding: 0; + background: none; + border: none; + margin: auto 0 0 5px; + width: 10px; } label { - font-weight: bolder; - line-height: 30px; text-align: right; width: 100%; display: inline-block; } - input { + input, + select { width: 100%; padding: 6px; @@ -108,7 +131,8 @@ } button:disabled, - input:disabled { + input:disabled, + select:disabled { color: #808080; } diff --git a/client/src/app/modals/deploy-diagram/__tests__/DeployDiagramModalSpec.js b/client/src/app/modals/deploy-diagram/__tests__/DeployDiagramModalSpec.js index e6db0d29a..7b4959f48 100644 --- a/client/src/app/modals/deploy-diagram/__tests__/DeployDiagramModalSpec.js +++ b/client/src/app/modals/deploy-diagram/__tests__/DeployDiagramModalSpec.js @@ -9,6 +9,7 @@ import { import { DeployDiagramModal } from '..'; import View from '../View'; +import AuthTypes from '../AuthTypes'; const MOCK_ENDPOINT_URL = 'http://example.com/deployment/create'; @@ -337,6 +338,116 @@ describe('', function() { }); + describe('authentication', function() { + + it('should not pass auth option when no auth method was chosen', async function() { + // given + const endpointUrl = 'http://example.com/', + deploymentName = 'deploymentName'; + + const onDeployStub = sinon.stub().resolves(); + + const wrapper = shallow( + + ); + const instance = wrapper.instance(); + + // when + await instance.handleDeploy({ + endpointUrl, + deploymentName + }, { + setSubmitting: sinon.spy() + }); + + // expect + expect(onDeployStub).to.be.calledOnce; + + const payload = onDeployStub.getCall(0).args[0]; + + expect(payload).to.not.have.property('auth'); + }); + + + it('should pass username and password when authenticating with Basic', async function() { + // given + const endpointUrl = 'http://example.com/', + deploymentName = 'deploymentName', + username = 'username', + password = 'password', + authType = AuthTypes.basic; + + const onDeployStub = sinon.stub().resolves(); + + const wrapper = shallow( + + ); + const instance = wrapper.instance(); + + // when + await instance.handleDeploy({ + endpointUrl, + deploymentName, + username, + password, + authType + }, { + setSubmitting: sinon.spy() + }); + + // expect + expect(onDeployStub).to.be.calledOnce; + + const payload = onDeployStub.getCall(0).args[0]; + + expect(payload).to.have.property('auth'); + expect(payload.auth).to.have.property('username').eql(username); + expect(payload.auth).to.have.property('password').eql(password); + }); + + + it('should pass token when authenticating with Bearer', async function() { + // given + const endpointUrl = 'http://example.com/', + deploymentName = 'deploymentName', + bearer = 'bearer', + authType = AuthTypes.bearer; + + const onDeployStub = sinon.stub().resolves(); + + const wrapper = shallow( + + ); + const instance = wrapper.instance(); + + // when + await instance.handleDeploy({ + endpointUrl, + deploymentName, + bearer, + authType + }, { + setSubmitting: sinon.spy() + }); + + // expect + expect(onDeployStub).to.be.calledOnce; + + const payload = onDeployStub.getCall(0).args[0]; + + expect(payload).to.have.property('auth'); + expect(payload.auth).to.have.property('bearer').eql(bearer); + }); + + }); + + describe('', function() { it('should render', function() { @@ -346,7 +457,7 @@ describe('', function() { it('should render error message', function() { // given - const wrapper = mount(); + const wrapper = mount(); // then expect(wrapper.find('.deploy-message.error')).to.have.lengthOf(1); @@ -357,7 +468,7 @@ describe('', function() { it('should render success message', function() { // given - const wrapper = mount(); + const wrapper = mount(); // then expect(wrapper.find('.deploy-message.success')).to.have.lengthOf(1);