From 187ba8fea74c5e51eb170947820b199a3a7f7af3 Mon Sep 17 00:00:00 2001 From: Nathaniel Steers Date: Mon, 9 Dec 2024 19:16:41 +0000 Subject: [PATCH] PP-12444 cypress test responsible person / government entity tasks (#4379) cypress test responsible person / government entity tasks - cypress tests for the responsible person and government entity simplified account stripe on-boarding tasks - stripe client updated to correctly override the host value for Stripe file operations - prevent users from accessing the government entity upload task without completing all other on-boarding steps --- .../government-entity-document.controller.js | 2 +- app/errors.js | 9 +- app/middleware/error-handler.js | 9 +- .../check-task-completion.middleware.js | 11 +- app/services/clients/stripe/stripe.client.js | 5 +- .../settings/stripe-details/tasks.js | 8 +- .../government-entity-document.cy.js | 289 +++++++++++++++ .../stripe-details/responsible-person.cy.js | 342 ++++++++++++++++++ test/cypress/stubs/stripe-psp-stubs.js | 27 +- test/cypress/test.env | 1 + 10 files changed, 694 insertions(+), 9 deletions(-) create mode 100644 test/cypress/integration/simplified-account/service-settings/stripe-details/government-entity-document.cy.js create mode 100644 test/cypress/integration/simplified-account/service-settings/stripe-details/responsible-person.cy.js diff --git a/app/controllers/simplified-account/settings/stripe-details/government-entity-document/government-entity-document.controller.js b/app/controllers/simplified-account/settings/stripe-details/government-entity-document/government-entity-document.controller.js index 6703b95d8..6cee6d8b3 100644 --- a/app/controllers/simplified-account/settings/stripe-details/government-entity-document/government-entity-document.controller.js +++ b/app/controllers/simplified-account/settings/stripe-details/government-entity-document/government-entity-document.controller.js @@ -52,7 +52,7 @@ async function post (req, res, next) { summary: [{ text: 'Error uploading file to stripe. Try uploading a file with one of the following types: pdf, jpeg, png', href: '#government-entity-document' }] }) } - next(err) + return next(err) } req.flash('messages', { state: 'success', icon: '✓', heading: 'Service connected to Stripe', body: 'This service can now take payments' }) res.redirect(formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.stripeDetails.index, req.service.externalId, req.account.type)) diff --git a/app/errors.js b/app/errors.js index 8dd1540c9..8f2275c61 100644 --- a/app/errors.js +++ b/app/errors.js @@ -96,6 +96,12 @@ class ExpiredInviteError extends DomainError { class TaskAlreadyCompletedError extends DomainError { } +/** + * Thrown when trying to visit a task page when the requisite tasks have not been completed + */ +class TaskAccessedOutOfSequenceError extends DomainError { +} + module.exports = { NotAuthenticatedError, UserAccountDisabledError, @@ -109,5 +115,6 @@ module.exports = { ExpiredInviteError, GatewayTimeoutError, GatewayTimeoutForAllServicesSearchError, - TaskAlreadyCompletedError + TaskAlreadyCompletedError, + TaskAccessedOutOfSequenceError } diff --git a/app/middleware/error-handler.js b/app/middleware/error-handler.js index 64406bba6..4c7e605f9 100644 --- a/app/middleware/error-handler.js +++ b/app/middleware/error-handler.js @@ -17,7 +17,9 @@ const { InvalidConfigurationError, ExpiredInviteError, GatewayTimeoutError, - GatewayTimeoutForAllServicesSearchError, TaskAlreadyCompletedError + GatewayTimeoutForAllServicesSearchError, + TaskAlreadyCompletedError, + TaskAccessedOutOfSequenceError } = require('../errors') const paths = require('../paths') const { renderErrorView, response } = require('../utils/response') @@ -78,6 +80,11 @@ module.exports = function errorHandler (err, req, res, next) { return renderErrorView(req, res, 'This invitation is no longer valid', 410) } + if (err instanceof TaskAccessedOutOfSequenceError) { + logger.info(`TaskAccessedOutOfSequenceError handled: ${err.message}. Redirecting`) + return res.redirect(formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.stripeDetails.index, req.service.externalId, req.account.type)) + } + if (err instanceof TaskAlreadyCompletedError) { logger.info(`TaskAlreadyCompletedError handled: ${err.message}. Rendering error page`) res.status(302) diff --git a/app/middleware/simplified-account/check-task-completion.middleware.js b/app/middleware/simplified-account/check-task-completion.middleware.js index 37362a336..5d15f8eeb 100644 --- a/app/middleware/simplified-account/check-task-completion.middleware.js +++ b/app/middleware/simplified-account/check-task-completion.middleware.js @@ -1,10 +1,17 @@ -const { TaskAlreadyCompletedError } = require('../../errors') +const { TaskAlreadyCompletedError, TaskAccessedOutOfSequenceError } = require('@root/errors') +const { + stripeDetailsTasks, + canStartGovernmentEntityDocument +} = require('@utils/simplified-account/settings/stripe-details/tasks') module.exports = function checkTaskCompletion (task) { return function (req, res, next) { const stripeTaskProgress = req.account.connectorGatewayAccountStripeProgress if (stripeTaskProgress[task]) { - next(new TaskAlreadyCompletedError(`Attempted to access task page after completion [task: ${task}]`)) + next(new TaskAlreadyCompletedError(`Attempted to access task page after completion [task: ${task}, serviceExternalId: ${req.service.externalId}]`)) + } + if (task === stripeDetailsTasks.governmentEntityDocument.name && !canStartGovernmentEntityDocument(stripeTaskProgress)) { + next(new TaskAccessedOutOfSequenceError(`Attempted to access task page before completing requisite tasks [task: ${task}, serviceExternalId: ${req.service.externalId}]`)) } next() } diff --git a/app/services/clients/stripe/stripe.client.js b/app/services/clients/stripe/stripe.client.js index 8f27ac486..b0c3fa0db 100644 --- a/app/services/clients/stripe/stripe.client.js +++ b/app/services/clients/stripe/stripe.client.js @@ -1,5 +1,3 @@ -'use strict' - const ProxyAgent = require('https-proxy-agent') const StripeBankAccount = require('./StripeBankAccount.class') @@ -11,6 +9,7 @@ const StripeOrganisationDetails = require('./StripeOrganisationDetails.class') // Constants const STRIPE_HOST = process.env.STRIPE_HOST +const STRIPE_FILES_HOST = process.env.STRIPE_FILES_HOST const STRIPE_PORT = process.env.STRIPE_PORT const STRIPE_PROTOCOL = process.env.STRIPE_PROTOCOL @@ -124,6 +123,8 @@ module.exports = { type: fileType }, purpose: 'identity_document' + }, STRIPE_FILES_HOST && { + host: STRIPE_FILES_HOST }) } } diff --git a/app/utils/simplified-account/settings/stripe-details/tasks.js b/app/utils/simplified-account/settings/stripe-details/tasks.js index d387e3024..a9fbb2fc1 100644 --- a/app/utils/simplified-account/settings/stripe-details/tasks.js +++ b/app/utils/simplified-account/settings/stripe-details/tasks.js @@ -56,6 +56,11 @@ const orderTasks = (target) => { return orderedTasks } +const canStartGovernmentEntityDocument = (gatewayAccountStripeProgress) => { + return Object.entries(gatewayAccountStripeProgress) + .every(([key, value]) => key === stripeDetailsTasks.governmentEntityDocument.name ? value !== true : value === true) +} + /** * @typedef {Object} friendlyStripeTasks * @property {string} href - formatted path for task @@ -95,5 +100,6 @@ const friendlyStripeTasks = (account, service) => { module.exports = { friendlyStripeTasks, - stripeDetailsTasks + stripeDetailsTasks, + canStartGovernmentEntityDocument } diff --git a/test/cypress/integration/simplified-account/service-settings/stripe-details/government-entity-document.cy.js b/test/cypress/integration/simplified-account/service-settings/stripe-details/government-entity-document.cy.js new file mode 100644 index 000000000..be4c2421d --- /dev/null +++ b/test/cypress/integration/simplified-account/service-settings/stripe-details/government-entity-document.cy.js @@ -0,0 +1,289 @@ +const userStubs = require('@test/cypress/stubs/user-stubs') +const gatewayAccountStubs = require('@test/cypress/stubs/gateway-account-stubs') +const stripeAccountSetupStubs = require('@test/cypress/stubs/stripe-account-setup-stub') +const { STRIPE, WORLDPAY } = require('@models/payment-providers') +const stripePspStubs = require('@test/cypress/stubs/stripe-psp-stubs') +const ROLES = require('@test/fixtures/roles.fixtures') + +const USER_EXTERNAL_ID = 'user-123-abc' +const SERVICE_EXTERNAL_ID = 'service-456-def' +const SERVICE_NAME = { + en: 'McDuck Enterprises', cy: 'Mentrau McDuck' +} +const LIVE_ACCOUNT_TYPE = 'live' +const GATEWAY_ACCOUNT_ID = 10 +const STRIPE_ACCOUNT_ID = 'acct_123example123' + +const STRIPE_DETAILS_SETTINGS_URL = `/simplified/service/${SERVICE_EXTERNAL_ID}/account/${LIVE_ACCOUNT_TYPE}/settings/stripe-details` + +const setStubs = (opts = {}, additionalStubs = []) => { + cy.task('setupStubs', [ + userStubs.getUserSuccess({ + userExternalId: USER_EXTERNAL_ID, + gatewayAccountId: GATEWAY_ACCOUNT_ID, + serviceName: SERVICE_NAME, + serviceExternalId: SERVICE_EXTERNAL_ID, + merchantDetails: { + name: 'McDuck Enterprises', + address_line1: 'McDuck Manor', + address_city: 'Duckburg', + address_postcode: 'SW1A 1AA' + }, + role: ROLES[opts.role || 'admin'], + features: 'degatewayaccountification' // TODO remove features once simplified accounts are live + }), + gatewayAccountStubs.getAccountByServiceIdAndAccountType(SERVICE_EXTERNAL_ID, LIVE_ACCOUNT_TYPE, { + gateway_account_id: GATEWAY_ACCOUNT_ID, + type: LIVE_ACCOUNT_TYPE, + payment_provider: opts.paymentProvider || STRIPE, + provider_switch_enabled: opts.providerSwitchEnabled || false + }), + ...additionalStubs]) +} + +describe('Stripe details settings', () => { + beforeEach(() => { + cy.setEncryptedCookies(USER_EXTERNAL_ID) + }) + describe('The government entity document task', () => { + describe('For a non-admin', () => { + beforeEach(() => { + setStubs({ + role: 'view-and-refund' + }) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/government-entity-document', { failOnStatusCode: false }) + }) + it('should show not found page', () => { + cy.title().should('eq', 'Page not found - GOV.UK Pay') + cy.get('h1').should('contain.text', 'Page not found') + }) + }) + describe('For a non-stripe service', () => { + beforeEach(() => { + setStubs({ + paymentProvider: WORLDPAY + }) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/government-entity-document', { failOnStatusCode: false }) + }) + it('should show not found page', () => { + cy.title().should('eq', 'Page not found - GOV.UK Pay') + cy.get('h1').should('contain.text', 'Page not found') + }) + }) + describe('Completed', () => { + beforeEach(() => { + setStubs({}, [ + stripeAccountSetupStubs.getStripeSetupProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE, + bankAccount: true, + companyNumber: true, + vatNumber: true, + director: true, + responsiblePerson: true, + organisationDetails: true, + governmentEntityDocument: true + }) + ]) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/government-entity-document', { failOnStatusCode: false }) + }) + it('should show the task already completed page', () => { + cy.title().should('eq', 'An error occurred - GOV.UK Pay') + cy.get('h1').should('contain', 'You\'ve already completed this task') + }) + }) + + describe('Cannot start yet', () => { + beforeEach(() => { + setStubs({}, [ + stripeAccountSetupStubs.getStripeSetupProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE, + bankAccount: true, + companyNumber: true, + vatNumber: true, + director: false, + responsiblePerson: false, + organisationDetails: false, + governmentEntityDocument: false + }) + ]) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/government-entity-document') + }) + it('should redirect to the task summary', () => { + cy.title().should('eq', 'Settings - Stripe details - GOV.UK Pay') + cy.get('h1').should('contain', 'Stripe details') + cy.location('pathname').should('not.contain', '/government-entity-document') + }) + }) + describe('Not yet started', () => { + beforeEach(() => { + setStubs({}, [ + stripeAccountSetupStubs.getStripeSetupProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE, + bankAccount: true, + companyNumber: true, + vatNumber: true, + director: true, + responsiblePerson: true, + organisationDetails: true, + governmentEntityDocument: false + }) + ]) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/government-entity-document') + }) + describe('The settings navigation', () => { + it('should show stripe details', () => { + cy.get('.service-settings-nav') + .find('li') + .contains('Stripe details') + .then(li => { + cy.wrap(li) + .should('have.attr', 'href', STRIPE_DETAILS_SETTINGS_URL) + .parent().should('have.class', 'service-settings-nav__li--active') + }) + }) + }) + describe('The task page', () => { + it('should show the correct title', () => { + cy.title().should('eq', 'Settings - Stripe details - Government entity document - GOV.UK Pay') + }) + it('should show the correct heading', () => { + cy.get('h1').should('contain', 'Upload a government entity document') + }) + }) + describe('When uploading an invalid file', () => { + describe('Wrong file type', () => { + it('should render error', () => { + const wrongFileTypeError = 'File type must be PDF, JPG or PNG' + + cy.get('input[name="governmentEntityDocument"]').selectFile({ + contents: Cypress.Buffer.from('file contents'), + fileName: 'file.json' + }) + + cy.get('.govuk-error-summary').should('not.exist') + + cy.get('#government-entity-document-submit').click() + + cy.get('.govuk-error-summary') + .should('exist') + .should('contain', '') + .should('contain', wrongFileTypeError) + cy.get('input[name="governmentEntityDocument"]').should('have.class', 'govuk-file-upload--error') + cy.get('#government-entity-document-error').should('contain.text', wrongFileTypeError) + }) + }) + describe('File too large', () => { + it('should render error', () => { + const fileTooLargeError = 'File size must be less than 10MB' + const moreThan10MB = (10 * 1024 * 1024) + 1 + const largeFile = Cypress.Buffer.alloc(moreThan10MB) + largeFile.write('a', moreThan10MB) + + cy.get('input[name="governmentEntityDocument"]').selectFile({ + contents: largeFile, + fileName: 'file.png', + mimeType: 'image/png' + }) + + cy.get('.govuk-error-summary').should('not.exist') + + cy.get('#government-entity-document-submit').click() + + cy.get('.govuk-error-summary') + .should('exist') + .should('contain', '') + .should('contain', fileTooLargeError) + cy.get('input[name="governmentEntityDocument"]').should('have.class', 'govuk-file-upload--error') + cy.get('#government-entity-document-error').should('contain.text', fileTooLargeError) + }) + }) + + describe('File missing', () => { + it('should render error', () => { + const fileMissingError = 'Select a file to upload' + cy.get('.govuk-error-summary').should('not.exist') + cy.get('#government-entity-document-submit').click() + cy.get('.govuk-error-summary') + .should('exist') + .should('contain', '') + .should('contain', fileMissingError) + cy.get('input[name="governmentEntityDocument"]').should('have.class', 'govuk-file-upload--error') + cy.get('#government-entity-document-error').should('contain.text', fileMissingError) + }) + }) + }) + describe('When uploading a valid file', () => { + beforeEach(() => { + setStubs({}, [ + gatewayAccountStubs.getStripeAccountByServiceIdAndAccountType( + SERVICE_EXTERNAL_ID, + LIVE_ACCOUNT_TYPE, + { + stripeAccountId: STRIPE_ACCOUNT_ID + } + ), + stripePspStubs.uploadFile(), + stripePspStubs.updateAccount({ + stripeAccountId: STRIPE_ACCOUNT_ID + }), + stripeAccountSetupStubs.patchStripeProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE + }), + stripePspStubs.retrieveAccountDetails({ + stripeAccountId: STRIPE_ACCOUNT_ID + }), + stripePspStubs.listPersons({ + stripeAccountId: STRIPE_ACCOUNT_ID, + director: true, + representative: true, + firstName: 'Scrooge', + lastName: 'McDuck' + }), + stripePspStubs.listBankAccount({ + stripeAccountId: STRIPE_ACCOUNT_ID, + director: true, + representative: true + }) + ]) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/government-entity-document') + }) + + it('should redirect to the task summary page on success', () => { + cy.get('input[name="governmentEntityDocument"]').selectFile({ + contents: Cypress.Buffer.from('file contents'), + fileName: 'file.png', + mimeType: 'image/png' + }) + + setStubs({}, [ + stripeAccountSetupStubs.getStripeSetupProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE, + bankAccount: true, + companyNumber: true, + vatNumber: true, + director: true, + responsiblePerson: true, + organisationDetails: true, + governmentEntityDocument: true + }) + ]) + + cy.get('#government-entity-document-submit').click() + + cy.title().should('eq', 'Settings - Stripe details - GOV.UK Pay') + cy.get('h1').should('contain', 'Stripe details') + cy.location('pathname').should('not.contain', '/government-entity-document') + cy.get('.govuk-notification-banner') + .contains('Service connected to Stripe') + .parent() + .contains('This service can now take payments') + }) + }) + }) + }) +}) diff --git a/test/cypress/integration/simplified-account/service-settings/stripe-details/responsible-person.cy.js b/test/cypress/integration/simplified-account/service-settings/stripe-details/responsible-person.cy.js new file mode 100644 index 000000000..f333f7705 --- /dev/null +++ b/test/cypress/integration/simplified-account/service-settings/stripe-details/responsible-person.cy.js @@ -0,0 +1,342 @@ +const userStubs = require('@test/cypress/stubs/user-stubs') +const gatewayAccountStubs = require('@test/cypress/stubs/gateway-account-stubs') +const stripeAccountSetupStubs = require('@test/cypress/stubs/stripe-account-setup-stub') +const { STRIPE, WORLDPAY } = require('@models/payment-providers') +const stripePspStubs = require('@test/cypress/stubs/stripe-psp-stubs') +const ROLES = require('@test/fixtures/roles.fixtures') + +const USER_EXTERNAL_ID = 'user-123-abc' +const SERVICE_EXTERNAL_ID = 'service-456-def' +const SERVICE_NAME = { + en: 'McDuck Enterprises', cy: 'Mentrau McDuck' +} +const LIVE_ACCOUNT_TYPE = 'live' +const GATEWAY_ACCOUNT_ID = 10 +const STRIPE_ACCOUNT_ID = 'acct_123example123' + +const STRIPE_DETAILS_SETTINGS_URL = `/simplified/service/${SERVICE_EXTERNAL_ID}/account/${LIVE_ACCOUNT_TYPE}/settings/stripe-details` + +const setStubs = (opts = {}, additionalStubs = []) => { + cy.task('setupStubs', [ + userStubs.getUserSuccess({ + userExternalId: USER_EXTERNAL_ID, + gatewayAccountId: GATEWAY_ACCOUNT_ID, + serviceName: SERVICE_NAME, + serviceExternalId: SERVICE_EXTERNAL_ID, + merchantDetails: { + name: 'McDuck Enterprises', + address_line1: 'McDuck Manor', + address_city: 'Duckburg', + address_postcode: 'SW1A 1AA' + }, + role: ROLES[opts.role || 'admin'], + features: 'degatewayaccountification' // TODO remove features once simplified accounts are live + }), + gatewayAccountStubs.getAccountByServiceIdAndAccountType(SERVICE_EXTERNAL_ID, LIVE_ACCOUNT_TYPE, { + gateway_account_id: GATEWAY_ACCOUNT_ID, + type: LIVE_ACCOUNT_TYPE, + payment_provider: opts.paymentProvider || STRIPE, + provider_switch_enabled: opts.providerSwitchEnabled || false + }), + ...additionalStubs]) +} + +describe('Stripe details settings', () => { + beforeEach(() => { + cy.setEncryptedCookies(USER_EXTERNAL_ID) + }) + describe('The responsible person task', () => { + describe('For a non-admin', () => { + beforeEach(() => { + setStubs({ + role: 'view-and-refund' + }) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/responsible-person', { failOnStatusCode: false }) + }) + it('should show not found page', () => { + cy.title().should('eq', 'Page not found - GOV.UK Pay') + cy.get('h1').should('contain.text', 'Page not found') + }) + }) + describe('For a non-stripe service', () => { + beforeEach(() => { + setStubs({ + paymentProvider: WORLDPAY + }) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/responsible-person', { failOnStatusCode: false }) + }) + it('should show not found page', () => { + cy.title().should('eq', 'Page not found - GOV.UK Pay') + cy.get('h1').should('contain.text', 'Page not found') + }) + }) + describe('Completed', () => { + beforeEach(() => { + setStubs({}, [ + stripeAccountSetupStubs.getStripeSetupProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE, + responsiblePerson: true + }) + ]) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/responsible-person') + }) + it('should show the task already completed page', () => { + cy.title().should('eq', 'An error occurred - GOV.UK Pay') + cy.get('h1').should('contain', 'You\'ve already completed this task') + }) + }) + describe('Not yet started', () => { + beforeEach(() => { + setStubs({}, [ + stripeAccountSetupStubs.getStripeSetupProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE + }) + ]) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/responsible-person') + }) + describe('The settings navigation', () => { + it('should show stripe details', () => { + cy.get('.service-settings-nav') + .find('li') + .contains('Stripe details') + .then(li => { + cy.wrap(li) + .should('have.attr', 'href', STRIPE_DETAILS_SETTINGS_URL) + .parent().should('have.class', 'service-settings-nav__li--active') + }) + }) + }) + describe('The task page', () => { + it('should show the correct title', () => { + cy.title().should('eq', 'Settings - Stripe details - Responsible person - GOV.UK Pay') + }) + it('should show the correct heading', () => { + cy.get('h1').should('contain', 'Responsible person') + }) + }) + describe('When entering invalid details', () => { + describe('Name and DOB sub-task', () => { + it('should render errors when submitting bad inputs', () => { + const emptyFirstNameError = 'Enter your first name' + const emptyLastNameError = 'Enter your last name' + const tooOldError = 'Enter a valid year of birth' + + cy.get('.govuk-error-summary').should('not.exist') + + cy.get('input[name="firstName"]') + .clear({ force: true }) + cy.get('input[name="lastName"]') + .clear({ force: true }) + cy.get('input[name="dobDay"]') + .clear({ force: true }) + .type('01') + cy.get('input[name="dobMonth"]') + .clear({ force: true }) + .type('01') + cy.get('input[name="dobYear"]') + .clear({ force: true }) + .type('1899') + + cy.get('#responsible-person-form button[type="submit"]').click() + cy.get('.govuk-error-summary') + .should('exist') + .should('contain', emptyFirstNameError) + .should('contain', emptyLastNameError) + .should('contain', tooOldError) + cy.get('input[name="firstName"]').should('have.class', 'govuk-input--error') + cy.get('#first-name-error').should('contain.text', emptyFirstNameError) + cy.get('input[name="lastName"]').should('have.class', 'govuk-input--error') + cy.get('#last-name-error').should('contain.text', emptyLastNameError) + cy.get('#dob-error').should('contain.text', tooOldError) + }) + }) + + describe('Home address sub-task', () => { + beforeEach(() => { + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/responsible-person/home-address') + }) + + it('should render errors when submitting bad inputs', () => { + const emptyAddressError = 'Address line 1 is required' + const emptyAddressCityError = 'Town or city is required' + const invalidPostcodeError = 'Enter a real postcode' + + cy.get('.govuk-error-summary').should('not.exist') + + cy.get('input[name="homeAddressLine1"]') + .clear({ force: true }) + cy.get('input[name="homeAddressCity"]') + .clear({ force: true }) + cy.get('input[name="homeAddressPostcode"]') + .clear({ force: true }) + .type('hmmm') + + cy.get('#responsible-person-home-address-form button[type="submit"]').click() + cy.get('.govuk-error-summary') + .should('exist') + .should('contain', emptyAddressError) + .should('contain', emptyAddressCityError) + .should('contain', invalidPostcodeError) + cy.get('input[name="homeAddressLine1"]').should('have.class', 'govuk-input--error') + cy.get('#home-address-line-1-error').should('contain.text', emptyAddressError) + cy.get('input[name="homeAddressCity"]').should('have.class', 'govuk-input--error') + cy.get('#home-address-city-error').should('contain.text', emptyAddressCityError) + cy.get('input[name="homeAddressPostcode"]').should('have.class', 'govuk-input--error') + cy.get('#home-address-postcode-error').should('contain.text', invalidPostcodeError) + }) + }) + + describe('Phone number and email address sub-task', () => { + beforeEach(() => { + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/responsible-person/contact-details') + }) + + it('should render errors when submitting bad inputs', () => { + const invalidEmailAddress = 'Enter a real email address' + const invalidPhoneNumber = 'Enter a valid work telephone number' + + cy.get('.govuk-error-summary').should('not.exist') + + cy.get('input[name="workTelephoneNumber"]') + .clear({ force: true }) + .type('hmmm') + cy.get('input[name="workEmail"]') + .clear({ force: true }) + .type('hmmm') + + cy.get('#responsible-person-contact-details-form button[type="submit"]').click() + cy.get('.govuk-error-summary') + .should('exist') + .should('contain', invalidPhoneNumber) + .should('contain', invalidEmailAddress) + cy.get('input[name="workTelephoneNumber"]').should('have.class', 'govuk-input--error') + cy.get('#work-telephone-number-error').should('contain.text', invalidPhoneNumber) + cy.get('input[name="workEmail"]').should('have.class', 'govuk-input--error') + cy.get('#work-email-error').should('contain.text', invalidEmailAddress) + }) + }) + + describe('Check your answers sub-task', () => { + beforeEach(() => { + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/responsible-person/check-your-answers') + }) + + it('should redirect to the Name and DOB sub-task when answers are incomplete', () => { + cy.location('pathname').should('not.contain', '/check-your-answers') + cy.get('h1').should('contain', 'Responsible person') + }) + }) + }) + describe('When entering valid details', () => { + beforeEach(() => { + setStubs({}, [ + gatewayAccountStubs.getStripeAccountByServiceIdAndAccountType( + SERVICE_EXTERNAL_ID, + LIVE_ACCOUNT_TYPE, + { + stripeAccountId: STRIPE_ACCOUNT_ID + } + ), + stripePspStubs.listPersons({ + stripeAccountId: STRIPE_ACCOUNT_ID + }), + stripePspStubs.createOrUpdatePerson({ + stripeAccountId: STRIPE_ACCOUNT_ID + }), + stripePspStubs.updateAccount({ + stripeAccountId: STRIPE_ACCOUNT_ID + }), + stripeAccountSetupStubs.patchStripeProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE + }) + ]) + cy.visit(STRIPE_DETAILS_SETTINGS_URL + '/responsible-person') + }) + + it('should redirect to the task summary page on success', () => { + cy.get('input[name="firstName"]') + .clear({ force: true }) + .type('Scrooge') + cy.get('input[name="lastName"]') + .clear({ force: true }) + .type('McDuck') + cy.get('input[name="dobDay"]') + .clear({ force: true }) + .type('01') + cy.get('input[name="dobMonth"]') + .clear({ force: true }) + .type('01') + cy.get('input[name="dobYear"]') + .clear({ force: true }) + .type('1901') + + cy.get('#responsible-person-form button[type="submit"]').click() + + cy.get('input[name="homeAddressLine1"]') + .clear({ force: true }) + .type('McDuck Manor') + cy.get('input[name="homeAddressCity"]') + .clear({ force: true }) + .type('Duckburg') + cy.get('input[name="homeAddressPostcode"]') + .clear({ force: true }) + .type('SW1A 1AA') + + cy.get('#responsible-person-home-address-form button[type="submit"]').click() + + cy.get('input[name="workTelephoneNumber"]') + .clear({ force: true }) + .type('01611234567') + cy.get('input[name="workEmail"]') + .clear({ force: true }) + .type('scrooge.mcduck@pay.gov.uk') + + cy.get('#responsible-person-contact-details-form button[type="submit"]').click() + + cy.get('.govuk-summary-list__row').should('have.length', 5) + + cy.get('.govuk-summary-list__row') + .should('contain.text', 'Scrooge McDuck') + + cy.get('.govuk-summary-list__row') + .should('contain.text', '01 January 1901') + + cy.get('.govuk-summary-list__row') + .contains('McDuck Manor').should('exist') + .contains('Duckburg').should('exist') + .contains('SW1A 1AA').should('exist') + + cy.get('.govuk-summary-list__row') + .should('contain.text', '+44 161 123 4567') + + cy.get('.govuk-summary-list__row') + .should('contain.text', 'scrooge.mcduck@pay.gov.uk') + + setStubs({}, [ + stripeAccountSetupStubs.getStripeSetupProgressByServiceExternalIdAndAccountType({ + serviceExternalId: SERVICE_EXTERNAL_ID, + accountType: LIVE_ACCOUNT_TYPE, + responsiblePerson: true + }) + ]) + + cy.get('#responsible-person-check-your-answers-form button[type="submit"]').click() + + cy.title().should('eq', 'Settings - Stripe details - GOV.UK Pay') + cy.get('h1').should('contain', 'Stripe details') + cy.location('pathname').should('not.contain', '/responsible-person') + cy.get('.govuk-task-list__item') + .contains('Responsible person') + .parent() + .parent() + .within(() => { + cy.get('.govuk-task-list__status').should('contain.text', 'Complete') + }) + }) + }) + }) + }) +}) diff --git a/test/cypress/stubs/stripe-psp-stubs.js b/test/cypress/stubs/stripe-psp-stubs.js index 0bb0dd241..2fe791a66 100644 --- a/test/cypress/stubs/stripe-psp-stubs.js +++ b/test/cypress/stubs/stripe-psp-stubs.js @@ -80,6 +80,30 @@ function updateAccount (opts) { }) } +function uploadFile () { + const path = '/v1/files' + return stubBuilder('POST', path, 200, { + response: { + id: 'file_abc123efg456', + object: 'file', + created: 1733741405, + expires_at: null, + filename: 'entity_document_for_account_X', + links: { + object: 'list', + data: [], + has_more: false, + url: '/v1/file_links?file=file_abc123efg456' + }, + purpose: 'identity_document', + size: 5818, + title: null, + type: 'png', + url: null + } + }) +} + module.exports = { listPersons, listBankAccount, @@ -87,5 +111,6 @@ module.exports = { updateListPerson, createOrUpdatePerson, updateCompany, - updateAccount + updateAccount, + uploadFile } diff --git a/test/cypress/test.env b/test/cypress/test.env index 909aed08e..f696042fb 100644 --- a/test/cypress/test.env +++ b/test/cypress/test.env @@ -16,6 +16,7 @@ NODE_TEST_MODE=true NODE_ENV=test ZENDESK_URL=http://127.0.0.1:8000/zendesk STRIPE_HOST=127.0.0.1 +STRIPE_FILES_HOST=127.0.0.1 STRIPE_PORT=8000 STRIPE_PROTOCOL=http GOCARDLESS_TEST_OAUTH_BASE_URL=http://127.0.0.1:8000