From 6513a312e4592dfafd6fa64ce2f0397dbceddde6 Mon Sep 17 00:00:00 2001 From: unakb Date: Tue, 11 Jun 2024 14:53:08 +0000 Subject: [PATCH 1/3] feat(j-s): Deliver defender info to robot for indictment cases --- .../modules/case/internalCase.controller.ts | 25 ++++ .../app/modules/case/internalCase.service.ts | 112 +++++++++++------- ...liverIndictmentDefenderInfoToCourt.spec.ts | 94 +++++++++++++++ ...ndictmentDefenderInfoToCourtGuards.spec.ts | 50 ++++++++ ...deliverIndictmentInfoToCourtGuards.spec.ts | 2 +- .../src/app/modules/court/court.service.ts | 42 ++++++- .../message/src/lib/message.ts | 2 + 7 files changed, 280 insertions(+), 47 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourt.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourtGuards.spec.ts diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts index 06fb7ff427c8..4733cf4cbd67 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts @@ -169,6 +169,31 @@ export class InternalCaseController { ) } + @UseGuards(CaseExistsGuard, new CaseTypeGuard(indictmentCases)) + @Post( + `case/:caseId/${ + messageEndpoint[MessageType.DELIVERY_TO_COURT_INDICTMENT_DEFENDER] + }`, + ) + @ApiOkResponse({ + type: DeliverResponse, + description: 'Delivers indictment case defender info to court', + }) + deliverIndictmentDefenderInfoToCourt( + @Param('caseId') caseId: string, + @CurrentCase() theCase: Case, + @Body() deliverDto: DeliverDto, + ): Promise { + this.logger.debug( + `Delivering the indictment defender info for case ${caseId} to court`, + ) + + return this.internalCaseService.deliverIndictmentDefenderInfoToCourt( + theCase, + deliverDto.user, + ) + } + @UseGuards(CaseExistsGuard, new CaseTypeGuard(indictmentCases)) @Post( `case/:caseId/${ diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts index 3f011be35ca2..fa2f48a5ab7c 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts @@ -544,6 +544,73 @@ export class InternalCaseService { }) } + async deliverIndictmentInfoToCourt( + theCase: Case, + user: TUser, + ): Promise { + const subtypeList = theCase.indictmentSubtypes + ? Object.values(theCase.indictmentSubtypes).flat() + : [] + + const mappedSubtypes = subtypeList.flatMap((key) => courtSubtypes[key]) + + return this.courtService + .updateIndictmentCaseWithIndictmentInfo( + user, + theCase.id, + theCase.courtCaseNumber, + theCase.eventLogs?.find( + (eventLog) => eventLog.eventType === EventType.CASE_RECEIVED_BY_COURT, + )?.created, + theCase.eventLogs?.find( + (eventLog) => eventLog.eventType === EventType.INDICTMENT_CONFIRMED, + )?.created, + theCase.policeCaseNumbers[0], + mappedSubtypes, + theCase.defendants?.map((defendant) => ({ + name: defendant.name, + nationalId: defendant.nationalId, + })), + theCase.prosecutor + ? { + name: theCase.prosecutor.name, + nationalId: theCase.prosecutor.nationalId, + } + : undefined, + ) + .then(() => ({ delivered: true })) + .catch((reason) => { + this.logger.error( + `Failed to update indictment case ${theCase.id} with indictment info`, + { reason }, + ) + + return { delivered: false } + }) + } + + async deliverIndictmentDefenderInfoToCourt( + theCase: Case, + user: TUser, + ): Promise { + return this.courtService + .updateIndictmentWithDefenderInfo( + user, + theCase.id, + theCase.courtCaseNumber, + theCase.defendants, + ) + .then(() => ({ delivered: true })) + .catch((reason) => { + this.logger.error( + `Failed to update indictment case ${theCase.id} with defender info`, + { reason }, + ) + + return { delivered: false } + }) + } + async deliverCaseFilesRecordToCourt( theCase: Case, policeCaseNumber: string, @@ -751,51 +818,6 @@ export class InternalCaseService { }) } - async deliverIndictmentInfoToCourt( - theCase: Case, - user: TUser, - ): Promise { - const subtypeList = theCase.indictmentSubtypes - ? Object.values(theCase.indictmentSubtypes).flat() - : [] - - const mappedSubtypes = subtypeList.flatMap((key) => courtSubtypes[key]) - - return this.courtService - .updateIndictmentCaseWithIndictmentInfo( - user, - theCase.id, - theCase.courtCaseNumber, - theCase.eventLogs?.find( - (eventLog) => eventLog.eventType === EventType.CASE_RECEIVED_BY_COURT, - )?.created, - theCase.eventLogs?.find( - (eventLog) => eventLog.eventType === EventType.INDICTMENT_CONFIRMED, - )?.created, - theCase.policeCaseNumbers[0], - mappedSubtypes, - theCase.defendants?.map((defendant) => ({ - name: defendant.name, - nationalId: defendant.nationalId, - })), - theCase.prosecutor - ? { - name: theCase.prosecutor.name, - nationalId: theCase.prosecutor.nationalId, - } - : undefined, - ) - .then(() => ({ delivered: true })) - .catch((reason) => { - this.logger.error( - `Failed to update indictment case ${theCase.id} with indictment info`, - { reason }, - ) - - return { delivered: false } - }) - } - private async deliverCaseToPoliceWithFiles( theCase: Case, user: TUser, diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourt.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourt.spec.ts new file mode 100644 index 000000000000..1004462d4931 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourt.spec.ts @@ -0,0 +1,94 @@ +import { uuid } from 'uuidv4' + +import { CaseType, User } from '@island.is/judicial-system/types' + +import { createTestingCaseModule } from '../createTestingCaseModule' + +import { CourtService } from '../../../court' +import { DeliverDto } from '../../dto/deliver.dto' +import { Case } from '../../models/case.model' +import { DeliverResponse } from '../../models/deliver.response' + +interface Then { + result: DeliverResponse + error: Error +} + +type GivenWhenThen = ( + caseId: string, + theCase: Case, + body: DeliverDto, +) => Promise + +describe('InternalCaseController - Deliver indictment defender info to court', () => { + const user = { id: uuid() } as User + const caseId = uuid() + const courtCaseNumber = uuid() + + const theCase = { + id: caseId, + type: CaseType.INDICTMENT, + courtCaseNumber, + defendants: [ + { + name: 'Test Ákærði', + nationalId: '1234567890', + defenderNationalId: '1234567899', + defenderName: 'Test Verjandi', + defenderEmail: 'defenderEmail', + }, + { + name: 'Test Ákærði 2', + nationalId: '1234567891', + defenderNationalId: '1234567898', + defenderName: 'Test Verjandi 2', + defenderEmail: 'defenderEmail2', + }, + ], + } as Case + + let mockCourtService: CourtService + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { courtService, internalCaseController } = + await createTestingCaseModule() + + mockCourtService = courtService + const mockUpdateIndictmentCaseWithIndictmentInfo = + mockCourtService.updateIndictmentWithDefenderInfo as jest.Mock + mockUpdateIndictmentCaseWithIndictmentInfo.mockResolvedValue(uuid()) + + givenWhenThen = async (caseId: string, theCase: Case) => { + const then = {} as Then + + await internalCaseController + .deliverIndictmentDefenderInfoToCourt(caseId, theCase, { user }) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('deliver indictment defender to court', () => { + let then: Then + + beforeEach(async () => { + then = await givenWhenThen(caseId, theCase, { user }) + }) + + it('should deliver the defender information to court', () => { + expect( + mockCourtService.updateIndictmentWithDefenderInfo, + ).toHaveBeenCalledWith( + user, + theCase.id, + theCase.courtCaseNumber, + theCase.defendants, + ) + + expect(then.result).toEqual({ delivered: true }) + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourtGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourtGuards.spec.ts new file mode 100644 index 000000000000..4fa794094133 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourtGuards.spec.ts @@ -0,0 +1,50 @@ +import { CanActivate } from '@nestjs/common' + +import { indictmentCases } from '@island.is/judicial-system/types' + +import { CaseExistsGuard } from '../../guards/caseExists.guard' +import { CaseTypeGuard } from '../../guards/caseType.guard' +import { InternalCaseController } from '../../internalCase.controller' + +describe('InternalCaseController - Deliver indictment defender info to court guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalCaseController.prototype.deliverIndictmentDefenderInfoToCourt, + ) + }) + + it('should have two guards', () => { + expect(guards).toHaveLength(2) + }) + + describe('CaseExistsGuard', () => { + let guard: CanActivate + + beforeEach(() => { + guard = new guards[0]() + }) + + it('should have CaseExistsGuard as guard 1', () => { + expect(guard).toBeInstanceOf(CaseExistsGuard) + }) + }) + + describe('CaseTypeGuard', () => { + let guard: CanActivate + + beforeEach(() => { + guard = guards[1] + }) + + it('should have CaseTypeGuard as guard 2', () => { + expect(guard).toBeInstanceOf(CaseTypeGuard) + expect(guard).toEqual({ + allowedCaseTypes: indictmentCases, + }) + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentInfoToCourtGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentInfoToCourtGuards.spec.ts index d2b689d2b2a6..24b0d46a1803 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentInfoToCourtGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentInfoToCourtGuards.spec.ts @@ -6,7 +6,7 @@ import { CaseExistsGuard } from '../../guards/caseExists.guard' import { CaseTypeGuard } from '../../guards/caseType.guard' import { InternalCaseController } from '../../internalCase.controller' -describe('InternalCaseController - Deliver indictment case to police guards', () => { +describe('InternalCaseController - Deliver indictment case info to court guards', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let guards: any[] diff --git a/apps/judicial-system/backend/src/app/modules/court/court.service.ts b/apps/judicial-system/backend/src/app/modules/court/court.service.ts index 3c407a5d4d3b..8f02ebe81bd3 100644 --- a/apps/judicial-system/backend/src/app/modules/court/court.service.ts +++ b/apps/judicial-system/backend/src/app/modules/court/court.service.ts @@ -24,6 +24,7 @@ import { } from '@island.is/judicial-system/types' import { nowFactory } from '../../factories' +import { Defendant } from '../defendant' import { EventService } from '../event' import { RobotLog } from './models/robotLog.model' import { courtModuleConfig } from './court.config' @@ -127,6 +128,7 @@ enum RobotEmailType { APPEAL_CASE_CONCLUSION = 'APPEAL_CASE_CONCLUSION', APPEAL_CASE_FILE = 'APPEAL_CASE_FILE', NEW_INDICTMENT_INFO = 'INDICTMENT_INFO', + UPDATE_INDICTMENT_DEFENDER_INFO = 'UPDATE_INDICTMENT_DEFENDER_INFO', } @Injectable() @@ -603,6 +605,44 @@ export class CourtService { } } + async updateIndictmentWithDefenderInfo( + user: User, + caseId: string, + courtCaseNumber?: string, + defendants?: Defendant[], + ): Promise { + try { + const defendantInfo = defendants?.map((defendant) => ({ + nationalId: defendant.nationalId, + defenderName: defendant.defenderName, + defenderEmail: defendant.defenderEmail, + })) + + const subject = `Ákæra - ${courtCaseNumber} - verjanda upplýsingar` + const content = JSON.stringify(defendantInfo) + + return this.sendToRobot( + subject, + content, + RobotEmailType.UPDATE_INDICTMENT_DEFENDER_INFO, + caseId, + ) + } catch (error) { + this.eventService.postErrorEvent( + 'Failed to update indictment with defender info', + { + caseId, + actor: user.name, + institution: user.institution?.name, + courtCaseNumber, + }, + error, + ) + + throw error + } + } + async updateAppealCaseWithReceivedDate( user: User, caseId: string, @@ -784,7 +824,7 @@ export class CourtService { .then((log) => [log.id, log.seqNumber]) } - private async sendToRobot( + async sendToRobot( subject: string, content: string, type: RobotEmailType, diff --git a/libs/judicial-system/message/src/lib/message.ts b/libs/judicial-system/message/src/lib/message.ts index c4ea1d83a92f..07e728588336 100644 --- a/libs/judicial-system/message/src/lib/message.ts +++ b/libs/judicial-system/message/src/lib/message.ts @@ -5,6 +5,7 @@ export enum MessageType { DELIVERY_TO_COURT_DEFENDANT = 'DELIVERY_TO_COURT_DEFENDANT', DELIVERY_TO_COURT_INDICTMENT = 'DELIVERY_TO_COURT_INDICTMENT', DELIVERY_TO_COURT_INDICTMENT_INFO = 'DELIVERY_TO_COURT_INDICTMENT_INFO', + DELIVERY_TO_COURT_INDICTMENT_DEFENDER = 'DELIVERY_TO_COURT_INDICTMENT_DEFENDER', DELIVERY_TO_COURT_CASE_FILE = 'DELIVERY_TO_COURT_CASE_FILE', DELIVERY_TO_COURT_CASE_FILES_RECORD = 'DELIVERY_TO_COURT_CASE_FILES_RECORD', DELIVERY_TO_COURT_REQUEST = 'DELIVERY_TO_COURT_REQUEST', @@ -32,6 +33,7 @@ export const messageEndpoint: { [key in MessageType]: string } = { DELIVERY_TO_COURT_DEFENDANT: 'deliverDefendantToCourt', DELIVERY_TO_COURT_INDICTMENT: 'deliverIndictmentToCourt', DELIVERY_TO_COURT_INDICTMENT_INFO: 'deliverIndictmentInfoToCourt', + DELIVERY_TO_COURT_INDICTMENT_DEFENDER: 'deliverIndictmentDefenderToCourt', DELIVERY_TO_COURT_CASE_FILE: 'deliverCaseFileToCourt', DELIVERY_TO_COURT_CASE_FILES_RECORD: 'deliverCaseFilesRecordToCourt', DELIVERY_TO_COURT_REQUEST: 'deliverRequestToCourt', From 45788c026d138a88e4a79b7a98da4f63a6a0b373 Mon Sep 17 00:00:00 2001 From: unakb Date: Tue, 11 Jun 2024 15:07:41 +0000 Subject: [PATCH 2/3] Update deliverIndictmentDefenderInfoToCourt.spec.ts --- ...liverIndictmentDefenderInfoToCourt.spec.ts | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourt.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourt.spec.ts index 1004462d4931..2664f609102a 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourt.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/deliverIndictmentDefenderInfoToCourt.spec.ts @@ -10,8 +10,8 @@ import { Case } from '../../models/case.model' import { DeliverResponse } from '../../models/deliver.response' interface Then { - result: DeliverResponse - error: Error + result?: DeliverResponse + error?: Error } type GivenWhenThen = ( @@ -47,23 +47,21 @@ describe('InternalCaseController - Deliver indictment defender info to court', ( ], } as Case - let mockCourtService: CourtService + let mockCourtService: jest.Mocked let givenWhenThen: GivenWhenThen beforeEach(async () => { const { courtService, internalCaseController } = await createTestingCaseModule() - mockCourtService = courtService - const mockUpdateIndictmentCaseWithIndictmentInfo = - mockCourtService.updateIndictmentWithDefenderInfo as jest.Mock - mockUpdateIndictmentCaseWithIndictmentInfo.mockResolvedValue(uuid()) + mockCourtService = courtService as jest.Mocked + mockCourtService.updateIndictmentWithDefenderInfo.mockResolvedValue(uuid()) - givenWhenThen = async (caseId: string, theCase: Case) => { + givenWhenThen = async (caseId: string, theCase: Case, body: DeliverDto) => { const then = {} as Then await internalCaseController - .deliverIndictmentDefenderInfoToCourt(caseId, theCase, { user }) + .deliverIndictmentDefenderInfoToCourt(caseId, theCase, body) .then((result) => (then.result = result)) .catch((error) => (then.error = error)) @@ -71,24 +69,30 @@ describe('InternalCaseController - Deliver indictment defender info to court', ( } }) - describe('deliver indictment defender to court', () => { - let then: Then - - beforeEach(async () => { - then = await givenWhenThen(caseId, theCase, { user }) - }) - - it('should deliver the defender information to court', () => { - expect( - mockCourtService.updateIndictmentWithDefenderInfo, - ).toHaveBeenCalledWith( - user, - theCase.id, - theCase.courtCaseNumber, - theCase.defendants, - ) - - expect(then.result).toEqual({ delivered: true }) - }) + it('should deliver the defender information to court', async () => { + const then = await givenWhenThen(caseId, theCase, { user }) + + expect( + mockCourtService.updateIndictmentWithDefenderInfo, + ).toHaveBeenCalledWith( + user, + theCase.id, + theCase.courtCaseNumber, + theCase.defendants, + ) + + expect(then.result).toEqual({ delivered: true }) + expect(then.error).toBeUndefined() + }) + + it('should handle not deliver if error occurs', async () => { + const error = new Error('Service error') + mockCourtService.updateIndictmentWithDefenderInfo.mockRejectedValueOnce( + error, + ) + + const then = await givenWhenThen(caseId, theCase, { user }) + + expect(then.result).toEqual({ delivered: false }) }) }) From d35aba9c66d2ec590414206afdec42e13123fa56 Mon Sep 17 00:00:00 2001 From: unakb Date: Wed, 12 Jun 2024 11:27:30 +0000 Subject: [PATCH 3/3] Update court.service.ts --- .../backend/src/app/modules/court/court.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/judicial-system/backend/src/app/modules/court/court.service.ts b/apps/judicial-system/backend/src/app/modules/court/court.service.ts index 8f02ebe81bd3..1637d8e76534 100644 --- a/apps/judicial-system/backend/src/app/modules/court/court.service.ts +++ b/apps/judicial-system/backend/src/app/modules/court/court.service.ts @@ -128,7 +128,7 @@ enum RobotEmailType { APPEAL_CASE_CONCLUSION = 'APPEAL_CASE_CONCLUSION', APPEAL_CASE_FILE = 'APPEAL_CASE_FILE', NEW_INDICTMENT_INFO = 'INDICTMENT_INFO', - UPDATE_INDICTMENT_DEFENDER_INFO = 'UPDATE_INDICTMENT_DEFENDER_INFO', + INDICTMENT_CASE_DEFENDER_INFO = 'INDICTMENT_CASE_DEFENDER_INFO', } @Injectable() @@ -624,7 +624,7 @@ export class CourtService { return this.sendToRobot( subject, content, - RobotEmailType.UPDATE_INDICTMENT_DEFENDER_INFO, + RobotEmailType.INDICTMENT_CASE_DEFENDER_INFO, caseId, ) } catch (error) {