Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(APG-470) Update CYA page to only show participations added as part of referral #844

Merged
merged 3 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions integration_tests/e2e/refer/new/submit.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand Down
37 changes: 30 additions & 7 deletions server/controllers/refer/new/referralsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
courseAudienceFactory,
courseFactory,
courseOfferingFactory,
courseParticipationFactory,
organisationFactory,
personFactory,
referralFactory,
Expand Down Expand Up @@ -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<GovukFrontendSummaryListWithRowsWithKeysAndValues> = [
{
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)
Expand All @@ -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) => {
Expand All @@ -410,20 +421,32 @@ 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)

expect(referralService.getReferral).toHaveBeenCalledWith(username, referralId)
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', {
Expand Down
20 changes: 11 additions & 9 deletions server/controllers/refer/new/referralsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down
108 changes: 16 additions & 92 deletions server/services/courseService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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', () => {
Expand Down
37 changes: 14 additions & 23 deletions server/services/courseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Array<GovukFrontendSummaryListWithRowsWithKeysAndValues>> {
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'],
Expand Down Expand Up @@ -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(
Expand Down
Loading