From dab5b5c67def7d4897053447db152d4c6b0fbf8a Mon Sep 17 00:00:00 2001 From: pmcphee77 <150798161+pmcphee77@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:47:57 +0000 Subject: [PATCH] PI-1858: Added throw-not-found property for disabling in dev (#3210) * PI-1858: Added throw-not-found property for disabling in dev --- .../cas2-and-delius/deploy/values-dev.yml | 2 +- .../application-status-updated-not-found.json | 15 ++++ .../application-submitted-bad-request.json | 15 ++++ .../application-submitted-not-found.json | 15 ++++ .../mappings/approved-premises-api.json | 61 +++++++++++++++++ .../justice/digital/hmpps/IntegrationTest.kt | 28 ++++++++ .../digital/hmpps/NotFoundIntegrationTest.kt | 68 +++++++++++++++++++ .../digital/hmpps/messaging/Handler.kt | 18 +++-- .../src/main/resources/application.yml | 2 + .../digital/hmpps/messaging/HandlerTest.kt | 55 ++++++++++++++- 10 files changed, 272 insertions(+), 7 deletions(-) create mode 100644 projects/cas2-and-delius/src/dev/resources/messages/application-status-updated-not-found.json create mode 100644 projects/cas2-and-delius/src/dev/resources/messages/application-submitted-bad-request.json create mode 100644 projects/cas2-and-delius/src/dev/resources/messages/application-submitted-not-found.json create mode 100644 projects/cas2-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/NotFoundIntegrationTest.kt diff --git a/projects/cas2-and-delius/deploy/values-dev.yml b/projects/cas2-and-delius/deploy/values-dev.yml index 0be9100b8e..7a6cb073a2 100644 --- a/projects/cas2-and-delius/deploy/values-dev.yml +++ b/projects/cas2-and-delius/deploy/values-dev.yml @@ -8,7 +8,7 @@ generic-service: env: SENTRY_ENVIRONMENT: dev SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_HMPPS-AUTH_TOKEN-URI: http://hmpps-auth.hmpps-auth-dev.svc.cluster.local/auth/oauth/token - + EVENT_EXCEPTION_THROWNOTFOUND: false LOGGING_LEVEL_UK_GOV_DIGITAL_JUSTICE_HMPPS: DEBUG generic-prometheus-alerts: diff --git a/projects/cas2-and-delius/src/dev/resources/messages/application-status-updated-not-found.json b/projects/cas2-and-delius/src/dev/resources/messages/application-status-updated-not-found.json new file mode 100644 index 0000000000..4a7e912475 --- /dev/null +++ b/projects/cas2-and-delius/src/dev/resources/messages/application-status-updated-not-found.json @@ -0,0 +1,15 @@ +{ + "eventType": "applications.cas2.application.status-updated", + "version": 1, + "description": "A CAS2 application has been updated", + "detailUrl": "http://localhost:{wiremock.port}/approved-premises-api/events/cas2/application-status-updated/4444", + "occurredAt": "2020-01-01T12:34:56Z[Europe/London]", + "personReference": { + "identifiers": [ + { + "type": "CRN", + "value": "A000001" + } + ] + } +} \ No newline at end of file diff --git a/projects/cas2-and-delius/src/dev/resources/messages/application-submitted-bad-request.json b/projects/cas2-and-delius/src/dev/resources/messages/application-submitted-bad-request.json new file mode 100644 index 0000000000..ee8cc30c5a --- /dev/null +++ b/projects/cas2-and-delius/src/dev/resources/messages/application-submitted-bad-request.json @@ -0,0 +1,15 @@ +{ + "eventType": "applications.cas2.application.submitted", + "version": 1, + "description": "A CAS2 application has been submitted", + "detailUrl": "http://localhost:{wiremock.port}/approved-premises-api/events/cas2/application-submitted/5555", + "occurredAt": "2020-01-01T12:34:56Z[Europe/London]", + "personReference": { + "identifiers": [ + { + "type": "CRN", + "value": "A005555" + } + ] + } +} \ No newline at end of file diff --git a/projects/cas2-and-delius/src/dev/resources/messages/application-submitted-not-found.json b/projects/cas2-and-delius/src/dev/resources/messages/application-submitted-not-found.json new file mode 100644 index 0000000000..2c440a06c4 --- /dev/null +++ b/projects/cas2-and-delius/src/dev/resources/messages/application-submitted-not-found.json @@ -0,0 +1,15 @@ +{ + "eventType": "applications.cas2.application.submitted", + "version": 1, + "description": "A CAS2 application has been submitted", + "detailUrl": "http://localhost:{wiremock.port}/approved-premises-api/events/cas2/application-submitted/3333", + "occurredAt": "2020-01-01T12:34:56Z[Europe/London]", + "personReference": { + "identifiers": [ + { + "type": "CRN", + "value": "A003333" + } + ] + } +} \ No newline at end of file diff --git a/projects/cas2-and-delius/src/dev/resources/simulations/mappings/approved-premises-api.json b/projects/cas2-and-delius/src/dev/resources/simulations/mappings/approved-premises-api.json index 44476103f2..c5ab521235 100644 --- a/projects/cas2-and-delius/src/dev/resources/simulations/mappings/approved-premises-api.json +++ b/projects/cas2-and-delius/src/dev/resources/simulations/mappings/approved-premises-api.json @@ -13,6 +13,19 @@ "bodyFileName": "application-submitted.json" } }, + { + "request": { + "method": "GET", + "urlPath": "/approved-premises-api/events/cas2/application-submitted/4444" + }, + "response": { + "headers": { + "Content-Type": "application/json" + }, + "status": 200, + "bodyFileName": "application-status-updated.json" + } + }, { "request": { "method": "GET", @@ -25,6 +38,54 @@ "status": 200, "bodyFileName": "application-status-updated.json" } + }, + { + "request": { + "method": "GET", + "urlPath": "/approved-premises-api/events/cas2/application-submitted/3333" + }, + "response": { + "status": 404, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "status": 404, + "detail": "No DomainEvent with an ID of 3333 could be found" + } + } + }, + { + "request": { + "method": "GET", + "urlPath": "/approved-premises-api/events/cas2/application-submitted/5555" + }, + "response": { + "status": 400, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "status": 400, + "detail": "Bad Request" + } + } + }, + { + "request": { + "method": "GET", + "urlPath": "/approved-premises-api/events/cas2/application-status-updated/4444" + }, + "response": { + "status": 404, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "status": 404, + "detail": "No DomainEvent with an ID of 4444 could be found" + } + } } ] } \ No newline at end of file diff --git a/projects/cas2-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt b/projects/cas2-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt index 7ceb7bb6aa..b6ac7f4339 100644 --- a/projects/cas2-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt +++ b/projects/cas2-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt @@ -4,11 +4,16 @@ import com.github.tomakehurst.wiremock.WireMockServer import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.* import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mockito +import org.mockito.kotlin.any import org.mockito.kotlin.verify import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.boot.test.system.CapturedOutput +import org.springframework.boot.test.system.OutputCaptureExtension import uk.gov.justice.digital.hmpps.datetime.EuropeLondon import uk.gov.justice.digital.hmpps.entity.ContactRepository import uk.gov.justice.digital.hmpps.entity.ContactType.Companion.REFERRAL_SUBMITTED @@ -20,6 +25,7 @@ import java.time.LocalDate import java.time.ZonedDateTime @SpringBootTest +@ExtendWith(OutputCaptureExtension::class) internal class IntegrationTest { @Value("\${messaging.consumer.queue}") lateinit var queueName: String @@ -120,4 +126,26 @@ internal class IntegrationTest { mapOf() ) } + + @Test + fun `application submitted not found enabled`(output: CapturedOutput) { + // Given a message + val event = prepEvent("application-submitted-not-found", wireMockServer.port()) + channelManager.getChannel(queueName).publishAndWait(event) + //Assert that expected exception exists in output + assertThat(output.all, containsString("No DomainEvent with an ID of 3333 could be found")) + //Assert that only 1 trackEvent for Notification Received has occurred + verify(telemetryService, Mockito.times(1)).trackEvent(any(), any(), any()) + } + + @Test + fun `application status not found enabled`(output: CapturedOutput) { + // Given a message + val event = prepEvent("application-status-updated-not-found", wireMockServer.port()) + channelManager.getChannel(queueName).publishAndWait(event) + //Assert that expected exception exists in output + assertThat(output.all, containsString("No DomainEvent with an ID of 4444 could be found")) + //Assert that only 1 trackEvent for Notification Received has occurred + verify(telemetryService, Mockito.times(1)).trackEvent(any(), any(), any()) + } } diff --git a/projects/cas2-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/NotFoundIntegrationTest.kt b/projects/cas2-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/NotFoundIntegrationTest.kt new file mode 100644 index 0000000000..ed1f422705 --- /dev/null +++ b/projects/cas2-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/NotFoundIntegrationTest.kt @@ -0,0 +1,68 @@ +package uk.gov.justice.digital.hmpps + +import com.github.tomakehurst.wiremock.WireMockServer +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.containsString +import org.hamcrest.Matchers.not +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.verify +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.boot.test.system.CapturedOutput +import org.springframework.boot.test.system.OutputCaptureExtension +import uk.gov.justice.digital.hmpps.messaging.HmppsChannelManager +import uk.gov.justice.digital.hmpps.telemetry.TelemetryService + +@SpringBootTest(properties = ["event.exception.throw-not-found: false"]) +@ExtendWith(OutputCaptureExtension::class) +internal class NotFoundIntegrationTest { + @Value("\${messaging.consumer.queue}") + lateinit var queueName: String + + @Autowired + lateinit var channelManager: HmppsChannelManager + + @Autowired + lateinit var wireMockServer: WireMockServer + + @MockBean + lateinit var telemetryService: TelemetryService + + @Test + fun `application submitted not found enabled`(output: CapturedOutput) { + // Given a message + val event = prepEvent("application-submitted-not-found", wireMockServer.port()) + channelManager.getChannel(queueName).publishAndWait(event) + //Assert that expected exception exists in output + assertThat(output.all, not(containsString("No DomainEvent with an ID of 3333 could be found"))) + //Assert that only 1 trackEvent for Notification Received has occurred + verify(telemetryService, Mockito.times(1)).trackEvent(any(), any(), any()) + } + + @Test + fun `application status not found enabled`(output: CapturedOutput) { + // Given a message + val event = prepEvent("application-status-updated-not-found", wireMockServer.port()) + channelManager.getChannel(queueName).publishAndWait(event) + //Assert that expected exception exists in output + assertThat(output.all, not(containsString("No DomainEvent with an ID of 4444 could be found"))) + //Assert that only 1 trackEvent for Notification Received has occurred + verify(telemetryService, Mockito.times(1)).trackEvent(any(), any(), any()) + } + + @Test + fun `application submitted bad request still thrown`(output: CapturedOutput) { + // Given a message + val event = prepEvent("application-submitted-bad-request", wireMockServer.port()) + channelManager.getChannel(queueName).publishAndWait(event) + //Assert that expected exception exists in output + assertThat(output.all, containsString("Bad Request")) + //Assert that only 1 trackEvent for Notification Received has occurred + verify(telemetryService, Mockito.times(1)).trackEvent(any(), any(), any()) + } +} diff --git a/projects/cas2-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt b/projects/cas2-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt index b466a2cb4a..e1ac390e62 100644 --- a/projects/cas2-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt +++ b/projects/cas2-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt @@ -1,6 +1,9 @@ package uk.gov.justice.digital.hmpps.messaging +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpStatus import org.springframework.stereotype.Component +import org.springframework.web.client.HttpStatusCodeException import uk.gov.justice.digital.hmpps.converter.NotificationConverter import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent import uk.gov.justice.digital.hmpps.message.Notification @@ -10,6 +13,7 @@ import uk.gov.justice.digital.hmpps.telemetry.notificationReceived @Component class Handler( + @Value("\${event.exception.throw-not-found:true}") private val throwNotFound: Boolean, override val converter: NotificationConverter, private val telemetryService: TelemetryService, private val cas2Service: Cas2Service, @@ -17,11 +21,17 @@ class Handler( override fun handle(notification: Notification) { telemetryService.notificationReceived(notification) val event = notification.message - when (event.eventType) { - "applications.cas2.application.submitted" -> cas2Service.applicationSubmitted(event) - "applications.cas2.application.status-updated" -> cas2Service.applicationStatusUpdated(event) + try { + when (event.eventType) { + "applications.cas2.application.submitted" -> cas2Service.applicationSubmitted(event) + "applications.cas2.application.status-updated" -> cas2Service.applicationStatusUpdated(event) - else -> throw IllegalArgumentException("Unexpected event type ('${event.eventType}')") + else -> throw IllegalArgumentException("Unexpected event type ('${event.eventType}')") + } + } catch (ex: HttpStatusCodeException) { + if (ex.statusCode != HttpStatus.NOT_FOUND || throwNotFound) { + throw ex + } } } } diff --git a/projects/cas2-and-delius/src/main/resources/application.yml b/projects/cas2-and-delius/src/main/resources/application.yml index dd5f063c34..23f38dcd08 100644 --- a/projects/cas2-and-delius/src/main/resources/application.yml +++ b/projects/cas2-and-delius/src/main/resources/application.yml @@ -51,6 +51,8 @@ context.initializer.classes: uk.gov.justice.digital.hmpps.wiremock.WireMockIniti messaging.consumer.queue: message-queue +event.exception.throw-not-found: true + integrations: example: url: http://localhost:${wiremock.port}/example diff --git a/projects/cas2-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/HandlerTest.kt b/projects/cas2-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/HandlerTest.kt index 92ceaeabde..a17b81ad67 100644 --- a/projects/cas2-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/HandlerTest.kt +++ b/projects/cas2-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/HandlerTest.kt @@ -1,22 +1,28 @@ package uk.gov.justice.digital.hmpps.messaging import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.containsString import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.whenever +import org.springframework.http.HttpStatus +import org.springframework.web.client.HttpClientErrorException import uk.gov.justice.digital.hmpps.converter.NotificationConverter import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent import uk.gov.justice.digital.hmpps.message.MessageAttributes import uk.gov.justice.digital.hmpps.message.Notification +import uk.gov.justice.digital.hmpps.prepEvent import uk.gov.justice.digital.hmpps.service.Cas2Service import uk.gov.justice.digital.hmpps.telemetry.TelemetryService @ExtendWith(MockitoExtension::class) internal class HandlerTest { + @Mock lateinit var converter: NotificationConverter @@ -26,15 +32,60 @@ internal class HandlerTest { @Mock lateinit var cas2Service: Cas2Service - @InjectMocks lateinit var handler: Handler @Test fun `handles unexpected event type`() { + handler = Handler(true, converter, telemetryService, cas2Service) val exception = assertThrows { handler.handle(Notification(HmppsDomainEvent("unknown", 1), MessageAttributes("unknown"))) } assertThat(exception.message, equalTo("Unexpected event type ('unknown')")) } + + @Test + fun `throws NotFoundException`() { + handler = Handler(true, converter, telemetryService, cas2Service) + val event = prepEvent("application-submitted") + whenever(cas2Service.applicationSubmitted(event.message)).thenThrow( + HttpClientErrorException( + HttpStatus.NOT_FOUND, + "DomainEvent not found" + ) + ) + val exception = assertThrows { + handler.handle(Notification(event.message, event.attributes, event.id)) + } + assertThat(exception.message, containsString("DomainEvent not found")) + } + + @Test + fun `does not throw NotFoundException`() { + handler = Handler(false, converter, telemetryService, cas2Service) + val event = prepEvent("application-submitted") + whenever(cas2Service.applicationSubmitted(event.message)).thenThrow( + HttpClientErrorException( + HttpStatus.NOT_FOUND, + "DomainEvent not found" + ) + ) + assertDoesNotThrow { handler.handle(Notification(event.message, event.attributes, event.id)) } + } + + @Test + fun `still throws Bad Request exception`() { + handler = Handler(false, converter, telemetryService, cas2Service) + val event = prepEvent("application-submitted") + whenever(cas2Service.applicationSubmitted(event.message)).thenThrow( + HttpClientErrorException( + HttpStatus.BAD_REQUEST, + "Bad Request" + ) + ) + val exception = assertThrows { + handler.handle(Notification(event.message, event.attributes, event.id)) + } + assertThat(exception.message, containsString("Bad Request")) + } }