Skip to content

Commit

Permalink
PP-13525 show only one test account for degatewayed users
Browse files Browse the repository at this point in the history
- update `my-services` controller to enable conditional filtering of test gateway accounts when user is opted into degateway
- simplify `my services` styles and views
- remove reference to `service-switcher` now that accounts are presented as links, not forms
- update routes and paths
- remove unnecessary call to connector when determining if the service is a Worldpay test service in the `dashboard activity` controller
- remove old `ui` tests, prefer cypress and mocha
- fix issue where account and service resolution middleware would throw an error when adminusers and connector disagree about which gateway account should be returned for a service
  - the middleware now logs a warning when there is a mismatch between connector and adminusers, we prefer connector as the source of truth in this situation
  • Loading branch information
nlsteers committed Feb 28, 2025
1 parent a017024 commit 2b8d2c7
Show file tree
Hide file tree
Showing 54 changed files with 498 additions and 811 deletions.
13 changes: 7 additions & 6 deletions dev-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import { spawn } from 'child_process'
import { clientBuild, serverBuild } from './esbuild.config.mjs'
import { rm, access, constants } from 'node:fs'

const args = ['dist/application.js']
const args = ['--enable-source-maps', '--inspect', 'dist/application.js']
let server

const startServer = async () => {
if (server) server.kill()
console.log(`💻\x1b[32m node\x1b[0m\x1b[33m ${args.join(' ')}\x1b[0m`)
server = spawn('node', args, {
stdio: 'inherit'
})
}

