Skip to content

Commit

Permalink
Merge pull request #236 from ministryofjustice/EMP-434-fe-crm-4-repor…
Browse files Browse the repository at this point in the history
…ting-ui-validation

EMP-434: CRM 4 Reporting UI validation
  • Loading branch information
muyinatech committed Aug 14, 2024
2 parents cc44ca6 + ba7c3f7 commit ee6dae6
Show file tree
Hide file tree
Showing 23 changed files with 617 additions and 207 deletions.
9 changes: 9 additions & 0 deletions server/@types/crmReport/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type ReportError = {
status: number
message: string
}

export type CrmReportResponse = {
text: string
error?: ReportError
}
2 changes: 0 additions & 2 deletions server/@types/eqApi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,3 @@ export type EvidenceFiles = {
}

export interface CrmResponse {}

export type CrmReportResponse = { text: string }
121 changes: 101 additions & 20 deletions server/controllers/generateReportController.test.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,144 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest'
import type { NextFunction, Request, Response } from 'express'
import { CrmReportResponse } from '@eqApi'
import CrmReportApiService from '../services/crmReportApiService'
import { CrmReportResponse } from '@crmReport'
import GenerateReportService from '../services/generateReportService'
import GenerateReportController from './generateReportController'

jest.mock('../services/crmReportApiService')
jest.mock('../services/generateReportService')
jest.mock('../utils/userProfileGroups', () => {
return jest.fn().mockReturnValue('1,4,5,6')
})

