Skip to content

Commit

Permalink
PP-13312 Get controller, templates and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
james-peacock-gds committed Dec 5, 2024
1 parent e337a3b commit 3b978eb
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 5 deletions.
12 changes: 10 additions & 2 deletions app/assets/sass/components/service-settings.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// https://getbem.com/naming/
s// https://getbem.com/naming/

.service-settings-nav {
@include govuk-font(16);
Expand Down Expand Up @@ -43,15 +43,23 @@
width: 30% !important;
}
}

.task-list {
a:visited, a:link {
color: govuk-colour("blue");
}

a:hover {
color: govuk-colour("dark-blue");
}

a:active, a:focus {
color: govuk-colour("black");
}
}

.card-types-list {
display: inline-block;
margin-bottom: govuk-spacing(2);
padding: 8px govuk-spacing(1)
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
const { response } = require('@utils/response')
const { formatCardTypesForTemplate } = require('@utils/simplified-account/format/format-card-types')
const { getAllCardTypes, getAcceptedCardTypesForServiceAndAccountType } = require('@services/card-types.service')

function get (req, res) {
response(req, res, 'simplified-account/settings/card-types/index', { })
async function get (req, res, next) {
const serviceId = req.service.externalId
const accountType = req.account.type
const isAdminUser = req.user.isAdminUserForService(serviceId)
try {
const { card_types: allCards } = await getAllCardTypes()
const { card_types: acceptedCards } = await getAcceptedCardTypesForServiceAndAccountType(serviceId, accountType)
const cardTypes = formatCardTypesForTemplate(allCards, acceptedCards, req.account, isAdminUser)
response(req, res, 'simplified-account/settings/card-types/index',
{ cardTypes, isAdminUser })
} catch (err) {
next(err)
}
}

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
const sinon = require('sinon')
const { expect } = require('chai')
const User = require('@models/User.class')
const userFixtures = require('@test/fixtures/user.fixtures')
const proxyquire = require('proxyquire')

const ACCOUNT_TYPE = 'live'
const SERVICE_ID = 'service-id-123abc'

const adminUser = new User(userFixtures.validUserResponse({
external_id: 'user-id-for-admin-user',
service_roles: {
service: {
service: { external_id: SERVICE_ID },
role: { name: 'admin' }
}
}
}))
const viewOnlyUser = new User(userFixtures.validUserResponse(
{
external_id: 'user-id-for-view-only-user',
service_roles: {
service:
{
service: { external_id: SERVICE_ID },
role: { name: 'view-only' }
}
}
}))

const allCardTypes = [{
id: 'id-001',
brand: 'visa',
label: 'Visa',
type: 'DEBIT',
requires3ds: false
},
{
id: 'id-002',
brand: 'visa',
label: 'Visa',
type: 'CREDIT',
requires3ds: false
}]

let req, res, responseStub, getAllCardTypesStub, getAcceptedCardTypesForServiceAndAccountTypeStub, cardTypesController

const getController = (stubs = {}) => {
return proxyquire('./card-types.controller', {
'@utils/response': { response: stubs.response },
'@services/card-types.service': {
getAllCardTypes: stubs.getAllCardTypes,
getAcceptedCardTypesForServiceAndAccountType: stubs.getAcceptedCardTypesForServiceAndAccountType
}
})
}

const setupTest = (method, user, additionalReqProps = {}, additionalStubs = {}) => {
responseStub = sinon.spy()
getAllCardTypesStub = sinon.stub().returns({ card_types: allCardTypes })
getAcceptedCardTypesForServiceAndAccountTypeStub = sinon.stub().resolves({ card_types: [allCardTypes[0]] })

cardTypesController = getController({
response: responseStub,
getAllCardTypes: getAllCardTypesStub,
getAcceptedCardTypesForServiceAndAccountType: getAcceptedCardTypesForServiceAndAccountTypeStub,
...additionalStubs
})
res = {
redirect: sinon.spy()
}
req = {
user: user,
service: {
externalId: SERVICE_ID
},
account: {
type: ACCOUNT_TYPE
},
...additionalReqProps
}
cardTypesController[method](req, res)
}

describe('Controller: settings/card-types', () => {
describe('get for admin user', () => {
before(() => setupTest('get', adminUser))

it('should call the response method', () => {
expect(responseStub.called).to.be.true // eslint-disable-line
})

it('should pass req, res and template path to the response method', () => {
expect(responseStub.args[0][0]).to.deep.equal(req)
expect(responseStub.args[0][1]).to.deep.equal(res)
expect(responseStub.args[0][2]).to.equal('simplified-account/settings/card-types/index')
})

it('should pass context data to the response method', () => {
expect(responseStub.args[0][3]).to.have.property('cardTypes').to.have.property('debitCards').length(1)
expect(responseStub.args[0][3].cardTypes.debitCards[0]).to.have.property('text').to.equal('Visa debit')
expect(responseStub.args[0][3].cardTypes.debitCards[0]).to.have.property('checked').to.equal(true)
expect(responseStub.args[0][3]).to.have.property('cardTypes').to.have.property('creditCards').length(1)
expect(responseStub.args[0][3].cardTypes.creditCards[0]).to.have.property('text').to.equal('Visa credit')
expect(responseStub.args[0][3].cardTypes.creditCards[0]).to.have.property('checked').to.equal(false)
expect(responseStub.args[0][3]).to.have.property('isAdminUser').to.equal(true)
})
})

describe('get for non-admin user', () => {
before(() => setupTest('get', viewOnlyUser))

it('should call the response method', () => {
expect(responseStub.called).to.be.true // eslint-disable-line
})

it('should pass req, res and template path to the response method', () => {
expect(responseStub.args[0][0]).to.deep.equal(req)
expect(responseStub.args[0][1]).to.deep.equal(res)
expect(responseStub.args[0][2]).to.equal('simplified-account/settings/card-types/index')
})

it('should pass context data to the response method', () => {
expect(responseStub.args[0][3]).to.have.property('cardTypes').to.have.property('Enabled debit cards').to.have.length(1)
expect(responseStub.args[0][3].cardTypes).to.have.property('Not enabled debit cards').to.have.length(0)
expect(responseStub.args[0][3].cardTypes).to.have.property('Enabled credit cards').to.have.length(0)
expect(responseStub.args[0][3].cardTypes).to.have.property('Not enabled credit cards').to.have.length(1)
expect(responseStub.args[0][3]).to.have.property('isAdminUser').to.equal(false)
})
})
})
17 changes: 17 additions & 0 deletions app/services/card-types.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict'

const ConnectorClient = require('./clients/connector.client.js').ConnectorClient
const connectorClient = new ConnectorClient(process.env.CONNECTOR_URL)

async function getAllCardTypes () {
return connectorClient.getAllCardTypes()
}

async function getAcceptedCardTypesForServiceAndAccountType (serviceId, accountType) {
return connectorClient.getAcceptedCardsForServiceAndAccountType(serviceId, accountType)
}

module.exports = {
getAllCardTypes,
getAcceptedCardTypesForServiceAndAccountType
}
14 changes: 14 additions & 0 deletions app/services/clients/connector.client.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,20 @@ ConnectorClient.prototype = {
return response.data
},

/**
* Retrieves the accepted card Types for the given account
* @param gatewayAccountId (required)
* @returns {Promise<Object>}
*/
getAcceptedCardsForServiceAndAccountType: async function (serviceId, accountType) {
const url = `${this.connectorUrl}/v1/frontend/service/{serviceId}/account/{accountType}/card-types`
.replace('{serviceId}', encodeURIComponent(serviceId))
.replace('{accountType}', encodeURIComponent(accountType))
configureClient(client, url)
const response = await client.get(url, 'get accepted card types for account')
return response.data
},

/**
* Updates the accepted card Types for to the given gateway account
* @param gatewayAccountId (required)
Expand Down
72 changes: 72 additions & 0 deletions app/utils/simplified-account/format/format-card-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const formatLabel = (card) => {
if (card.brand === 'visa' || card.brand === 'master-card') {
return `${card.label} ${card.type.toLowerCase()}`
} else {
return card.brand === 'jcb' ? card.label.toUpperCase() : card.label
}
}

const formatCardTypesForAdminTemplate = (allCards, acceptedCards, account) => {
const cardDataChecklistItem = (card) => {
return {
value: card.id,
text: formatLabel(card),
checked: acceptedCards.filter(accepted => accepted.id === card.id).length !== 0,
requires3ds: card.requires3ds
}
}
const disableCheckboxIf3dsRequiredButNotEnabled = (cardTypeChecklistItem) => {
if (cardTypeChecklistItem.requires3ds && !account.requires3ds) {
cardTypeChecklistItem.disabled = true
cardTypeChecklistItem.hint = {
html: account.type === 'test' ? `${cardTypeChecklistItem.text} is not available on test accounts` : `${cardTypeChecklistItem.text} cannot be used because 3D Secure is not available. Please contact support`
}
}
return cardTypeChecklistItem
}
const addHintForAmexAndUnionpay = (cardTypeChecklistItem) => {
if (['American Express', 'Union Pay'].includes(cardTypeChecklistItem.text)) {
if (account.paymentProvider === 'worldpay') {
cardTypeChecklistItem.hint = {
html: 'You must have already enabled this with Worldpay'
}
}
}
return cardTypeChecklistItem
}
const debitCardChecklistItems = allCards.filter(card => card.type === 'DEBIT')
.map(card => cardDataChecklistItem(card))
.map(cardTypeChecklistItem => disableCheckboxIf3dsRequiredButNotEnabled(cardTypeChecklistItem))

const creditCardChecklistItems = allCards.filter(card => card.type === 'CREDIT')
.map(card => cardDataChecklistItem(card))
.map(cardTypeChecklistItem => addHintForAmexAndUnionpay(cardTypeChecklistItem))

return { debitCards: debitCardChecklistItems, creditCards: creditCardChecklistItems }
}

const formatCardTypesForNonAdminTemplate = (allCards, acceptedCards) => {
const acceptedCardTypeIds = acceptedCards.map(card => card.id)
const formattedCardTypes = {
'Enabled debit cards': [],
'Not enabled debit cards': [],
'Enabled credit cards': [],
'Not enabled credit cards': []
}
allCards.forEach(card => {
const cardIsEnabled = acceptedCardTypeIds.includes(card.id) ? 'Enabled' : 'Not enabled'
formattedCardTypes[`${cardIsEnabled} ${card.type.toLowerCase()} cards`].push(formatLabel(card))
})
return formattedCardTypes
}

const formatCardTypesForTemplate = (allCards, acceptedCards, account, isAdminUser) => {
if (isAdminUser) {
return formatCardTypesForAdminTemplate(allCards, acceptedCards, account)
}
return formatCardTypesForNonAdminTemplate(allCards, acceptedCards)
}

module.exports = {
formatCardTypesForTemplate
}
78 changes: 78 additions & 0 deletions app/utils/simplified-account/format/format-card-types.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const { expect } = require('chai')
const { formatCardTypesForTemplate } = require('@utils/simplified-account/format/format-card-types')

const allCards = [
{
id: 'id-001',
brand: 'visa',
label: 'Visa',
type: 'DEBIT',
requires3ds: false
},
{
id: 'id-002',
brand: 'visa',
label: 'Visa',
type: 'CREDIT',
requires3ds: false
},
{
id: 'id-003',
brand: 'master-card',
label: 'Mastercard',
type: 'DEBIT',
requires3ds: false
},
{
id: 'id-004',
brand: 'american-express',
label: 'American Express',
type: 'CREDIT',
requires3ds: false
},
{
id: 'id-005',
brand: 'jcb',
label: 'Jcb',
type: 'CREDIT',
requires3ds: false
},
{
id: 'id-006',
brand: 'maestro',
label: 'Maestro',
type: 'DEBIT',
requires3ds: true
}
]
describe('format-card-types for template', () => {
describe('present checkboxes for admin user', () => {
it('should return all card types if they are all enabled', () => {
const acceptedCards = [...allCards]
const account = { requires3ds: true }
const cards = formatCardTypesForTemplate(allCards, acceptedCards, account, true)
expect(cards.debitCards).to.have.length(3)
expect(cards.creditCards).to.have.length(3)
})

it('should set checkbox to disabled for requires3ds card types if 3ds not enabled on account', () => {
const acceptedCards = [...allCards]
const account = { requires3ds: false }
const cards = formatCardTypesForTemplate(allCards, acceptedCards, account, true)
expect(cards.debitCards.filter(card => card.disabled === true)).to.have.length(1)
expect(cards.debitCards.filter(card => card.disabled === true)[0]).to.have.property('text').to.equal('Maestro')
})
})

describe('present read-only list for non-admin user', () => {
it('should return all card types arranged by type and whether accepted or not', () => {
const acceptedCards = [...allCards].filter(card => card.id !== 'id-001' && card.id !== 'id-002')
const account = { requires3ds: true }
const cards = formatCardTypesForTemplate(allCards, acceptedCards, account, false)
expect(cards['Enabled debit cards']).to.have.length(2).to.deep.equal(['Mastercard debit', 'Maestro'])
expect(cards['Not enabled debit cards']).to.have.length(1).to.deep.equal(['Visa debit'])
expect(cards['Enabled credit cards']).to.have.length(2).to.deep.equal(['American Express', 'JCB'])
expect(cards['Not enabled credit cards']).to.have.length(1).to.deep.equal(['Visa credit'])
})
})
})
Loading

0 comments on commit 3b978eb

Please sign in to comment.