From a7ec960d5f87abdffbb175ec1b909a5888d8a158 Mon Sep 17 00:00:00 2001 From: unakb Date: Tue, 7 Jan 2025 14:06:09 +0000 Subject: [PATCH] feat(j-s): Deliver subpoena revocation to police (#17291) * feat(j-s): Revoke subpoenas from police when indictment cancelled * Update case.service.ts * feat(j-s): Send subpoena revocation to police * Create deliverSubpoenaRevokedToPoliceGuards.spec.ts --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/app/modules/case/case.service.ts | 17 ++++- .../src/app/modules/police/police.service.ts | 52 ++++++++++++- .../subpoena/internalSubpoena.controller.ts | 34 +++++++++ .../app/modules/subpoena/subpoena.service.ts | 33 +++++++++ .../deliverSubpoenaRevokedToPolice.spec.ts | 73 +++++++++++++++++++ ...liverSubpoenaRevokedToPoliceGuards.spec.ts | 29 ++++++++ .../message/src/lib/message.ts | 2 + 7 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaRevokedToPolice.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaRevokedToPoliceGuards.spec.ts diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 35a85982f572..41350c8064a1 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -1069,7 +1069,7 @@ export class CaseService { ) } - private addMessagesForRevokedIndictmentCaseToQueue( + private async addMessagesForRevokedIndictmentCaseToQueue( theCase: Case, user: TUser, ): Promise { @@ -1084,6 +1084,21 @@ export class CaseService { }) } + const subpoenasToRevoke = await this.subpoenaService.findByCaseId( + theCase.id, + ) + + if (subpoenasToRevoke?.length > 0) { + messages.push( + ...subpoenasToRevoke.map((subpoena) => ({ + type: MessageType.DELIVERY_TO_POLICE_SUBPOENA_REVOCATION, + user, + caseId: theCase.id, + elementId: [subpoena.defendantId, subpoena.id], + })), + ) + } + return this.messageService.sendMessagesToQueue(messages) } diff --git a/apps/judicial-system/backend/src/app/modules/police/police.service.ts b/apps/judicial-system/backend/src/app/modules/police/police.service.ts index bdb539eb73cd..a1f73841232b 100644 --- a/apps/judicial-system/backend/src/app/modules/police/police.service.ts +++ b/apps/judicial-system/backend/src/app/modules/police/police.service.ts @@ -34,7 +34,7 @@ import { AwsS3Service } from '../aws-s3' import { Case } from '../case' import { Defendant } from '../defendant/models/defendant.model' import { EventService } from '../event' -import { SubpoenaService } from '../subpoena' +import { Subpoena, SubpoenaService } from '../subpoena' import { UploadPoliceCaseFileDto } from './dto/uploadPoliceCaseFile.dto' import { CreateSubpoenaResponse } from './models/createSubpoena.response' import { PoliceCaseFile } from './models/policeCaseFile.model' @@ -684,4 +684,54 @@ export class PoliceService { throw error } } + + async revokeSubpoena( + workingCase: Case, + subpoena: Subpoena, + user: User, + ): Promise { + const { name: actor } = user + + const subpoenaId = subpoena.subpoenaId + + try { + const res = await this.fetchPoliceCaseApi( + `${this.xRoadPath}/InvalidateCourtSummon?sekGuid=${subpoenaId}`, + { + method: 'POST', + headers: { + accept: '*/*', + 'X-Road-Client': this.config.clientId, + 'X-API-KEY': this.config.policeApiKey, + }, + agent: this.agent, + } as RequestInit, + ) + + if (res.ok) { + return true + } + + throw await res.text() + } catch (error) { + this.logger.error( + `Failed revoke subpoena with id ${subpoenaId} for case ${workingCase.id} from police`, + { + error, + }, + ) + + this.eventService.postErrorEvent( + 'Failed to revoke subpoena from police', + { + caseId: workingCase.id, + subpoenaId, + actor, + }, + error, + ) + + return false + } + } } diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts index 2937e657a3f4..d23b0f0ad0dd 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts @@ -125,4 +125,38 @@ export class InternalSubpoenaController { deliverDto.user, ) } + + @UseGuards( + CaseExistsGuard, + new CaseTypeGuard(indictmentCases), + DefendantExistsGuard, + SubpoenaExistsGuard, + ) + @Post( + `case/:caseId/${ + messageEndpoint[MessageType.DELIVERY_TO_POLICE_SUBPOENA_REVOCATION] + }/:defendantId/:subpoenaId`, + ) + @ApiOkResponse({ + type: DeliverResponse, + description: 'Delivers subpoena revocation to police', + }) + deliverSubpoenaRevokedToPolice( + @Param('caseId') caseId: string, + @Param('defendantId') defendantId: string, + @Param('subpoenaId') subpoenaId: string, + @CurrentCase() theCase: Case, + @CurrentSubpoena() subpoena: Subpoena, + @Body() deliverDto: DeliverDto, + ): Promise { + this.logger.debug( + `Delivering subpoena revocation of ${subpoenaId} to police for defendant ${defendantId} of case ${caseId}`, + ) + + return this.subpoenaService.deliverSubpoenaRevokedToPolice( + theCase, + subpoena, + deliverDto.user, + ) + } } diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts index a83405e8a488..673973d5cc10 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts @@ -285,6 +285,13 @@ export class SubpoenaService { return subpoena } + async findByCaseId(caseId: string): Promise { + return this.subpoenaModel.findAll({ + include, + where: { caseId }, + }) + } + async getIndictmentPdf(theCase: Case): Promise { if (isTrafficViolationCase(theCase)) { return await this.pdfService.getIndictmentPdf(theCase) @@ -392,7 +399,33 @@ export class SubpoenaService { return { delivered: false } }) } + async deliverSubpoenaRevokedToPolice( + theCase: Case, + subpoena: Subpoena, + user: TUser, + ): Promise { + if (!subpoena.subpoenaId) { + this.logger.warn( + `Attempted to revoke a subpoena with id ${subpoena.id} that had not been delivered to the police`, + ) + return { delivered: true } + } + const subpoenaRevoked = await this.policeService.revokeSubpoena( + theCase, + subpoena, + user, + ) + + if (subpoenaRevoked) { + this.logger.info( + `Subpoena ${subpoena.subpoenaId} successfully revoked from police`, + ) + return { delivered: true } + } else { + return { delivered: false } + } + } async getSubpoena(subpoena: Subpoena, user?: TUser): Promise { if (!subpoena.subpoenaId) { // The subpoena has not been delivered to the police diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaRevokedToPolice.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaRevokedToPolice.spec.ts new file mode 100644 index 000000000000..2578537da4d7 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaRevokedToPolice.spec.ts @@ -0,0 +1,73 @@ +import { uuid } from 'uuidv4' + +import { createTestingSubpoenaModule } from '../createTestingSubpoenaModule' + +import { Case } from '../../../case' +import { DeliverDto } from '../../dto/deliver.dto' +import { DeliverResponse } from '../../models/deliver.response' +import { Subpoena } from '../../models/subpoena.model' +import { SubpoenaService } from '../../subpoena.service' + +interface Then { + error: Error +} + +type GivenWhenThen = () => Promise + +describe('InternalSubpoenaController - Deliver subpoena revocation to police', () => { + const caseId = uuid() + const subpoenaId = uuid() + const defendantId = uuid() + + const subpoena = { id: subpoenaId } as Subpoena + const theCase = { id: caseId } as Case + const user = { user: { id: uuid() } } as DeliverDto + const delivered = { delivered: true } as DeliverResponse + + let mockSubpoenaService: SubpoenaService + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { subpoenaService, internalSubpoenaController } = + await createTestingSubpoenaModule() + + mockSubpoenaService = subpoenaService + + const deliverSubpoenaRevokedToPoliceMock = jest.fn() + mockSubpoenaService.deliverSubpoenaRevokedToPolice = + deliverSubpoenaRevokedToPoliceMock + + deliverSubpoenaRevokedToPoliceMock.mockResolvedValueOnce(delivered) + + givenWhenThen = async () => { + const then = {} as Then + + try { + await internalSubpoenaController.deliverSubpoenaRevokedToPolice( + caseId, + defendantId, + subpoenaId, + theCase, + subpoena, + user, + ) + } catch (error) { + then.error = error as Error + } + + return then + } + }) + + describe('subpoena revoked delivered to police', () => { + beforeEach(async () => { + await givenWhenThen() + }) + + it('should call deliverSubpoenaRevokedToPolice', () => { + expect( + mockSubpoenaService.deliverSubpoenaRevokedToPolice, + ).toHaveBeenCalledWith(theCase, subpoena, user.user) + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaRevokedToPoliceGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaRevokedToPoliceGuards.spec.ts new file mode 100644 index 000000000000..de1573345645 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverSubpoenaRevokedToPoliceGuards.spec.ts @@ -0,0 +1,29 @@ +import { indictmentCases } from '@island.is/judicial-system/types' + +import { CaseExistsGuard, CaseTypeGuard } from '../../../case' +import { DefendantExistsGuard } from '../../../defendant' +import { SubpoenaExistsGuard } from '../../guards/subpoenaExists.guard' +import { InternalSubpoenaController } from '../../internalSubpoena.controller' + +describe('InternalSubpoenaController - Deliver subpoena revoked to police guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalSubpoenaController.prototype.deliverSubpoenaRevokedToPolice, + ) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(4) + expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard) + expect(guards[1]).toBeInstanceOf(CaseTypeGuard) + expect(guards[1]).toEqual({ + allowedCaseTypes: indictmentCases, + }) + expect(new guards[2]()).toBeInstanceOf(DefendantExistsGuard) + expect(new guards[3]()).toBeInstanceOf(SubpoenaExistsGuard) + }) +}) diff --git a/libs/judicial-system/message/src/lib/message.ts b/libs/judicial-system/message/src/lib/message.ts index 2472dfa4c37c..9a7a4cfc16b6 100644 --- a/libs/judicial-system/message/src/lib/message.ts +++ b/libs/judicial-system/message/src/lib/message.ts @@ -24,6 +24,7 @@ export enum MessageType { DELIVERY_TO_POLICE_INDICTMENT = 'DELIVERY_TO_POLICE_INDICTMENT', DELIVERY_TO_POLICE_CASE_FILES_RECORD = 'DELIVERY_TO_POLICE_CASE_FILES_RECORD', DELIVERY_TO_POLICE_SUBPOENA = 'DELIVERY_TO_POLICE_SUBPOENA', + DELIVERY_TO_POLICE_SUBPOENA_REVOCATION = 'DELIVERY_TO_POLICE_SUBPOENA_REVOCATION', DELIVERY_TO_POLICE_SIGNED_COURT_RECORD = 'DELIVERY_TO_POLICE_SIGNED_COURT_RECORD', DELIVERY_TO_POLICE_SIGNED_RULING = 'DELIVERY_TO_POLICE_SIGNED_RULING', DELIVERY_TO_POLICE_APPEAL = 'DELIVERY_TO_POLICE_APPEAL', @@ -65,6 +66,7 @@ export const messageEndpoint: { [key in MessageType]: string } = { DELIVERY_TO_POLICE_INDICTMENT: 'deliverIndictmentToPolice', DELIVERY_TO_POLICE_CASE_FILES_RECORD: 'deliverCaseFilesRecordToPolice', DELIVERY_TO_POLICE_SUBPOENA: 'deliverSubpoenaToPolice', + DELIVERY_TO_POLICE_SUBPOENA_REVOCATION: 'deliverSubpoenaRevocationToPolice', DELIVERY_TO_POLICE_SIGNED_COURT_RECORD: 'deliverSignedCourtRecordToPolice', DELIVERY_TO_POLICE_SIGNED_RULING: 'deliverSignedRulingToPolice', DELIVERY_TO_POLICE_APPEAL: 'deliverAppealToPolice',