describe('downloadEvidenceController', () => {
let mockCrmReportApiService: jest.Mocked<CrmReportApiService>
describe('generateReportController', () => {
let mockGenerateReportService: jest.Mocked<GenerateReportService>
let request: DeepMocked<Request>
let response: DeepMocked<Response>
const next: DeepMocked<NextFunction> = createMock<NextFunction>({})

beforeEach(() => {
request = createMock<Request>({})
response = createMock<Response>({})
mockCrmReportApiService = new CrmReportApiService(null) as jest.Mocked<CrmReportApiService>
mockGenerateReportService = new GenerateReportService(null) as jest.Mocked<GenerateReportService>
})

it('should render generate report page', async () => {
const generateReportController = new GenerateReportController(mockCrmReportApiService)
const generateReportController = new GenerateReportController(mockGenerateReportService)
const requestHandler = generateReportController.show()

await requestHandler(request, response, next)

expect(response.render).toHaveBeenCalledWith('pages/generateReport')
expect(response.render).toHaveBeenCalledWith('pages/generateReport', { backUrl: '/' })
})

it('should download the requested CRM file', async () => {
mockCrmReportApiService.getCrmReport.mockResolvedValue(successResponse())
it('should download the requested CRM report', async () => {
const crmReportResponse = getCrmReportResponse()

const downloadData =
' `Client UFN,Usn,Provider Account,Firm Name,Client Name,Rep Order Number,Maat ID,Prison Law,Date Received,Decision Date,Decision,Expenditure Type,Expert Name,Quantity,Rate,Unit,Total Cost,Additional Expenditure,Total Authority,Total Granted,Granting Caseworker\n' +
' 031022/777,5001613,0D182J,ABELS,Joe modo,78543657,,No,2023-03-16,2023-03-16,Grant,a Psychiatrist,tyjtjtjt,4.0,50.0,Hour(s),200.0,0.0,200.0,200.0,Sym-G`'
mockGenerateReportService.getCrmReport.mockResolvedValue(crmReportResponse)

const generateReportController = new GenerateReportController(mockCrmReportApiService)
const generateReportController = new GenerateReportController(mockGenerateReportService)
const requestHandler = generateReportController.submit()
request.body = {
crmType: 'crm4',
startDate: '2023-03-01',
endDate: '2023-03-30',
profileAcceptedTypes: '1,4,5,6',
}

await requestHandler(request, response, next)

expect(response.setHeader).toHaveBeenCalledWith('Content-Disposition', 'attachment; filename=crm4Report.csv')
expect(response.send).toHaveBeenCalledWith(downloadData)
expect(response.send).toHaveBeenCalledWith(crmReportResponse.text)

expect(mockCrmReportApiService.getCrmReport).toHaveBeenCalledWith('2023-03-01', '2023-03-30', '1,4,5,6')
expect(mockGenerateReportService.getCrmReport).toHaveBeenCalledWith('2023-03-01', '2023-03-30', '1,4,5,6')
})

it('should render generate report page with field errors', async () => {
const generateReportController = new GenerateReportController(mockGenerateReportService)
const requestHandler = generateReportController.submit()
request.body = {
crmType: '',
startDate: '2023-03-01',
endDate: '2023-03-30',
}

await requestHandler(request, response, next)

expect(response.render).toHaveBeenCalledWith('pages/generateReport', {
results: [],
errors: {
list: [
{
href: '#crmType',
text: 'CRM type must be selected',
},
],
messages: {
crmType: {
text: 'CRM type must be selected',
},
},
},
backUrl: '/',
formValues: {
crmType: '',
startDate: '2023-03-01',
endDate: '2023-03-30',
},
})
})

it.each([
['Not authorised to generate report', 401],
['Not authorised to generate report', 403],
['No report data found', 404],
['Something went wrong with generate report', 500],
])('should render generate page with "%s" error for status %s', async (errorMessage, errorStatus) => {
const crmReportResponse: CrmReportResponse = {
text: null,
error: {
status: errorStatus,
message: 'error',
},
}

mockGenerateReportService.getCrmReport.mockResolvedValue(crmReportResponse)

const generateReportController = new GenerateReportController(mockGenerateReportService)
const requestHandler = generateReportController.submit()
request.body = {
crmType: 'crm4',
startDate: '2023-03-01',
endDate: '2023-03-30',
}

await requestHandler(request, response, next)

expect(response.render).toHaveBeenCalledWith('pages/generateReport', {
results: [],
errors: {
list: [
{
href: '#',
text: errorMessage,
},
],
},
backUrl: '/',
formValues: {
crmType: 'crm4',
startDate: '2023-03-01',
endDate: '2023-03-30',
},
})
})
})

const successResponse = (): CrmReportResponse => {
const getCrmReportResponse = (): CrmReportResponse => {
return {
text:
' `Client UFN,Usn,Provider Account,Firm Name,Client Name,Rep Order Number,Maat ID,Prison Law,Date Received,Decision Date,Decision,Expenditure Type,Expert Name,Quantity,Rate,Unit,Total Cost,Additional Expenditure,Total Authority,Total Granted,Granting Caseworker\n' +
' 031022/777,5001613,0D182J,ABELS,Joe modo,78543657,,No,2023-03-16,2023-03-16,Grant,a Psychiatrist,tyjtjtjt,4.0,50.0,Hour(s),200.0,0.0,200.0,200.0,Sym-G`',
'Client UFN,Usn,Provider Account,Firm Name,Client Name,Rep Order Number,Maat ID,Prison Law,Date Received,' +
'Decision Date,Decision,Expenditure Type,Expert Name,Quantity,Rate,Unit,Total Cost,Additional Expenditure,' +
'Total Authority,Total Granted,Granting Caseworker\n' +
'031022/777,123456789,1234AB,Some Firm,Some Client,999999999,,No,2023-03-16,2023-03-16,Grant,a Psychiatrist,' +
'tyjtjtjt,4.0,50.0,Hour(s),200.0,0.0,200.0,200.0,Sym-G`',
}
}
64 changes: 54 additions & 10 deletions server/controllers/generateReportController.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,69 @@
import type { Request, RequestHandler, Response } from 'express'
import getProfileAcceptedTypes from '../utils/userProfileGroups'
import CrmReportApiService from '../services/crmReportApiService'
import GenerateReportService from '../services/generateReportService'
import validateReportParams from '../utils/generateReportValidation'
import manageBackLink from '../utils/crmBackLink'
import { buildErrors } from '../utils/errorDisplayHelper'

const CURRENT_URL = '/generate-report'
const VIEW_PATH = 'pages/generateReport'

export default class GenerateReportController {
constructor(private readonly crmReportApiService: CrmReportApiService) {}
constructor(private readonly generateReportService: GenerateReportService) {}

show(): RequestHandler {
return async (req: Request, res: Response): Promise<void> => {
res.render('pages/generateReport')
const backUrl = manageBackLink(req, CURRENT_URL)
res.render(VIEW_PATH, { backUrl })
}
}

submit(): RequestHandler {
return async (req: Request, res: Response): Promise<void> => {
const response = await this.crmReportApiService.getCrmReport(
req.body.startDate,
req.body.endDate,
getProfileAcceptedTypes(res),
)
res.setHeader('Content-Disposition', `attachment; filename=crm4Report.csv`)
res.send(response.text)
const reportParams: Record<string, string> = {
crmType: req.body.crmType as string,
startDate: req.body.startDate as string,
endDate: req.body.endDate as string,
}
const validationErrors = validateReportParams(reportParams)
if (validationErrors) {
res.render(VIEW_PATH, {
results: [],
errors: validationErrors,
formValues: reportParams,
backUrl: manageBackLink(req, CURRENT_URL),
})
} else {
const reportResponse = await this.generateReportService.getCrmReport(
req.body.startDate,
req.body.endDate,
getProfileAcceptedTypes(res),
)
if (reportResponse.error) {
const errors = buildErrors(reportResponse.error, this.getErrorMessage)
res.render(VIEW_PATH, {
results: [],
errors,
formValues: reportParams,
backUrl: manageBackLink(req, CURRENT_URL),
})
} else {
res.setHeader('Content-Disposition', 'attachment; filename=crm4Report.csv')
res.send(reportResponse.text)
}
}
}
}

private getErrorMessage(errorStatus: number): string {
switch (errorStatus) {
case 401:
case 403:
return 'Not authorised to generate report'
case 404:
return 'No report data found'
default:
return 'Something went wrong with generate report'
}
}
}
2 changes: 1 addition & 1 deletion server/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import GenerateReportController from './generateReportController'
export const controllers = (services: Services) => {
const downloadEvidenceController = new DownloadEvidenceController(services.downloadEvidenceService)
const searchEformController = new SearchEformController(services.searchEformService)
const generateReportController = new GenerateReportController(services.crmReportApiService)
const generateReportController = new GenerateReportController(services.generateReportService)
return { downloadEvidenceController, generateReportController, searchEformController, ...crmControllers(services) }
}

Expand Down
12 changes: 6 additions & 6 deletions server/controllers/searchEformController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('Search Eform Controller', () => {
mockSearchEformService = new SearchEformService(null) as jest.Mocked<SearchEformService>
})

it('should render initial eform', async () => {
it('should render initial search eform', async () => {
const searchEformController = new SearchEformController(mockSearchEformService)
const requestHandler = searchEformController.show()
await requestHandler(request, response, next)
Expand All @@ -36,7 +36,7 @@ describe('Search Eform Controller', () => {
})
})

it('should render eform with search results', async () => {
it('should render search eform with search results', async () => {
const searchResponse = {
results: [
{
Expand Down Expand Up @@ -119,7 +119,7 @@ describe('Search Eform Controller', () => {
})
})

it('should render eform with field errors', async () => {
it('should render search eform with field errors', async () => {
const searchEformController = new SearchEformController(mockSearchEformService)
const requestHandler = searchEformController.show()
request.query = {
Expand Down Expand Up @@ -159,7 +159,7 @@ describe('Search Eform Controller', () => {
expect(mockSearchEformService.search).not.toHaveBeenCalled()
})

it('should render eform with error when empty form submitted', async () => {
it('should render search eform with error when empty form submitted', async () => {
const searchEformController = new SearchEformController(mockSearchEformService)
const requestHandler = searchEformController.show()
request.query = {
Expand Down Expand Up @@ -199,7 +199,7 @@ describe('Search Eform Controller', () => {
['Not authorised to search', 403],
['No search result found', 404],
['Something went wrong with the search', 500],
])('should render eform with errors for "%s" api error and status %s', async (errorMessage, errorStatus) => {
])('should render search eform with "%s" error for status %s', async (errorMessage, errorStatus) => {
const searchResponse: SearchResponse = {
results: [],
error: {
Expand Down Expand Up @@ -254,7 +254,7 @@ describe('Search Eform Controller', () => {
})
})

it('should submit eform', async () => {
it('should submit search eform', async () => {
const searchEformController = new SearchEformController(mockSearchEformService)
const requestHandler = searchEformController.submit()
request.body = {
Expand Down
Loading

0 comments on commit ee6dae6

Please sign in to comment.