From 168a9b169d6f18b8fc87b29d58a5f41094b3ed12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Tue, 10 Feb 2026 14:00:08 +0000 Subject: [PATCH 01/10] Added E2E integration tests --- ...NotificationControllerIntegrationTest.java | 11 +- .../NotificationControllerValidationTest.java | 6 +- .../SubscriptionControllerValidationTest.java | 4 +- ...criptionSaveControllerIntegrationTest.java | 2 +- ...iptionUpdateControllerIntegrationTest.java | 2 +- .../NotificationPcrE2EIntegrationTest.java | 196 ++++++++++++++++++ .../material-content-full-mapping.json | 13 -- .../pcr-request-custodial-result.json | 0 .../pcr-request-material-not-found.json | 0 .../pcr-request-material-timeout.json | 0 .../pcr-request-missing-event.json | 0 .../pcr-request-missing-material.json | 0 .../pcr-request-prison-court-register.json | 0 .../pcr-request-valid.json | 0 .../subscription-pcr-request.json | 8 + .../subscription-request-bad-event-type.json | 0 ...cription-request-invalid-callback-url.json | 0 .../subscription-request-valid.json | 0 .../__files/material-content.pdf | Bin .../__files/material-response.json | 2 +- .../__files/material-with-contenturl.json | 2 +- .../mappings/material-binary-mapping.json} | 2 +- .../material-content-full-mapping.json | 16 ++ .../mappings/material-metadata-mapping.json | 0 .../material-metadata-timeout-mapping.json | 0 .../__files/material-content.pdf | Bin 0 -> 8682 bytes .../__files/material-response.json | 7 + .../__files/material-with-contenturl.json | 9 + .../mappings/material-binary-mapping.json | 2 +- .../material-content-full-mapping.json | 16 ++ .../mappings/material-metadata-mapping.json | 6 +- .../material-metadata-timeout-mapping.json | 9 + 32 files changed, 281 insertions(+), 32 deletions(-) create mode 100644 src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPcrE2EIntegrationTest.java delete mode 100644 src/test/resources/mappings/material-content-full-mapping.json rename src/test/resources/stubs/requests/{ => progression-pcr}/pcr-request-custodial-result.json (100%) rename src/test/resources/stubs/requests/{ => progression-pcr}/pcr-request-material-not-found.json (100%) rename src/test/resources/stubs/requests/{ => progression-pcr}/pcr-request-material-timeout.json (100%) rename src/test/resources/stubs/requests/{ => progression-pcr}/pcr-request-missing-event.json (100%) rename src/test/resources/stubs/requests/{ => progression-pcr}/pcr-request-missing-material.json (100%) rename src/test/resources/stubs/requests/{ => progression-pcr}/pcr-request-prison-court-register.json (100%) rename src/test/resources/stubs/requests/{ => progression-pcr}/pcr-request-valid.json (100%) create mode 100644 src/test/resources/stubs/requests/subscription/subscription-pcr-request.json rename src/test/resources/stubs/requests/{ => subscription}/subscription-request-bad-event-type.json (100%) rename src/test/resources/stubs/requests/{ => subscription}/subscription-request-invalid-callback-url.json (100%) rename src/test/resources/stubs/requests/{ => subscription}/subscription-request-valid.json (100%) rename src/test/resources/{ => wiremock/material-client}/__files/material-content.pdf (100%) rename src/test/resources/{ => wiremock/material-client}/__files/material-response.json (99%) rename src/test/resources/{ => wiremock/material-client}/__files/material-with-contenturl.json (59%) rename src/test/resources/{mappings/material-content-mapping.json => wiremock/material-client/mappings/material-binary-mapping.json} (87%) create mode 100644 src/test/resources/wiremock/material-client/mappings/material-content-full-mapping.json rename src/test/resources/{ => wiremock/material-client}/mappings/material-metadata-mapping.json (100%) rename src/test/resources/{ => wiremock/material-client}/mappings/material-metadata-timeout-mapping.json (100%) create mode 100644 stubs/material-client/__files/material-content.pdf create mode 100644 stubs/material-client/__files/material-response.json create mode 100644 stubs/material-client/__files/material-with-contenturl.json rename {src/test/resources => stubs/material-client}/mappings/material-binary-mapping.json (68%) create mode 100644 stubs/material-client/mappings/material-content-full-mapping.json rename src/test/resources/mappings/material-metadata-not-found-mapping.json => stubs/material-client/mappings/material-metadata-mapping.json (62%) create mode 100644 stubs/material-client/mappings/material-metadata-timeout-mapping.json diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java index fc8b0516..6e3c8a77 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java @@ -31,7 +31,7 @@ import uk.gov.hmcts.cp.subscription.services.CallbackDeliveryService; -@EnableWireMock({@ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 18081)}) +@EnableWireMock({@ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 0)}) @TestPropertySource(properties = { "material-client.retry.timeoutMilliSecs=500", "material-client.retry.intervalMilliSecs=100" @@ -54,7 +54,7 @@ void setUp() { @Test void prison_court_register_generated_should_return_success() throws Exception { - String pcrPayload = loadPayload("stubs/requests/pcr-request-prison-court-register.json"); + String pcrPayload = loadPayload("stubs/requests/progression-pcr/pcr-request-prison-court-register.json"); mockMvc.perform(post(NOTIFICATION_PCR_URI) .contentType(MediaType.APPLICATION_JSON) @@ -68,7 +68,7 @@ void prison_court_register_generated_should_return_success() throws Exception { @Test void custodial_result_should_return_unsupported() throws Exception { - String pcrPayload = loadPayload("stubs/requests/pcr-request-custodial-result.json"); + String pcrPayload = loadPayload("stubs/requests/progression-pcr/pcr-request-custodial-result.json"); doThrow(new UnsupportedOperationException("CUSTODIAL_RESULT not implemented")) .when(callbackDeliveryService).processPcrEvent(any(PcrEventPayload.class), any(UUID.class)); @@ -86,7 +86,7 @@ void custodial_result_should_return_unsupported() throws Exception { @Test void material_metadata_not_found_should_return_404() throws Exception { - String pcrPayload = loadPayload("stubs/requests/pcr-request-material-not-found.json"); + String pcrPayload = loadPayload("stubs/requests/progression-pcr/pcr-request-material-not-found.json"); mockMvc.perform(post(NOTIFICATION_PCR_URI) .contentType(MediaType.APPLICATION_JSON) @@ -98,7 +98,8 @@ void material_metadata_not_found_should_return_404() throws Exception { @Test void material_metadata_timeout_should_return_504_via_global_exception_handler() throws Exception { - String pcrPayload = loadPayload("stubs/requests/pcr-request-material-timeout.json"); + String pcrPayload = loadPayload("stubs/requests/progression-pcr/pcr-request-material-timeout.json"); + mockMvc.perform(post(NOTIFICATION_PCR_URI) .contentType(MediaType.APPLICATION_JSON) diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerValidationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerValidationTest.java index 9b3d5657..dd89b324 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerValidationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerValidationTest.java @@ -28,9 +28,9 @@ class NotificationControllerValidationTest extends IntegrationTestBase { private static final String NOTIFICATION_PCR_URI = "/notifications/pcr"; - private static final String PCR_REQUEST_VALID = "stubs/requests/pcr-request-valid.json"; - private static final String PCR_REQUEST_MISSING_MATERIAL = "stubs/requests/pcr-request-missing-material.json"; - private static final String PCR_REQUEST_MISSING_EVENT = "stubs/requests/pcr-request-missing-event.json"; + private static final String PCR_REQUEST_VALID = "stubs/requests/progression-pcr/pcr-request-valid.json"; + private static final String PCR_REQUEST_MISSING_MATERIAL = "stubs/requests/progression-pcr/pcr-request-missing-material.json"; + private static final String PCR_REQUEST_MISSING_EVENT = "stubs/requests/progression-pcr/pcr-request-missing-event.json"; private static final UUID SUBSCRIPTION_ID = randomUUID(); private static final UUID DOCUMENT_ID = randomUUID(); private static final String SUBSCRIPTION_DOCUMENT_URI = "/client-subscriptions/{clientSubscriptionId}/documents/{documentId}"; diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionControllerValidationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionControllerValidationTest.java index 1fff37fd..21611a7d 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionControllerValidationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionControllerValidationTest.java @@ -11,8 +11,8 @@ class SubscriptionControllerValidationTest extends IntegrationTestBase { private static final String CLIENT_SUBSCRIPTIONS = "/client-subscriptions"; - private static final String SUBSCRIPTION_REQUEST_BAD_EVENT = "stubs/requests/subscription-request-bad-event-type.json"; - private static final String SUBSCRIPTION_REQUEST_INVALID_CALLBACK = "stubs/requests/subscription-request-invalid-callback-url.json"; + private static final String SUBSCRIPTION_REQUEST_BAD_EVENT = "stubs/requests/subscription/subscription-request-bad-event-type.json"; + private static final String SUBSCRIPTION_REQUEST_INVALID_CALLBACK = "stubs/requests/subscription/subscription-request-invalid-callback-url.json"; @Test void bad_event_type_should_return_400() throws Exception { diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionSaveControllerIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionSaveControllerIntegrationTest.java index 04011f2d..f44dec3b 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionSaveControllerIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionSaveControllerIntegrationTest.java @@ -17,7 +17,7 @@ class SubscriptionSaveControllerIntegrationTest extends IntegrationTestBase { - private static final String SUBSCRIPTION_REQUEST_VALID = "stubs/requests/subscription-request-valid.json"; + private static final String SUBSCRIPTION_REQUEST_VALID = "stubs/requests/subscription/subscription-request-valid.json"; @BeforeEach void beforeEach() { diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionUpdateControllerIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionUpdateControllerIntegrationTest.java index 0f1cd909..8b919d72 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionUpdateControllerIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionUpdateControllerIntegrationTest.java @@ -22,7 +22,7 @@ @Slf4j class SubscriptionUpdateControllerIntegrationTest extends IntegrationTestBase { - private static final String SUBSCRIPTION_REQUEST_VALID = "stubs/requests/subscription-request-valid.json"; + private static final String SUBSCRIPTION_REQUEST_VALID = "stubs/requests/subscription/subscription-request-valid.json"; @BeforeEach void beforeEach() { diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPcrE2EIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPcrE2EIntegrationTest.java new file mode 100644 index 00000000..dcc1040e --- /dev/null +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPcrE2EIntegrationTest.java @@ -0,0 +1,196 @@ +package uk.gov.hmcts.cp.subscription.integration.e2e; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.wiremock.spring.ConfigureWireMock; +import org.wiremock.spring.EnableWireMock; + +import uk.gov.hmcts.cp.material.openapi.api.MaterialApi; +import uk.gov.hmcts.cp.openapi.model.PcrEventPayload; +import uk.gov.hmcts.cp.subscription.integration.IntegrationTestBase; +import uk.gov.hmcts.cp.subscription.model.EntityEventType; +import uk.gov.hmcts.cp.subscription.services.CallbackDeliveryService; +import uk.gov.hmcts.cp.subscription.services.DocumentService; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * E2E: success = subscription → material API → callback → document (full chain). + * failure = metadata never ready → 504, no callback. + */ +// TODO - comments can be removed after handover to QA + +@EnableWireMock({@ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 0)}) +@TestPropertySource(properties = { + "material-client.retry.timeoutMilliSecs=500", + "material-client.retry.intervalMilliSecs=100" +}) +class NotificationPcrE2EIntegrationTest extends IntegrationTestBase { + + private static final String NOTIFICATION_PCR_URI = "/notifications/pcr"; + private static final String CLIENT_SUBSCRIPTIONS_URI = "/client-subscriptions"; + private static final String DOCUMENT_URI = "/client-subscriptions/{clientSubscriptionId}/documents/{documentId}"; + private static final String SUBSCRIPTION_REQUEST_E2E = "stubs/requests/subscription/subscription-pcr-request.json"; + private static final String PCR_EVENT_PAYLOAD_PATH = "stubs/requests/progression-pcr/pcr-request-prison-court-register.json"; + private static final String PCR_EVENT_TIMEOUT_PATH = "stubs/requests/progression-pcr/pcr-request-material-timeout.json"; + private static final UUID MATERIAL_ID = UUID.fromString("6c198796-08bb-4803-b456-fa0c29ca6021"); + private static final UUID MATERIAL_ID_TIMEOUT = UUID.fromString("11111111-1111-1111-1111-111111111112"); + + @MockitoBean + private CallbackDeliveryService callbackDeliveryService; + + @MockitoSpyBean + private MaterialApi materialApi; + + @MockitoSpyBean + private DocumentService documentService; + + private UUID subscriptionId; + private UUID callbackDocumentId; + + @BeforeEach + void setUp() { + reset(callbackDeliveryService); + clearAllTables(); + } + + @Test + void test_document_retrieval_success() throws Exception { + given_i_am_a_subscriber_with_a_subscription(); + given_i_have_a_callback_endpoint(); + given_material_service_returns_document_success(); + + when_a_pcr_event_is_posted(); + when_material_service_responds(); + + then_the_material_api_is_called(); + then_the_subscriber_receives_a_callback(); + then_the_subscriber_can_retrieve_the_document(); + } + + @Test + void test_document_retrieval_failure() throws Exception { + given_i_am_a_subscriber_with_a_subscription(); + given_i_have_a_callback_endpoint(); + given_material_service_returns_document_not_found(); + + when_a_pcr_event_is_posted_with_timeout(); + when_material_service_responds(); + + then_the_material_api_was_polled(); + then_the_subscriber_does_not_receive_a_callback(); + then_the_subscriber_cannot_retrieve_the_document(); + } + + private void given_i_am_a_subscriber_with_a_subscription() throws Exception { + createSubscription(); + } + + private void given_i_have_a_callback_endpoint() { + // Callback endpoint represented by @MockitoBean CallbackDeliveryService + } + + private void given_material_service_returns_document_success() { + /*WireMock (material-client) stubs from wiremock/material-client/mappings/: + material-metadata-mapping.json GET .../material/6c198796-08bb-4803-b456-fa0c29ca6021/metadata → 200, __files/material-response.json + material-content-full-mapping.json GET .../material/6c198796-08bb-4803-b456-fa0c29ca6021/content → 200, __files/material-with-contenturl.json + material-binary-mapping.json GET .../material/6c198796-08bb-4803-b456-fa0c29ca6021/binary → 200, __files/material-content.pdf*/ + } + + private void given_material_service_returns_document_not_found() { + /*Request payload: stubs/requests/progression-pcr/pcr-request-material-timeout.json + eventId: 11111111-1111-1111-1111-111111111111, materialId: 11111111-1111-1111-1111-111111111112, eventType: PRISON_COURT_REGISTER_GENERATED + WireMock stub (wiremock/material-client/mappings/): + material-metadata-timeout-mapping.json GET .../material/11111111-1111-1111-1111-111111111112/metadata → 204 (no body) + App polls until Awaitility times out → 504 "Material metadata not ready", no save/callback.*/ + } + + private void when_a_pcr_event_is_posted() throws Exception { + mockMvc.perform(post(NOTIFICATION_PCR_URI) + .contentType(MediaType.APPLICATION_JSON) + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + .content(loadPayload(PCR_EVENT_PAYLOAD_PATH))) + .andDo(print()) + .andExpect(status().isAccepted()); + } + + private void when_a_pcr_event_is_posted_with_timeout() throws Exception { + mockMvc.perform(post(NOTIFICATION_PCR_URI) + .contentType(MediaType.APPLICATION_JSON) + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + .content(loadPayload(PCR_EVENT_TIMEOUT_PATH))) + .andDo(print()) + .andExpect(status().isGatewayTimeout()) + .andExpect(content().string("Material metadata not ready")); + } + + private void when_material_service_responds() { + /* This step does nothing; the material response already happens during when_a_pcr_event_is_posted (app calls MaterialApi, WireMock serves stubs) + Success: GET metadata → 200 → save document mapping → callback. + Failure: GET metadata → 204 until timeout → 504.*/ + } + + private void then_the_material_api_is_called() { + verify(materialApi, times(1)).getMaterialMetadataByMaterialId(eq(MATERIAL_ID)); + } + + private void then_the_material_api_was_polled() { + verify(materialApi, atLeastOnce()).getMaterialMetadataByMaterialId(eq(MATERIAL_ID_TIMEOUT)); + } + + private void then_the_subscriber_receives_a_callback() { + ArgumentCaptor documentIdCaptor = ArgumentCaptor.forClass(UUID.class); + verify(callbackDeliveryService, times(1)) + .processPcrEvent(any(PcrEventPayload.class), documentIdCaptor.capture()); + callbackDocumentId = documentIdCaptor.getValue(); + assertThat(callbackDocumentId).isNotNull(); + } + + private void then_the_subscriber_does_not_receive_a_callback() { + verify(callbackDeliveryService, times(0)).processPcrEvent(any(PcrEventPayload.class), any()); + } + + private void then_the_subscriber_can_retrieve_the_document() throws Exception { + mockMvc.perform(get(DOCUMENT_URI, subscriptionId, callbackDocumentId)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(header().string("Content-Type", org.hamcrest.Matchers.containsString("application/pdf"))) + .andExpect(header().string("Content-Disposition", org.hamcrest.Matchers.containsString("PrisonCourtRegister"))) + .andExpect(content().contentType(MediaType.APPLICATION_PDF)); + } + + private void then_the_subscriber_cannot_retrieve_the_document() { + verify(documentService, times(0)).saveDocumentMapping(any(UUID.class), any(EntityEventType.class)); + } + + private void createSubscription() throws Exception { + String json = mockMvc.perform(post(CLIENT_SUBSCRIPTIONS_URI) + .contentType(MediaType.APPLICATION_JSON) + .content(loadPayload(SUBSCRIPTION_REQUEST_E2E))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.clientSubscriptionId").exists()) + .andReturn().getResponse().getContentAsString(); + subscriptionId = UUID.fromString(new ObjectMapper().readTree(json).get("clientSubscriptionId").asText()); + } +} diff --git a/src/test/resources/mappings/material-content-full-mapping.json b/src/test/resources/mappings/material-content-full-mapping.json deleted file mode 100644 index 4ea452ec..00000000 --- a/src/test/resources/mappings/material-content-full-mapping.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "request": { - "method": "GET", - "urlPathPattern": "/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/content" - }, - "response": { - "status": 200, - "headers": { - "Content-Type": "application/vnd.material.query.material+json" - }, - "bodyFileName": "material-with-contenturl.json" - } -} diff --git a/src/test/resources/stubs/requests/pcr-request-custodial-result.json b/src/test/resources/stubs/requests/progression-pcr/pcr-request-custodial-result.json similarity index 100% rename from src/test/resources/stubs/requests/pcr-request-custodial-result.json rename to src/test/resources/stubs/requests/progression-pcr/pcr-request-custodial-result.json diff --git a/src/test/resources/stubs/requests/pcr-request-material-not-found.json b/src/test/resources/stubs/requests/progression-pcr/pcr-request-material-not-found.json similarity index 100% rename from src/test/resources/stubs/requests/pcr-request-material-not-found.json rename to src/test/resources/stubs/requests/progression-pcr/pcr-request-material-not-found.json diff --git a/src/test/resources/stubs/requests/pcr-request-material-timeout.json b/src/test/resources/stubs/requests/progression-pcr/pcr-request-material-timeout.json similarity index 100% rename from src/test/resources/stubs/requests/pcr-request-material-timeout.json rename to src/test/resources/stubs/requests/progression-pcr/pcr-request-material-timeout.json diff --git a/src/test/resources/stubs/requests/pcr-request-missing-event.json b/src/test/resources/stubs/requests/progression-pcr/pcr-request-missing-event.json similarity index 100% rename from src/test/resources/stubs/requests/pcr-request-missing-event.json rename to src/test/resources/stubs/requests/progression-pcr/pcr-request-missing-event.json diff --git a/src/test/resources/stubs/requests/pcr-request-missing-material.json b/src/test/resources/stubs/requests/progression-pcr/pcr-request-missing-material.json similarity index 100% rename from src/test/resources/stubs/requests/pcr-request-missing-material.json rename to src/test/resources/stubs/requests/progression-pcr/pcr-request-missing-material.json diff --git a/src/test/resources/stubs/requests/pcr-request-prison-court-register.json b/src/test/resources/stubs/requests/progression-pcr/pcr-request-prison-court-register.json similarity index 100% rename from src/test/resources/stubs/requests/pcr-request-prison-court-register.json rename to src/test/resources/stubs/requests/progression-pcr/pcr-request-prison-court-register.json diff --git a/src/test/resources/stubs/requests/pcr-request-valid.json b/src/test/resources/stubs/requests/progression-pcr/pcr-request-valid.json similarity index 100% rename from src/test/resources/stubs/requests/pcr-request-valid.json rename to src/test/resources/stubs/requests/progression-pcr/pcr-request-valid.json diff --git a/src/test/resources/stubs/requests/subscription/subscription-pcr-request.json b/src/test/resources/stubs/requests/subscription/subscription-pcr-request.json new file mode 100644 index 00000000..7bcd2d1c --- /dev/null +++ b/src/test/resources/stubs/requests/subscription/subscription-pcr-request.json @@ -0,0 +1,8 @@ +{ + "notificationEndpoint": { + "callbackUrl": "https://callback.example.com" + }, + "eventTypes": [ + "PRISON_COURT_REGISTER_GENERATED" + ] +} diff --git a/src/test/resources/stubs/requests/subscription-request-bad-event-type.json b/src/test/resources/stubs/requests/subscription/subscription-request-bad-event-type.json similarity index 100% rename from src/test/resources/stubs/requests/subscription-request-bad-event-type.json rename to src/test/resources/stubs/requests/subscription/subscription-request-bad-event-type.json diff --git a/src/test/resources/stubs/requests/subscription-request-invalid-callback-url.json b/src/test/resources/stubs/requests/subscription/subscription-request-invalid-callback-url.json similarity index 100% rename from src/test/resources/stubs/requests/subscription-request-invalid-callback-url.json rename to src/test/resources/stubs/requests/subscription/subscription-request-invalid-callback-url.json diff --git a/src/test/resources/stubs/requests/subscription-request-valid.json b/src/test/resources/stubs/requests/subscription/subscription-request-valid.json similarity index 100% rename from src/test/resources/stubs/requests/subscription-request-valid.json rename to src/test/resources/stubs/requests/subscription/subscription-request-valid.json diff --git a/src/test/resources/__files/material-content.pdf b/src/test/resources/wiremock/material-client/__files/material-content.pdf similarity index 100% rename from src/test/resources/__files/material-content.pdf rename to src/test/resources/wiremock/material-client/__files/material-content.pdf diff --git a/src/test/resources/__files/material-response.json b/src/test/resources/wiremock/material-client/__files/material-response.json similarity index 99% rename from src/test/resources/__files/material-response.json rename to src/test/resources/wiremock/material-client/__files/material-response.json index 30f20a05..9baf7c5b 100644 --- a/src/test/resources/__files/material-response.json +++ b/src/test/resources/wiremock/material-client/__files/material-response.json @@ -4,4 +4,4 @@ "fileName": "PrisonCourtRegister_20251219083322.pdf", "mimeType": "application/pdf", "materialAddedDate": "2025-12-19T08:33:29.866Z" -} \ No newline at end of file +} diff --git a/src/test/resources/__files/material-with-contenturl.json b/src/test/resources/wiremock/material-client/__files/material-with-contenturl.json similarity index 59% rename from src/test/resources/__files/material-with-contenturl.json rename to src/test/resources/wiremock/material-client/__files/material-with-contenturl.json index f0b007a2..f8832eb9 100644 --- a/src/test/resources/__files/material-with-contenturl.json +++ b/src/test/resources/wiremock/material-client/__files/material-with-contenturl.json @@ -5,5 +5,5 @@ "fileName": "PrisonCourtRegister_20251219083322.pdf", "mimeType": "application/pdf" }, - "contentUrl": "http://localhost:18081/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/binary" + "contentUrl": "http://{{request.host}}:{{request.port}}/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/binary" } diff --git a/src/test/resources/mappings/material-content-mapping.json b/src/test/resources/wiremock/material-client/mappings/material-binary-mapping.json similarity index 87% rename from src/test/resources/mappings/material-content-mapping.json rename to src/test/resources/wiremock/material-client/mappings/material-binary-mapping.json index 89d431bb..f12bf4a6 100644 --- a/src/test/resources/mappings/material-content-mapping.json +++ b/src/test/resources/wiremock/material-client/mappings/material-binary-mapping.json @@ -1,7 +1,7 @@ { "request": { "method": "GET", - "url": "/material-query-api/query/api/rest/material/material/7c198796-08bb-4803-b456-fa0c29ca6021/content" + "url": "/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/binary" }, "response": { "status": 200, diff --git a/src/test/resources/wiremock/material-client/mappings/material-content-full-mapping.json b/src/test/resources/wiremock/material-client/mappings/material-content-full-mapping.json new file mode 100644 index 00000000..fcf1ab05 --- /dev/null +++ b/src/test/resources/wiremock/material-client/mappings/material-content-full-mapping.json @@ -0,0 +1,16 @@ +{ + "request": { + "method": "GET", + "url": "/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/content" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/vnd.material.query.material+json" + }, + "bodyFileName": "material-with-contenturl.json", + "transformers": [ + "response-template" + ] + } +} diff --git a/src/test/resources/mappings/material-metadata-mapping.json b/src/test/resources/wiremock/material-client/mappings/material-metadata-mapping.json similarity index 100% rename from src/test/resources/mappings/material-metadata-mapping.json rename to src/test/resources/wiremock/material-client/mappings/material-metadata-mapping.json diff --git a/src/test/resources/mappings/material-metadata-timeout-mapping.json b/src/test/resources/wiremock/material-client/mappings/material-metadata-timeout-mapping.json similarity index 100% rename from src/test/resources/mappings/material-metadata-timeout-mapping.json rename to src/test/resources/wiremock/material-client/mappings/material-metadata-timeout-mapping.json diff --git a/stubs/material-client/__files/material-content.pdf b/stubs/material-client/__files/material-content.pdf new file mode 100644 index 0000000000000000000000000000000000000000..94e3be392efef104e18d421ad10f13c11aafe301 GIT binary patch literal 8682 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 VMa97{C-DQp5IiO(Y2_z){{tT(mKy*7 literal 0 HcmV?d00001 diff --git a/stubs/material-client/__files/material-response.json b/stubs/material-client/__files/material-response.json new file mode 100644 index 00000000..9baf7c5b --- /dev/null +++ b/stubs/material-client/__files/material-response.json @@ -0,0 +1,7 @@ +{ + "materialId": "6c198796-08bb-4803-b456-fa0c29ca6021", + "alfrescoAssetId": "82257b1b-571d-432e-8871-b0c5b4bd18b1", + "fileName": "PrisonCourtRegister_20251219083322.pdf", + "mimeType": "application/pdf", + "materialAddedDate": "2025-12-19T08:33:29.866Z" +} diff --git a/stubs/material-client/__files/material-with-contenturl.json b/stubs/material-client/__files/material-with-contenturl.json new file mode 100644 index 00000000..f8832eb9 --- /dev/null +++ b/stubs/material-client/__files/material-with-contenturl.json @@ -0,0 +1,9 @@ +{ + "materialId": "6c198796-08bb-4803-b456-fa0c29ca6021", + "metadata": { + "materialId": "6c198796-08bb-4803-b456-fa0c29ca6021", + "fileName": "PrisonCourtRegister_20251219083322.pdf", + "mimeType": "application/pdf" + }, + "contentUrl": "http://{{request.host}}:{{request.port}}/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/binary" +} diff --git a/src/test/resources/mappings/material-binary-mapping.json b/stubs/material-client/mappings/material-binary-mapping.json similarity index 68% rename from src/test/resources/mappings/material-binary-mapping.json rename to stubs/material-client/mappings/material-binary-mapping.json index 9bb54e7c..f12bf4a6 100644 --- a/src/test/resources/mappings/material-binary-mapping.json +++ b/stubs/material-client/mappings/material-binary-mapping.json @@ -1,7 +1,7 @@ { "request": { "method": "GET", - "urlPathPattern": "/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/binary" + "url": "/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/binary" }, "response": { "status": 200, diff --git a/stubs/material-client/mappings/material-content-full-mapping.json b/stubs/material-client/mappings/material-content-full-mapping.json new file mode 100644 index 00000000..fcf1ab05 --- /dev/null +++ b/stubs/material-client/mappings/material-content-full-mapping.json @@ -0,0 +1,16 @@ +{ + "request": { + "method": "GET", + "url": "/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/content" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/vnd.material.query.material+json" + }, + "bodyFileName": "material-with-contenturl.json", + "transformers": [ + "response-template" + ] + } +} diff --git a/src/test/resources/mappings/material-metadata-not-found-mapping.json b/stubs/material-client/mappings/material-metadata-mapping.json similarity index 62% rename from src/test/resources/mappings/material-metadata-not-found-mapping.json rename to stubs/material-client/mappings/material-metadata-mapping.json index ecb33f4d..083403ea 100644 --- a/src/test/resources/mappings/material-metadata-not-found-mapping.json +++ b/stubs/material-client/mappings/material-metadata-mapping.json @@ -1,13 +1,13 @@ { "request": { "method": "GET", - "url": "/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6022/metadata" + "url": "/material-query-api/query/api/rest/material/material/6c198796-08bb-4803-b456-fa0c29ca6021/metadata" }, "response": { - "status": 404, + "status": 200, "headers": { "Content-Type": "application/json" }, - "body": "{\"error\": \"Material not found\"}" + "bodyFileName": "material-response.json" } } diff --git a/stubs/material-client/mappings/material-metadata-timeout-mapping.json b/stubs/material-client/mappings/material-metadata-timeout-mapping.json new file mode 100644 index 00000000..63ed8524 --- /dev/null +++ b/stubs/material-client/mappings/material-metadata-timeout-mapping.json @@ -0,0 +1,9 @@ +{ + "request": { + "method": "GET", + "url": "/material-query-api/query/api/rest/material/material/11111111-1111-1111-1111-111111111112/metadata" + }, + "response": { + "status": 204 + } +} From 2afd3f4e50a3ca00b27f7881748b2b440f029aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Tue, 10 Feb 2026 14:43:20 +0000 Subject: [PATCH 02/10] Resolved review comments on Naming conventions --- ...t.java => NotificationPCRE2EIntegrationTest.java} | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) rename src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/{NotificationPcrE2EIntegrationTest.java => NotificationPCRE2EIntegrationTest.java} (95%) diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPcrE2EIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java similarity index 95% rename from src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPcrE2EIntegrationTest.java rename to src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java index dcc1040e..ca14f316 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPcrE2EIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java @@ -35,10 +35,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -/** - * E2E: success = subscription → material API → callback → document (full chain). - * failure = metadata never ready → 504, no callback. - */ // TODO - comments can be removed after handover to QA @EnableWireMock({@ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 0)}) @@ -48,9 +44,9 @@ }) class NotificationPcrE2EIntegrationTest extends IntegrationTestBase { - private static final String NOTIFICATION_PCR_URI = "/notifications/pcr"; + private static final String NOTIFICATIONS_PCR_URI = "/notifications/pcr"; private static final String CLIENT_SUBSCRIPTIONS_URI = "/client-subscriptions"; - private static final String DOCUMENT_URI = "/client-subscriptions/{clientSubscriptionId}/documents/{documentId}"; + private static final String DOCUMENT_URI = CLIENT_SUBSCRIPTIONS_URI + "/{clientSubscriptionId}/documents/{documentId}"; private static final String SUBSCRIPTION_REQUEST_E2E = "stubs/requests/subscription/subscription-pcr-request.json"; private static final String PCR_EVENT_PAYLOAD_PATH = "stubs/requests/progression-pcr/pcr-request-prison-court-register.json"; private static final String PCR_EVENT_TIMEOUT_PATH = "stubs/requests/progression-pcr/pcr-request-material-timeout.json"; @@ -127,7 +123,7 @@ WireMock stub (wiremock/material-client/mappings/): } private void when_a_pcr_event_is_posted() throws Exception { - mockMvc.perform(post(NOTIFICATION_PCR_URI) + mockMvc.perform(post(NOTIFICATIONS_PCR_URI) .contentType(MediaType.APPLICATION_JSON) .header("Accept", MediaType.APPLICATION_JSON_VALUE) .content(loadPayload(PCR_EVENT_PAYLOAD_PATH))) @@ -136,7 +132,7 @@ private void when_a_pcr_event_is_posted() throws Exception { } private void when_a_pcr_event_is_posted_with_timeout() throws Exception { - mockMvc.perform(post(NOTIFICATION_PCR_URI) + mockMvc.perform(post(NOTIFICATIONS_PCR_URI) .contentType(MediaType.APPLICATION_JSON) .header("Accept", MediaType.APPLICATION_JSON_VALUE) .content(loadPayload(PCR_EVENT_TIMEOUT_PATH))) From 0f9d07897f10835837a6a5b61d4b0bd3fb081097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Tue, 10 Feb 2026 19:56:39 +0000 Subject: [PATCH 03/10] Added improvements for stubs, callback wiremock --- .../config/SSLTrustingRestTemplateConfig.java | 39 +++++ .../integration/IntegrationTestBase.java | 64 +++++++ .../NotificationPCRE2EIntegrationTest.java | 161 ++++++++++-------- .../subscription-pcr-request.json | 2 +- .../__files/callback-accepted.json | 3 + 5 files changed, 201 insertions(+), 68 deletions(-) create mode 100644 src/test/java/uk/gov/hmcts/cp/subscription/config/SSLTrustingRestTemplateConfig.java create mode 100644 src/test/resources/wiremock/callback-client/__files/callback-accepted.json diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/config/SSLTrustingRestTemplateConfig.java b/src/test/java/uk/gov/hmcts/cp/subscription/config/SSLTrustingRestTemplateConfig.java new file mode 100644 index 00000000..e7c463c2 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/cp/subscription/config/SSLTrustingRestTemplateConfig.java @@ -0,0 +1,39 @@ +package uk.gov.hmcts.cp.subscription.config; + +import jakarta.annotation.PostConstruct; +import org.springframework.context.annotation.Configuration; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +@Configuration +public class SSLTrustingRestTemplateConfig { + + private static final X509TrustManager TRUST_ALL = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + @PostConstruct + public void trustAllSsl() throws Exception { + var ssl = SSLContext.getInstance("TLS"); + ssl.init(null, new TrustManager[]{TRUST_ALL}, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(ssl.getSocketFactory()); + HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); + } +} diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java index 5fee2a0e..622e9460 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java @@ -8,6 +8,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.MockMvc; + import uk.gov.hmcts.cp.openapi.model.ClientSubscriptionRequest; import uk.gov.hmcts.cp.openapi.model.NotificationEndpoint; import uk.gov.hmcts.cp.subscription.integration.config.TestContainersInitialise; @@ -23,6 +24,13 @@ import java.util.List; import java.util.UUID; +import com.github.tomakehurst.wiremock.WireMockServer; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; import static uk.gov.hmcts.cp.openapi.model.EventType.CUSTODIAL_RESULT; import static uk.gov.hmcts.cp.openapi.model.EventType.PRISON_COURT_REGISTER_GENERATED; @@ -32,6 +40,15 @@ @Slf4j public abstract class IntegrationTestBase { + protected static final String MATERIAL_METADATA_RESPONSE_PATH = "wiremock/material-client/__files/material-response.json"; + protected static final String MATERIAL_CONTENT_RESPONSE_PATH = "wiremock/material-client/__files/material-with-contenturl.json"; + protected static final String MATERIAL_PDF_PATH = "wiremock/material-client/__files/material-content.pdf"; + protected static final String CALLBACK_RESPONSE_PATH = "wiremock/callback-client/__files/callback-accepted.json"; + protected static final String MATERIAL_URI = "/material-query-api/query/api/rest/material/material/"; + public static final String METADATA = "/metadata"; + public static final String APPLICATION_JSON = "application/json"; + public static final String CONTENT_TYPE = "Content-Type"; + @Resource protected MockMvc mockMvc; @@ -91,4 +108,51 @@ protected DocumentMappingEntity insertDocument(UUID materialId, EntityEventType protected String loadPayload(String path) throws IOException { return new ClassPathResource(path).getContentAsString(StandardCharsets.UTF_8); } + + protected void stubMaterialMetadata(UUID materialId) throws IOException { + String materialPath = MATERIAL_URI + materialId; + String metadataBody = new ClassPathResource(MATERIAL_METADATA_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); + stubFor(get(urlPathMatching(".*" + materialPath + METADATA)) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, APPLICATION_JSON) + .withBody(metadataBody))); + } + + protected void stubMaterialContent(UUID materialId) throws IOException { + String materialPath = MATERIAL_URI + materialId; + String contentBody = new ClassPathResource(MATERIAL_CONTENT_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); + stubFor(get(urlPathMatching(".*" + materialPath + "/content")) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, "application/vnd.material.query.material+json") + .withBody(contentBody) + .withTransformers("response-template"))); + } + + protected void stubMaterialBinary(UUID materialId) throws IOException { + String materialPath = MATERIAL_URI + materialId; + byte[] pdfBody = new ClassPathResource(MATERIAL_PDF_PATH).getContentAsByteArray(); + stubFor(get(urlPathMatching(".*" + materialPath + "/binary")) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, "application/pdf") + .withHeader("Content-Disposition", "inline; filename=\"material-content.pdf\"") + .withBody(pdfBody))); + } + + protected void stubMaterialMetadataNoContent(UUID materialId) { + String materialPath = MATERIAL_URI + materialId; + stubFor(get(urlPathMatching(".*" + materialPath + METADATA)) + .willReturn(aResponse().withStatus(204))); + } + + protected void stubCallbackEndpoint(WireMockServer server, String callbackUri) throws IOException { + String body = new ClassPathResource(CALLBACK_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); + server.stubFor(post(urlPathEqualTo(callbackUri)) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, APPLICATION_JSON) + .withBody(body))); + } } diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java index ca14f316..5099b167 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java @@ -1,32 +1,42 @@ package uk.gov.hmcts.cp.subscription.integration.e2e; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.wiremock.spring.ConfigureWireMock; import org.wiremock.spring.EnableWireMock; +import org.wiremock.spring.InjectWireMock; + +import com.github.tomakehurst.wiremock.WireMockServer; import uk.gov.hmcts.cp.material.openapi.api.MaterialApi; -import uk.gov.hmcts.cp.openapi.model.PcrEventPayload; +import uk.gov.hmcts.cp.subscription.config.SSLTrustingRestTemplateConfig; import uk.gov.hmcts.cp.subscription.integration.IntegrationTestBase; -import uk.gov.hmcts.cp.subscription.model.EntityEventType; -import uk.gov.hmcts.cp.subscription.services.CallbackDeliveryService; -import uk.gov.hmcts.cp.subscription.services.DocumentService; +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThat; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static java.util.Objects.nonNull; +import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; + +import org.springframework.test.web.servlet.ResultActions; + import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -35,39 +45,44 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -// TODO - comments can be removed after handover to QA - -@EnableWireMock({@ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 0)}) +@EnableWireMock({ + @ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 0), + @ConfigureWireMock(name = "callback-client", httpsBaseUrlProperties = "callback-client.url", httpsPort = 0) +}) @TestPropertySource(properties = { "material-client.retry.timeoutMilliSecs=500", "material-client.retry.intervalMilliSecs=100" }) +@Import(SSLTrustingRestTemplateConfig.class) class NotificationPcrE2EIntegrationTest extends IntegrationTestBase { + private UUID subscriptionId; + private UUID callbackDocumentId; + private static final UUID MATERIAL_ID = UUID.fromString("6c198796-08bb-4803-b456-fa0c29ca6021"); + private static final UUID MATERIAL_ID_TIMEOUT = UUID.fromString("11111111-1111-1111-1111-111111111112"); private static final String NOTIFICATIONS_PCR_URI = "/notifications/pcr"; private static final String CLIENT_SUBSCRIPTIONS_URI = "/client-subscriptions"; + private static final String CALLBACK_URI = "/callback/notify"; private static final String DOCUMENT_URI = CLIENT_SUBSCRIPTIONS_URI + "/{clientSubscriptionId}/documents/{documentId}"; private static final String SUBSCRIPTION_REQUEST_E2E = "stubs/requests/subscription/subscription-pcr-request.json"; private static final String PCR_EVENT_PAYLOAD_PATH = "stubs/requests/progression-pcr/pcr-request-prison-court-register.json"; private static final String PCR_EVENT_TIMEOUT_PATH = "stubs/requests/progression-pcr/pcr-request-material-timeout.json"; - private static final UUID MATERIAL_ID = UUID.fromString("6c198796-08bb-4803-b456-fa0c29ca6021"); - private static final UUID MATERIAL_ID_TIMEOUT = UUID.fromString("11111111-1111-1111-1111-111111111112"); - @MockitoBean - private CallbackDeliveryService callbackDeliveryService; + @InjectWireMock("callback-client") + private WireMockServer callbackWireMock; - @MockitoSpyBean - private MaterialApi materialApi; + @Value("${callback-client.url}") + private String callbackHttpsBaseUrl; @MockitoSpyBean - private DocumentService documentService; - - private UUID subscriptionId; - private UUID callbackDocumentId; + private MaterialApi materialApi; @BeforeEach void setUp() { - reset(callbackDeliveryService); + WireMock.reset(); + if (nonNull(callbackWireMock)) { + callbackWireMock.resetAll(); + } clearAllTables(); } @@ -80,7 +95,6 @@ void test_document_retrieval_success() throws Exception { when_a_pcr_event_is_posted(); when_material_service_responds(); - then_the_material_api_is_called(); then_the_subscriber_receives_a_callback(); then_the_subscriber_can_retrieve_the_document(); } @@ -96,59 +110,47 @@ void test_document_retrieval_failure() throws Exception { then_the_material_api_was_polled(); then_the_subscriber_does_not_receive_a_callback(); - then_the_subscriber_cannot_retrieve_the_document(); } private void given_i_am_a_subscriber_with_a_subscription() throws Exception { createSubscription(); } - private void given_i_have_a_callback_endpoint() { - // Callback endpoint represented by @MockitoBean CallbackDeliveryService + private void given_i_have_a_callback_endpoint() throws IOException { + stubCallbackEndpoint(callbackWireMock, CALLBACK_URI); } - private void given_material_service_returns_document_success() { - /*WireMock (material-client) stubs from wiremock/material-client/mappings/: - material-metadata-mapping.json GET .../material/6c198796-08bb-4803-b456-fa0c29ca6021/metadata → 200, __files/material-response.json - material-content-full-mapping.json GET .../material/6c198796-08bb-4803-b456-fa0c29ca6021/content → 200, __files/material-with-contenturl.json - material-binary-mapping.json GET .../material/6c198796-08bb-4803-b456-fa0c29ca6021/binary → 200, __files/material-content.pdf*/ + private void given_material_service_returns_document_success() throws IOException { + stubMaterialMetadata(MATERIAL_ID); + stubMaterialContent(MATERIAL_ID); + stubMaterialBinary(MATERIAL_ID); } private void given_material_service_returns_document_not_found() { - /*Request payload: stubs/requests/progression-pcr/pcr-request-material-timeout.json - eventId: 11111111-1111-1111-1111-111111111111, materialId: 11111111-1111-1111-1111-111111111112, eventType: PRISON_COURT_REGISTER_GENERATED - WireMock stub (wiremock/material-client/mappings/): - material-metadata-timeout-mapping.json GET .../material/11111111-1111-1111-1111-111111111112/metadata → 204 (no body) - App polls until Awaitility times out → 504 "Material metadata not ready", no save/callback.*/ + stubMaterialMetadataNoContent(MATERIAL_ID_TIMEOUT); } private void when_a_pcr_event_is_posted() throws Exception { - mockMvc.perform(post(NOTIFICATIONS_PCR_URI) - .contentType(MediaType.APPLICATION_JSON) - .header("Accept", MediaType.APPLICATION_JSON_VALUE) - .content(loadPayload(PCR_EVENT_PAYLOAD_PATH))) - .andDo(print()) - .andExpect(status().isAccepted()); + postPcrEvent(PCR_EVENT_PAYLOAD_PATH).andExpect(status().isAccepted()); } private void when_a_pcr_event_is_posted_with_timeout() throws Exception { - mockMvc.perform(post(NOTIFICATIONS_PCR_URI) - .contentType(MediaType.APPLICATION_JSON) - .header("Accept", MediaType.APPLICATION_JSON_VALUE) - .content(loadPayload(PCR_EVENT_TIMEOUT_PATH))) - .andDo(print()) + postPcrEvent(PCR_EVENT_TIMEOUT_PATH) .andExpect(status().isGatewayTimeout()) .andExpect(content().string("Material metadata not ready")); } - private void when_material_service_responds() { - /* This step does nothing; the material response already happens during when_a_pcr_event_is_posted (app calls MaterialApi, WireMock serves stubs) - Success: GET metadata → 200 → save document mapping → callback. - Failure: GET metadata → 204 until timeout → 504.*/ + private ResultActions postPcrEvent(String payloadPath) throws Exception { + return mockMvc.perform(post(NOTIFICATIONS_PCR_URI) + .contentType(MediaType.APPLICATION_JSON) + .header("Accept", MediaType.APPLICATION_JSON_VALUE) + .content(loadPayload(payloadPath))) + .andDo(print()); } - private void then_the_material_api_is_called() { - verify(materialApi, times(1)).getMaterialMetadataByMaterialId(eq(MATERIAL_ID)); + private void when_material_service_responds() { + await().atMost(Duration.ofSeconds(5)) + .untilAsserted(() -> verify(materialApi, atLeastOnce()).getMaterialMetadataByMaterialId(any(UUID.class))); } private void then_the_material_api_was_polled() { @@ -156,19 +158,42 @@ private void then_the_material_api_was_polled() { } private void then_the_subscriber_receives_a_callback() { - ArgumentCaptor documentIdCaptor = ArgumentCaptor.forClass(UUID.class); - verify(callbackDeliveryService, times(1)) - .processPcrEvent(any(PcrEventPayload.class), documentIdCaptor.capture()); - callbackDocumentId = documentIdCaptor.getValue(); - assertThat(callbackDocumentId).isNotNull(); + callbackWireMock.verify(1, postRequestedFor(urlPathEqualTo(CALLBACK_URI))); + callbackDocumentId = getDocumentIdFromCallbackServeEvents(); + } + + private UUID getDocumentIdFromCallbackServeEvents() { + return callbackWireMock.getAllServeEvents().stream() + .map(ServeEvent::getRequest) + .filter(r -> nonNull(r.getUrl()) && r.getUrl().contains(CALLBACK_URI)) + .map(r -> parseDocumentIdFromBody(r.getBodyAsString())) + .findFirst() + .orElseThrow(() -> + new AssertionError("Callback request body did not contain documentId")); + } + + private UUID parseDocumentIdFromBody(String body) { + if (body == null || body.isBlank()) return null; + try { + return Optional.ofNullable(new ObjectMapper().readTree(body).get("documentId")) + .map(JsonNode::asText) + .map(UUID::fromString) + .orElse(null); + } catch (Exception e) { + return null; + } } private void then_the_subscriber_does_not_receive_a_callback() { - verify(callbackDeliveryService, times(0)).processPcrEvent(any(PcrEventPayload.class), any()); + callbackWireMock.verify(0, postRequestedFor(urlPathEqualTo(CALLBACK_URI))); } private void then_the_subscriber_can_retrieve_the_document() throws Exception { - mockMvc.perform(get(DOCUMENT_URI, subscriptionId, callbackDocumentId)) + getDocumentAndExpectPdf(subscriptionId, callbackDocumentId); + } + + private void getDocumentAndExpectPdf(UUID subId, UUID docId) throws Exception { + mockMvc.perform(get(DOCUMENT_URI, subId, docId)) .andDo(print()) .andExpect(status().isOk()) .andExpect(header().string("Content-Type", org.hamcrest.Matchers.containsString("application/pdf"))) @@ -176,17 +201,19 @@ private void then_the_subscriber_can_retrieve_the_document() throws Exception { .andExpect(content().contentType(MediaType.APPLICATION_PDF)); } - private void then_the_subscriber_cannot_retrieve_the_document() { - verify(documentService, times(0)).saveDocumentMapping(any(UUID.class), any(EntityEventType.class)); + private void createSubscription() throws Exception { + String callbackUrl = callbackHttpsBaseUrl.endsWith("/") ? callbackHttpsBaseUrl + CALLBACK_URI.substring(1) : callbackHttpsBaseUrl + CALLBACK_URI; + String body = loadPayload(SUBSCRIPTION_REQUEST_E2E).replace("{{callback.url}}", callbackUrl); + String json = postSubscriptionAndReturnJson(body); + subscriptionId = UUID.fromString(new ObjectMapper().readTree(json).get("clientSubscriptionId").asText()); } - private void createSubscription() throws Exception { - String json = mockMvc.perform(post(CLIENT_SUBSCRIPTIONS_URI) + private String postSubscriptionAndReturnJson(String body) throws Exception { + return mockMvc.perform(post(CLIENT_SUBSCRIPTIONS_URI) .contentType(MediaType.APPLICATION_JSON) - .content(loadPayload(SUBSCRIPTION_REQUEST_E2E))) + .content(body)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.clientSubscriptionId").exists()) .andReturn().getResponse().getContentAsString(); - subscriptionId = UUID.fromString(new ObjectMapper().readTree(json).get("clientSubscriptionId").asText()); } } diff --git a/src/test/resources/stubs/requests/subscription/subscription-pcr-request.json b/src/test/resources/stubs/requests/subscription/subscription-pcr-request.json index 7bcd2d1c..f3e9fe87 100644 --- a/src/test/resources/stubs/requests/subscription/subscription-pcr-request.json +++ b/src/test/resources/stubs/requests/subscription/subscription-pcr-request.json @@ -1,6 +1,6 @@ { "notificationEndpoint": { - "callbackUrl": "https://callback.example.com" + "callbackUrl": "{{callback.url}}" }, "eventTypes": [ "PRISON_COURT_REGISTER_GENERATED" diff --git a/src/test/resources/wiremock/callback-client/__files/callback-accepted.json b/src/test/resources/wiremock/callback-client/__files/callback-accepted.json new file mode 100644 index 00000000..4228b900 --- /dev/null +++ b/src/test/resources/wiremock/callback-client/__files/callback-accepted.json @@ -0,0 +1,3 @@ +{ + "status": "accepted" +} \ No newline at end of file From aeefe253fa1ee7f3108d75ee5a748057144bfeb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Tue, 10 Feb 2026 20:23:55 +0000 Subject: [PATCH 04/10] Added test profile --- .../NotificationControllerIntegrationTest.java | 7 ++----- .../e2e/NotificationPCRE2EIntegrationTest.java | 11 ++++------- src/test/resources/application-test.properties | 2 ++ 3 files changed, 8 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/application-test.properties diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java index 6e3c8a77..043d7101 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.wiremock.spring.ConfigureWireMock; import org.wiremock.spring.EnableWireMock; @@ -32,10 +32,7 @@ @EnableWireMock({@ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 0)}) -@TestPropertySource(properties = { - "material-client.retry.timeoutMilliSecs=500", - "material-client.retry.intervalMilliSecs=100" -}) +@ActiveProfiles("test") class NotificationControllerIntegrationTest extends IntegrationTestBase { private static final String NOTIFICATION_PCR_URI = "/notifications/pcr"; diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java index 5099b167..10f32465 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java @@ -9,7 +9,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.wiremock.spring.ConfigureWireMock; import org.wiremock.spring.EnableWireMock; @@ -49,10 +49,7 @@ @ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 0), @ConfigureWireMock(name = "callback-client", httpsBaseUrlProperties = "callback-client.url", httpsPort = 0) }) -@TestPropertySource(properties = { - "material-client.retry.timeoutMilliSecs=500", - "material-client.retry.intervalMilliSecs=100" -}) +@ActiveProfiles("test") @Import(SSLTrustingRestTemplateConfig.class) class NotificationPcrE2EIntegrationTest extends IntegrationTestBase { @@ -72,7 +69,7 @@ class NotificationPcrE2EIntegrationTest extends IntegrationTestBase { private WireMockServer callbackWireMock; @Value("${callback-client.url}") - private String callbackHttpsBaseUrl; + private String callbackBaseUrl; @MockitoSpyBean private MaterialApi materialApi; @@ -202,7 +199,7 @@ private void getDocumentAndExpectPdf(UUID subId, UUID docId) throws Exception { } private void createSubscription() throws Exception { - String callbackUrl = callbackHttpsBaseUrl.endsWith("/") ? callbackHttpsBaseUrl + CALLBACK_URI.substring(1) : callbackHttpsBaseUrl + CALLBACK_URI; + String callbackUrl = callbackBaseUrl.endsWith("/") ? callbackBaseUrl + CALLBACK_URI.substring(1) : callbackBaseUrl + CALLBACK_URI; String body = loadPayload(SUBSCRIPTION_REQUEST_E2E).replace("{{callback.url}}", callbackUrl); String json = postSubscriptionAndReturnJson(body); subscriptionId = UUID.fromString(new ObjectMapper().readTree(json).get("clientSubscriptionId").asText()); diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 00000000..9a6d7c32 --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,2 @@ +material-client.retry.timeoutMilliSecs=500 +material-client.retry.intervalMilliSecs=100 \ No newline at end of file From e627a25a68835277e48efecef2c35f4e70892596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Thu, 12 Feb 2026 10:00:49 +0000 Subject: [PATCH 05/10] Resolved review comments for folder naming and constants reusability --- apiTest/docker-compose.yml | 2 +- .../integration/IntegrationTestBase.java | 21 +++++++++++------- .../NotificationPCRE2EIntegrationTest.java | 4 ---- .../{__files => files}/callback-accepted.json | 0 .../{__files => files}/material-content.pdf | Bin .../{__files => files}/material-response.json | 0 .../material-with-contenturl.json | 0 7 files changed, 14 insertions(+), 13 deletions(-) rename src/test/resources/wiremock/callback-client/{__files => files}/callback-accepted.json (100%) rename src/test/resources/wiremock/material-client/{__files => files}/material-content.pdf (100%) rename src/test/resources/wiremock/material-client/{__files => files}/material-response.json (100%) rename src/test/resources/wiremock/material-client/{__files => files}/material-with-contenturl.json (100%) diff --git a/apiTest/docker-compose.yml b/apiTest/docker-compose.yml index e3c5eaeb..dffd82a7 100644 --- a/apiTest/docker-compose.yml +++ b/apiTest/docker-compose.yml @@ -32,7 +32,7 @@ services: - "9999:9999" volumes: - ./src/test/resources/mappings:/home/wiremock/mappings - - ./src/test/resources/__files:/home/wiremock/__files + - ./src/test/resources/files:/home/wiremock/files command: - "--global-response-templating" - "--verbose" diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java index 622e9460..92883eeb 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java @@ -40,14 +40,19 @@ @Slf4j public abstract class IntegrationTestBase { - protected static final String MATERIAL_METADATA_RESPONSE_PATH = "wiremock/material-client/__files/material-response.json"; - protected static final String MATERIAL_CONTENT_RESPONSE_PATH = "wiremock/material-client/__files/material-with-contenturl.json"; - protected static final String MATERIAL_PDF_PATH = "wiremock/material-client/__files/material-content.pdf"; - protected static final String CALLBACK_RESPONSE_PATH = "wiremock/callback-client/__files/callback-accepted.json"; - protected static final String MATERIAL_URI = "/material-query-api/query/api/rest/material/material/"; - public static final String METADATA = "/metadata"; - public static final String APPLICATION_JSON = "application/json"; - public static final String CONTENT_TYPE = "Content-Type"; + private static final String MATERIAL_METADATA_RESPONSE_PATH = "wiremock/material-client/files/material-response.json"; + private static final String MATERIAL_CONTENT_RESPONSE_PATH = "wiremock/material-client/files/material-with-contenturl.json"; + private static final String MATERIAL_PDF_PATH = "wiremock/material-client/files/material-content.pdf"; + private static final String CALLBACK_RESPONSE_PATH = "wiremock/callback-client/files/callback-accepted.json"; + private static final String MATERIAL_URI = "/material-query-api/query/api/rest/material/material/"; + private static final String METADATA = "/metadata"; + private static final String APPLICATION_JSON = "application/json"; + private static final String CONTENT_TYPE = "Content-Type"; + + protected static final UUID MATERIAL_ID_TIMEOUT = UUID.fromString("11111111-1111-1111-1111-111111111112"); + protected static final String NOTIFICATIONS_PCR_URI = "/notifications/pcr"; + protected static final String CLIENT_SUBSCRIPTIONS_URI = "/client-subscriptions"; + protected static final String CALLBACK_URI = "/callback/notify"; @Resource protected MockMvc mockMvc; diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java index 10f32465..84468850 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java @@ -56,10 +56,6 @@ class NotificationPcrE2EIntegrationTest extends IntegrationTestBase { private UUID subscriptionId; private UUID callbackDocumentId; private static final UUID MATERIAL_ID = UUID.fromString("6c198796-08bb-4803-b456-fa0c29ca6021"); - private static final UUID MATERIAL_ID_TIMEOUT = UUID.fromString("11111111-1111-1111-1111-111111111112"); - private static final String NOTIFICATIONS_PCR_URI = "/notifications/pcr"; - private static final String CLIENT_SUBSCRIPTIONS_URI = "/client-subscriptions"; - private static final String CALLBACK_URI = "/callback/notify"; private static final String DOCUMENT_URI = CLIENT_SUBSCRIPTIONS_URI + "/{clientSubscriptionId}/documents/{documentId}"; private static final String SUBSCRIPTION_REQUEST_E2E = "stubs/requests/subscription/subscription-pcr-request.json"; private static final String PCR_EVENT_PAYLOAD_PATH = "stubs/requests/progression-pcr/pcr-request-prison-court-register.json"; diff --git a/src/test/resources/wiremock/callback-client/__files/callback-accepted.json b/src/test/resources/wiremock/callback-client/files/callback-accepted.json similarity index 100% rename from src/test/resources/wiremock/callback-client/__files/callback-accepted.json rename to src/test/resources/wiremock/callback-client/files/callback-accepted.json diff --git a/src/test/resources/wiremock/material-client/__files/material-content.pdf b/src/test/resources/wiremock/material-client/files/material-content.pdf similarity index 100% rename from src/test/resources/wiremock/material-client/__files/material-content.pdf rename to src/test/resources/wiremock/material-client/files/material-content.pdf diff --git a/src/test/resources/wiremock/material-client/__files/material-response.json b/src/test/resources/wiremock/material-client/files/material-response.json similarity index 100% rename from src/test/resources/wiremock/material-client/__files/material-response.json rename to src/test/resources/wiremock/material-client/files/material-response.json diff --git a/src/test/resources/wiremock/material-client/__files/material-with-contenturl.json b/src/test/resources/wiremock/material-client/files/material-with-contenturl.json similarity index 100% rename from src/test/resources/wiremock/material-client/__files/material-with-contenturl.json rename to src/test/resources/wiremock/material-client/files/material-with-contenturl.json From 17e94d463a44ba469e8eca5875c1a45297e63376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Thu, 12 Feb 2026 10:11:45 +0000 Subject: [PATCH 06/10] Added CodeQL rule java/insecure-trustmanager is excluded via .github/codeql/codeql-config.yml --- .github/codeql/codeql-config.yml | 4 ++++ .github/workflows/codeql.yml | 1 + 2 files changed, 5 insertions(+) create mode 100644 .github/codeql/codeql-config.yml diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 00000000..5b8545d2 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,4 @@ +name: "CodeQL config" +query-filters: + - exclude: + id: java/insecure-trustmanager diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index df054d6e..abf2415b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,6 +35,7 @@ jobs: with: languages: ${{ matrix.language }} queries: security-extended + config-file: ./.github/codeql/codeql-config.yml - uses: actions/setup-java@v5 with: From a9242a1e23d70220b80a5232532d6eafc84951a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Thu, 12 Feb 2026 15:08:47 +0000 Subject: [PATCH 07/10] Added improvements for retry(callback),stubs refactor to specfic service, codeqlrule bypass to test only --- .github/codeql/codeql-config.yml | 5 +- .../cp/subscription/config/AppProperties.java | 12 +++- .../controllers/GlobalExceptionHandler.java | 12 +++- .../services/CallbackService.java | 29 +++++++- .../CallbackUrlDeliveryException.java | 16 ----- src/main/resources/application.yaml | 5 ++ .../GlobalExceptionHandlerTest.java | 12 +++- .../integration/IntegrationTestBase.java | 63 ----------------- ...NotificationControllerIntegrationTest.java | 8 +-- .../NotificationControllerValidationTest.java | 6 +- .../NotificationPCRE2EIntegrationTest.java | 67 +++++++++++-------- .../integration/stubs/CallbackStub.java | 67 +++++++++++++++++++ .../integration/stubs/MaterialStub.java | 64 ++++++++++++++++++ .../services/CallbackServiceTest.java | 31 ++++++++- .../NotificationControllerTest.java | 34 ++-------- .../managers/NotificationManagerTest.java | 3 - .../resources/application-test.properties | 4 +- .../pcr-request-custodial-result.json | 0 .../pcr-request-material-not-found.json | 0 .../pcr-request-material-timeout.json | 0 .../pcr-request-missing-event.json | 0 .../pcr-request-missing-material.json | 0 .../pcr-request-prison-court-register.json | 0 .../pcr-request-valid.json | 0 24 files changed, 280 insertions(+), 158 deletions(-) delete mode 100644 src/main/java/uk/gov/hmcts/cp/subscription/services/exceptions/CallbackUrlDeliveryException.java create mode 100644 src/test/java/uk/gov/hmcts/cp/subscription/integration/stubs/CallbackStub.java create mode 100644 src/test/java/uk/gov/hmcts/cp/subscription/integration/stubs/MaterialStub.java rename src/test/resources/stubs/requests/{progression-pcr => progression}/pcr-request-custodial-result.json (100%) rename src/test/resources/stubs/requests/{progression-pcr => progression}/pcr-request-material-not-found.json (100%) rename src/test/resources/stubs/requests/{progression-pcr => progression}/pcr-request-material-timeout.json (100%) rename src/test/resources/stubs/requests/{progression-pcr => progression}/pcr-request-missing-event.json (100%) rename src/test/resources/stubs/requests/{progression-pcr => progression}/pcr-request-missing-material.json (100%) rename src/test/resources/stubs/requests/{progression-pcr => progression}/pcr-request-prison-court-register.json (100%) rename src/test/resources/stubs/requests/{progression-pcr => progression}/pcr-request-valid.json (100%) diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml index 5b8545d2..4bbacea2 100644 --- a/.github/codeql/codeql-config.yml +++ b/.github/codeql/codeql-config.yml @@ -1,4 +1,3 @@ name: "CodeQL config" -query-filters: - - exclude: - id: java/insecure-trustmanager +paths-ignore: + - "**/src/test/**" diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/config/AppProperties.java b/src/main/java/uk/gov/hmcts/cp/subscription/config/AppProperties.java index 86cb5736..0ed14bd9 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/config/AppProperties.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/config/AppProperties.java @@ -10,12 +10,18 @@ @Service public class AppProperties { - private int materialRetryIntervalMilliSecs; - private int materialRetryTimeoutMilliSecs; + private final int materialRetryIntervalMilliSecs; + private final int materialRetryTimeoutMilliSecs; + private final int callbackRetryIntervalMilliSecs; + private final int callbackRetryTimeoutMilliSecs; public AppProperties(@Value("${material-client.retry.intervalMilliSecs}") final int materialRetryIntervalMilliSecs, - @Value("${material-client.retry.timeoutMilliSecs}") final int materialRetryTimeoutMilliSecs) { + @Value("${material-client.retry.timeoutMilliSecs}") final int materialRetryTimeoutMilliSecs, + @Value("${callback-client.retry.intervalMilliSecs}") final int callbackRetryIntervalMilliSecs, + @Value("${callback-client.retry.timeoutMilliSecs}") final int callbackRetryTimeoutMilliSecs) { this.materialRetryIntervalMilliSecs = materialRetryIntervalMilliSecs; this.materialRetryTimeoutMilliSecs = materialRetryTimeoutMilliSecs; + this.callbackRetryIntervalMilliSecs = callbackRetryIntervalMilliSecs; + this.callbackRetryTimeoutMilliSecs = callbackRetryTimeoutMilliSecs; } } diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandler.java b/src/main/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandler.java index 6de5c704..92c981fe 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandler.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandler.java @@ -20,6 +20,9 @@ @Slf4j public class GlobalExceptionHandler { + private static final String CALLBACK_NOT_READY = "Callback is not ready"; + private static final String MATERIAL_NOT_READY = "Material metadata not ready"; + @ExceptionHandler({EntityNotFoundException.class, NoHandlerFoundException.class}) public ResponseEntity handleNotFoundException(final Exception exception) { log.error("NotFoundException {}", exception.getMessage()); @@ -83,9 +86,14 @@ public ResponseEntity handleUnsupportedOperation(final UnsupportedOperat @ExceptionHandler(ConditionTimeoutException.class) public ResponseEntity handleConditionTimeout(final ConditionTimeoutException ex) { - log.error("Material metadata timed out: {}", ex.getMessage()); + boolean isCallbackTimeout = CALLBACK_NOT_READY.equals(ex.getMessage()); + if (isCallbackTimeout) { + log.error("Callback delivery timed out: {}", ex.getMessage()); + } else { + log.error("Material metadata timed out: {}", ex.getMessage()); + } return ResponseEntity .status(HttpStatus.GATEWAY_TIMEOUT) - .body("Material metadata not ready"); + .body(isCallbackTimeout ? CALLBACK_NOT_READY : MATERIAL_NOT_READY); } } \ No newline at end of file diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackService.java b/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackService.java index 5201c17e..4fd2da92 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackService.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackService.java @@ -2,18 +2,45 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.awaitility.core.ConditionTimeoutException; import org.springframework.stereotype.Service; + import uk.gov.hmcts.cp.openapi.model.EventNotificationPayload; import uk.gov.hmcts.cp.subscription.clients.CallbackClient; +import uk.gov.hmcts.cp.subscription.config.AppProperties; + +import java.time.Duration; + +import static org.awaitility.Awaitility.await; @Service @Slf4j @RequiredArgsConstructor public class CallbackService { + private final AppProperties appProperties; private final CallbackClient callbackClient; public void sendToSubscriber(final String url, final EventNotificationPayload eventNotificationPayload) { - callbackClient.sendNotification(url, eventNotificationPayload); + try { + waitForCallbackDelivery(url, eventNotificationPayload); + } catch (ConditionTimeoutException e) { + throw new ConditionTimeoutException("Callback is not ready", e); + } + } + + private void waitForCallbackDelivery(final String url, final EventNotificationPayload eventNotificationPayload) { + await() + .pollInterval(Duration.ofMillis(appProperties.getCallbackRetryIntervalMilliSecs())) + .atMost(Duration.ofMillis(appProperties.getCallbackRetryTimeoutMilliSecs())) + .until(() -> { + try { + callbackClient.sendNotification(url, eventNotificationPayload); + return true; + } catch (Exception e) { + log.warn("Callback delivery failed for {}, retrying: {}", url, e.getMessage()); + return false; + } + }); } } diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/services/exceptions/CallbackUrlDeliveryException.java b/src/main/java/uk/gov/hmcts/cp/subscription/services/exceptions/CallbackUrlDeliveryException.java deleted file mode 100644 index 4ff77a7b..00000000 --- a/src/main/java/uk/gov/hmcts/cp/subscription/services/exceptions/CallbackUrlDeliveryException.java +++ /dev/null @@ -1,16 +0,0 @@ -package uk.gov.hmcts.cp.subscription.services.exceptions; - -/** - * Thrown when callbackUrl delivery fails (network error or non-2xx response). Triggers retry via @Retryable. - */ -public class CallbackUrlDeliveryException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public CallbackUrlDeliveryException(final String message) { - super(message); - } - - public CallbackUrlDeliveryException(final String message, final Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index bc56655e..fd95a9d8 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -21,6 +21,11 @@ material-client: intervalMilliSecs: 5000 timeoutMilliSecs: 30000 +callback-client: + retry: + intervalMilliSecs: 1000 + timeoutMilliSecs: 10000 + document-service: url: ${DOCUMENT_SERVICE_URL:http://localhost:8082} diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandlerTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandlerTest.java index 08a8e09d..59792ab1 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandlerTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandlerTest.java @@ -79,7 +79,7 @@ void unsupported_operation_should_handle_ok() { } @Test - void condition_timeout_should_handle_ok() { + void condition_timeout_material_should_handle_ok() { ConditionTimeoutException e = new ConditionTimeoutException("timed out"); ResponseEntity response = @@ -88,6 +88,16 @@ void condition_timeout_should_handle_ok() { assertErrorFields(response, HttpStatus.GATEWAY_TIMEOUT, "Material metadata not ready"); } + @Test + void condition_timeout_callback_should_handle_ok() { + ConditionTimeoutException e = new ConditionTimeoutException("Callback is not ready"); + + ResponseEntity response = + globalExceptionHandler.handleConditionTimeout(e); + + assertErrorFields(response, HttpStatus.GATEWAY_TIMEOUT, "Callback is not ready"); + } + private void assertErrorFields(ResponseEntity errorResponse, HttpStatusCode httpStatusCode, String message) { assertThat(errorResponse.getStatusCode()).isEqualTo(httpStatusCode); assertThat(errorResponse.getBody()).isEqualTo(message); diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java index 92883eeb..bb1d2ccb 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/IntegrationTestBase.java @@ -24,13 +24,6 @@ import java.util.List; import java.util.UUID; -import com.github.tomakehurst.wiremock.WireMockServer; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; import static uk.gov.hmcts.cp.openapi.model.EventType.CUSTODIAL_RESULT; import static uk.gov.hmcts.cp.openapi.model.EventType.PRISON_COURT_REGISTER_GENERATED; @@ -40,15 +33,6 @@ @Slf4j public abstract class IntegrationTestBase { - private static final String MATERIAL_METADATA_RESPONSE_PATH = "wiremock/material-client/files/material-response.json"; - private static final String MATERIAL_CONTENT_RESPONSE_PATH = "wiremock/material-client/files/material-with-contenturl.json"; - private static final String MATERIAL_PDF_PATH = "wiremock/material-client/files/material-content.pdf"; - private static final String CALLBACK_RESPONSE_PATH = "wiremock/callback-client/files/callback-accepted.json"; - private static final String MATERIAL_URI = "/material-query-api/query/api/rest/material/material/"; - private static final String METADATA = "/metadata"; - private static final String APPLICATION_JSON = "application/json"; - private static final String CONTENT_TYPE = "Content-Type"; - protected static final UUID MATERIAL_ID_TIMEOUT = UUID.fromString("11111111-1111-1111-1111-111111111112"); protected static final String NOTIFICATIONS_PCR_URI = "/notifications/pcr"; protected static final String CLIENT_SUBSCRIPTIONS_URI = "/client-subscriptions"; @@ -113,51 +97,4 @@ protected DocumentMappingEntity insertDocument(UUID materialId, EntityEventType protected String loadPayload(String path) throws IOException { return new ClassPathResource(path).getContentAsString(StandardCharsets.UTF_8); } - - protected void stubMaterialMetadata(UUID materialId) throws IOException { - String materialPath = MATERIAL_URI + materialId; - String metadataBody = new ClassPathResource(MATERIAL_METADATA_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); - stubFor(get(urlPathMatching(".*" + materialPath + METADATA)) - .willReturn(aResponse() - .withStatus(200) - .withHeader(CONTENT_TYPE, APPLICATION_JSON) - .withBody(metadataBody))); - } - - protected void stubMaterialContent(UUID materialId) throws IOException { - String materialPath = MATERIAL_URI + materialId; - String contentBody = new ClassPathResource(MATERIAL_CONTENT_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); - stubFor(get(urlPathMatching(".*" + materialPath + "/content")) - .willReturn(aResponse() - .withStatus(200) - .withHeader(CONTENT_TYPE, "application/vnd.material.query.material+json") - .withBody(contentBody) - .withTransformers("response-template"))); - } - - protected void stubMaterialBinary(UUID materialId) throws IOException { - String materialPath = MATERIAL_URI + materialId; - byte[] pdfBody = new ClassPathResource(MATERIAL_PDF_PATH).getContentAsByteArray(); - stubFor(get(urlPathMatching(".*" + materialPath + "/binary")) - .willReturn(aResponse() - .withStatus(200) - .withHeader(CONTENT_TYPE, "application/pdf") - .withHeader("Content-Disposition", "inline; filename=\"material-content.pdf\"") - .withBody(pdfBody))); - } - - protected void stubMaterialMetadataNoContent(UUID materialId) { - String materialPath = MATERIAL_URI + materialId; - stubFor(get(urlPathMatching(".*" + materialPath + METADATA)) - .willReturn(aResponse().withStatus(204))); - } - - protected void stubCallbackEndpoint(WireMockServer server, String callbackUri) throws IOException { - String body = new ClassPathResource(CALLBACK_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); - server.stubFor(post(urlPathEqualTo(callbackUri)) - .willReturn(aResponse() - .withStatus(200) - .withHeader(CONTENT_TYPE, APPLICATION_JSON) - .withBody(body))); - } } diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java index 043d7101..3d8f2da3 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerIntegrationTest.java @@ -51,7 +51,7 @@ void setUp() { @Test void prison_court_register_generated_should_return_success() throws Exception { - String pcrPayload = loadPayload("stubs/requests/progression-pcr/pcr-request-prison-court-register.json"); + String pcrPayload = loadPayload("stubs/requests/progression/pcr-request-prison-court-register.json"); mockMvc.perform(post(NOTIFICATION_PCR_URI) .contentType(MediaType.APPLICATION_JSON) @@ -65,7 +65,7 @@ void prison_court_register_generated_should_return_success() throws Exception { @Test void custodial_result_should_return_unsupported() throws Exception { - String pcrPayload = loadPayload("stubs/requests/progression-pcr/pcr-request-custodial-result.json"); + String pcrPayload = loadPayload("stubs/requests/progression/pcr-request-custodial-result.json"); doThrow(new UnsupportedOperationException("CUSTODIAL_RESULT not implemented")) .when(callbackDeliveryService).processPcrEvent(any(PcrEventPayload.class), any(UUID.class)); @@ -83,7 +83,7 @@ void custodial_result_should_return_unsupported() throws Exception { @Test void material_metadata_not_found_should_return_404() throws Exception { - String pcrPayload = loadPayload("stubs/requests/progression-pcr/pcr-request-material-not-found.json"); + String pcrPayload = loadPayload("stubs/requests/progression/pcr-request-material-not-found.json"); mockMvc.perform(post(NOTIFICATION_PCR_URI) .contentType(MediaType.APPLICATION_JSON) @@ -95,7 +95,7 @@ void material_metadata_not_found_should_return_404() throws Exception { @Test void material_metadata_timeout_should_return_504_via_global_exception_handler() throws Exception { - String pcrPayload = loadPayload("stubs/requests/progression-pcr/pcr-request-material-timeout.json"); + String pcrPayload = loadPayload("stubs/requests/progression/pcr-request-material-timeout.json"); mockMvc.perform(post(NOTIFICATION_PCR_URI) diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerValidationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerValidationTest.java index dd89b324..cbcb09a2 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerValidationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/NotificationControllerValidationTest.java @@ -28,9 +28,9 @@ class NotificationControllerValidationTest extends IntegrationTestBase { private static final String NOTIFICATION_PCR_URI = "/notifications/pcr"; - private static final String PCR_REQUEST_VALID = "stubs/requests/progression-pcr/pcr-request-valid.json"; - private static final String PCR_REQUEST_MISSING_MATERIAL = "stubs/requests/progression-pcr/pcr-request-missing-material.json"; - private static final String PCR_REQUEST_MISSING_EVENT = "stubs/requests/progression-pcr/pcr-request-missing-event.json"; + private static final String PCR_REQUEST_VALID = "stubs/requests/progression/pcr-request-valid.json"; + private static final String PCR_REQUEST_MISSING_MATERIAL = "stubs/requests/progression/pcr-request-missing-material.json"; + private static final String PCR_REQUEST_MISSING_EVENT = "stubs/requests/progression/pcr-request-missing-event.json"; private static final UUID SUBSCRIPTION_ID = randomUUID(); private static final UUID DOCUMENT_ID = randomUUID(); private static final String SUBSCRIPTION_DOCUMENT_URI = "/client-subscriptions/{clientSubscriptionId}/documents/{documentId}"; diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java index 84468850..55e0b458 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/e2e/NotificationPCRE2EIntegrationTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; @@ -21,11 +20,20 @@ import uk.gov.hmcts.cp.subscription.config.SSLTrustingRestTemplateConfig; import uk.gov.hmcts.cp.subscription.integration.IntegrationTestBase; +import static uk.gov.hmcts.cp.subscription.integration.stubs.CallbackStub.getDocumentIdFromCallbackServeEvents; +import static uk.gov.hmcts.cp.subscription.integration.stubs.CallbackStub.stubCallbackEndpoint; +import static uk.gov.hmcts.cp.subscription.integration.stubs.CallbackStub.stubCallbackEndpointReturnsServerError; +import static uk.gov.hmcts.cp.subscription.integration.stubs.MaterialStub.stubMaterialBinary; +import static uk.gov.hmcts.cp.subscription.integration.stubs.MaterialStub.stubMaterialContent; +import static uk.gov.hmcts.cp.subscription.integration.stubs.MaterialStub.stubMaterialMetadata; +import static uk.gov.hmcts.cp.subscription.integration.stubs.MaterialStub.stubMaterialMetadataNoContent; + import java.io.IOException; import java.time.Duration; import java.util.Optional; import java.util.UUID; +import static com.github.tomakehurst.wiremock.client.WireMock.moreThanOrExactly; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static java.util.Objects.nonNull; @@ -58,8 +66,8 @@ class NotificationPcrE2EIntegrationTest extends IntegrationTestBase { private static final UUID MATERIAL_ID = UUID.fromString("6c198796-08bb-4803-b456-fa0c29ca6021"); private static final String DOCUMENT_URI = CLIENT_SUBSCRIPTIONS_URI + "/{clientSubscriptionId}/documents/{documentId}"; private static final String SUBSCRIPTION_REQUEST_E2E = "stubs/requests/subscription/subscription-pcr-request.json"; - private static final String PCR_EVENT_PAYLOAD_PATH = "stubs/requests/progression-pcr/pcr-request-prison-court-register.json"; - private static final String PCR_EVENT_TIMEOUT_PATH = "stubs/requests/progression-pcr/pcr-request-material-timeout.json"; + private static final String PCR_EVENT_PAYLOAD_PATH = "stubs/requests/progression/pcr-request-prison-court-register.json"; + private static final String PCR_EVENT_TIMEOUT_PATH = "stubs/requests/progression/pcr-request-material-timeout.json"; @InjectWireMock("callback-client") private WireMockServer callbackWireMock; @@ -80,7 +88,7 @@ void setUp() { } @Test - void test_document_retrieval_success() throws Exception { + void should_document_retrieval_success() throws Exception { given_i_am_a_subscriber_with_a_subscription(); given_i_have_a_callback_endpoint(); given_material_service_returns_document_success(); @@ -93,7 +101,7 @@ void test_document_retrieval_success() throws Exception { } @Test - void test_document_retrieval_failure() throws Exception { + void should_document_retrieval_failure() throws Exception { given_i_am_a_subscriber_with_a_subscription(); given_i_have_a_callback_endpoint(); given_material_service_returns_document_not_found(); @@ -105,6 +113,17 @@ void test_document_retrieval_failure() throws Exception { then_the_subscriber_does_not_receive_a_callback(); } + @Test + void should_return_504_when_callback_client_does_not_respond() throws Exception { + given_i_am_a_subscriber_with_a_subscription(); + given_callback_endpoint_returns_server_error(); + given_material_service_returns_document_success(); + + when_a_pcr_event_is_posted_expect_callback_delivery_timeout(); + + then_callback_was_attempted(); + } + private void given_i_am_a_subscriber_with_a_subscription() throws Exception { createSubscription(); } @@ -123,6 +142,20 @@ private void given_material_service_returns_document_not_found() { stubMaterialMetadataNoContent(MATERIAL_ID_TIMEOUT); } + private void given_callback_endpoint_returns_server_error() { + stubCallbackEndpointReturnsServerError(callbackWireMock, CALLBACK_URI); + } + + private void when_a_pcr_event_is_posted_expect_callback_delivery_timeout() throws Exception { + postPcrEvent(PCR_EVENT_PAYLOAD_PATH) + .andExpect(status().isGatewayTimeout()) + .andExpect(content().string("Callback is not ready")); + } + + private void then_callback_was_attempted() { + callbackWireMock.verify(moreThanOrExactly(1), postRequestedFor(urlPathEqualTo(CALLBACK_URI))); + } + private void when_a_pcr_event_is_posted() throws Exception { postPcrEvent(PCR_EVENT_PAYLOAD_PATH).andExpect(status().isAccepted()); } @@ -152,29 +185,7 @@ private void then_the_material_api_was_polled() { private void then_the_subscriber_receives_a_callback() { callbackWireMock.verify(1, postRequestedFor(urlPathEqualTo(CALLBACK_URI))); - callbackDocumentId = getDocumentIdFromCallbackServeEvents(); - } - - private UUID getDocumentIdFromCallbackServeEvents() { - return callbackWireMock.getAllServeEvents().stream() - .map(ServeEvent::getRequest) - .filter(r -> nonNull(r.getUrl()) && r.getUrl().contains(CALLBACK_URI)) - .map(r -> parseDocumentIdFromBody(r.getBodyAsString())) - .findFirst() - .orElseThrow(() -> - new AssertionError("Callback request body did not contain documentId")); - } - - private UUID parseDocumentIdFromBody(String body) { - if (body == null || body.isBlank()) return null; - try { - return Optional.ofNullable(new ObjectMapper().readTree(body).get("documentId")) - .map(JsonNode::asText) - .map(UUID::fromString) - .orElse(null); - } catch (Exception e) { - return null; - } + callbackDocumentId = getDocumentIdFromCallbackServeEvents(callbackWireMock, CALLBACK_URI); } private void then_the_subscriber_does_not_receive_a_callback() { diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/stubs/CallbackStub.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/stubs/CallbackStub.java new file mode 100644 index 00000000..2ef4ff97 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/stubs/CallbackStub.java @@ -0,0 +1,67 @@ +package uk.gov.hmcts.cp.subscription.integration.stubs; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static java.util.Objects.nonNull; + +/** + * WireMock stub for the callback endpoint (event notification delivery). + * Use with the callback-client WireMock server (e.g. from @EnableWireMock). + */ +public final class CallbackStub { + + private static final String CALLBACK_RESPONSE_PATH = "wiremock/callback-client/files/callback-accepted.json"; + private static final String APPLICATION_JSON = "application/json"; + private static final String CONTENT_TYPE = "Content-Type"; + + private CallbackStub() { + } + + public static void stubCallbackEndpoint(WireMockServer server, String callbackUri) throws IOException { + String body = new ClassPathResource(CALLBACK_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); + server.stubFor(post(urlPathEqualTo(callbackUri)) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, APPLICATION_JSON) + .withBody(body))); + } + + public static void stubCallbackEndpointReturnsServerError(WireMockServer server, String callbackUri) { + server.stubFor(post(urlPathEqualTo(callbackUri)) + .willReturn(aResponse().withStatus(500))); + } + + public static UUID getDocumentIdFromCallbackServeEvents(WireMockServer server, String callbackUri) { + return server.getAllServeEvents().stream() + .map(ServeEvent::getRequest) + .filter(r -> nonNull(r.getUrl()) && r.getUrl().contains(callbackUri)) + .map(r -> parseDocumentIdFromBody(r.getBodyAsString())) + .findFirst() + .orElseThrow(() -> + new AssertionError("Callback request body did not contain documentId")); + } + + private static UUID parseDocumentIdFromBody(String body) { + if (body.isEmpty()) return null; + try { + return Optional.ofNullable(new ObjectMapper().readTree(body).get("documentId")) + .map(JsonNode::asText) + .map(UUID::fromString) + .orElse(null); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/stubs/MaterialStub.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/stubs/MaterialStub.java new file mode 100644 index 00000000..9febb958 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/stubs/MaterialStub.java @@ -0,0 +1,64 @@ +package uk.gov.hmcts.cp.subscription.integration.stubs; + +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; + +public final class MaterialStub { + + private static final String MATERIAL_METADATA_RESPONSE_PATH = "wiremock/material-client/files/material-response.json"; + private static final String MATERIAL_CONTENT_RESPONSE_PATH = "wiremock/material-client/files/material-with-contenturl.json"; + private static final String MATERIAL_PDF_PATH = "wiremock/material-client/files/material-content.pdf"; + private static final String MATERIAL_URI = "/material-query-api/query/api/rest/material/material/"; + private static final String METADATA = "/metadata"; + private static final String APPLICATION_JSON = "application/json"; + private static final String CONTENT_TYPE = "Content-Type"; + + private MaterialStub() { + } + + public static void stubMaterialMetadata(UUID materialId) throws IOException { + String materialPath = MATERIAL_URI + materialId; + String metadataBody = new ClassPathResource(MATERIAL_METADATA_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); + stubFor(get(urlPathMatching(".*" + materialPath + METADATA)) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, APPLICATION_JSON) + .withBody(metadataBody))); + } + + public static void stubMaterialContent(UUID materialId) throws IOException { + String materialPath = MATERIAL_URI + materialId; + String contentBody = new ClassPathResource(MATERIAL_CONTENT_RESPONSE_PATH).getContentAsString(StandardCharsets.UTF_8); + stubFor(get(urlPathMatching(".*" + materialPath + "/content")) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, "application/vnd.material.query.material+json") + .withBody(contentBody) + .withTransformers("response-template"))); + } + + public static void stubMaterialBinary(UUID materialId) throws IOException { + String materialPath = MATERIAL_URI + materialId; + byte[] pdfBody = new ClassPathResource(MATERIAL_PDF_PATH).getContentAsByteArray(); + stubFor(get(urlPathMatching(".*" + materialPath + "/binary")) + .willReturn(aResponse() + .withStatus(200) + .withHeader(CONTENT_TYPE, "application/pdf") + .withHeader("Content-Disposition", "inline; filename=\"material-content.pdf\"") + .withBody(pdfBody))); + } + + public static void stubMaterialMetadataNoContent(UUID materialId) { + String materialPath = MATERIAL_URI + materialId; + stubFor(get(urlPathMatching(".*" + materialPath + METADATA)) + .willReturn(aResponse().withStatus(204))); + } +} diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackServiceTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackServiceTest.java index ddf21e0e..bc575361 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackServiceTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackServiceTest.java @@ -7,21 +7,48 @@ import org.mockito.junit.jupiter.MockitoExtension; import uk.gov.hmcts.cp.openapi.model.EventNotificationPayload; import uk.gov.hmcts.cp.subscription.clients.CallbackClient; +import uk.gov.hmcts.cp.subscription.config.AppProperties; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class CallbackServiceTest { + + @Mock + private AppProperties appProperties; + @Mock - CallbackClient callbackClient; + private CallbackClient callbackClient; @InjectMocks - CallbackService callbackService; + private CallbackService callbackService; @Test void send_to_subscriber_should_post() { + when(appProperties.getCallbackRetryIntervalMilliSecs()).thenReturn(10); + when(appProperties.getCallbackRetryTimeoutMilliSecs()).thenReturn(1000); + EventNotificationPayload eventNotificationPayload = EventNotificationPayload.builder().build(); callbackService.sendToSubscriber("url", eventNotificationPayload); + verify(callbackClient).sendNotification("url", eventNotificationPayload); } + + @Test + void send_to_subscriber_should_retry_until_success() { + when(appProperties.getCallbackRetryIntervalMilliSecs()).thenReturn(10); + when(appProperties.getCallbackRetryTimeoutMilliSecs()).thenReturn(500); + EventNotificationPayload payload = EventNotificationPayload.builder().build(); + + doThrow(new RuntimeException("intermittent failure")) + .doNothing() + .when(callbackClient).sendNotification("url", payload); + + callbackService.sendToSubscriber("url", payload); + + verify(callbackClient, times(2)).sendNotification("url", payload); + } } \ No newline at end of file diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/unit/controllers/NotificationControllerTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/unit/controllers/NotificationControllerTest.java index b5935a2f..3388c023 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/unit/controllers/NotificationControllerTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/unit/controllers/NotificationControllerTest.java @@ -1,6 +1,5 @@ package uk.gov.hmcts.cp.subscription.unit.controllers; -import com.fasterxml.jackson.core.JsonProcessingException; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -13,9 +12,7 @@ import uk.gov.hmcts.cp.subscription.controllers.NotificationController; import uk.gov.hmcts.cp.subscription.managers.NotificationManager; import uk.gov.hmcts.cp.subscription.model.DocumentContent; -import uk.gov.hmcts.cp.subscription.services.exceptions.CallbackUrlDeliveryException; -import java.net.URISyntaxException; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -71,40 +68,21 @@ void runtime_exception_should_propagate() { @SneakyThrows @Test - void json_processing_exception_in_callback_should_throw_callback_url_delivery_exception() { + void exception_from_manager_should_propagate_for_global_handler() { PcrEventPayload payload = PcrEventPayload.builder() .materialId(MATERIAL_ID) .eventType(EventType.PRISON_COURT_REGISTER_GENERATED) .build(); - JsonProcessingException cause = new JsonProcessingException("Invalid JSON") {}; + RuntimeException failure = new RuntimeException("Callback delivery failed"); - doThrow(new CallbackUrlDeliveryException("PCR - Failed to build or deliver callback payload: " + cause.getMessage(), cause)) + doThrow(failure) .when(notificationManager).processPcrNotification(any(PcrEventPayload.class)); - CallbackUrlDeliveryException thrown = assertThrows(CallbackUrlDeliveryException.class, + RuntimeException thrown = assertThrows(RuntimeException.class, () -> notificationController.createNotificationPCR(payload)); - assertThat(thrown.getMessage()).contains("PCR - Failed to build or deliver callback payload"); - assertThat(thrown.getCause()).isEqualTo(cause); - } - - @SneakyThrows - @Test - void uri_syntax_exception_in_callback_should_throw_callback_url_delivery_exception() { - PcrEventPayload payload = PcrEventPayload.builder() - .materialId(MATERIAL_ID) - .eventType(EventType.PRISON_COURT_REGISTER_GENERATED) - .build(); - URISyntaxException cause = new URISyntaxException("invalid", "bad uri"); - - doThrow(new CallbackUrlDeliveryException("PCR - Failed to build or deliver callback payload: " + cause.getMessage(), cause)) - .when(notificationManager).processPcrNotification(any(PcrEventPayload.class)); - - CallbackUrlDeliveryException thrown = assertThrows(CallbackUrlDeliveryException.class, - () -> notificationController.createNotificationPCR(payload)); - - assertThat(thrown.getMessage()).contains("PCR - Failed to build or deliver callback payload"); - assertThat(thrown.getCause()).isEqualTo(cause); + assertThat(thrown).isSameAs(failure); + verify(notificationManager).processPcrNotification(eq(payload)); } @Test diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/unit/managers/NotificationManagerTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/unit/managers/NotificationManagerTest.java index ed6df33e..7e5e64e9 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/unit/managers/NotificationManagerTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/unit/managers/NotificationManagerTest.java @@ -1,6 +1,5 @@ package uk.gov.hmcts.cp.subscription.unit.managers; -import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -18,9 +17,7 @@ import uk.gov.hmcts.cp.subscription.services.DocumentService; import uk.gov.hmcts.cp.subscription.services.NotificationService; import uk.gov.hmcts.cp.subscription.services.SubscriptionService; -import uk.gov.hmcts.cp.subscription.services.exceptions.CallbackUrlDeliveryException; -import java.net.URISyntaxException; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties index 9a6d7c32..509fc05d 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.properties @@ -1,2 +1,4 @@ material-client.retry.timeoutMilliSecs=500 -material-client.retry.intervalMilliSecs=100 \ No newline at end of file +material-client.retry.intervalMilliSecs=100 +callback-client.retry.timeoutMilliSecs=5000 +callback-client.retry.intervalMilliSecs=200 \ No newline at end of file diff --git a/src/test/resources/stubs/requests/progression-pcr/pcr-request-custodial-result.json b/src/test/resources/stubs/requests/progression/pcr-request-custodial-result.json similarity index 100% rename from src/test/resources/stubs/requests/progression-pcr/pcr-request-custodial-result.json rename to src/test/resources/stubs/requests/progression/pcr-request-custodial-result.json diff --git a/src/test/resources/stubs/requests/progression-pcr/pcr-request-material-not-found.json b/src/test/resources/stubs/requests/progression/pcr-request-material-not-found.json similarity index 100% rename from src/test/resources/stubs/requests/progression-pcr/pcr-request-material-not-found.json rename to src/test/resources/stubs/requests/progression/pcr-request-material-not-found.json diff --git a/src/test/resources/stubs/requests/progression-pcr/pcr-request-material-timeout.json b/src/test/resources/stubs/requests/progression/pcr-request-material-timeout.json similarity index 100% rename from src/test/resources/stubs/requests/progression-pcr/pcr-request-material-timeout.json rename to src/test/resources/stubs/requests/progression/pcr-request-material-timeout.json diff --git a/src/test/resources/stubs/requests/progression-pcr/pcr-request-missing-event.json b/src/test/resources/stubs/requests/progression/pcr-request-missing-event.json similarity index 100% rename from src/test/resources/stubs/requests/progression-pcr/pcr-request-missing-event.json rename to src/test/resources/stubs/requests/progression/pcr-request-missing-event.json diff --git a/src/test/resources/stubs/requests/progression-pcr/pcr-request-missing-material.json b/src/test/resources/stubs/requests/progression/pcr-request-missing-material.json similarity index 100% rename from src/test/resources/stubs/requests/progression-pcr/pcr-request-missing-material.json rename to src/test/resources/stubs/requests/progression/pcr-request-missing-material.json diff --git a/src/test/resources/stubs/requests/progression-pcr/pcr-request-prison-court-register.json b/src/test/resources/stubs/requests/progression/pcr-request-prison-court-register.json similarity index 100% rename from src/test/resources/stubs/requests/progression-pcr/pcr-request-prison-court-register.json rename to src/test/resources/stubs/requests/progression/pcr-request-prison-court-register.json diff --git a/src/test/resources/stubs/requests/progression-pcr/pcr-request-valid.json b/src/test/resources/stubs/requests/progression/pcr-request-valid.json similarity index 100% rename from src/test/resources/stubs/requests/progression-pcr/pcr-request-valid.json rename to src/test/resources/stubs/requests/progression/pcr-request-valid.json From cbad6f1ea7d8731285e295bd186cdf82521d9774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Thu, 12 Feb 2026 15:22:48 +0000 Subject: [PATCH 08/10] Added final to variable --- .../cp/subscription/controllers/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandler.java b/src/main/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandler.java index 92c981fe..b5f86837 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandler.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/controllers/GlobalExceptionHandler.java @@ -86,7 +86,7 @@ public ResponseEntity handleUnsupportedOperation(final UnsupportedOperat @ExceptionHandler(ConditionTimeoutException.class) public ResponseEntity handleConditionTimeout(final ConditionTimeoutException ex) { - boolean isCallbackTimeout = CALLBACK_NOT_READY.equals(ex.getMessage()); + final boolean isCallbackTimeout = CALLBACK_NOT_READY.equals(ex.getMessage()); if (isCallbackTimeout) { log.error("Callback delivery timed out: {}", ex.getMessage()); } else { From 9948cc4d264b87833d873b8e85a781a47e4c518c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Thu, 12 Feb 2026 15:43:05 +0000 Subject: [PATCH 09/10] Changes to avoid catching generic exceptions in try-catch blocks --- .../uk/gov/hmcts/cp/subscription/services/CallbackService.java | 3 ++- .../hmcts/cp/subscription/services/CallbackServiceTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackService.java b/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackService.java index 4fd2da92..93a6ba27 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackService.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackService.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.awaitility.core.ConditionTimeoutException; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; import uk.gov.hmcts.cp.openapi.model.EventNotificationPayload; import uk.gov.hmcts.cp.subscription.clients.CallbackClient; @@ -37,7 +38,7 @@ private void waitForCallbackDelivery(final String url, final EventNotificationPa try { callbackClient.sendNotification(url, eventNotificationPayload); return true; - } catch (Exception e) { + } catch (RestClientException e) { log.warn("Callback delivery failed for {}, retrying: {}", url, e.getMessage()); return false; } diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackServiceTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackServiceTest.java index bc575361..e44c7b41 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackServiceTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackServiceTest.java @@ -5,6 +5,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.client.RestClientException; import uk.gov.hmcts.cp.openapi.model.EventNotificationPayload; import uk.gov.hmcts.cp.subscription.clients.CallbackClient; import uk.gov.hmcts.cp.subscription.config.AppProperties; @@ -43,7 +44,7 @@ void send_to_subscriber_should_retry_until_success() { when(appProperties.getCallbackRetryTimeoutMilliSecs()).thenReturn(500); EventNotificationPayload payload = EventNotificationPayload.builder().build(); - doThrow(new RuntimeException("intermittent failure")) + doThrow(new RestClientException("intermittent failure")) .doNothing() .when(callbackClient).sendNotification("url", payload); From 52e0be65d2e313603be62405d7fb279c478ca5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?srivani=2Emuddineni=C2=A0?= Date: Thu, 12 Feb 2026 16:00:10 +0000 Subject: [PATCH 10/10] relaxed tls for wiremock --- .../subscription/config/SSLTrustingRestTemplateConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/config/SSLTrustingRestTemplateConfig.java b/src/test/java/uk/gov/hmcts/cp/subscription/config/SSLTrustingRestTemplateConfig.java index e7c463c2..6d635c90 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/config/SSLTrustingRestTemplateConfig.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/config/SSLTrustingRestTemplateConfig.java @@ -9,6 +9,7 @@ import javax.net.ssl.X509TrustManager; import java.security.SecureRandom; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @Configuration @@ -20,7 +21,10 @@ public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (chain == null || chain.length == 0) { + throw new CertificateException("No server certificates"); + } } @Override