diff --git a/build.gradle b/build.gradle index 24574fd0..4f3d2511 100644 --- a/build.gradle +++ b/build.gradle @@ -38,4 +38,5 @@ dependencies { // Yet we still need to add it manually. Grrr. TBC. implementation "io.swagger.core.v3:swagger-annotations:2.2.42" implementation("org.owasp.encoder:encoder:1.4.0") + implementation 'org.awaitility:awaitility:4.2.0' } diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/config/AppConfig.java b/src/main/java/uk/gov/hmcts/cp/subscription/config/AppConfig.java index 79aff516..f04bb2de 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/config/AppConfig.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/config/AppConfig.java @@ -2,15 +2,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import uk.gov.hmcts.cp.subscription.services.ClockService; -import uk.gov.hmcts.cp.subscription.services.exceptions.MaterialMetadataNotReadyException; -import uk.gov.hmcts.cp.subscription.services.exceptions.CallbackUrlDeliveryException; import java.time.Clock; -import java.util.Map; @Configuration public class AppConfig { @@ -29,12 +25,4 @@ public RestClient restClient() { public ClockService clockService() { return new ClockService(Clock.systemDefaultZone()); } - - @Bean - public RetryTemplate retryTemplate() { - return RetryTemplateConfig.retryConfig().toRetryTemplate( Map.of( - MaterialMetadataNotReadyException.class, true, - CallbackUrlDeliveryException.class, true - )); - } } 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 new file mode 100644 index 00000000..86cb5736 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/cp/subscription/config/AppProperties.java @@ -0,0 +1,21 @@ +package uk.gov.hmcts.cp.subscription.config; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Builder +@Getter +@Service +public class AppProperties { + + private int materialRetryIntervalMilliSecs; + private int materialRetryTimeoutMilliSecs; + + public AppProperties(@Value("${material-client.retry.intervalMilliSecs}") final int materialRetryIntervalMilliSecs, + @Value("${material-client.retry.timeoutMilliSecs}") final int materialRetryTimeoutMilliSecs) { + this.materialRetryIntervalMilliSecs = materialRetryIntervalMilliSecs; + this.materialRetryTimeoutMilliSecs = materialRetryTimeoutMilliSecs; + } +} diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/config/RetryTemplateConfig.java b/src/main/java/uk/gov/hmcts/cp/subscription/config/RetryTemplateConfig.java deleted file mode 100644 index 5cd120d8..00000000 --- a/src/main/java/uk/gov/hmcts/cp/subscription/config/RetryTemplateConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package uk.gov.hmcts.cp.subscription.config; - -import lombok.Builder; -import lombok.Value; -import org.springframework.retry.backoff.ExponentialBackOffPolicy; -import org.springframework.retry.policy.SimpleRetryPolicy; -import org.springframework.retry.support.RetryTemplate; - -import java.util.Map; - -@Value -@Builder -public class RetryTemplateConfig { - - int maxAttempts; - long initialDelayMs; - double multiplier; - long maxDelayMs; - - public RetryTemplate toRetryTemplate(final Map, Boolean> retryableExceptions) { - final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxAttempts, retryableExceptions); - final ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); - backOffPolicy.setInitialInterval(initialDelayMs); - backOffPolicy.setMultiplier(multiplier); - backOffPolicy.setMaxInterval(maxDelayMs); - - final RetryTemplate template = new RetryTemplate(); - template.setRetryPolicy(retryPolicy); - template.setBackOffPolicy(backOffPolicy); - return template; - } - - public static RetryTemplateConfig retryConfig() { - return RetryTemplateConfig.builder() - .maxAttempts(3) - .initialDelayMs(50) - .multiplier(2) - .maxDelayMs(5000) - .build(); - } -} 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 0db02485..6de5c704 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 @@ -3,6 +3,7 @@ import jakarta.persistence.EntityNotFoundException; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; +import org.awaitility.core.ConditionTimeoutException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -79,4 +80,12 @@ public ResponseEntity handleUnsupportedOperation(final UnsupportedOperat .status(HttpStatus.NOT_IMPLEMENTED) .body("Unsupported"); } + + @ExceptionHandler(ConditionTimeoutException.class) + public ResponseEntity handleConditionTimeout(final ConditionTimeoutException ex) { + log.error("Material metadata timed out: {}", ex.getMessage()); + return ResponseEntity + .status(HttpStatus.GATEWAY_TIMEOUT) + .body("Material metadata not ready"); + } } \ No newline at end of file diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/model/PcrOutboundPayload.java b/src/main/java/uk/gov/hmcts/cp/subscription/model/PcrOutboundPayload.java index c424fdd2..87c2ec5c 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/model/PcrOutboundPayload.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/model/PcrOutboundPayload.java @@ -4,13 +4,21 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import uk.gov.hmcts.cp.openapi.model.PcrEventPayload; +import uk.gov.hmcts.cp.openapi.model.EventType; +import uk.gov.hmcts.cp.openapi.model.PcrEventPayloadDefendant; + +import java.time.Instant; +import java.util.UUID; @Data @NoArgsConstructor @AllArgsConstructor @Builder public class PcrOutboundPayload { - private PcrEventPayload pcrEventPayload; - private String documentId; + + private UUID eventId; + private EventType eventType; + private UUID documentId; + private Instant timestamp; + private PcrEventPayloadDefendant defendant; } diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackDeliveryService.java b/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackDeliveryService.java index d703e437..97682940 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackDeliveryService.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/services/CallbackDeliveryService.java @@ -52,10 +52,15 @@ private void deliverToSubscriber(final Subscriber subscriber, final UUID documen log.info("Subscriber {} notified via callbackUrl {} for documentId {}", subscriber.getId(), callbackURL, documentId); } - private PcrOutboundPayload createPcrOutboundPayload(final PcrEventPayload pcrEventPayload, final UUID documentId) { + private PcrOutboundPayload createPcrOutboundPayload(final PcrEventPayload pcrEventPayload, + final UUID documentId) { return PcrOutboundPayload.builder() - .pcrEventPayload(pcrEventPayload) - .documentId(documentId.toString()) + .eventId(pcrEventPayload.getEventId()) + .eventType(pcrEventPayload.getEventType()) + .documentId(documentId) + .timestamp(pcrEventPayload.getTimestamp()) + //TBD - post to api changes + .defendant(pcrEventPayload.getDefendant()) .build(); } } diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/services/NotificationService.java b/src/main/java/uk/gov/hmcts/cp/subscription/services/NotificationService.java index c6202629..648e553e 100644 --- a/src/main/java/uk/gov/hmcts/cp/subscription/services/NotificationService.java +++ b/src/main/java/uk/gov/hmcts/cp/subscription/services/NotificationService.java @@ -1,41 +1,45 @@ package uk.gov.hmcts.cp.subscription.services; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Service; import uk.gov.hmcts.cp.material.openapi.api.MaterialApi; import uk.gov.hmcts.cp.material.openapi.model.MaterialMetadata; import uk.gov.hmcts.cp.openapi.model.PcrEventPayload; +import uk.gov.hmcts.cp.subscription.config.AppProperties; import uk.gov.hmcts.cp.subscription.model.EntityEventType; -import uk.gov.hmcts.cp.subscription.services.exceptions.MaterialMetadataNotReadyException; +import java.time.Duration; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import static org.awaitility.Awaitility.await; @Service -@RequiredArgsConstructor @Slf4j +@AllArgsConstructor public class NotificationService { + private final AppProperties appProperties; private final MaterialApi materialApi; private final DocumentService documentService; - @Qualifier("retryTemplate") - private final RetryTemplate materialRetryTemplate; public void processInboundEvent(final PcrEventPayload pcrEventPayload) { - final MaterialMetadata materialMetadata = materialRetryTemplate.execute(context -> - waitForMaterialMetadata(pcrEventPayload.getMaterialId())); - + final MaterialMetadata materialMetadata = waitForMaterialMetadata(pcrEventPayload.getMaterialId()); final EntityEventType eventType = EntityEventType.valueOf(pcrEventPayload.getEventType().name()); documentService.saveDocumentMapping(materialMetadata.getMaterialId(), eventType); } private MaterialMetadata waitForMaterialMetadata(final UUID materialId) { - final MaterialMetadata response = materialApi.getMaterialMetadataByMaterialId(materialId); - if (response == null) { - throw new MaterialMetadataNotReadyException("PCR - Material metadata not ready for materialId: " + materialId); - } - return response; + final AtomicReference materialResponse = new AtomicReference<>(); + await() + .pollInterval(Duration.ofMillis(appProperties.getMaterialRetryIntervalMilliSecs())) + .atMost(Duration.ofMillis(appProperties.getMaterialRetryTimeoutMilliSecs())) + .until(() -> { + final MaterialMetadata response = materialApi.getMaterialMetadataByMaterialId(materialId); + materialResponse.set(response); + return response != null; + }); + return materialResponse.get(); } } diff --git a/src/main/java/uk/gov/hmcts/cp/subscription/services/exceptions/MaterialMetadataNotReadyException.java b/src/main/java/uk/gov/hmcts/cp/subscription/services/exceptions/MaterialMetadataNotReadyException.java deleted file mode 100644 index 49f16e76..00000000 --- a/src/main/java/uk/gov/hmcts/cp/subscription/services/exceptions/MaterialMetadataNotReadyException.java +++ /dev/null @@ -1,12 +0,0 @@ -package uk.gov.hmcts.cp.subscription.services.exceptions; - -/** - * Thrown when material metadata is not ready yet (null response). Triggers retry via @Retryable. - */ -public class MaterialMetadataNotReadyException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public MaterialMetadataNotReadyException(final String message) { - super(message); - } -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 85dc9378..bc56655e 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,9 +1,6 @@ server: port: ${SERVER_PORT:4550} -material-client: - url: ${MATERIAL_CLIENT_URL:http://localhost:8081} - spring: application: name: service-cp-crime-hearing-case-event-subscription @@ -18,6 +15,12 @@ spring: locations: classpath:db/migration baseline-on-migrate: true +material-client: + url: ${MATERIAL_CLIENT_URL:http://localhost:8081} + retry: + intervalMilliSecs: 5000 + timeoutMilliSecs: 30000 + 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 e1d4e34e..08a8e09d 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 @@ -1,21 +1,20 @@ package uk.gov.hmcts.cp.subscription.controllers; import jakarta.persistence.EntityNotFoundException; +import org.awaitility.core.ConditionTimeoutException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.NoHandlerFoundException; -import org.springframework.web.servlet.resource.NoResourceFoundException; -import uk.gov.hmcts.cp.openapi.model.ErrorResponse; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.NOT_FOUND; @@ -58,7 +57,36 @@ void generic_exception_should_handle_ok() { assertErrorFields(response, INTERNAL_SERVER_ERROR, "message"); } - // TODO - Add tests for the other exception handlers ... if we decide that we really do need 10 + @Test + void unsupported_media_type_should_handle_ok() { + HttpMediaTypeNotSupportedException e = + new HttpMediaTypeNotSupportedException("application/xml"); + + ResponseEntity response = + globalExceptionHandler.handleHttpMediaTypeNotSupportedException(e); + + assertErrorFields(response, HttpStatus.UNSUPPORTED_MEDIA_TYPE, e.getMessage()); + } + + @Test + void unsupported_operation_should_handle_ok() { + UnsupportedOperationException e = new UnsupportedOperationException("not implemented"); + + ResponseEntity response = + globalExceptionHandler.handleUnsupportedOperation(e); + + assertErrorFields(response, HttpStatus.NOT_IMPLEMENTED, "Unsupported"); + } + + @Test + void condition_timeout_should_handle_ok() { + ConditionTimeoutException e = new ConditionTimeoutException("timed out"); + + ResponseEntity response = + globalExceptionHandler.handleConditionTimeout(e); + + assertErrorFields(response, HttpStatus.GATEWAY_TIMEOUT, "Material metadata not ready"); + } private void assertErrorFields(ResponseEntity errorResponse, HttpStatusCode httpStatusCode, String message) { assertThat(errorResponse.getStatusCode()).isEqualTo(httpStatusCode); 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 6c88b7c1..767982dc 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 @@ -41,6 +41,7 @@ class NotificationControllerValidationTest { private static final String PCR_REQUEST_MISSING_EVENT = "stubs/requests/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}"; @Autowired private MockMvc mockMvc; @@ -120,7 +121,7 @@ void get_document_should_return_200_with_pdf_content_when_subscription_has_acces when(notificationManager.getPcrDocumentContent(eq(SUBSCRIPTION_ID), eq(DOCUMENT_ID))).thenReturn(documentContent); - mockMvc.perform(get("/client-subscriptions/{clientSubscriptionId}/documents/{documentId}", + mockMvc.perform(get(SUBSCRIPTION_DOCUMENT_URI, SUBSCRIPTION_ID, DOCUMENT_ID)) .andDo(print()) .andExpect(status().isOk()) @@ -134,7 +135,7 @@ void get_document_should_return_403_when_subscription_does_not_have_access() thr doThrow(new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied: subscription does not have access to this document")) .when(notificationManager).getPcrDocumentContent(eq(SUBSCRIPTION_ID), eq(DOCUMENT_ID)); - mockMvc.perform(get("/client-subscriptions/{clientSubscriptionId}/documents/{documentId}", + mockMvc.perform(get(SUBSCRIPTION_DOCUMENT_URI, SUBSCRIPTION_ID, DOCUMENT_ID)) .andDo(print()) .andExpect(status().isForbidden()) @@ -143,7 +144,7 @@ void get_document_should_return_403_when_subscription_does_not_have_access() thr @Test void get_document_should_return_400_when_invalid_subscription_uuid() throws Exception { - mockMvc.perform(get("/client-subscriptions/{clientSubscriptionId}/documents/{documentId}", + mockMvc.perform(get(SUBSCRIPTION_DOCUMENT_URI, "invalid-uuid", DOCUMENT_ID)) .andDo(print()) .andExpect(status().isBadRequest()); @@ -151,7 +152,7 @@ void get_document_should_return_400_when_invalid_subscription_uuid() throws Exce @Test void get_document_should_return_400_when_invalid_document_uuid() throws Exception { - mockMvc.perform(get("/client-subscriptions/{clientSubscriptionId}/documents/{documentId}", + mockMvc.perform(get(SUBSCRIPTION_DOCUMENT_URI, SUBSCRIPTION_ID, "invalid-uuid")) .andDo(print()) .andExpect(status().isBadRequest()); @@ -162,7 +163,7 @@ void get_document_should_return_404_when_document_not_found() throws Exception { doThrow(new ResponseStatusException(HttpStatus.NOT_FOUND, "Document not found: " + DOCUMENT_ID)) .when(notificationManager).getPcrDocumentContent(eq(SUBSCRIPTION_ID), eq(DOCUMENT_ID)); - mockMvc.perform(get("/client-subscriptions/{clientSubscriptionId}/documents/{documentId}", + mockMvc.perform(get(SUBSCRIPTION_DOCUMENT_URI, SUBSCRIPTION_ID, DOCUMENT_ID)) .andDo(print()) .andExpect(status().isNotFound()); diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionGetControllerIntegrationTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionGetControllerIntegrationTest.java index f7f6aef8..ed8949ea 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionGetControllerIntegrationTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/integration/controllers/SubscriptionGetControllerIntegrationTest.java @@ -17,13 +17,13 @@ class SubscriptionGetControllerIntegrationTest extends IntegrationTestBase { + UUID subscriptionId=UUID.fromString("0a3f88fb-1573-43aa-92be-40ad86e561fe"); + @BeforeEach void beforeEach() { clearClientSubscriptionTable(); } - UUID subscriptionId=UUID.fromString("0a3f88fb-1573-43aa-92be-40ad86e561fe"); - @Test void get_subscription_should_return_expected() throws Exception { ClientSubscriptionEntity entity = insertSubscription("https://example.com/event", List.of(EntityEventType.PRISON_COURT_REGISTER_GENERATED)); diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackDeliveryServiceTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackDeliveryServiceTest.java new file mode 100644 index 00000000..fcd23aa3 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/cp/subscription/services/CallbackDeliveryServiceTest.java @@ -0,0 +1,85 @@ +package uk.gov.hmcts.cp.subscription.services; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.gov.hmcts.cp.openapi.model.EventType; +import uk.gov.hmcts.cp.openapi.model.PcrEventPayload; +import uk.gov.hmcts.cp.subscription.entities.ClientSubscriptionEntity; +import uk.gov.hmcts.cp.subscription.mappers.SubscriberMapper; +import uk.gov.hmcts.cp.subscription.model.EntityEventType; +import uk.gov.hmcts.cp.subscription.model.Subscriber; +import uk.gov.hmcts.cp.subscription.repositories.SubscriptionRepository; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import static java.util.UUID.randomUUID; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CallbackDeliveryServiceTest { + @Mock private SubscriptionRepository subscriptionRepository; + @Mock private SubscriberMapper subscriberMapper; + @Mock private CallbackService callbackService; + @Mock private PcrEventPayload pcrEventPayload; + + @InjectMocks + private CallbackDeliveryService callbackDeliveryService; + + private static final UUID DOCUMENT_ID = randomUUID(); + private static final UUID EVENT_ID = randomUUID(); + private static final String CALLBACK_URL = "https://callback.example.com"; + private static Instant timestamp = Instant.now(); + private static final PcrEventPayload PCR_EVENT_PAYLOAD = PcrEventPayload.builder() + .eventType(EventType.PRISON_COURT_REGISTER_GENERATED) + .eventId(EVENT_ID) + .timestamp(timestamp) + .defendant(null) //TODO verify for few more params after latest api changes + .build(); + private static final ClientSubscriptionEntity SUB_ENTITY = ClientSubscriptionEntity.builder() + .id(randomUUID()) + .notificationEndpoint(CALLBACK_URL) + .eventTypes(List.of(EntityEventType.PRISON_COURT_REGISTER_GENERATED)) + .build(); + private static final Subscriber SUBSCRIBER = Subscriber.builder() + .id(SUB_ENTITY.getId()) + .notificationEndpoint(CALLBACK_URL) + .eventTypes(SUB_ENTITY.getEventTypes()) + .clientId(randomUUID()) + .build(); + + @Test + void processPcrEvent_custodialResult_shouldThrowUnsupportedOperation() { + when(pcrEventPayload.getEventType()).thenReturn(EventType.CUSTODIAL_RESULT); + + assertThrows(UnsupportedOperationException.class, + () -> callbackDeliveryService.processPcrEvent(pcrEventPayload, DOCUMENT_ID)); + + verifyNoInteractions(subscriptionRepository, subscriberMapper, callbackService); + } + //TODO verify for few more params after latest api changes + @Test + void processPcrEvent_shouldMapOutboundPayloadFieldsCorrectly() { + when(subscriptionRepository.findByEventType(EntityEventType.PRISON_COURT_REGISTER_GENERATED.name())) + .thenReturn(List.of(SUB_ENTITY)); + when(subscriberMapper.toSubscriber(SUB_ENTITY)).thenReturn(SUBSCRIBER); + + callbackDeliveryService.processPcrEvent(PCR_EVENT_PAYLOAD, DOCUMENT_ID); + + verify(callbackService).sendToSubscriber(eq(CALLBACK_URL), argThat(p -> + p.getEventId().equals(EVENT_ID) + && p.getEventType() == EventType.PRISON_COURT_REGISTER_GENERATED + && p.getDocumentId().equals(DOCUMENT_ID) + && p.getTimestamp().equals(timestamp) + && p.getDefendant() == null)); + } +} \ No newline at end of file diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/services/DocumentServiceTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/services/DocumentServiceTest.java index a550580e..7a760f2e 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/services/DocumentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/services/DocumentServiceTest.java @@ -1,5 +1,6 @@ package uk.gov.hmcts.cp.subscription.services; +import lombok.Builder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -77,12 +78,28 @@ void get_document_content_should_return_response() { } private Material createMaterial() { - // TODO Lets make material have a builder so we can make it immutable! - Material material = new Material(); - material.setContentUrl("http://material-servce"); - MaterialMetadata materialMetadata = new MaterialMetadata(); - materialMetadata.setFileName("file.pdf"); - material.setMetadata(materialMetadata); - return material; + return TestMaterialBuilder.builder() + .contentUrl("http://material-servce") + .fileName("file.pdf") + .build() + .toMaterial(); + } + + @Builder + private static final class TestMaterialBuilder { + + private final String contentUrl; + private final String fileName; + + Material toMaterial() { + Material material = new Material(); + material.setContentUrl(contentUrl); + + MaterialMetadata metadata = new MaterialMetadata(); + metadata.setFileName(fileName); + material.setMetadata(metadata); + + return material; + } } } \ No newline at end of file diff --git a/src/test/java/uk/gov/hmcts/cp/subscription/unit/services/NotificationServiceTest.java b/src/test/java/uk/gov/hmcts/cp/subscription/unit/services/NotificationServiceTest.java index a731b44d..9e50c5c6 100644 --- a/src/test/java/uk/gov/hmcts/cp/subscription/unit/services/NotificationServiceTest.java +++ b/src/test/java/uk/gov/hmcts/cp/subscription/unit/services/NotificationServiceTest.java @@ -1,19 +1,18 @@ package uk.gov.hmcts.cp.subscription.unit.services; -import org.junit.jupiter.api.BeforeEach; +import org.awaitility.core.ConditionTimeoutException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.retry.support.RetryTemplate; import uk.gov.hmcts.cp.material.openapi.api.MaterialApi; import uk.gov.hmcts.cp.material.openapi.model.MaterialMetadata; import uk.gov.hmcts.cp.openapi.model.EventType; import uk.gov.hmcts.cp.openapi.model.PcrEventPayload; +import uk.gov.hmcts.cp.subscription.config.AppProperties; import uk.gov.hmcts.cp.subscription.services.DocumentService; import uk.gov.hmcts.cp.subscription.services.NotificationService; -import uk.gov.hmcts.cp.subscription.services.exceptions.MaterialMetadataNotReadyException; import java.util.UUID; @@ -21,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static uk.gov.hmcts.cp.subscription.model.EntityEventType.PRISON_COURT_REGISTER_GENERATED; @@ -28,26 +29,21 @@ @ExtendWith(MockitoExtension.class) class NotificationServiceTest { + @Mock + AppProperties appProperties; @Mock private MaterialApi materialApi; - @Mock private DocumentService documentService; - @Mock - private RetryTemplate materialRetryTemplate; - @InjectMocks private NotificationService notificationService; - @BeforeEach - void setUp() { - when(materialRetryTemplate.execute(any())).thenAnswer(invocation -> - invocation.getArgument(0, org.springframework.retry.RetryCallback.class).doWithRetry(null)); - } @Test void shouldSaveDocumentMappingWithEventTypeWhenMetadataPresent() { + when(appProperties.getMaterialRetryIntervalMilliSecs()).thenReturn(10); + when(appProperties.getMaterialRetryTimeoutMilliSecs()).thenReturn(50); UUID materialId = randomUUID(); PcrEventPayload payload = PcrEventPayload.builder() .materialId(materialId) @@ -65,6 +61,8 @@ void shouldSaveDocumentMappingWithEventTypeWhenMetadataPresent() { @Test void shouldThrowExceptionWhenMaterialMetadataNotReady() { + when(appProperties.getMaterialRetryIntervalMilliSecs()).thenReturn(100); + when(appProperties.getMaterialRetryTimeoutMilliSecs()).thenReturn(400); UUID materialId = randomUUID(); PcrEventPayload payload = PcrEventPayload.builder() .materialId(materialId) @@ -72,6 +70,9 @@ void shouldThrowExceptionWhenMaterialMetadataNotReady() { .build(); when(materialApi.getMaterialMetadataByMaterialId(any(UUID.class))).thenReturn(null); - assertThrows(MaterialMetadataNotReadyException.class, () -> notificationService.processInboundEvent(payload)); + assertThrows(ConditionTimeoutException.class, () -> notificationService.processInboundEvent(payload)); + + verify(materialApi, times(3)).getMaterialMetadataByMaterialId(materialId); + verify(documentService, never()).saveDocumentMapping(eq(materialId), eq(PRISON_COURT_REGISTER_GENERATED)); } }