From 506b145e75b270cf1c17277b088aeab25e9d12f2 Mon Sep 17 00:00:00 2001 From: Stephen Daly Date: Wed, 20 Jan 2021 11:22:24 +0000 Subject: [PATCH] Revert "PP-7583 Update credentials routes" This reverts commit c913abe1fdf25c926f7a5a2b3406e6038b93d4f0. --- .secrets.baseline | 4 +- app/controllers/credentials.controller.js | 2 +- app/paths.js | 37 +- app/routes.js | 24 +- app/utils/nav-builder.js | 4 +- app/views/credentials/epdq.njk | 2 +- app/views/credentials/smartpay.njk | 4 +- app/views/credentials/worldpay.njk | 2 +- app/views/your-psp/_epdq.njk | 10 +- app/views/your-psp/_smartpay.njk | 10 +- app/views/your-psp/_worldpay.njk | 6 +- .../integration/settings/your-psp.cy.test.js | 2 +- test/integration/credentials.ft.test.js | 623 ++++++++++++++++++ test/ui/credentials.ui.test.js | 152 +++++ 14 files changed, 834 insertions(+), 48 deletions(-) create mode 100644 test/integration/credentials.ft.test.js create mode 100644 test/ui/credentials.ui.test.js diff --git a/.secrets.baseline b/.secrets.baseline index 1a6d084bcb..6e33eb9a23 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "package-lock.json", "lines": null }, - "generated_at": "2021-01-19T17:38:25Z", + "generated_at": "2021-01-19T16:07:47Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -72,7 +72,7 @@ "hashed_secret": "ece65afda87c1c6120602c9a3b66890308d7e53c", "is_secret": false, "is_verified": false, - "line_number": 138, + "line_number": 127, "type": "Secret Keyword" } ], diff --git a/app/controllers/credentials.controller.js b/app/controllers/credentials.controller.js index 1c25941d9d..d5ab40efde 100644 --- a/app/controllers/credentials.controller.js +++ b/app/controllers/credentials.controller.js @@ -97,7 +97,7 @@ module.exports = { if (_.get(req, 'session.flash.genericError.length')) { _.set(req, 'session.pageData.editNotificationCredentials', { username, password }) - return res.redirect(formatAccountPathsFor(paths.account.notificationCredentials.edit, req.account && req.account.external_id)) + return res.redirect(paths.notificationCredentials.edit) } const correlationId = req.headers[CORRELATION_HEADER] || '' diff --git a/app/paths.js b/app/paths.js index 14ebbcb794..982a573110 100644 --- a/app/paths.js +++ b/app/paths.js @@ -19,10 +19,6 @@ module.exports = { revoke: '/api-keys/revoke', update: '/api-keys/update' }, - credentials: { - index: '/credentials', - edit: '/credentials/edit' - }, digitalWallet: { applePay: '/digital-wallet/apple-pay', googlePay: '/digital-wallet/google-pay' @@ -39,10 +35,6 @@ module.exports = { confirmation: '/email-settings-confirmation', refund: '/email-settings-refund' }, - notificationCredentials: { - edit: '/notification-credentials/edit', - update: '/notification-credentials' - }, paymentLinks: { start: '/create-payment-link', information: '/create-payment-link/information', @@ -87,15 +79,6 @@ module.exports = { }, settings: { index: '/settings' - }, - stripe: { - addPspAccountDetails: '/stripe/add-psp-account-details' - }, - stripeSetup: { - bankDetails: '/bank-details', - responsiblePerson: '/responsible-person', - vatNumber: '/vat-number', - companyNumber: '/company-number' }, toggle3ds: { index: '/3ds' @@ -111,6 +94,15 @@ module.exports = { index: '/your-psp', flex: '/your-psp/flex', worldpay3dsFlex: '/your-psp/worldpay-3ds-flex' + }, + stripeSetup: { + bankDetails: '/bank-details', + responsiblePerson: '/responsible-person', + vatNumber: '/vat-number', + companyNumber: '/company-number' + }, + stripe: { + addPspAccountDetails: '/stripe/add-psp-account-details' } }, redirects: { @@ -127,7 +119,16 @@ module.exports = { index: '/all-service-transactions', download: '/all-service-transactions/download' }, - + credentials: { + index: '/credentials', + edit: '/credentials/edit', + create: '/credentials' + }, + notificationCredentials: { + index: '/credentials', + edit: '/notification-credentials/edit', + update: '/notification-credentials' + }, user: { logIn: '/login', otpLogIn: '/otp-login', diff --git a/app/routes.js b/app/routes.js index d6628a924a..b39efe4d4e 100644 --- a/app/routes.js +++ b/app/routes.js @@ -86,27 +86,26 @@ const stripeSetupDashboardRedirectController = require('./controllers/stripe-set // Assignments const { - healthcheck, registerUser, user, dashboard, selfCreateService, transactions, - serviceSwitcher,teamMembers, staticPaths, inviteValidation, editServiceName, merchantDetails, + healthcheck, registerUser, user, dashboard, selfCreateService, transactions, credentials, + serviceSwitcher, teamMembers, staticPaths, inviteValidation, editServiceName, merchantDetails, + notificationCredentials, requestToGoLive, policyPages, allServiceTransactions, payouts, redirects } = paths const { apiKeys, - credentials, digitalWallet, emailNotifications, - notificationCredentials, paymentLinks, paymentTypes, prototyping, settings, - stripe, - stripeSetup, toggle3ds, toggleBillingAddress, toggleMotoMaskCardNumberAndSecurityCode, - yourPsp + yourPsp, + stripeSetup, + stripe } = paths.account // Exports @@ -184,10 +183,13 @@ module.exports.bind = function (app) { const authenticatedPaths = [ ...lodash.values(transactions), ...lodash.values(allServiceTransactions), + ...lodash.values(credentials), + ...lodash.values(notificationCredentials), ...lodash.values(editServiceName), ...lodash.values(serviceSwitcher), ...lodash.values(teamMembers), ...lodash.values(merchantDetails), + ...lodash.values(paymentLinks), ...lodash.values(user.profile), ...lodash.values(requestToGoLive), ...lodash.values(policyPages), @@ -289,9 +291,17 @@ module.exports.bind = function (app) { account.post(yourPsp.flex, permission('gateway-credentials:update'), paymentMethodIsCard, yourPspController.postFlex) // Credentials + app.get(credentials.index, permission('gateway-credentials:read'), getAccount, paymentMethodIsCard, credentialsController.index) + app.get(credentials.edit, permission('gateway-credentials:update'), getAccount, paymentMethodIsCard, credentialsController.editCredentials) + app.post(credentials.index, permission('gateway-credentials:update'), getAccount, paymentMethodIsCard, credentialsController.update) + app.get(notificationCredentials.index, permission('gateway-credentials:read'), getAccount, paymentMethodIsCard, credentialsController.index) + app.get(notificationCredentials.edit, permission('gateway-credentials:update'), getAccount, paymentMethodIsCard, credentialsController.editNotificationCredentials) + app.post(notificationCredentials.update, permission('gateway-credentials:update'), getAccount, paymentMethodIsCard, credentialsController.updateNotificationCredentials) + account.get(credentials.index, permission('gateway-credentials:read'), paymentMethodIsCard, credentialsController.index) account.get(credentials.edit, permission('gateway-credentials:update'), paymentMethodIsCard, credentialsController.editCredentials) account.post(credentials.index, permission('gateway-credentials:update'), paymentMethodIsCard, credentialsController.update) + account.get(notificationCredentials.index, permission('gateway-credentials:read'), paymentMethodIsCard, credentialsController.index) account.get(notificationCredentials.edit, permission('gateway-credentials:update'), paymentMethodIsCard, credentialsController.editNotificationCredentials) account.post(notificationCredentials.update, permission('gateway-credentials:update'), paymentMethodIsCard, credentialsController.updateNotificationCredentials) diff --git a/app/utils/nav-builder.js b/app/utils/nav-builder.js index 6ab35cc713..85ca327500 100644 --- a/app/utils/nav-builder.js +++ b/app/utils/nav-builder.js @@ -17,8 +17,8 @@ const mainSettingsPaths = [ const yourPspPaths = [ paths.account.yourPsp, - paths.account.credentials, - paths.account.notificationCredentials + paths.credentials, + paths.notificationCredentials ] const serviceNavigationItems = (currentPath, permissions, type, account = {}) => { diff --git a/app/views/credentials/epdq.njk b/app/views/credentials/epdq.njk index fe1291e8fd..daad52f1ae 100644 --- a/app/views/credentials/epdq.njk +++ b/app/views/credentials/epdq.njk @@ -3,7 +3,7 @@ {% block provider_content %} {% if permissions.gateway_credentials_update %} -
+ {{ govukInput({ diff --git a/app/views/credentials/smartpay.njk b/app/views/credentials/smartpay.njk index d7c535740e..7a42e8247c 100644 --- a/app/views/credentials/smartpay.njk +++ b/app/views/credentials/smartpay.njk @@ -2,7 +2,7 @@ {% block provider_content %} {% if editNotificationCredentialsMode %} - + {% set username %}{{lastNotificationsData.username or currentGatewayAccount.notificationCredentials.userName}}{% endset %} @@ -47,7 +47,7 @@
{% else %} {% if permissions.gateway_credentials_update %} -
+ {{ govukInput({ diff --git a/app/views/credentials/worldpay.njk b/app/views/credentials/worldpay.njk index 5f99f23f29..ff7b825a29 100644 --- a/app/views/credentials/worldpay.njk +++ b/app/views/credentials/worldpay.njk @@ -2,7 +2,7 @@ {% block provider_content %} {% if permissions.gateway_credentials_update %} - + {% if change === 'merchantId' %} diff --git a/app/views/your-psp/_epdq.njk b/app/views/your-psp/_epdq.njk index d738cab40e..ea0b8d046d 100644 --- a/app/views/your-psp/_epdq.njk +++ b/app/views/your-psp/_epdq.njk @@ -18,7 +18,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=merchantId", + href: routes.credentials.edit + "?change=merchantId", text: "Change", visuallyHiddenText: "account credentials", attributes: { @@ -39,7 +39,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=username", + href: routes.credentials.edit + "?change=username", text: "Change", visuallyHiddenText: "account credentials" } @@ -57,7 +57,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=password", + href: routes.credentials.edit + "?change=password", text: "Change", visuallyHiddenText: "account credentials" } @@ -75,7 +75,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=password", + href: routes.credentials.edit + "?change=password", text: "Change", visuallyHiddenText: "account credentials" } @@ -93,7 +93,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=password", + href: routes.credentials.edit + "?change=password", text: "Change", visuallyHiddenText: "account credentials" } diff --git a/app/views/your-psp/_smartpay.njk b/app/views/your-psp/_smartpay.njk index 1c1236c4ff..654113a90d 100644 --- a/app/views/your-psp/_smartpay.njk +++ b/app/views/your-psp/_smartpay.njk @@ -17,7 +17,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=merchantId", + href: routes.credentials.edit + "?change=merchantId", text: "Change", visuallyHiddenText: "account credentials", attributes: { @@ -38,7 +38,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=username", + href: routes.credentials.edit + "?change=username", text: "Change", visuallyHiddenText: "account credentials" } @@ -56,7 +56,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=password", + href: routes.credentials.edit + "?change=password", text: "Change", visuallyHiddenText: "account credentials" } @@ -94,7 +94,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.notificationCredentials.edit, currentGatewayAccount.external_id) + "?change=username", + href: routes.notificationCredentials.edit + "?change=username", text: "Change", visuallyHiddenText: "account credentials", attributes: { @@ -115,7 +115,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.notificationCredentials.edit, currentGatewayAccount.external_id) + "?change=password", + href: routes.notificationCredentials.edit + "?change=password", text: "Change", visuallyHiddenText: "account credentials" } diff --git a/app/views/your-psp/_worldpay.njk b/app/views/your-psp/_worldpay.njk index 433cc2fc44..30e8bd3cff 100644 --- a/app/views/your-psp/_worldpay.njk +++ b/app/views/your-psp/_worldpay.njk @@ -17,7 +17,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=merchantId", + href: routes.credentials.edit + "?change=merchantId", text: "Change", visuallyHiddenText: "account credentials", attributes: { @@ -38,7 +38,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=username", + href: routes.credentials.edit + "?change=username", text: "Change", visuallyHiddenText: "account credentials" } @@ -56,7 +56,7 @@ actions: { items: [ { - href: formatAccountPathsFor(routes.account.credentials.edit, currentGatewayAccount.external_id) + "?change=password", + href: routes.credentials.edit + "?change=password", text: "Change", visuallyHiddenText: "account credentials" } diff --git a/test/cypress/integration/settings/your-psp.cy.test.js b/test/cypress/integration/settings/your-psp.cy.test.js index a4df82085b..d9ac04d098 100644 --- a/test/cypress/integration/settings/your-psp.cy.test.js +++ b/test/cypress/integration/settings/your-psp.cy.test.js @@ -9,7 +9,7 @@ describe('Your PSP settings page', () => { const gatewayAccountExternalId = 'a-valid-external-id' const serviceName = 'Purchase a positron projection permit' const yourPspPath = `/account/${gatewayAccountExternalId}/your-psp` - + const testCredentials = { merchant_id: 'positron-permit-people', username: 'jonheslop', diff --git a/test/integration/credentials.ft.test.js b/test/integration/credentials.ft.test.js new file mode 100644 index 0000000000..7883499a54 --- /dev/null +++ b/test/integration/credentials.ft.test.js @@ -0,0 +1,623 @@ +'use strict' + +const request = require('supertest') +const nock = require('nock') +const csrf = require('csrf') +const { expect } = require('chai') + +const userCreator = require('../test-helpers/user-creator.js') +require('../test-helpers/serialize-mock.js') +const getApp = require('../../server.js').getApp +const paths = require('../../app/paths.js') +const mockSession = require('../test-helpers/mock-session.js') +const gatewayAccountFixtures = require('../fixtures/gateway-account.fixtures') + +const ACCOUNT_ID = '182364' +const ACCOUNT_EXTERNAL_ID = 'an-account-external-id' +const CONNECTOR_ACCOUNT_PATH = '/v1/frontend/accounts/' + ACCOUNT_ID +// const CONNECTOR_ACCOUNT_BY_EXTERNAL_ID_PATH = '/v1/api/accounts/external-id/' + ACCOUNT_ID +const CONNECTOR_ACCOUNT_CREDENTIALS_PATH = CONNECTOR_ACCOUNT_PATH + '/credentials' +const CONNECTOR_ACCOUNT_NOTIFICATION_CREDENTIALS_PATH = '/v1/api/accounts/' + ACCOUNT_ID + '/notification-credentials' + +const requestId = 'some-unique-id' +const defaultCorrelationHeader = { + reqheaders: { 'x-request-id': requestId } +} +const yourPspPath = `/account/${ACCOUNT_EXTERNAL_ID}/your-psp` + +const connectorMock = nock(process.env.CONNECTOR_URL, defaultCorrelationHeader) + +let app + +describe('Credentials endpoints', () => { + describe('The ' + paths.credentials.index + ' endpoint', function () { + afterEach(function () { + nock.cleanAll() + app = null + }) + + beforeEach(function (done) { + const permissions = 'gateway-credentials:read' + const user = mockSession.getUser({ + gateway_account_ids: [ACCOUNT_ID], permissions: [{ name: permissions }] + }) + app = mockSession.getAppWithLoggedInUser(getApp(), user) + + userCreator.mockUserResponse(user.toJson(), done) + }) + + it('should display empty credential values when no gateway credentials are set', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + credentials: {} + }) + + buildGetRequest(paths.credentials.index, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.credentials).to.be.empty // eslint-disable-line + }) + .end(done) + }) + + it('should display received credentials from connector', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: { username: 'a-username' } + }) + + buildGetRequest(paths.credentials.index, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.credentials).to.deep.equal({ username: 'a-username' }) + }) + .end(done) + }) + + it('should return the account', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: { username: 'a-username', merchant_id: 'a-merchant-id' } + }) + + buildGetRequest(paths.credentials.index, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.gateway_account_id).to.equal('1') + }) + .end(done) + }) + + it('should display an error if the account does not exist', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(404, { + message: "The gateway account id '" + ACCOUNT_ID + "' does not exist" + }) + + buildGetRequest(paths.credentials.index, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + + it('should display an error if connector returns any other error', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(999, { + message: 'Some error in Connector' + }) + + buildGetRequest(paths.credentials.index, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + + it('should display an error if the connection to connector fails', function (done) { + // No connectorMock defined on purpose to mock a network failure + + buildGetRequest(paths.credentials.index, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + }) + + describe('The ' + paths.credentials.edit + ' endpoint', function () { + afterEach(function () { + nock.cleanAll() + app = null + }) + + beforeEach(function (done) { + const permissions = 'gateway-credentials:update' + const user = mockSession.getUser({ + gateway_account_ids: [ACCOUNT_ID], permissions: [{ name: permissions }] + }) + app = mockSession.getAppWithLoggedInUser(getApp(), user) + + userCreator.mockUserResponse(user.toJson(), done) + }) + + it('should display payment provider name', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: {} + }) + + buildGetRequest(paths.credentials.edit, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.payment_provider).to.equal('sandbox') + }) + .end(done) + }) + + it('should display empty credential values when no gateway credentials are set', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: {} + }) + + buildGetRequest(paths.credentials.edit, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.credentials).to.be.empty // eslint-disable-line + }) + .end(done) + }) + + it('should display received credentials from connector', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: { username: 'a-username' } + }) + + buildGetRequest(paths.credentials.edit, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.credentials).to.deep.equal({ username: 'a-username' }) + }) + .end(done) + }) + + it('should return the account', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: { username: 'a-username' } + }) + + buildGetRequest(paths.credentials.edit, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.gateway_account_id).to.equal('1') + }) + .end(done) + }) + + it('should display an error if the account does not exist', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(404, { + message: "The gateway account id '" + ACCOUNT_ID + "' does not exist" + }) + + buildGetRequest(paths.credentials.edit, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + + it('should display an error if connector returns any other error', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(999, { + message: 'Some error in Connector' + }) + + buildGetRequest(paths.credentials.edit, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + + it('should display an error if the connection to connector fails', function (done) { + // No connectorMock defined on purpose to mock a network failure + + buildGetRequest(paths.credentials.edit, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + }) + + describe('The ' + paths.notificationCredentials.edit + ' endpoint', function () { + afterEach(function () { + nock.cleanAll() + app = null + }) + + beforeEach(function (done) { + const permissions = 'gateway-credentials:update' + const user = mockSession.getUser({ + gateway_account_ids: [ACCOUNT_ID], permissions: [{ name: permissions }] + }) + app = mockSession.getAppWithLoggedInUser(getApp(), user) + + userCreator.mockUserResponse(user.toJson(), done) + }) + + it('should display payment provider name', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: {} + }) + + buildGetRequest(paths.notificationCredentials.edit, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.payment_provider).to.equal('sandbox') + }) + .end(done) + }) + + it('should display empty credential values when no gateway credentials are set', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: {} + }) + + buildGetRequest(paths.notificationCredentials.edit, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.credentials).to.be.empty // eslint-disable-line + }) + .end(done) + }) + + it('should display received credentials from connector', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: { username: 'a-username' } + }) + + buildGetRequest(paths.notificationCredentials.edit, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.credentials).to.be.deep.equal({ username: 'a-username' }) + }) + .end(done) + }) + + it('should return the account', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'sandbox', + gateway_account_id: '1', + credentials: { username: 'a-username', merchant_id: 'a-merchant-id' } + }) + + buildGetRequest(paths.notificationCredentials.edit, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.gateway_account_id).to.equal('1') + }) + .end(done) + }) + + it('should display an error if the account does not exist', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(404, { + message: "The gateway account id '" + ACCOUNT_ID + "' does not exist" + }) + + buildGetRequest(paths.notificationCredentials.edit, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + + it('should display an error if connector returns any other error', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(999, { + message: 'Some error in Connector' + }) + + buildGetRequest(paths.notificationCredentials.edit, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + + it('should display an error if the connection to connector fails', function (done) { + // No connectorMock defined on purpose to mock a network failure + + buildGetRequest(paths.notificationCredentials.edit, app) + .expect(500, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + }) + + describe('The notification credentials', function () { + afterEach(function () { + nock.cleanAll() + app = null + }) + + beforeEach(function (done) { + const permissions = 'gateway-credentials:read' + const user = mockSession.getUser({ + gateway_account_ids: [ACCOUNT_ID], permissions: [{ name: permissions }] + }) + app = mockSession.getAppWithLoggedInUser(getApp(), user) + + userCreator.mockUserResponse(user.toJson(), done) + }) + + it('should pass through the notification credentials', function (done) { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, { + payment_provider: 'smartpay', + gateway_account_id: '1', + external_id: ACCOUNT_EXTERNAL_ID, + credentials: { + username: 'a-username', + merchant_id: 'a-merchant-id' + }, + 'notificationCredentials': { username: 'a-notification-username' } + }) + + buildGetRequest(paths.notificationCredentials.index, app) + .expect(200) + .expect(response => { + expect(response.body.currentGatewayAccount.notificationCredentials).to.deep.equal({ username: 'a-notification-username' }) + }) + .end(done) + }) + }) + + describe('The provider update credentials endpoint', function () { + afterEach(function () { + nock.cleanAll() + app = null + }) + + beforeEach(function (done) { + const permissions = 'gateway-credentials:update' + const user = mockSession.getUser({ + gateway_account_ids: [ACCOUNT_ID], permissions: [{ name: permissions }] + }) + app = mockSession.getAppWithLoggedInUser(getApp(), user) + + userCreator.mockUserResponse(user.toJson(), done) + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, + gatewayAccountFixtures.validGatewayAccountResponse({ + gateway_account_id: ACCOUNT_ID, + external_id: ACCOUNT_EXTERNAL_ID + })) + }) + + it('should send new username, password and merchant_id credentials to connector', function (done) { + connectorMock.patch(CONNECTOR_ACCOUNT_CREDENTIALS_PATH, { + credentials: { + username: 'a-username', + password: 'a-password', + merchant_id: 'a-merchant-id' + } + }).reply(200, {}) + + const sendData = { username: 'a-username', password: 'a-password', merchantId: 'a-merchant-id' } + const path = paths.credentials.index + buildFormPostRequest(path, sendData, true, app) + .expect(303, {}) + .expect('Location', yourPspPath) + .end(done) + }) + + it('should send new username, password, merchant_id, sha_in_passphrase and sha_out_passphrase credentials to connector', function (done) { + connectorMock.patch(CONNECTOR_ACCOUNT_CREDENTIALS_PATH, { + credentials: { + username: 'a-username', + password: 'a-password', + merchant_id: 'a-psp-id', + sha_in_passphrase: 'a-sha-in-passphrase', + sha_out_passphrase: 'a-sha-out-passphrase' + + } + }).reply(200, {}) + + const sendData = { + username: 'a-username', + password: 'a-password', + merchantId: 'a-psp-id', + shaInPassphrase: 'a-sha-in-passphrase', + shaOutPassphrase: 'a-sha-out-passphrase' + } + const path = paths.credentials.index + buildFormPostRequest(path, sendData, true, app) + .expect(303, {}) + .expect('Location', yourPspPath) + .end(done) + }) + + it('should send any arbitrary credentials together with username and password to connector', function (done) { + connectorMock.patch(CONNECTOR_ACCOUNT_CREDENTIALS_PATH, { + credentials: { + username: 'a-username', + password: 'a-password' + } + }) + .reply(200, {}) + + const sendData = { username: 'a-username', password: 'a-password' } + const path = paths.credentials.index + buildFormPostRequest(path, sendData, true, app) + .expect(303, {}) + .expect('Location', yourPspPath) + .end(done) + }) + + it('should display an error if connector returns failure', function (done) { + connectorMock.patch(CONNECTOR_ACCOUNT_PATH, { + username: 'a-username', + password: 'a-password' + }) + .reply(999, { + message: 'Error message' + }) + + const sendData = { username: 'a-username', password: 'a-password' } + const expectedData = { message: 'There is a problem with the payments platform' } + const path = paths.credentials.index + buildFormPostRequest(path, sendData, true, app) + .expect(500, expectedData) + .end(done) + }) + + it('should display an error if the connection to connector fails', function (done) { + // No connectorMock defined on purpose to mock a network failure + + const sendData = { username: 'a-username', password: 'a-password' } + const expectedData = { message: 'There is a problem with the payments platform' } + const path = paths.credentials.index + buildFormPostRequest(path, sendData, true, app) + .expect(500, expectedData) + .end(done) + }) + + it('should fail if there is no csrf', done => { + connectorMock.patch(CONNECTOR_ACCOUNT_CREDENTIALS_PATH, { + username: 'a-username', + password: 'a-password' + }).reply(200, {}) + + const sendData = { username: 'a-username', password: 'a-password' } + const path = paths.credentials.index + buildFormPostRequest(path, sendData, false, app) + .expect(400, { message: 'There is a problem with the payments platform' }) + .end(done) + }) + }) + + describe('The provider update notification credentials endpoint', function () { + let session + afterEach(function () { + nock.cleanAll() + app = null + }) + + beforeEach(function (done) { + const permissions = 'gateway-credentials:update' + const user = mockSession.getUser({ + gateway_account_ids: [ACCOUNT_ID], permissions: [{ name: permissions }] + }) + session = mockSession.getMockSession(user) + app = mockSession.createAppWithSession(getApp(), session) + + userCreator.mockUserResponse(user.toJson(), done) + mockConnectorGetAccount() + }) + + it('should send new username and password notification credentials to connector', function (done) { + connectorMock.post(CONNECTOR_ACCOUNT_NOTIFICATION_CREDENTIALS_PATH, { + username: 'a-notification-username', + password: 'a-notification-password' + }) + .reply(200, {}) + + const sendData = { username: 'a-notification-username', password: 'a-notification-password' } + const path = paths.notificationCredentials.update + buildFormPostRequest(path, sendData, true, app) + .expect(303, {}) + .expect('Location', yourPspPath) + .end(done) + }) + + it('should should flash a relevant error if no password is sent', function (done) { + const sendData = { password: 'a-notification-password' } + const path = paths.notificationCredentials.update + buildFormPostRequest(path, sendData, true, app) + .expect(res => { + expect(res.statusCode).to.equal(302) + expect(res.headers.location).to.equal(paths.notificationCredentials.edit) + expect(session.flash.genericError).to.have.property('length').to.equal(1) + expect(session.flash.genericError[0]).to.equal('Enter a username') + }) + .end(done) + }) + it('should should flash a relevant error if no password is sent', function (done) { + const sendData = { username: 'a-notification-username' } + const path = paths.notificationCredentials.update + buildFormPostRequest(path, sendData, true, app) + .expect(res => { + expect(res.statusCode).to.equal(302) + expect(res.headers.location).to.equal(paths.notificationCredentials.edit) + expect(session.flash.genericError).to.have.property('length').to.equal(1) + expect(session.flash.genericError[0]).to.equal('Enter a password') + }) + .end(done) + }) + + it('should should flash a relevant error if too short a password is sent', function (done) { + const sendData = { username: 'a-notification-username', password: '123456789' } + const path = paths.notificationCredentials.update + buildFormPostRequest(path, sendData, true, app) + .expect(res => { + expect(res.statusCode).to.equal(302) + expect(res.headers.location).to.equal(paths.notificationCredentials.edit) + expect(session.flash.genericError).to.have.property('length').to.equal(1) + expect(session.flash.genericError[0]).to.equal('Password must be 10 characters or more') + }) + .end(done) + }) + }) +}) + +function mockConnectorGetAccount () { + connectorMock.get(CONNECTOR_ACCOUNT_PATH) + .reply(200, + gatewayAccountFixtures.validGatewayAccountResponse({ + gateway_account_id: ACCOUNT_ID, + external_id: ACCOUNT_EXTERNAL_ID + })) +} + +function buildGetRequest (path, app) { + return request(app) + .get(path) + .set('Accept', 'application/json') + .set('x-request-id', requestId) +} + +function buildFormPostRequest (path, sendData, sendCSRF, app) { + sendCSRF = (sendCSRF === undefined) ? true : sendCSRF + if (sendCSRF) { + sendData.csrfToken = csrf().create('123') + } + + return request(app) + .post(path) + .set('Accept', 'application/json') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('x-request-id', requestId) + .send(sendData) +} diff --git a/test/ui/credentials.ui.test.js b/test/ui/credentials.ui.test.js new file mode 100644 index 0000000000..138f8394f7 --- /dev/null +++ b/test/ui/credentials.ui.test.js @@ -0,0 +1,152 @@ +'use strict' + +const path = require('path') + +const renderTemplate = require(path.join(__dirname, '/../test-helpers/html-assertions.js')).render +const paths = require(path.join(__dirname, '/../../app/paths.js')) + +describe('The credentials view in edit mode', function () { + it('should display credentials view for a worldpay account', function () { + const templateData = { + currentGatewayAccount: { + 'payment_provider': 'worldpay', + 'credentials': { + 'username': 'a-username', + 'merchant_id': 'a-merchant-id' + } + }, + 'editMode': 'true', + permissions: { + gateway_credentials_update: true + } + } + + const body = renderTemplate('credentials/worldpay', templateData) + + body.should.containSelector('#view-title').withExactText('Your Worldpay credentials') + + body.should.containSelector('form#credentials-form') + .withAttribute('method', 'post') + .withAttribute('action', paths.credentials.create) + + body.should.not.containSelector('a#edit-credentials-link') + + body.should.containInputField('merchantId', 'text') + .withAttribute('value', 'a-merchant-id') + .withLabel('Merchant code') + + body.should.containInputField('username', 'text') + .withAttribute('value', 'a-username') + .withLabel('Username') + + body.should.containInputField('password', 'password') + .withLabel('Password') + + body.should.containSelector('#submitCredentials') + }) + + it('should display credentials view for a smartpay account', function () { + const templateData = { + currentGatewayAccount: { + 'payment_provider': 'smartpay', + 'credentials': { + 'username': 'a-username', + 'merchant_id': 'a-merchant-id' + } + }, + 'editMode': 'true', + permissions: { + gateway_credentials_update: true + } + } + + const body = renderTemplate('credentials/smartpay', templateData) + + body.should.containSelector('#view-title').withExactText('Your Smartpay credentials') + + body.should.containSelector('form#credentials-form') + .withAttribute('method', 'post') + .withAttribute('action', paths.credentials.create) + + body.should.not.containSelector('a#edit-credentials-link') + + body.should.containInputField('merchantId', 'text') + .withAttribute('value', 'a-merchant-id') + .withLabel('Merchant account code') + + body.should.containInputField('username', 'text') + .withAttribute('value', 'a-username') + .withLabel('Username') + + body.should.containInputField('password', 'password') + .withLabel('Password') + + body.should.containSelector('#submitCredentials') + }) + + it('should display credentials view for a ePDQ account', function () { + const templateData = { + currentGatewayAccount: { + 'payment_provider': 'epdq', + 'credentials': { + 'username': 'a-username', + 'merchant_id': 'a-psp-id' + } + }, + 'editMode': 'true', + permissions: { + gateway_credentials_update: true + } + } + + let body = renderTemplate('credentials/epdq', templateData) + + body.should.containSelector('#view-title').withExactText('Your ePDQ credentials') + + body.should.containSelector('form#credentials-form') + .withAttribute('method', 'post') + .withAttribute('action', paths.credentials.create) + + body.should.not.containSelector('a#edit-credentials-link') + + body.should.containInputField('merchantId', 'text') + .withAttribute('value', 'a-psp-id') + .withLabel('PSP ID') + + body.should.containInputField('username', 'text') + .withAttribute('value', 'a-username') + .withLabel('Username') + + body.should.containInputField('password', 'password') + .withLabel('Password') + + body.should.containInputField('shaInPassphrase', 'password') + .withLabel('SHA-IN passphrase') + + body.should.containInputField('shaOutPassphrase', 'password') + .withLabel('SHA-OUT passphrase') + + body.should.containSelector('#submitCredentials') + }) + + it('should display error page for `stripe`', function () { + const templateData = { + currentGatewayAccount: { + 'payment_provider': 'stripe', + 'credentials': {} + }, + 'editMode': 'true', + permissions: { + gateway_credentials_update: true + } + } + + const body = renderTemplate('404', templateData) + + body.should.containSelector('h1:first-of-type').withExactText('Page not found') + + body.should.not.containSelector('form#credentials-form') + body.should.not.containSelector('a#edit-credentials-link') + body.should.not.containSelector('input#submitCredentials') + }) +})