Skip to content

Commit

Permalink
NON-312: Update node REST client for new re-open endpoint (#170)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ushkarev authored Nov 14, 2023
1 parent 3d113e3 commit 8ca9606
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 59 deletions.
1 change: 1 addition & 0 deletions clients/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 26 additions & 4 deletions clients/node/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -12,10 +19,6 @@ import type {
NonAssociation,
OpenNonAssociation,
ClosedNonAssociation,
CreateNonAssociationRequest,
UpdateNonAssociationRequest,
CloseNonAssociationRequest,
DeleteNonAssociationRequest,
} from './responseTypes'
import { parseDates } from './parseDates'
import { sanitiseError } from './sanitiseError'
Expand Down Expand Up @@ -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<ErrorResponse>
*/
reopenNonAssociation(id: number, payload: ReopenNonAssociationRequest): Promise<OpenNonAssociation> {
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)
})
}
}
2 changes: 2 additions & 0 deletions clients/node/src/errorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ export enum ErrorCode {
OpenNonAssociationAlreadyExist = 101,
ValidationFailure = 102,
NullPrisonerLocations = 103,
NonAssociationAlreadyOpen = 104,
UserInContextMissing = 401,
NonAssociationNotFound = 404,
}
1 change: 1 addition & 0 deletions clients/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
66 changes: 66 additions & 0 deletions clients/node/src/requestTypes.ts
Original file line number Diff line number Diff line change
@@ -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
}
57 changes: 2 additions & 55 deletions clients/node/src/responseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
33 changes: 33 additions & 0 deletions clients/node/test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit 8ca9606

Please sign in to comment.