diff --git a/integration_tests/e2e/refer/new/submit.cy.ts b/integration_tests/e2e/refer/new/submit.cy.ts index a33cd5c9a..cb77ea051 100644 --- a/integration_tests/e2e/refer/new/submit.cy.ts +++ b/integration_tests/e2e/refer/new/submit.cy.ts @@ -118,7 +118,7 @@ context('Submitting a referral', () => { }) it('Shows the information the user has submitted in the referral form', () => { - cy.task('stubParticipationsByPerson', { courseParticipations, prisonNumber: prisoner.prisonerNumber }) + cy.task('stubParticipationsByReferral', { courseParticipations, referralId: submittableReferral.id }) cy.visit(path) @@ -151,7 +151,7 @@ context('Submitting a referral', () => { describe('for a person with no programme history', () => { it('indicates that there is no programme history', () => { - cy.task('stubParticipationsByPerson', { courseParticipations: [], prisonNumber: prisoner.prisonerNumber }) + cy.task('stubParticipationsByReferral', { courseParticipations: [], referralId: submittableReferral.id }) cy.visit(path) @@ -173,7 +173,7 @@ context('Submitting a referral', () => { describe('When submitting a referral', () => { beforeEach(() => { cy.task('stubCourse', course) - cy.task('stubParticipationsByPerson', { courseParticipations, prisonNumber: prisoner.prisonerNumber }) + cy.task('stubParticipationsByReferral', { courseParticipations, referralId: submittableReferral.id }) }) it('redirects to the referral complete page when the user confirms the details', () => { diff --git a/server/controllers/refer/new/courseParticipationsController.test.ts b/server/controllers/refer/new/courseParticipationsController.test.ts index 1072882a8..85ce82b48 100644 --- a/server/controllers/refer/new/courseParticipationsController.test.ts +++ b/server/controllers/refer/new/courseParticipationsController.test.ts @@ -49,7 +49,6 @@ describe('NewReferralsCourseParticipationsController', () => { controller = new NewReferralsCourseParticipationsController(courseService, personService, referralService) courseService.presentCourseParticipation.mockResolvedValue(summaryListOptions) - courseService.getAndPresentParticipationsByPerson.mockResolvedValue([summaryListOptions, summaryListOptions]) courseService.getParticipationsByPerson.mockResolvedValue(existingParticipations) courseService.getParticipationsByReferral.mockResolvedValue(referralParticipations) }) diff --git a/server/controllers/refer/new/referralsController.test.ts b/server/controllers/refer/new/referralsController.test.ts index a1f7d8b6c..4e67e5518 100644 --- a/server/controllers/refer/new/referralsController.test.ts +++ b/server/controllers/refer/new/referralsController.test.ts @@ -11,6 +11,7 @@ import { courseAudienceFactory, courseFactory, courseOfferingFactory, + courseParticipationFactory, organisationFactory, personFactory, referralFactory, @@ -380,7 +381,17 @@ describe('NewReferralsController', () => { audience: courseAudienceFactory.build(), }) const organisation = organisationFactory.build({ id: referableCourseOffering.organisationId }) - const summaryListOptions = 'summary list options' as unknown as GovukFrontendSummaryListWithRowsWithKeysAndValues + const participationsForReferral = courseParticipationFactory.buildList(2, { isDraft: true }) + const summaryListOptions: Array = [ + { + card: { title: { text: 'Participation 1' } }, + rows: [{ key: { text: 'Setting' }, value: { text: 'Community' } }], + }, + { + card: { title: { text: 'Participation 2' } }, + rows: [{ key: { text: 'Setting' }, value: { text: 'Custody' } }], + }, + ] beforeEach(() => { courseService.getCourseByOffering.mockResolvedValue(course) @@ -401,7 +412,7 @@ describe('NewReferralsController', () => { courseService.getCourse.mockResolvedValue(course) ;(CourseUtils.presentCourse as jest.Mock).mockReturnValue(coursePresenter) - courseService.getAndPresentParticipationsByPerson.mockResolvedValue([summaryListOptions, summaryListOptions]) + courseService.getParticipationsByReferral.mockResolvedValue(participationsForReferral) const emptyErrorsLocal = { list: [], messages: {} } ;(FormUtils.setFieldErrors as jest.Mock).mockImplementation((_request, _response, _fields) => { @@ -410,6 +421,10 @@ describe('NewReferralsController', () => { }) it('renders the referral check answers page and sets the `returnTo` value in the sesssion', async () => { + const actions = { + change: true, + remove: false, + } const requestHandler = controller.checkAnswers() await requestHandler(request, response, next) @@ -417,13 +432,21 @@ describe('NewReferralsController', () => { expect(personService.getPerson).toHaveBeenCalledWith(username, submittableReferral.prisonNumber) expect(userService.getFullNameFromUsername).toHaveBeenCalledWith(userToken, submittableReferral.referrerUsername) expect(FormUtils.setFieldErrors).toHaveBeenCalledWith(request, response, ['confirmation']) - expect(courseService.getAndPresentParticipationsByPerson).toHaveBeenCalledWith( - username, + expect(courseService.getParticipationsByReferral).toHaveBeenCalledWith(username, referralId) + expect(courseService.presentCourseParticipation).toHaveBeenCalledTimes(2) + expect(courseService.presentCourseParticipation).toHaveBeenCalledWith( + userToken, + participationsForReferral[0], + referralId, + undefined, + actions, + ) + expect(courseService.presentCourseParticipation).toHaveBeenCalledWith( userToken, - person.prisonNumber, + participationsForReferral[1], referralId, - { change: true, remove: false }, - 3, + undefined, + actions, ) expect(request.session.returnTo).toBe('check-answers') expect(response.render).toHaveBeenCalledWith('referrals/new/checkAnswers', { diff --git a/server/controllers/refer/new/referralsController.ts b/server/controllers/refer/new/referralsController.ts index 746eeed07..098e13884 100644 --- a/server/controllers/refer/new/referralsController.ts +++ b/server/controllers/refer/new/referralsController.ts @@ -44,18 +44,20 @@ export default class NewReferralsController { this.userService.getEmailFromUsername(req.user.token, referral.referrerUsername), ]) - const [organisation, participationSummaryListsOptions] = await Promise.all([ + const [organisation, participationsForReferral] = await Promise.all([ this.organisationService.getOrganisation(req.user.token, courseOffering.organisationId), - this.courseService.getAndPresentParticipationsByPerson( - req.user.username, - req.user.token, - person.prisonNumber, - referralId, - { change: true, remove: false }, - 3, - ), + this.courseService.getParticipationsByReferral(req.user.username, referralId), ]) + const participationSummaryListsOptions = await Promise.all( + participationsForReferral.map(participation => + this.courseService.presentCourseParticipation(req.user.token, participation, referralId, undefined, { + change: true, + remove: false, + }), + ), + ) + const coursePresenter = CourseUtils.presentCourse(course) FormUtils.setFieldErrors(req, res, ['confirmation']) diff --git a/server/services/courseService.test.ts b/server/services/courseService.test.ts index 48ca8204a..7451e4e33 100644 --- a/server/services/courseService.test.ts +++ b/server/services/courseService.test.ts @@ -167,96 +167,6 @@ describe('CourseService', () => { }) }) - describe('getAndPresentCourseParticipationsByPrisonNumber', () => { - const person = personFactory.build() - const addedByUser = userFactory.build({ name: 'john smith' }) - - const earliestCourseParticipation = courseParticipationFactory.build({ - addedBy: addedByUser.username, - createdAt: '2022-01-01T12:00:00.000Z', - prisonNumber: person.prisonNumber, - }) - const latestCourseParticipation = courseParticipationFactory.build({ - addedBy: addedByUser.username, - createdAt: '2023-01-01T12:00:00.000Z', - prisonNumber: person.prisonNumber, - }) - const addedByDisplayName = StringUtils.convertToTitleCase(addedByUser.name) - - beforeEach(() => { - courseClient.findParticipationsByPerson.mockResolvedValue([ - latestCourseParticipation, - earliestCourseParticipation, - ]) - - userService.getFullNameFromUsername.mockResolvedValue(addedByDisplayName) - }) - - describe('when no actions are passed', () => { - it('fetches the creator, then formats the participation and creator in the appropriate format for passing to a GOV.UK summary list Nunjucks macro', async () => { - when(CourseParticipationUtils.summaryListOptions as jest.Mock) - .calledWith({ ...earliestCourseParticipation, addedByDisplayName }, 'a-referral-id', undefined, { - change: true, - remove: true, - }) - .mockReturnValue('course participation 1 options') - when(CourseParticipationUtils.summaryListOptions as jest.Mock) - .calledWith({ ...latestCourseParticipation, addedByDisplayName }, 'a-referral-id', undefined, { - change: true, - remove: true, - }) - .mockReturnValue('course participation 2 options') - - const result = await service.getAndPresentParticipationsByPerson( - username, - userToken, - person.prisonNumber, - 'a-referral-id', - ) - - expect(result).toEqual(['course participation 1 options', 'course participation 2 options']) - }) - }) - - describe('when actions and headingLevel are passed', () => { - it('uses the specified actions when creating options for the summary list Nunjucks macro', async () => { - await service.getAndPresentParticipationsByPerson( - username, - userToken, - person.prisonNumber, - 'a-referral-id', - { - change: false, - remove: false, - }, - 3, - ) - - expect(CourseParticipationUtils.summaryListOptions).toHaveBeenNthCalledWith( - 1, - { ...earliestCourseParticipation, addedByDisplayName }, - 'a-referral-id', - 3, - { - change: false, - remove: false, - }, - ) - - expect(CourseParticipationUtils.summaryListOptions).toHaveBeenNthCalledWith( - 2, - { ...latestCourseParticipation, addedByDisplayName }, - 'a-referral-id', - 3, - { - change: false, - remove: false, - }, - ) - }) - }) - }) - describe('getBuildingChoicesVariants', () => { it('returns the courses associated with a given offering', async () => { const course = courseFactory.build() @@ -473,18 +383,32 @@ describe('CourseService', () => { describe('getParticipationsByReferral', () => { const referral = referralFactory.build() - it('returns a list of participations for a given referral', async () => { + it('returns a list of participationssorted by `createdAt` for a given referral', async () => { const courseParticipations = courseParticipationFactory.buildList(2, { referralId: referral.id }) when(courseClient.findParticipationsByReferral).calledWith(referral.id).mockResolvedValue(courseParticipations) const result = await service.getParticipationsByReferral(username, referral.id) - expect(result).toEqual(courseParticipations) + expect(result).toEqual(courseParticipations.slice(0).sort((a, b) => a.createdAt.localeCompare(b.createdAt))) expect(courseClientBuilder).toHaveBeenCalledWith(systemToken) expect(courseClient.findParticipationsByReferral).toHaveBeenCalledWith(referral.id) }) + + describe('when there is an error fetching the participations for a given referral', () => { + it('throws an error', async () => { + const clientError = createError(500) + courseClient.findParticipationsByReferral.mockRejectedValue(clientError) + + await expect(() => service.getParticipationsByReferral(username, referral.id)).rejects.toThrow( + `Error fetching course participations for referral with ID ${referral.id}.`, + ) + + expect(courseClientBuilder).toHaveBeenCalledWith(systemToken) + expect(courseClient.findParticipationsByReferral).toHaveBeenCalledWith(referral.id) + }) + }) }) describe('presentCourseParticipation', () => { diff --git a/server/services/courseService.ts b/server/services/courseService.ts index fa7f02f4b..8dcfdabdd 100644 --- a/server/services/courseService.ts +++ b/server/services/courseService.ts @@ -85,28 +85,6 @@ export default class CourseService { return courseClient.destroyParticipation(courseParticipationId) } - async getAndPresentParticipationsByPerson( - username: Express.User['username'], - userToken: Express.User['token'], - prisonNumber: Person['prisonNumber'], - referralId: Referral['id'], - withActions?: { - change: boolean - remove: boolean - }, - headingLevel?: number, - ): Promise> { - const sortedCourseParticipations = (await this.getParticipationsByPerson(username, prisonNumber)).sort( - (participationA, participationB) => participationA.createdAt.localeCompare(participationB.createdAt), - ) - - return Promise.all( - sortedCourseParticipations.map(participation => - this.presentCourseParticipation(userToken, participation, referralId, headingLevel, withActions), - ), - ) - } - async getBuildingChoicesVariants( username: Express.User['username'], courseId: Course['id'], @@ -246,7 +224,20 @@ export default class CourseService { const systemToken = await hmppsAuthClient.getSystemClientToken(username) const courseClient = this.courseClientBuilder(systemToken) - return courseClient.findParticipationsByReferral(referralId) + try { + const participations = await courseClient.findParticipationsByReferral(referralId) + + return participations.sort((participationA, participationB) => + participationA.createdAt.localeCompare(participationB.createdAt), + ) + } catch (error) { + const knownError = error as ResponseError + + throw createError( + knownError.status || 500, + `Error fetching course participations for referral with ID ${referralId}.`, + ) + } } async presentCourseParticipation(