From 107c41e725562a7f77155586651025f32c1bc00e Mon Sep 17 00:00:00 2001 From: Daniel Burnley Date: Thu, 2 Nov 2023 14:45:29 +0000 Subject: [PATCH] CDPS-387: Remove local mock data (#329) * Remove local mock data from the prison API Client * Remove adjudications mock data catch * Remove curious API mock data call * Remove incentives API mock data call * Remove non-associations API mock data call * Ignore 404s from visit balances * Handle 404s from visit balances * Remove indirection to rest client * Handle 404s from addresses endpoint --- integration_tests/e2e/addAlertPage.cy.ts | 2 + integration_tests/e2e/addCaseNotePage.cy.ts | 2 + integration_tests/e2e/alertsPage.cy.ts | 4 + integration_tests/e2e/personalPage.cy.ts | 1 + integration_tests/e2e/photoPage.cy.ts | 7 +- integration_tests/index.d.ts | 1 + integration_tests/mockApis/prison.ts | 18 ++- integration_tests/support/commands.ts | 4 +- .../prisonerScheduleController.test.ts | 12 +- server/data/adjudicationsApiClient.ts | 13 +- server/data/curiousApiClient.ts | 23 +-- server/data/incentivesApiClient.ts | 13 +- server/data/interfaces/prisonApiClient.ts | 2 +- server/data/nonAssociationsApiClient.ts | 21 +-- server/data/prisonApiClient.ts | 145 +++++++++--------- server/data/restClient.ts | 2 +- server/services/overviewPageService.test.ts | 22 +++ server/services/overviewPageService.ts | 8 +- server/services/personalPageService.test.ts | 7 + 19 files changed, 158 insertions(+), 149 deletions(-) diff --git a/integration_tests/e2e/addAlertPage.cy.ts b/integration_tests/e2e/addAlertPage.cy.ts index 4176082c8..dabf9028d 100644 --- a/integration_tests/e2e/addAlertPage.cy.ts +++ b/integration_tests/e2e/addAlertPage.cy.ts @@ -13,6 +13,7 @@ const visitAlertsPage = (): AlertsPage => { context('Add Alert Page', () => { beforeEach(() => { cy.task('reset') + cy.setupBannerStubs({ prisonerNumber: 'G6123VU' }) cy.setupUserAuth({ roles: [Role.GlobalSearch, Role.UpdateAlert] }) }) @@ -21,6 +22,7 @@ context('Add Alert Page', () => { let addAlertPage: AddAlertPage beforeEach(() => { + cy.setupBannerStubs({ prisonerNumber: 'G6123VU' }) cy.setupAlertsPageStubs({ prisonerNumber: 'G6123VU', bookingId: 1102484 }) cy.task('stubGetAlertTypes') cy.task('stubCreateAlert') diff --git a/integration_tests/e2e/addCaseNotePage.cy.ts b/integration_tests/e2e/addCaseNotePage.cy.ts index 2625199bd..6d96098c3 100644 --- a/integration_tests/e2e/addCaseNotePage.cy.ts +++ b/integration_tests/e2e/addCaseNotePage.cy.ts @@ -14,6 +14,8 @@ context('Add Case Note Page', () => { beforeEach(() => { cy.task('reset') cy.setupUserAuth() + cy.task('stubGetCaseNotes', 'G6123VU') + cy.task('stubGetCaseNotesUsage', 'G6123VU') cy.task('stubGetCaseNoteTypes') cy.task('stubGetCaseNoteTypesForUser') cy.task('stubAddCaseNote') diff --git a/integration_tests/e2e/alertsPage.cy.ts b/integration_tests/e2e/alertsPage.cy.ts index 9640ee695..5b5c1f36c 100644 --- a/integration_tests/e2e/alertsPage.cy.ts +++ b/integration_tests/e2e/alertsPage.cy.ts @@ -19,6 +19,7 @@ const visitEmptyAlertsPage = () => { context('Alerts Page - Permissions', () => { context('Active alerts', () => { const visitPage = prisonerDataOverrides => { + cy.setupBannerStubs({ prisonerNumber: 'G6123VU' }) cy.setupAlertsPageStubs({ prisonerNumber: 'G6123VU', bookingId: 1102484, prisonerDataOverrides }) visitActiveAlertsPage({ failOnStatusCode: false }) } @@ -35,6 +36,7 @@ context('Alerts Page - User does not have Update Alerts role', () => { beforeEach(() => { cy.task('reset') cy.setupUserAuth() + cy.setupBannerStubs({ prisonerNumber: 'G6123VU' }) }) context('Active Alerts', () => { @@ -119,6 +121,7 @@ context('Alerts Page - User does not have Update Alerts role', () => { beforeEach(() => { cy.setupAlertsPageStubs({ prisonerNumber: 'A1234BC', bookingId: 1234567 }) + cy.setupBannerStubs({ prisonerNumber: 'G6123VU', bookingId: 1234567 }) visitEmptyAlertsPage() alertsPage = Page.verifyOnPageWithTitle(AlertsPage, 'Active alerts') }) @@ -209,6 +212,7 @@ context('Alerts Page - User has Update Alert role', () => { roles: [Role.PrisonUser, Role.UpdateAlert], caseLoads: [{ caseloadFunction: '', caseLoadId: 'MDI', currentlyActive: true, description: '', type: '' }], }) + cy.setupBannerStubs({ prisonerNumber: 'G6123VU' }) cy.setupAlertsPageStubs({ prisonerNumber: 'G6123VU', bookingId: 1102484 }) visitActiveAlertsPage() alertsPage = Page.verifyOnPageWithTitle(AlertsPage, 'Active alerts') diff --git a/integration_tests/e2e/personalPage.cy.ts b/integration_tests/e2e/personalPage.cy.ts index be0692715..5d6044e19 100644 --- a/integration_tests/e2e/personalPage.cy.ts +++ b/integration_tests/e2e/personalPage.cy.ts @@ -45,6 +45,7 @@ context('When signed in', () => { cy.task('stubHealthTreatmentReferenceDomain') cy.task('stubReasonableAdjustments', 1102484) cy.task('stubPersonalCareNeeds', 1102484) + cy.task('stubIdentifiers', 1102484) visitPersonalDetailsPage() }) diff --git a/integration_tests/e2e/photoPage.cy.ts b/integration_tests/e2e/photoPage.cy.ts index 881b5873f..86086fe0e 100644 --- a/integration_tests/e2e/photoPage.cy.ts +++ b/integration_tests/e2e/photoPage.cy.ts @@ -8,14 +8,17 @@ context('Photo Page', () => { const bookingId = 1102484 context('Permissions', () => { - const visitPage = () => cy.signIn({ failOnStatusCode: false, redirectPath: 'prisoner/G6123VU/image' }) + const visitPage = prisonerDataOverrides => { + cy.setupBannerStubs({ prisonerNumber, bookingId, prisonerDataOverrides }) + cy.signIn({ failOnStatusCode: false, redirectPath: 'prisoner/G6123VU/image' }) + } permissionsTests({ prisonerNumber, visitPage, pageToDisplay: PrisonerPhotoPage }) }) beforeEach(() => { cy.task('reset') cy.setupUserAuth() - cy.setupBannerStubs({ prisonerNumber }) + cy.setupBannerStubs({ prisonerNumber, bookingId }) cy.setupOverviewPageStubs({ prisonerNumber, bookingId }) }) diff --git a/integration_tests/index.d.ts b/integration_tests/index.d.ts index 53e07f428..320c418b2 100644 --- a/integration_tests/index.d.ts +++ b/integration_tests/index.d.ts @@ -13,6 +13,7 @@ declare global { setupBannerStubs(options: { prisonerNumber: string prisonerDataOverrides?: Partial + bookingId?: number }): Chainable setupOverviewPageStubs(options: { prisonerNumber: string diff --git a/integration_tests/mockApis/prison.ts b/integration_tests/mockApis/prison.ts index ad290866b..3843cbe68 100644 --- a/integration_tests/mockApis/prison.ts +++ b/integration_tests/mockApis/prison.ts @@ -914,7 +914,7 @@ export default { return stubFor({ request: { method: 'GET', - urlPattern: `/prison/api/agencies/${agencyId}\\?activeOnly=false`, + urlPattern: `/prison/api/agencies/${agencyId}\\?.*`, }, response: { status: 200, @@ -1132,4 +1132,20 @@ export default { }, }) }, + + stubIdentifiers: (bookingId: number) => { + return stubFor({ + request: { + method: 'GET', + urlPattern: `/prison/api/bookings/${bookingId}/identifiers`, + }, + response: { + status: 200, + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, + jsonBody: identifiersMock, + }, + }) + }, } diff --git a/integration_tests/support/commands.ts b/integration_tests/support/commands.ts index ceb3d26e2..c7b731b99 100644 --- a/integration_tests/support/commands.ts +++ b/integration_tests/support/commands.ts @@ -4,9 +4,11 @@ Cypress.Commands.add('signIn', (options = { failOnStatusCode: true, redirectPath return cy.task('getSignInUrl').then((url: string) => cy.visit(url, { failOnStatusCode })) }) -Cypress.Commands.add('setupBannerStubs', ({ prisonerNumber, prisonerDataOverrides }) => { +Cypress.Commands.add('setupBannerStubs', ({ prisonerNumber, prisonerDataOverrides, bookingId = 1102484 }) => { cy.task('stubPrisonerData', { prisonerNumber, overrides: prisonerDataOverrides }) cy.task('stubEventsForProfileImage', prisonerNumber) + cy.task('stubAssessments', bookingId) + cy.task('stubInmateDetail', bookingId) }) Cypress.Commands.add( diff --git a/server/controllers/prisonerScheduleController.test.ts b/server/controllers/prisonerScheduleController.test.ts index 1ae527d2e..6a2b5f4a0 100644 --- a/server/controllers/prisonerScheduleController.test.ts +++ b/server/controllers/prisonerScheduleController.test.ts @@ -1,17 +1,14 @@ import { PrisonerMockDataA } from '../data/localMockData/prisoner' import { inmateDetailMock } from '../data/localMockData/inmateDetailMock' import PrisonerScheduleController from './prisonerScheduleController' -import { dataAccess } from '../data' import { PrisonerScheduleThisWeekMock } from '../data/localMockData/prisonerScheduleMock' import { getEventsNextWeekMock, getEventsThisWeekMock } from '../data/localMockData/getEventsMock' +import { prisonApiClientMock } from '../../tests/mocks/prisonApiClientMock' +import { PrisonApiClient } from '../data/interfaces/prisonApiClient' describe('Prisoner schedule', () => { const offenderNo = 'ABC123' - const prisonApi = { - getDetails: jest.fn(), - getScheduledEventsForThisWeek: jest.fn(), - getScheduledEventsForNextWeek: jest.fn(), - } + let prisonApi: PrisonApiClient let req: any let res: any @@ -31,9 +28,10 @@ describe('Prisoner schedule', () => { } res = { locals: {}, render: jest.fn(), status: jest.fn() } + prisonApi = prisonApiClientMock() prisonApi.getScheduledEventsForThisWeek = jest.fn().mockResolvedValue(getEventsThisWeekMock) prisonApi.getScheduledEventsForNextWeek = jest.fn().mockResolvedValue(getEventsNextWeekMock) - controller = new PrisonerScheduleController(dataAccess.prisonApiClientBuilder) + controller = new PrisonerScheduleController(() => prisonApi) }) afterEach(() => { diff --git a/server/data/adjudicationsApiClient.ts b/server/data/adjudicationsApiClient.ts index f7f8e4def..e2564cf99 100644 --- a/server/data/adjudicationsApiClient.ts +++ b/server/data/adjudicationsApiClient.ts @@ -10,18 +10,7 @@ export default class AdjudicationsApiRestClient implements AdjudicationsApiClien this.restClient = new RestClient('Adjudications API', config.apis.adjudicationsApi, token) } - private async get(args: object, localMockData?: T): Promise { - try { - return await this.restClient.get(args) - } catch (error) { - if (config.localMockData === 'true' && localMockData) { - return localMockData - } - return error - } - } - async getAdjudications(bookingId: number): Promise { - return this.get({ path: `/adjudications/by-booking-id/${bookingId}` }) + return this.restClient.get({ path: `/adjudications/by-booking-id/${bookingId}` }) } } diff --git a/server/data/curiousApiClient.ts b/server/data/curiousApiClient.ts index 51c64f9f0..9e6109e6c 100644 --- a/server/data/curiousApiClient.ts +++ b/server/data/curiousApiClient.ts @@ -15,54 +15,43 @@ export default class CuriousRestApiClient implements CuriousApiClient { this.restClient = new RestClient('Curious API', config.apis.curiousApiUrl, token) } - private async get(args: object, localMockData?: T): Promise { - try { - return await this.restClient.get(args) - } catch (error) { - if (config.localMockData === 'true' && localMockData) { - return localMockData - } - return error - } - } - async getLearnerEmployabilitySkills(offenderNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/learnerEmployabilitySkills/${offenderNumber}`, ignore404: true, }) } async getLearnerProfile(offenderNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/learnerProfile/${offenderNumber}`, ignore404: true, }) } async getLearnerEducation(offenderNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/learnerEducation/${offenderNumber}`, ignore404: true, }) } async getLearnerLatestAssessments(offenderNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/latestLearnerAssessments/${offenderNumber}`, ignore404: true, }) } async getLearnerGoals(offenderNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/learnerGoals/${offenderNumber}`, ignore404: true, }) } async getLearnerNeurodivergence(offenderNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/learnerNeurodivergence/${offenderNumber}`, ignore404: true, }) diff --git a/server/data/incentivesApiClient.ts b/server/data/incentivesApiClient.ts index aaa84266b..580e6b5b2 100644 --- a/server/data/incentivesApiClient.ts +++ b/server/data/incentivesApiClient.ts @@ -10,19 +10,8 @@ export default class IncentivesApiRestClient implements IncentivesApiClient { this.restClient = new RestClient('Incentives API', config.apis.incentivesApi, token) } - private async get(args: object, localMockData?: T): Promise { - try { - return await this.restClient.get(args) - } catch (error) { - if (config.localMockData === 'true' && localMockData) { - return localMockData - } - return error - } - } - async getReviews(bookingId: number): Promise { - return this.get({ + return this.restClient.get({ path: `/iep/reviews/booking/${bookingId}`, ignore404: true, }) diff --git a/server/data/interfaces/prisonApiClient.ts b/server/data/interfaces/prisonApiClient.ts index c8de1b324..10e4d1643 100644 --- a/server/data/interfaces/prisonApiClient.ts +++ b/server/data/interfaces/prisonApiClient.ts @@ -49,7 +49,7 @@ export interface PrisonApiClient { getUserCaseLoads(): Promise getAccountBalances(bookingId: number): Promise getVisitSummary(bookingId: number): Promise - getVisitBalances(prisonerNumber: string): Promise + getVisitBalances(prisonerNumber: string): Promise getAssessments(bookingId: number): Promise getBookingContacts(bookingId: number): Promise getCaseNoteSummaryByTypes(params: object): Promise diff --git a/server/data/nonAssociationsApiClient.ts b/server/data/nonAssociationsApiClient.ts index db1a5d09d..3954f5ada 100644 --- a/server/data/nonAssociationsApiClient.ts +++ b/server/data/nonAssociationsApiClient.ts @@ -1,7 +1,6 @@ import config from '../config' import { NonAssociationDetails } from '../interfaces/nonAssociationDetails' import { NonAssociationsApiClient } from './interfaces/nonAssociationsApiClient' -import nonAssociationDetailsDummyData from './localMockData/nonAssociations' import RestClient from './restClient' export default class NonAssociationsApiRestClient implements NonAssociationsApiClient { @@ -11,23 +10,9 @@ export default class NonAssociationsApiRestClient implements NonAssociationsApiC this.restClient = new RestClient('Non associations API', config.apis.nonAssociationsApi, token) } - private async get(args: object, localMockData?: T): Promise { - try { - return await this.restClient.get(args) - } catch (error) { - if (config.localMockData === 'true' && localMockData) { - return localMockData - } - return error - } - } - getNonAssociationDetails(prisonerNumber: string): Promise { - return this.get( - { - path: `/legacy/api/offenders/${prisonerNumber}/non-association-details?currentPrisonOnly=true&excludeInactive=true`, - }, - nonAssociationDetailsDummyData, - ) + return this.restClient.get({ + path: `/legacy/api/offenders/${prisonerNumber}/non-association-details?currentPrisonOnly=true&excludeInactive=true`, + }) } } diff --git a/server/data/prisonApiClient.ts b/server/data/prisonApiClient.ts index 92cf7ea55..fdd6d08b7 100644 --- a/server/data/prisonApiClient.ts +++ b/server/data/prisonApiClient.ts @@ -1,7 +1,6 @@ import { Readable } from 'stream' import config from '../config' import RestClient from './restClient' -import { CaseLoadsDummyDataA } from './localMockData/caseLoad' import { CaseLoad } from '../interfaces/caseLoad' import { PrisonApiClient } from './interfaces/prisonApiClient' import { AccountBalances } from '../interfaces/accountBalances' @@ -12,7 +11,6 @@ import { ContactDetail } from '../interfaces/staffContacts' import { mapToQueryString } from '../utils/utils' import { CaseNote } from '../interfaces/caseNote' import { ScheduledEvent } from '../interfaces/scheduledEvent' -import dummyScheduledEvents from './localMockData/eventsForToday' import { PrisonerDetail } from '../interfaces/prisonerDetail' import { InmateDetail } from '../interfaces/prisonApi/inmateDetail' import { PersonalCareNeeds } from '../interfaces/personalCareNeeds' @@ -59,17 +57,6 @@ export default class PrisonApiRestClient implements PrisonApiClient { this.restClient = new RestClient('Prison API', config.apis.prisonApi, token) } - private async get(args: object, localMockData?: T): Promise { - try { - return await this.restClient.get(args) - } catch (error) { - if (config.localMockData === 'true' && localMockData) { - return localMockData - } - return error - } - } - private async post(args: object): Promise { return this.restClient.post(args) } @@ -79,13 +66,13 @@ export default class PrisonApiRestClient implements PrisonApiClient { fromDate: string, toDate: string, ): Promise { - return this.get({ + return this.restClient.get({ path: `/api/offender-activities/${prisonerNumber}/attendance-history?fromDate=${fromDate}&toDate=${toDate}&page=0&size=1000`, }) } async getUserCaseLoads(): Promise { - return this.get({ path: '/api/users/me/caseLoads', query: 'allCaseloads=true' }, CaseLoadsDummyDataA) + return this.restClient.get({ path: '/api/users/me/caseLoads', query: 'allCaseloads=true' }) } async getPrisonerImage(offenderNumber: string, fullSizeImage: boolean): Promise { @@ -99,32 +86,30 @@ export default class PrisonApiRestClient implements PrisonApiClient { } async getAccountBalances(bookingId: number): Promise { - return this.get({ + return this.restClient.get({ path: `/api/bookings/${bookingId}/balances`, }) } async getVisitSummary(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}/visits/summary` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/visits/summary` }) } - async getVisitBalances(prisonerNumber: string): Promise { - return this.get({ + async getVisitBalances(prisonerNumber: string): Promise { + return this.restClient.get({ path: `/api/bookings/offenderNo/${prisonerNumber}/visit/balances`, + ignore404: true, }) } async getAssessments(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}/assessments` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/assessments` }) } async getEventsScheduledForToday(bookingId: number): Promise { - return this.get( - { - path: `/api/bookings/${bookingId}/events/today`, - }, - dummyScheduledEvents, - ) + return this.restClient.get({ + path: `/api/bookings/${bookingId}/events/today`, + }) } async getBookingContacts(bookingId: number): Promise { @@ -136,7 +121,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { } async getPrisoner(prisonerNumber: string): Promise { - const prisoner = await this.get({ path: `/api/prisoners/${prisonerNumber}` }) + const prisoner = await this.restClient.get({ path: `/api/prisoners/${prisonerNumber}` }) // API returns array with one entry, so extract this to return a single object if (Array.isArray(prisoner)) { return prisoner[0] @@ -153,7 +138,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { } async getInmateDetail(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}` }) } async getPersonalCareNeeds(bookingId: number, types?: string[]): Promise { @@ -161,7 +146,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { if (types?.length) { query = `type=${types.join()}` } - return this.get({ path: `/api/bookings/${bookingId}/personal-care-needs`, query }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/personal-care-needs`, query }) } async getOffenderActivitiesHistory( @@ -178,7 +163,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { } async getSecondaryLanguages(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}/secondary-languages` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/secondary-languages` }) } async getAlerts(bookingId: number, queryParams?: PagedListQueryParams): Promise> { @@ -187,45 +172,51 @@ export default class PrisonApiRestClient implements PrisonApiClient { size: queryParams?.showAll ? 9999 : 20, ...queryParams, } - return this.get>({ path: `/api/bookings/${bookingId}/alerts/v2`, query: mapToQueryString(params) }) + return this.restClient.get>({ + path: `/api/bookings/${bookingId}/alerts/v2`, + query: mapToQueryString(params), + }) } async getProperty(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}/property` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/property` }) } async getCourtCases(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}/court-cases` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/court-cases` }) } async getOffenceHistory(prisonerNumber: string): Promise { - return this.get({ path: `/api/bookings/offenderNo/${prisonerNumber}/offenceHistory` }) + return this.restClient.get({ + path: `/api/bookings/offenderNo/${prisonerNumber}/offenceHistory`, + }) } async getSentenceTerms(bookingId: number): Promise { - return this.get({ + return this.restClient.get({ path: `/api/offender-sentences/booking/${bookingId}/sentenceTerms?filterBySentenceTermCodes=IMP&filterBySentenceTermCodes=LIC`, }) } async getPrisonerSentenceDetails(prisonerNumber: string): Promise { try { - return this.get({ path: `/api/offenders/${prisonerNumber}/sentences` }) + return this.restClient.get({ path: `/api/offenders/${prisonerNumber}/sentences` }) } catch (error) { return error } } + // NB: This can return 404 for released prisoners async getAddresses(prisonerNumber: string): Promise { - return this.get({ path: `/api/offenders/${prisonerNumber}/addresses` }) + return this.restClient.get({ path: `/api/offenders/${prisonerNumber}/addresses`, ignore404: true }) } async getAddressesForPerson(personId: number): Promise { - return this.get({ path: `/api/persons/${personId}/addresses` }) + return this.restClient.get({ path: `/api/persons/${personId}/addresses` }) } async getOffenderContacts(prisonerNumber: string): Promise { - return this.get({ path: `/api/offenders/${prisonerNumber}/contacts` }) + return this.restClient.get({ path: `/api/offenders/${prisonerNumber}/contacts` }) } async getImage(imageId: string, getFullSizedImage: boolean): Promise { @@ -239,21 +230,21 @@ export default class PrisonApiRestClient implements PrisonApiClient { } async getReferenceCodesByDomain(domain: ReferenceCodeDomain): Promise { - return this.get({ + return this.restClient.get({ path: `/api/reference-domains/domains/${domain}`, headers: { 'page-limit': '1000' }, }) } async getReasonableAdjustments(bookingId: number, treatmentCodes: string[]): Promise { - return this.get({ + return this.restClient.get({ path: `/api/bookings/${bookingId}/reasonable-adjustments?type=${treatmentCodes.join()}`, }) } async getCaseNotesUsage(prisonerNumber: string): Promise { const today = formatDateISO(new Date()) - return this.get({ + return this.restClient.get({ path: `/api/case-notes/usage`, query: `offenderNo=${prisonerNumber}&toDate=${today}&numMonths=1200`, }) @@ -266,106 +257,111 @@ export default class PrisonApiRestClient implements PrisonApiClient { fromDate: string, toDate: string, ): Promise { - return this.get({ + return this.restClient.get({ path: `/api/bookings/${bookingId}/caseNotes/${type}/${subType}/count`, query: `fromDate=${fromDate}&toDate=${toDate}`, }) } async getMainOffence(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}/mainOffence` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/mainOffence` }) } async getFullStatus(prisonerNumber: string): Promise { - return this.get({ path: `/api/prisoners/${prisonerNumber}/full-status` }) + return this.restClient.get({ path: `/api/prisoners/${prisonerNumber}/full-status` }) } async getCourtDateResults(prisonerNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/api/court-date-results/${prisonerNumber}`, }) } async getIdentifiers(bookingId: number): Promise { - return this.get({ + return this.restClient.get({ path: `/api/bookings/${bookingId}/identifiers`, }) } async getSentenceSummary(prisonerNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/api/offenders/${prisonerNumber}/booking/latest/sentence-summary`, }) } async getStaffRoles(staffId: number, agencyId: string): Promise { - return this.get({ path: `/api/staff/${staffId}/${agencyId}/roles` }) + return this.restClient.get({ path: `/api/staff/${staffId}/${agencyId}/roles` }) } async getAgencyDetails(agencyId: string): Promise { - return this.get({ + return this.restClient.get({ path: `/api/agencies/${agencyId}?activeOnly=false`, ignore404: true, }) } async getOffenderCellHistory(bookingId: number, params: object): Promise { - return this.get({ + return this.restClient.get({ path: `/api/bookings/${bookingId}/cell-history?${mapToQueryString(params)}`, }) } async getStaffDetails(staffId: string): Promise { - return this.get({ path: `/api/users/${staffId}` }) + return this.restClient.get({ path: `/api/users/${staffId}` }) } async getInmatesAtLocation(locationId: number, params: object): Promise { - return this.get({ path: `/api/locations/${locationId}/inmates?${mapToQueryString(params)}` }) + return this.restClient.get({ + path: `/api/locations/${locationId}/inmates?${mapToQueryString(params)}`, + }) } async getAlertTypes(): Promise { - return this.get({ + return this.restClient.get({ path: `/api/reference-domains/domains/ALERT?withSubCodes=true`, headers: { 'page-limit': '1000' }, }) } async createAlert(bookingId: number, alert: AlertForm): Promise { - return (await this.post({ path: `/api/bookings/${bookingId}/alert`, data: alert })) as Alert + return (await this.restClient.post({ + path: `/api/bookings/${bookingId}/alert`, + data: alert, + })) as Alert } async getCsraAssessment(bookingId: number, assessmentSeq: number): Promise { - return this.get({ + return this.restClient.get({ path: `/api/offender-assessments/csra/${bookingId}/assessment/${assessmentSeq}`, }) } async getCsraAssessmentsForPrisoner(prisonerNumber: string): Promise { - return this.get({ + return this.restClient.get({ path: `/api/offender-assessments/csra/${prisonerNumber}`, }) } async getTransactionHistory(prisonerNumber: string, params: object): Promise { - return this.get({ + return this.restClient.get({ path: `/api/offenders/${prisonerNumber}/transaction-history`, query: mapToQueryString(params), }) } async getDamageObligations(prisonerNumber: string, status?: string): Promise { - return this.get({ + return this.restClient.get({ path: `/api/offenders/${prisonerNumber}/damage-obligations`, query: mapToQueryString({ status: status || 'ACTIVE' }), }) } async getScheduledEventsForThisWeek(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}/events/thisWeek` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/events/thisWeek` }) } async getScheduledEventsForNextWeek(bookingId: number): Promise { - return this.get({ path: `/api/bookings/${bookingId}/events/nextWeek` }) + return this.restClient.get({ path: `/api/bookings/${bookingId}/events/nextWeek` }) } async getMovements( @@ -373,7 +369,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { movementTypes: MovementType[], latestOnly: boolean = true, ): Promise { - return (await this.post({ + return (await this.restClient.post({ path: `/api/movements/offenders`, query: mapToQueryString({ movementTypes, latestOnly }), data: prisonerNumbers, @@ -381,19 +377,22 @@ export default class PrisonApiRestClient implements PrisonApiClient { } async getLocationsForAppointments(agencyId: string): Promise { - return this.get({ path: `/api/agencies/${agencyId}/locations?eventType=APP` }) + return this.restClient.get({ path: `/api/agencies/${agencyId}/locations?eventType=APP` }) } async getAppointmentTypes(): Promise { - return this.get({ path: '/api/reference-domains/scheduleReasons?eventType=APP' }) + return this.restClient.get({ path: '/api/reference-domains/scheduleReasons?eventType=APP' }) } async getSentenceData(offenderNumbers: string[]): Promise { - return (await this.post({ path: '/api/offender-sentences', data: offenderNumbers })) as OffenderSentenceDetail[] + return (await this.restClient.post({ + path: '/api/offender-sentences', + data: offenderNumbers, + })) as OffenderSentenceDetail[] } async getCourtEvents(offenderNumbers: string[], agencyId: string, date: string): Promise { - return (await this.post({ + return (await this.restClient.post({ path: `/api/schedules/${agencyId}/courtEvents?date=${date}`, data: offenderNumbers, })) as PrisonerSchedule[] @@ -405,7 +404,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { date: string, timeSlot?: TimeSlot, ): Promise { - return (await this.post({ + return (await this.restClient.post({ path: `/api/schedules/${agencyId}/visits`, query: mapToQueryString({ date, timeSlot }), data: offenderNumbers, @@ -418,7 +417,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { date: string, timeSlot?: TimeSlot, ): Promise { - return (await this.post({ + return (await this.restClient.post({ path: `/api/schedules/${agencyId}/appointments`, query: mapToQueryString({ date, timeSlot }), data: offenderNumbers, @@ -431,7 +430,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { date: string, timeSlot?: TimeSlot, ): Promise { - return (await this.post({ + return (await this.restClient.post({ path: `/api/schedules/${agencyId}/activities`, query: mapToQueryString({ date, timeSlot }), data: offenderNumbers, @@ -439,14 +438,14 @@ export default class PrisonApiRestClient implements PrisonApiClient { } async getExternalTransfers(offenderNumbers: string[], agencyId: string, date: string): Promise { - return (await this.post({ + return (await this.restClient.post({ path: `/api/schedules/${agencyId}/externalTransfers?date=${date}`, data: offenderNumbers, })) as PrisonerSchedule[] } async getLocation(locationId: number): Promise { - return this.get({ path: `/api/locations/${locationId}` }) + return this.restClient.get({ path: `/api/locations/${locationId}` }) } async getActivitiesAtLocation( @@ -455,7 +454,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { timeSlot?: TimeSlot, includeSuspended = false, ): Promise { - return this.get({ + return this.restClient.get({ path: `/api/schedules/locations/${locationId}/activities`, query: mapToQueryString({ date, timeSlot, includeSuspended }), }) @@ -468,7 +467,7 @@ export default class PrisonApiRestClient implements PrisonApiClient { date: string, timeSlot?: TimeSlot, ): Promise { - return this.get({ + return this.restClient.get({ path: `/api/schedules/${agencyId}/locations/${locationId}/usage/${usage}`, query: mapToQueryString({ date, timeSlot }), }) diff --git a/server/data/restClient.ts b/server/data/restClient.ts index f274c75e1..2fbb1b919 100644 --- a/server/data/restClient.ts +++ b/server/data/restClient.ts @@ -21,7 +21,7 @@ interface PostRequest { path?: string headers?: Record responseType?: string - data?: Record + data?: object | string[] raw?: boolean query?: string } diff --git a/server/services/overviewPageService.test.ts b/server/services/overviewPageService.test.ts index b8073d132..8f3f5f057 100644 --- a/server/services/overviewPageService.test.ts +++ b/server/services/overviewPageService.test.ts @@ -182,6 +182,28 @@ describe('OverviewPageService', () => { }) describe('getMiniSummaryGroupA', () => { + describe('When visit balances returns 404', () => { + it('Displays no visit information', async () => { + const prisonerNumber = 'A1234BC' + const bookingId = 123456 + + prisonApiClient.getVisitBalances = jest.fn(async () => null) + const overviewPageService = overviewPageServiceConstruct() + const res = await overviewPageService.get( + 'token', + { prisonerNumber, bookingId, prisonId: 'MDI' } as Prisoner, + 1, + CaseLoadsDummyDataA, + ) + + expect(res.miniSummaryGroupA.length).toEqual(3) + const visitSummary = res.miniSummaryGroupA[2] + expect(visitSummary.data.bottomClass).toEqual('big') + expect(visitSummary.data.bottomContentLine1).toEqual('0') + expect(visitSummary.data.bottomContentLine3).toEqual('') + }) + }) + it('should get the account, adjudication and visit summaries for the prisoner', async () => { const prisonerNumber = 'A1234BC' const bookingId = 123456 diff --git a/server/services/overviewPageService.ts b/server/services/overviewPageService.ts index 7a2170031..d53e0b1a3 100644 --- a/server/services/overviewPageService.ts +++ b/server/services/overviewPageService.ts @@ -367,9 +367,9 @@ export default class OverviewPageService { ]) let privilegedVisitsDescription = '' - if (visitBalances.remainingPvo) { + if (visitBalances?.remainingPvo) { privilegedVisitsDescription = formatPrivilegedVisitsSummary(visitBalances.remainingPvo) - } else if (visitBalances.remainingVo) { + } else if (visitBalances?.remainingVo) { privilegedVisitsDescription = 'No privileged visits' } @@ -408,9 +408,9 @@ export default class OverviewPageService { topContent: visitSummary.startDateTime ? formatDate(visitSummary.startDateTime, 'short') : 'None scheduled', topClass: visitSummary.startDateTime ? 'big' : 'small', bottomLabel: 'Remaining visits', - bottomContentLine1: visitBalances.remainingVo ? visitBalances.remainingVo : '0', + bottomContentLine1: visitBalances?.remainingVo ? visitBalances.remainingVo : '0', bottomContentLine3: privilegedVisitsDescription, - bottomClass: visitBalances.remainingVo ? 'small' : 'big', + bottomClass: visitBalances?.remainingVo ? 'small' : 'big', linkLabel: 'Visits details', linkHref: `${config.serviceUrls.digitalPrison}/prisoner/${prisonerNumber}/visits-details`, } diff --git a/server/services/personalPageService.test.ts b/server/services/personalPageService.test.ts index 710763704..a83cef12e 100644 --- a/server/services/personalPageService.test.ts +++ b/server/services/personalPageService.test.ts @@ -270,6 +270,13 @@ describe('PersonalPageService', () => { }) describe('Addresses', () => { + it('Handles the API returning 404 for addresses', async () => { + prisonApiClient.getAddresses = jest.fn(async () => null) + const { addresses, addressSummary } = await constructService().get('token', PrisonerMockDataA) + expect(addresses).toBe(undefined) + expect(addressSummary).toEqual([]) + }) + it('Maps the data from the API for the primary address', async () => { const { addresses, addressSummary } = await constructService().get('token', PrisonerMockDataA) const expectedAddress = mockAddresses[0]