Skip to content

Commit 440966f

Browse files
Withdraw link permissions logic and tests added (#61)
1 parent 8be893e commit 440966f

8 files changed

+162
-9
lines changed

app/routes/view-application.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { administrator, processor, user } = require('../auth/permissions')
55
const getStyleClassByStatus = require('../constants/status')
66
const ViewModel = require('./models/view-application')
77
const { upperFirstLetter } = require('../lib/display-helper')
8+
const mapAuth = require('../auth/map-auth')
89

910
module.exports = {
1011
method: 'GET',
@@ -28,8 +29,10 @@ module.exports = {
2829

2930
const status = upperFirstLetter(application.status.status.toLowerCase())
3031
const statusClass = getStyleClassByStatus(application.status.status)
32+
const mappedAuth = mapAuth(request)
3133
const withdrawLinkStatus = ['IN CHECK', 'AGREED']
32-
const withdrawConfirmationForm = application.status.status !== 'WITHDRAWN' && withdrawLinkStatus.includes(application.status.status) && request.query.withdraw
34+
const withdrawLink = withdrawLinkStatus.includes(application.status.status) && mappedAuth.isAdministrator
35+
const withdrawConfirmationForm = application.status.status !== 'WITHDRAWN' && withdrawLink && request.query.withdraw
3336

3437
return h.view('view-application', {
3538
applicationId: application.reference,
@@ -38,7 +41,7 @@ module.exports = {
3841
organisationName: application?.data?.organisation?.name,
3942
vetVisit: application?.vetVisit,
4043
claimed: application?.claimed,
41-
withdrawLink: withdrawLinkStatus.includes(application.status.status),
44+
withdrawLink,
4245
withdrawConfirmationForm,
4346
payment: application?.payment,
4447
...new ViewModel(application),

app/routes/withdraw-application.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
const Joi = require('joi')
22
const { withdrawApplication } = require('../api/applications')
3+
const { administrator } = require('../auth/permissions')
34

45
module.exports = {
56
method: 'POST',
67
path: '/withdraw-application',
78
options: {
9+
auth: { scope: [administrator] },
810
validate: {
911
payload: Joi.object({
1012
withdrawConfirmation: Joi.string().valid('yes', 'no'),

package-lock.json

+20-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ffc-ahwr-backoffice",
3-
"version": "1.15.0",
3+
"version": "1.15.1",
44
"description": "Back office of the health and welfare of your livestock",
55
"homepage": "https://github.com/DEFRA/ffc-ahwr-backoffice",
66
"main": "app/index.js",
@@ -12,6 +12,7 @@
1212
"test:watch": "jest --coverage=false --onlyChanged --watch --runInBand",
1313
"test:watch:all": "npm-run-all --parallel test:watch build:watch",
1414
"test:lint": "standard",
15+
"test:lint-fix": "standard --fix",
1516
"test:debug": "node --inspect-brk=0.0.0.0 ./node_modules/jest/bin/jest.js --coverage=false --onlyChanged --watch --runInBand --no-cache",
1617
"start:watch": "npm-run-all --parallel build:watch start:nodemon",
1718
"start:debug": "nodemon --inspect-brk=0.0.0.0 --ext css,js,njk --legacy-watch app/index.js",
@@ -57,6 +58,7 @@
5758
"cheerio": "^1.0.0-rc.10",
5859
"clean-webpack-plugin": "^4.0.0",
5960
"css-loader": "^6.6.0",
61+
"dotenv": "^16.0.3",
6062
"file-loader": "^6.2.0",
6163
"html-webpack-plugin": "^5.5.0",
6264
"jest": "^28.1.3",

test/data/view-applications.json

+25
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,30 @@
137137
"createdAt": "2022-06-06T14:27:51.251Z",
138138
"updatedAt": "2022-06-06T14:27:51.775Z",
139139
"createdBy": "admin"
140+
},
141+
"incheck": {
142+
"id": "75216bb8-b4e6-40aa-b51f-24e8a90f7195",
143+
"reference": "AHWR-555A-FD4C",
144+
"status": { "status": "IN CHECK" },
145+
"data": {
146+
"declaration": true,
147+
"whichReview": "sheep",
148+
"offerStatus": "accepted",
149+
"organisation": {
150+
"cph": "33/333/3333",
151+
"sbi": "333333333",
152+
"name": "My Farm",
153+
"email": "[email protected]",
154+
"isTest": true,
155+
"farmerName": "Farmer name",
156+
"address": "Long dusty road, Middle-of-knowhere, In the countryside, CC33 3CC"
157+
},
158+
"eligibleSpecies": "yes",
159+
"confirmCheckDetails": "yes"
160+
},
161+
"claimed": false,
162+
"createdAt": "2022-06-06T14:27:51.251Z",
163+
"updatedAt": "2022-06-06T14:27:51.775Z",
164+
"createdBy": "admin"
140165
}
141166
}

test/integration/narrow/routes/view-application.test.js

+80-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@ const { administrator } = require('../../../../app/auth/permissions')
55
const viewApplicationData = require('.././../../data/view-applications.json')
66
const reference = 'AHWR-555A-FD4C'
77

8+
function expectWithdrawLink ($, reference, isWithdrawLinkVisible) {
9+
if (isWithdrawLinkVisible) {
10+
expect($('.govuk-link').hasClass)
11+
const withdrawLink = $('.govuk-link')
12+
expect(withdrawLink.text()).toMatch('Withdraw')
13+
expect(withdrawLink.attr('href')).toMatch(`/view-application/${reference}?page=1&withdraw=true`)
14+
} else {
15+
expect($('.govuk-link').not.hasClass)
16+
}
17+
}
18+
819
jest.mock('../../../../app/api/applications')
920

1021
describe('View Application test', () => {
1122
const url = `/view-application/${reference}`
1223
jest.mock('../../../../app/auth')
13-
const auth = { strategy: 'session-auth', credentials: { scope: [administrator] } }
24+
let auth = { strategy: 'session-auth', credentials: { scope: [administrator] } }
1425

1526
beforeEach(() => {
1627
jest.clearAllMocks()
@@ -38,7 +49,12 @@ describe('View Application test', () => {
3849
expect($('h1.govuk-heading-l').text()).toEqual('400 - Bad Request')
3950
expectPhaseBanner.ok($)
4051
})
41-
test('returns 200 application agreed', async () => {
52+
test.each([
53+
['administrator', true],
54+
['processor', false],
55+
['user', false]
56+
])('returns 200 application agreed - %s role', async (authScope, isWithdrawLinkVisible) => {
57+
auth = { strategy: 'session-auth', credentials: { scope: [authScope] } }
4258
applications.getApplication.mockReturnValueOnce(viewApplicationData.agreed)
4359
const options = {
4460
method: 'GET',
@@ -75,6 +91,9 @@ describe('View Application test', () => {
7591
expect($('tbody tr:nth-child(5)').text()).toContain('Agreement accepted')
7692
expect($('tbody tr:nth-child(5)').text()).toContain('Yes')
7793
expect($('#claim').text()).toContain('Not claimed yet')
94+
95+
expectWithdrawLink($, reference, isWithdrawLinkVisible)
96+
7897
expectPhaseBanner.ok($)
7998
})
8099
test('returns 200 application applied', async () => {
@@ -114,6 +133,9 @@ describe('View Application test', () => {
114133
expect($('tbody tr:nth-child(5)').text()).toContain('Agreement accepted')
115134
expect($('tbody tr:nth-child(5)').text()).toContain('No')
116135
expect($('#claim').text()).toContain('Not eligible to claim')
136+
137+
expectWithdrawLink($, reference, false)
138+
117139
expectPhaseBanner.ok($)
118140
})
119141
test('returns 200 application data inputted', async () => {
@@ -143,6 +165,9 @@ describe('View Application test', () => {
143165
expect($('.govuk-summary-list__value').eq(3).text()).toMatch('[email protected]')
144166

145167
expect($('#claim').text()).toContain('Not eligible to claim')
168+
169+
expectWithdrawLink($, reference, false)
170+
146171
expectPhaseBanner.ok($)
147172
})
148173
test('returns 200 application claim', async () => {
@@ -185,6 +210,9 @@ describe('View Application test', () => {
185210
expect($('tbody:nth-child(1) tr:nth-child(5)').text()).toContain('1234234')
186211
expect($('tbody:nth-child(1) tr:nth-child(6)').text()).toContain('Test results unique reference number (URN)')
187212
expect($('tbody:nth-child(1) tr:nth-child(6)').text()).toContain('134242')
213+
214+
expectWithdrawLink($, reference, false)
215+
188216
expectPhaseBanner.ok($)
189217
})
190218
test('returns 200 application paid', async () => {
@@ -214,6 +242,56 @@ describe('View Application test', () => {
214242
expect($('.govuk-summary-list__value').eq(3).text()).toMatch('[email protected]')
215243

216244
expect($('#claim').text()).toContain('Claimed')
245+
246+
expectWithdrawLink($, reference, false)
247+
248+
expectPhaseBanner.ok($)
249+
})
250+
test.each([
251+
['administrator', true],
252+
['processor', false],
253+
['user', false]
254+
])('returns 200 application in check - %s role', async (authScope, isWithdrawLinkVisible) => {
255+
auth = { strategy: 'session-auth', credentials: { scope: [authScope] } }
256+
applications.getApplication.mockReturnValueOnce(viewApplicationData.incheck)
257+
const options = {
258+
method: 'GET',
259+
url,
260+
auth
261+
}
262+
const res = await global.__SERVER__.inject(options)
263+
expect(res.statusCode).toBe(200)
264+
const $ = cheerio.load(res.payload)
265+
expect($('h1.govuk-caption-l').text()).toContain(`Agreement number: ${reference}`)
266+
expect($('h2.govuk-heading-l').text()).toContain('In check')
267+
expect($('title').text()).toContain('Administration: User Application')
268+
expect($('.govuk-summary-list__row').length).toEqual(4)
269+
expect($('.govuk-summary-list__key').eq(0).text()).toMatch('Name:')
270+
expect($('.govuk-summary-list__value').eq(0).text()).toMatch('Farmer name')
271+
272+
expect($('.govuk-summary-list__key').eq(1).text()).toMatch('SBI number:')
273+
expect($('.govuk-summary-list__value').eq(1).text()).toMatch('333333333')
274+
275+
expect($('.govuk-summary-list__key').eq(2).text()).toMatch('Address:')
276+
expect($('.govuk-summary-list__value').eq(2).text()).toMatch('Long dusty road, Middle-of-knowhere, In the countryside, CC33 3CC')
277+
278+
expect($('.govuk-summary-list__key').eq(3).text()).toMatch('Email address:')
279+
expect($('.govuk-summary-list__value').eq(3).text()).toMatch('[email protected]')
280+
281+
expect($('tbody tr:nth-child(1)').text()).toContain('Date of agreement')
282+
expect($('tbody tr:nth-child(1)').text()).toContain('06/06/2022')
283+
expect($('tbody tr:nth-child(2)').text()).toContain('Business details correct')
284+
expect($('tbody tr:nth-child(2)').text()).toContain('Yes')
285+
expect($('tbody tr:nth-child(3)').text()).toContain('Type of review')
286+
expect($('tbody tr:nth-child(3)').text()).toContain('Sheep')
287+
expect($('tbody tr:nth-child(4)').text()).toContain('Number of livestock')
288+
expect($('tbody tr:nth-child(4)').text()).toContain('Minimum 21')
289+
expect($('tbody tr:nth-child(5)').text()).toContain('Agreement accepted')
290+
expect($('tbody tr:nth-child(5)').text()).toContain('Yes')
291+
expect($('#claim').text()).toContain('Not claimed yet')
292+
293+
expectWithdrawLink($, reference, isWithdrawLinkVisible)
294+
217295
expectPhaseBanner.ok($)
218296
})
219297
})

test/integration/narrow/routes/withdraw-application.test.js

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const cheerio = require('cheerio')
22
const expectPhaseBanner = require('../../../utils/phase-banner-expect')
3-
const { administrator } = require('../../../../app/auth/permissions')
3+
const { administrator, processor, user } = require('../../../../app/auth/permissions')
44
const getCrumbs = require('../../../utils/get-crumbs')
55

66
const applications = require('../../../../app/api/applications')
@@ -14,7 +14,6 @@ describe('View Application test', () => {
1414
let crumb
1515
const url = '/withdraw-application/'
1616
jest.mock('../../../../app/auth')
17-
const auth = { strategy: 'session-auth', credentials: { scope: [administrator] } }
1817

1918
beforeEach(async () => {
2019
crumb = await getCrumbs(global.__SERVER__)
@@ -31,7 +30,29 @@ describe('View Application test', () => {
3130
expect(res.statusCode).toBe(302)
3231
})
3332

33+
test('returns 403 when scope is not administrator', async () => {
34+
const auth = { strategy: 'session-auth', credentials: { scope: [processor, user] } }
35+
const options = {
36+
method: 'POST',
37+
url,
38+
auth,
39+
headers: { cookie: `crumb=${crumb}` },
40+
payload: {
41+
reference,
42+
withdrawConfirmation: 'yes',
43+
page: 1,
44+
crumb
45+
}
46+
}
47+
const res = await global.__SERVER__.inject(options)
48+
expect(res.statusCode).toBe(403)
49+
const $ = cheerio.load(res.payload)
50+
expect($('h1.govuk-heading-l').text()).toEqual('403 - Forbidden')
51+
expectPhaseBanner.ok($)
52+
})
53+
3454
test('returns 403', async () => {
55+
const auth = { strategy: 'session-auth', credentials: { scope: [processor, user] } }
3556
const options = {
3657
method: 'POST',
3758
url,
@@ -48,6 +69,7 @@ describe('View Application test', () => {
4869
})
4970

5071
test('Approve withdraw application', async () => {
72+
const auth = { strategy: 'session-auth', credentials: { scope: [administrator] } }
5173
const options = {
5274
method: 'POST',
5375
url,
@@ -67,6 +89,7 @@ describe('View Application test', () => {
6789
})
6890

6991
test('Cancel withdraw application', async () => {
92+
const auth = { strategy: 'session-auth', credentials: { scope: [administrator] } }
7093
const options = {
7194
method: 'POST',
7295
url,

test/setup.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require('dotenv').config()
2+
13
beforeEach(async () => {
24
// Set reference to server in order to close the server during teardown.
35
jest.setTimeout(10000)

0 commit comments

Comments
 (0)