From 8ca9606622f6c56b8fcddfc880e9d62cb75c2185 Mon Sep 17 00:00:00 2001 From: ushkarev Date: Tue, 14 Nov 2023 01:00:43 +0000 Subject: [PATCH] NON-312: Update node REST client for new re-open endpoint (#170) * Refactor request/response types for node REST client * Add all special error codes to node REST client lib * Add new reopen non-association endpoint to node REST client --- clients/node/README.md | 1 + clients/node/src/client.ts | 30 ++++++++++++-- clients/node/src/errorTypes.ts | 2 + clients/node/src/index.ts | 1 + clients/node/src/requestTypes.ts | 66 +++++++++++++++++++++++++++++++ clients/node/src/responseTypes.ts | 57 +------------------------- clients/node/test/client.test.ts | 33 ++++++++++++++++ 7 files changed, 131 insertions(+), 59 deletions(-) create mode 100644 clients/node/src/requestTypes.ts diff --git a/clients/node/README.md b/clients/node/README.md index 27e38a4f..aa1617e5 100644 --- a/clients/node/README.md +++ b/clients/node/README.md @@ -110,6 +110,7 @@ General notes regarding permissions and roles: - Users also having the `GLOBAL_SEARCH` role can also _add_, _update_ and _close_ non-associations for prisoners in transfer and where one prisoner is not in a prison that’s not in their caseloads - Users also having the `INACTIVE_BOOKINGS` role can also _add_, _update_ and _close_ non-associations for prisoners outside any establishment / released - Users must _close_ rather than _delete_ non-associations +- Users must _add_ new non-associations rather than _reopen_ closed ones - No users should be able to _add_, _update_ or _close_ non-associations for prisoners without a booking / with a null location Release a new version diff --git a/clients/node/src/client.ts b/clients/node/src/client.ts index c62a9d8a..82150046 100644 --- a/clients/node/src/client.ts +++ b/clients/node/src/client.ts @@ -4,6 +4,13 @@ import superagent, { type Plugin, type SuperAgentRequest } from 'superagent' import type { SortBy, SortDirection } from './constants' import type { ErrorResponse } from './errorTypes' import type { Page, PageRequest } from './paginationTypes' +import type { + CreateNonAssociationRequest, + UpdateNonAssociationRequest, + CloseNonAssociationRequest, + DeleteNonAssociationRequest, + ReopenNonAssociationRequest, +} from './requestTypes' import type { Constants, NonAssociationsList, @@ -12,10 +19,6 @@ import type { NonAssociation, OpenNonAssociation, ClosedNonAssociation, - CreateNonAssociationRequest, - UpdateNonAssociationRequest, - CloseNonAssociationRequest, - DeleteNonAssociationRequest, } from './responseTypes' import { parseDates } from './parseDates' import { sanitiseError } from './sanitiseError' @@ -497,4 +500,23 @@ export class NonAssociationsApi { return this.sendRequest(request).then(() => null) } + + /** + * Reopen a closed non-association by ID. + * + * **Please note**: This is a special endpoint which should NOT be exposed to regular users, + * they should instead open new non-associations. + * + * Requires REOPEN_NON_ASSOCIATIONS role with write scope. + * + * @throws SanitisedError + */ + reopenNonAssociation(id: number, payload: ReopenNonAssociationRequest): Promise { + const request = superagent.put(this.buildUrl(`/non-associations/${encodeURIComponent(id)}/reopen`)).send(payload) + + return this.sendRequest(request).then(response => { + const nonAssociation: OpenNonAssociation = response.body + return parseDates(nonAssociation) + }) + } } diff --git a/clients/node/src/errorTypes.ts b/clients/node/src/errorTypes.ts index c1197e09..33110bf0 100644 --- a/clients/node/src/errorTypes.ts +++ b/clients/node/src/errorTypes.ts @@ -41,5 +41,7 @@ export enum ErrorCode { OpenNonAssociationAlreadyExist = 101, ValidationFailure = 102, NullPrisonerLocations = 103, + NonAssociationAlreadyOpen = 104, UserInContextMissing = 401, + NonAssociationNotFound = 404, } diff --git a/clients/node/src/index.ts b/clients/node/src/index.ts index 86def916..f6b24ef5 100644 --- a/clients/node/src/index.ts +++ b/clients/node/src/index.ts @@ -3,5 +3,6 @@ export * from './constants' export * from './errorTypes' export * from './paginationTypes' export * from './parseDates' +export * from './requestTypes' export * from './responseTypes' export * from './sanitiseError' diff --git a/clients/node/src/requestTypes.ts b/clients/node/src/requestTypes.ts new file mode 100644 index 00000000..27095478 --- /dev/null +++ b/clients/node/src/requestTypes.ts @@ -0,0 +1,66 @@ +import type { Reason, RestrictionType, Role } from './constants' + +/** + * Request payload for creating a new non-association + * + * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.CreateNonAssociationRequest class + * see https://github.com/ministryofjustice/hmpps-non-associations-api + */ +export interface CreateNonAssociationRequest { + firstPrisonerNumber: string + firstPrisonerRole: keyof Role + secondPrisonerNumber: string + secondPrisonerRole: keyof Role + reason: keyof Reason + restrictionType: keyof RestrictionType + comment: string +} + +/** + * Request payload for updating an existing non-association + * + * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.PatchNonAssociationRequest class + * see https://github.com/ministryofjustice/hmpps-non-associations-api + */ +export interface UpdateNonAssociationRequest { + firstPrisonerRole?: keyof Role + secondPrisonerRole?: keyof Role + reason?: keyof Reason + restrictionType?: keyof RestrictionType + comment?: string +} + +/** + * Request payload for closing an existing open non-association + * + * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.CloseNonAssociationRequest class + * see https://github.com/ministryofjustice/hmpps-non-associations-api + */ +export interface CloseNonAssociationRequest { + closedReason: string + closedAt?: Date + closedBy?: string +} + +/** + * Request payload for deleting an existing non-association + * + * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.DeleteNonAssociationRequest class + * see https://github.com/ministryofjustice/hmpps-non-associations-api + */ +export interface DeleteNonAssociationRequest { + deletionReason: string + staffUserNameRequestingDeletion: string +} + +/** + * Request payload for re-opening an existing closed non-association + * + * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.ReopenNonAssociationRequest class + * see https://github.com/ministryofjustice/hmpps-non-associations-api + */ +export interface ReopenNonAssociationRequest { + reopenReason: string + reopenedAt?: Date + reopenedBy?: string +} diff --git a/clients/node/src/responseTypes.ts b/clients/node/src/responseTypes.ts index 568dcb63..99c3b440 100644 --- a/clients/node/src/responseTypes.ts +++ b/clients/node/src/responseTypes.ts @@ -50,7 +50,7 @@ export interface ClosedNonAssociationsListItem extends BaseNonAssociationsListIt * List of non-associations for a particular prisoner * * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.PrisonerNonAssociations class - * https://github.com/ministryofjustice/hmpps-non-associations-api/blob/f6002aa1da50b8c4ccd3613e970327d5c67c44ae/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsnonassociationsapi/dto/PrisonerNonAssociations.kt#L8-L31 + * see https://github.com/ministryofjustice/hmpps-non-associations-api */ export interface NonAssociationsList< Item extends BaseNonAssociationsListItem = OpenNonAssociationsListItem | ClosedNonAssociationsListItem, @@ -103,59 +103,6 @@ export interface ClosedNonAssociation extends BaseNonAssociation { * Non-association between two prisoners * * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.NonAssociation class - * https://github.com/ministryofjustice/hmpps-non-associations-api/blob/f6002aa1da50b8c4ccd3613e970327d5c67c44ae/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsnonassociationsapi/dto/NonAssociation.kt#L10-L61 + * see https://github.com/ministryofjustice/hmpps-non-associations-api */ export type NonAssociation = OpenNonAssociation | ClosedNonAssociation - -/** - * Request payload for creating a new non-association - * - * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.CreateNonAssociationRequest class - * https://github.com/ministryofjustice/hmpps-non-associations-api/blob/f6002aa1da50b8c4ccd3613e970327d5c67c44ae/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsnonassociationsapi/dto/NonAssociation.kt#L63-L87 - */ -export interface CreateNonAssociationRequest { - firstPrisonerNumber: string - firstPrisonerRole: keyof Role - secondPrisonerNumber: string - secondPrisonerRole: keyof Role - reason: keyof Reason - restrictionType: keyof RestrictionType - comment: string -} - -/** - * Request payload for updating an existing non-association - * - * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.PatchNonAssociationRequest class - * https://github.com/ministryofjustice/hmpps-non-associations-api/blob/f6002aa1da50b8c4ccd3613e970327d5c67c44ae/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsnonassociationsapi/dto/NonAssociation.kt#L105-L124 - */ -export interface UpdateNonAssociationRequest { - firstPrisonerRole?: keyof Role - secondPrisonerRole?: keyof Role - reason?: keyof Reason - restrictionType?: keyof RestrictionType - comment?: string -} - -/** - * Request payload for closing an existing open non-association - * - * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.CloseNonAssociationRequest class - * https://github.com/ministryofjustice/hmpps-non-associations-api/blob/f6002aa1da50b8c4ccd3613e970327d5c67c44ae/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsnonassociationsapi/dto/NonAssociation.kt#L125-L138 - */ -export interface CloseNonAssociationRequest { - closedReason: string - closedAt?: Date - closedBy?: string -} - -/** - * Request payload for deleting an existing non-association - * - * Defined in uk.gov.justice.digital.hmpps.hmppsnonassociationsapi.dto.DeleteNonAssociationRequest class - * https://github.com/ministryofjustice/hmpps-non-associations-api/blob/f6002aa1da50b8c4ccd3613e970327d5c67c44ae/src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsnonassociationsapi/dto/NonAssociation.kt#L140-L151 - */ -export interface DeleteNonAssociationRequest { - deletionReason: string - staffUserNameRequestingDeletion: string -} diff --git a/clients/node/test/client.test.ts b/clients/node/test/client.test.ts index 80f419bb..9cf8b2ac 100644 --- a/clients/node/test/client.test.ts +++ b/clients/node/test/client.test.ts @@ -250,6 +250,20 @@ describe('REST Client', () => { expect(logger.error).not.toHaveBeenCalled() expect(plugin).toHaveBeenCalledWith(expect.objectContaining({ method: 'POST' })) }) + + it('when reopening a non-association', async () => { + mockResponse().put('/non-associations/101/reopen').reply(200, openNonAssociation) + + const nonAssociation: OpenNonAssociation = await client.reopenNonAssociation(101, { + reopenReason: 'Closed in error', + }) + expect(nonAssociation).toEqual(openNonAssociation) + expect(nock.isDone()).toEqual(true) + + expect(logger.info).toHaveBeenCalledTimes(1) + expect(logger.error).not.toHaveBeenCalled() + expect(plugin).toHaveBeenCalledWith(expect.objectContaining({ method: 'PUT' })) + }) }) describe('should retry on failure', () => { @@ -527,6 +541,25 @@ describe('REST Client', () => { expect(logger.info).toHaveBeenCalledTimes(1) expect(logger.error).toHaveBeenCalledTimes(1) }) + + it('when reopening a non-association', async () => { + mockResponse().put('/non-associations/101/reopen').reply(403) + + await expect( + client.reopenNonAssociation(101, { + reopenReason: 'Closed in error', + reopenedBy: 'abc123', + }), + ).rejects.toEqual( + expect.objectContaining({ + status: 403, + message: 'Forbidden', + }), + ) + + expect(logger.info).toHaveBeenCalledTimes(1) + expect(logger.error).toHaveBeenCalledTimes(1) + }) }) describe('should specify varying return types', () => {