async function startDevServer() {
async function startDevServer () {
const clientCtx = await context(clientBuild)

const serverCtx = await context({
Expand All @@ -22,7 +23,7 @@ async function startDevServer() {
...serverBuild.plugins,
{
name: 'server-rebuild',
setup(build) {
setup (build) {
build.onEnd(async result => {
if (result.errors.length === 0) {
await startServer()
Expand All @@ -35,7 +36,7 @@ async function startDevServer() {

await Promise.all([
clientCtx.watch(),
serverCtx.watch(),
serverCtx.watch()
])

const cleanup = async () => {
Expand All @@ -53,14 +54,14 @@ async function startDevServer() {
process.on('SIGTERM', cleanup)
}

await rm('dist', { recursive: true, force: true }, async () => {
rm('dist', { recursive: true, force: true }, async () => {
console.log('✅ [dist] cleared')
if (process.env.NODE_ENV === 'test') {
console.log('🧪 [cypress/test.env] loaded environment')
args.unshift('-r', 'dotenv/config')
args.push('dotenv_config_path=test/cypress/test.env')
} else {
await access('/.dockerenv', constants.R_OK, (err) => {
access('/.dockerenv', constants.R_OK, (err) => {
if (err) {
console.log('🔩 [.env] loaded environment')
args.unshift('-r', 'dotenv/config')
Expand Down
65 changes: 37 additions & 28 deletions src/assets/sass/components/my-services.scss
Original file line number Diff line number Diff line change
@@ -1,28 +1,3 @@
.service-switcher{
&.live {
position: relative;
margin-bottom: govuk-spacing(3);
padding-left: 2rem;
@include govuk-font($size: 19, $weight: bold);

&:before {
content: "";
position: absolute;
top: 50%;
left: 0;
-webkit-transform:translate(0, -50%);
-moz-transform:translate(0, -50%);
-ms-transform:translate(0, -50%);
-o-transform:translate(0, -50%);
transform:translate(0, -50%);
width: 1.5rem;
height: 1.5rem;
border-radius: 100%;
background-color: govuk-colour("green");
}
}
}

.reports {
margin-top: govuk-spacing(2);
margin-bottom: govuk-spacing(4);
Expand All @@ -32,7 +7,8 @@
margin-bottom: govuk-spacing(6);
}

.service_list_item {
.service-section {
margin-top: govuk-spacing(1);
hr {
border: none;
border-top: 1px solid $govuk-border-colour;
Expand All @@ -41,6 +17,39 @@
}
}

.service_list_item {
margin-top: govuk-spacing(1);
.govuk-tag--worldpay-test-service {
max-width: 200px;
}

.service-section__links {
margin-bottom: govuk-spacing(4);
li:first-child {
margin-top: 0;
margin-bottom: govuk-spacing(4);
}
li {
margin-bottom: govuk-spacing(1);
}
}

.service-section__link--test {
@include govuk-font($size: 16);
}

.service-section__link--live {
position: relative;
padding-left: 2rem;
@include govuk-font($size: 19, $weight: bold);

&:before {
content: "";
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
background-color: govuk-colour("green");
}
}
6 changes: 3 additions & 3 deletions src/controllers/create-service/create-service.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ function get (req, res) {
const createServiceState = _.get(req, 'session.pageData.createService', {})
const context = {
...createServiceState,
back_link: paths.serviceSwitcher.index,
submit_link: paths.serviceSwitcher.create.selectOrgType
back_link: paths.services.index,
submit_link: paths.services.create.selectOrgType
}
_.unset(req, 'session.pageData.createService')
return response(req, res, 'services/add-service', context)
Expand All @@ -28,7 +28,7 @@ async function post (req, res, next) {
_.set(req, 'session.pageData.createService.errors', {
organisation_type: 'Organisation type is required'
})
return res.redirect(paths.serviceSwitcher.create.selectOrgType)
return res.redirect(paths.services.create.selectOrgType)
}

try {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/create-service/get.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('Controller: createService, Method: get', () => {
})

it('should pass pageData to the responses.response method that has properly formatted \'submit_link\' and \'my_services\' properties', () => {
expect(mockResponses.response.args[0][3]).to.have.property('submit_link').to.equal('/my-services/create/select-org-type')
expect(mockResponses.response.args[0][3]).to.have.property('submit_link').to.equal('/services/create/select-org-type')
expect(mockResponses.response.args[0][3]).to.have.property('back_link').to.equal('/my-services')
})
})
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/create-service/post.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('Controller: createService, Method: post', () => {
})

it('should redirect back to select org type', () => {
sinon.assert.calledWith(res.redirect, paths.serviceSwitcher.create.selectOrgType)
sinon.assert.calledWith(res.redirect, paths.services.create.selectOrgType)
})

it('should set error information on the session', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ function get (req, res) {
const createServiceState = _.get(req, 'session.pageData.createService', {})
if (!createServiceState.current_name) {
logger.info('Page accessed out of sequence, redirecting to my-services/create')
return res.redirect(paths.serviceSwitcher.create.index)
return res.redirect(paths.services.create.index)
}
const context = {
...createServiceState,
back_link: paths.serviceSwitcher.create.index,
submit_link: paths.serviceSwitcher.create.index
back_link: paths.services.create.index,
submit_link: paths.services.create.index
}
_.unset(req, 'session.pageData.createService.errors')
return response(req, res, 'services/select-org-type', context)
Expand All @@ -32,11 +32,11 @@ function post (req, res) {
const errors = validateServiceName(req.body['service-name'], req.body['service-name-cy'])
if (!_.isEmpty(errors)) {
_.set(req, 'session.pageData.createService.errors', errors)
return res.redirect(paths.serviceSwitcher.create.index)
return res.redirect(paths.services.create.index)
}
const context = {
back_link: paths.serviceSwitcher.create.index,
submit_link: paths.serviceSwitcher.create.index
back_link: paths.services.create.index,
submit_link: paths.services.create.index
}
return response(req, res, 'services/select-org-type', context)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('Controller: selectOrganisationType, Method: get', () => {
redirect: sinon.spy()
}
selectOrganisationTypeController.get(req, res)
sinon.assert.calledWith(res.redirect, paths.serviceSwitcher.create.index)
sinon.assert.calledWith(res.redirect, paths.services.create.index)
})
})

Expand Down Expand Up @@ -60,8 +60,8 @@ describe('Controller: selectOrganisationType, Method: get', () => {
expect(responseContext).to.have.property('errors').to.deep.equal({
organisation_type: 'Organisation type is required'
})
expect(responseContext).to.have.property('back_link').to.equal(paths.serviceSwitcher.create.index)
expect(responseContext).to.have.property('submit_link').to.equal(paths.serviceSwitcher.create.index)
expect(responseContext).to.have.property('back_link').to.equal(paths.services.create.index)
expect(responseContext).to.have.property('submit_link').to.equal(paths.services.create.index)
})

it('should remove errors pageData from the session', () => {
Expand Down Expand Up @@ -93,8 +93,8 @@ describe('Controller: selectOrganisationType, Method: post', () => {
expect(createServiceState).to.have.property('current_name_cy').to.equal(WELSH_SERVICE_NAME)
expect(createServiceState).to.have.property('service_selected_cy').to.equal(true)
const responseContext = mockResponse.response.args[0][3]
expect(responseContext).to.have.property('back_link').to.equal(paths.serviceSwitcher.create.index)
expect(responseContext).to.have.property('submit_link').to.equal(paths.serviceSwitcher.create.index)
expect(responseContext).to.have.property('back_link').to.equal(paths.services.create.index)
expect(responseContext).to.have.property('submit_link').to.equal(paths.services.create.index)
})
})
describe('when request fails validation', () => {
Expand All @@ -116,7 +116,7 @@ describe('Controller: selectOrganisationType, Method: post', () => {

it('should redirect to the create service controller and set the session data', () => {
expect(mockResponse.response.called).to.equal(false)
sinon.assert.calledWith(res.redirect, paths.serviceSwitcher.create.index)
sinon.assert.calledWith(res.redirect, paths.services.create.index)
const expectedErrors = req.session.pageData.createService.errors
expect(expectedErrors).to.have.property('service_name').to.equal('Service name must be 50 characters or fewer')
expect(expectedErrors).to.have.property('service_name_cy').to.equal('Welsh service name must be 50 characters or fewer')
Expand Down
14 changes: 7 additions & 7 deletions src/controllers/dashboard/dashboard-activity.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const {
DENIED
} = require('@models/constants/go-live-stage')
const pspTestAccountStage = require('@models/constants/psp-test-account-stage')
const serviceService = require('../../services/service.service')
const { WORLDPAY } = require('@models/constants/payment-providers')

const links = {
demoPayment: 0,
Expand Down Expand Up @@ -102,19 +102,19 @@ const displayRequestTestStripeAccountLink = (service, account, user) => {
user.hasPermission(service.externalId, 'psp-test-account-stage:update')
}

async function isWorldpayTestService (gatewayAccountIds) {
const gatewayAccounts = await serviceService.getGatewayAccounts(gatewayAccountIds)
logger.info(`returned gateway accounts: ${JSON.stringify(gatewayAccounts)}`)
return gatewayAccounts.length === 1 && gatewayAccounts[0].type === 'test' &&
gatewayAccounts[0].payment_provider.toUpperCase() === 'WORLDPAY'
async function isWorldpayTestService (service, account) {
return service.gatewayAccountIds.length === 1 &&
parseInt(account.gateway_account_id) === parseInt(service.gatewayAccountIds[0]) &&
account.type === 'test' &&
account.payment_provider === WORLDPAY
}

module.exports = async (req, res) => {
const gatewayAccountId = req.account.gateway_account_id
const messages = res.locals.flash.messages
const period = _.get(req, 'query.period', 'today')
const telephonePaymentLink = await getTelephonePaymentLink(req.user, req.service, gatewayAccountId)
const worldpayTestService = await isWorldpayTestService(req.service.gatewayAccountIds)
const worldpayTestService = await isWorldpayTestService(req.service, req.account)
const linksToDisplay = getLinksToDisplay(req.service, req.account, req.user, telephonePaymentLink)
const model = {
serviceId: req.service.externalId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('Controller: Dashboard activity', () => {
req = {
account,
service: {
gatewayAccountIds: serviceGatewayAccountIds,
currentGoLiveStage: null
},
user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function getServiceName (req, res) {
}

pageData.submit_link = formatServicePathsFor(paths.service.editServiceName.update, req.service.externalId)
pageData.my_services = paths.serviceSwitcher.index
pageData.my_services = paths.services.index

return responses.response(req, res, 'services/edit-service-name', pageData)
}
Expand Down Expand Up @@ -56,7 +56,7 @@ async function postEditServiceName (req, res, next) {
} else {
try {
await serviceService.updateServiceName(serviceExternalId, serviceName, serviceNameCy)
res.redirect(paths.serviceSwitcher.index)
res.redirect(paths.services.index)
} catch (err) {
next(err)
}
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/invite-validation.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function validateInvite (req, res, next) {
if (invite.is_invite_to_join_service) {
res.redirect(paths.invite.subscribeService)
} else {
res.redirect(paths.serviceSwitcher.index)
res.redirect(paths.services.index)
}
} else {
res.redirect(paths.register.password)
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/invite-validation.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('Invite validation controller', function () {
await controller.validateInvite(req, res, next)

sinon.assert.calledWith(getValidatedInviteSpy, code)
sinon.assert.calledWith(res.redirect, paths.serviceSwitcher.index)
sinon.assert.calledWith(res.redirect, paths.services.index)
sinon.assert.notCalled(next)
})

Expand Down
Loading

0 comments on commit 2b8d2c7

Please sign in to comment.