Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ class SubscriptionApiTest {

@Test
void round_trip_subscription_should_work_ok() throws InterruptedException {
final String callbackUrl = "https://my-callback-url";
final String postUrl = String.format("%s/client-subscriptions?callbackUrl=%s", baseUrl, callbackUrl);
final String body = "{\"eventTypes\":[\"PRISON_COURT_REGISTER_GENERATED\",\"CUSTODIAL_RESULT\"]}";
final String postUrl = baseUrl + "/client-subscriptions";
final String body = "{\"notificationEndpoint\":{\"callbackUrl\":\"https://my-callback-url\"},\"eventTypes\":[\"PRISON_COURT_REGISTER_GENERATED\",\"CUSTODIAL_RESULT\"]}";
final var postResult = http.post()
.uri(postUrl)
.contentType(MediaType.APPLICATION_JSON)
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ apply {
}

dependencies {
implementation(group: 'uk.gov.hmcts.cp', name: 'api-cp-crime-hearing-case-event-subscription', version: '1.0.6')
implementation(group: 'uk.gov.hmcts.cp', name: 'api-cp-crime-hearing-case-event-subscription', version: '1.0.8')

// This is proving to be a real puzzle. This is actually included in the api published pom
// ( though with scope of "runtime" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import uk.gov.hmcts.cp.subscription.model.PcrOutboundPayload;
import uk.gov.hmcts.cp.openapi.model.EventNotificationPayload;

import static org.springframework.http.HttpMethod.POST;

Expand All @@ -18,11 +18,11 @@ public class CallbackClient {

private RestTemplate restTemplate;

public void sendNotification(final String url, final PcrOutboundPayload subscriberOutboundPayload) {
public void sendNotification(final String url, final EventNotificationPayload eventNotificationPayload) {
log.info("Sending notification to {}", url);
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
final HttpEntity<PcrOutboundPayload> req = new HttpEntity<>(subscriberOutboundPayload, headers);
final HttpEntity<EventNotificationPayload> req = new HttpEntity<>(eventNotificationPayload, headers);
restTemplate.exchange(url, POST, req, String.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import uk.gov.hmcts.cp.subscription.model.PcrOutboundPayload;

import static org.springframework.http.HttpMethod.GET;

Expand All @@ -23,7 +22,7 @@ public ResponseEntity<byte[]> getMaterialDocument(final String url) {
log.info("Getting material document from {}", url);
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
final HttpEntity<PcrOutboundPayload> req = new HttpEntity<>(headers);
final HttpEntity<Void> req = new HttpEntity<>(headers);
return restTemplate.exchange(url, GET, req, byte[].class);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package uk.gov.hmcts.cp.subscription.controllers;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.NonNull;
Expand Down Expand Up @@ -44,9 +45,9 @@ public ResponseEntity<Void> createNotificationPCR(@Valid @RequestBody final PcrE
}

@Override
public ResponseEntity<Resource> getPcrDocumentByClientSubscription(
@PathVariable final UUID clientSubscriptionId,
@PathVariable final UUID documentId) {
public ResponseEntity<Resource> getDocument(
@NotNull @PathVariable("clientSubscriptionId") final UUID clientSubscriptionId,
@NotNull @PathVariable("documentId") final UUID documentId) {
final DocumentContent content = notificationManager.getPcrDocumentContent(clientSubscriptionId, documentId);
final Resource resource = new ByteArrayResource(content.getBody());
final HttpHeaders headers = getHttpHeaders(content);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import uk.gov.hmcts.cp.openapi.api.SubscriptionApi;
import uk.gov.hmcts.cp.openapi.model.ClientSubscription;
import uk.gov.hmcts.cp.openapi.model.ClientSubscriptionRequest;
import uk.gov.hmcts.cp.openapi.model.CreateClientSubscriptionRequest;
import uk.gov.hmcts.cp.subscription.services.SubscriptionService;

import java.util.UUID;
Expand All @@ -25,10 +24,10 @@ public class SubscriptionController implements SubscriptionApi {
private static final String CLIENT_ID = "TODO";

@Override
public ResponseEntity<ClientSubscription> createClientSubscription(final String callbackUrl,
final CreateClientSubscriptionRequest request) {
log.info("createClientSubscription callbackUrl:{} clientId:{}", Encode.forJava(callbackUrl), CLIENT_ID);
final ClientSubscription response = subscriptionService.saveSubscription(callbackUrl, request);
public ResponseEntity<ClientSubscription> createClientSubscription(final ClientSubscriptionRequest request) {
log.info("createClientSubscription callbackUrl:{} clientId:{}",
Encode.forJava(request.getNotificationEndpoint().getCallbackUrl()), CLIENT_ID);
final ClientSubscription response = subscriptionService.saveSubscription(request);
log.info("createClientSubscription created subscription:{}", response.getClientSubscriptionId());
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.mapstruct.NullValueMappingStrategy;
import uk.gov.hmcts.cp.openapi.model.ClientSubscription;
import uk.gov.hmcts.cp.openapi.model.ClientSubscriptionRequest;
import uk.gov.hmcts.cp.openapi.model.CreateClientSubscriptionRequest;
import uk.gov.hmcts.cp.openapi.model.EventType;
import uk.gov.hmcts.cp.openapi.model.NotificationEndpoint;
import uk.gov.hmcts.cp.subscription.entities.ClientSubscriptionEntity;
Expand All @@ -25,10 +24,10 @@ public interface SubscriptionMapper {

@Mapping(target = "id", expression = "java(null)")
@Mapping(source = "request.eventTypes", target = "eventTypes", qualifiedByName = "mapWithSortedEventTypes")
@Mapping(target = "notificationEndpoint", expression = "java(callbackUrl)")
@Mapping(source = "request.notificationEndpoint", target = "notificationEndpoint", qualifiedByName = "mapFromNotificationEndpoint")
@Mapping(target = "createdAt", expression = "java(clockService.nowOffsetUTC())")
@Mapping(target = "updatedAt", expression = "java(clockService.nowOffsetUTC())")
ClientSubscriptionEntity mapCreateRequestToEntity(@Context ClockService clockService, String callbackUrl, CreateClientSubscriptionRequest request);
ClientSubscriptionEntity mapCreateRequestToEntity(@Context ClockService clockService, ClientSubscriptionRequest request);

@Mapping(source = "existing.id", target = "id")
@Mapping(source = "request.eventTypes", target = "eventTypes", qualifiedByName = "mapWithSortedEventTypes")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import uk.gov.hmcts.cp.openapi.model.EventNotificationPayload;
import uk.gov.hmcts.cp.openapi.model.EventNotificationPayloadCasesInner;
import uk.gov.hmcts.cp.openapi.model.PcrEventPayload;
import uk.gov.hmcts.cp.openapi.model.PcrEventPayloadDefendant;
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.PcrOutboundPayload;
import uk.gov.hmcts.cp.subscription.model.Subscriber;
import uk.gov.hmcts.cp.subscription.repositories.SubscriptionRepository;

Expand Down Expand Up @@ -39,28 +41,34 @@ private void deliverForPCREvent(final EntityEventType eventType,
throw new UnsupportedOperationException("CUSTODIAL_RESULT not implemented");
}
final List<ClientSubscriptionEntity> entities = subscriptionRepository.findByEventType(eventType.name());
final PcrOutboundPayload pcrOutboundPayload = createPcrOutboundPayload(pcrEventPayload, documentId);
final EventNotificationPayload eventNotificationPayload = createEventNotificationPayload(pcrEventPayload, documentId);
for (final ClientSubscriptionEntity entity : entities) {
final Subscriber subscriber = subscriberMapper.toSubscriber(entity);
deliverToSubscriber(subscriber, documentId, pcrOutboundPayload);
deliverToSubscriber(subscriber, eventNotificationPayload);
}
}

private void deliverToSubscriber(final Subscriber subscriber, final UUID documentId, final PcrOutboundPayload pcrOutboundPayload) {
private void deliverToSubscriber(final Subscriber subscriber, final EventNotificationPayload eventNotificationPayload) {
final String callbackURL = subscriber.getNotificationEndpoint();
callbackService.sendToSubscriber(callbackURL, pcrOutboundPayload);
log.info("Subscriber {} notified via callbackUrl {} for documentId {}", subscriber.getId(), callbackURL, documentId);
callbackService.sendToSubscriber(callbackURL, eventNotificationPayload);
log.info("Subscriber {} notified via callbackUrl {} for documentId {}", subscriber.getId(), callbackURL, eventNotificationPayload.getDocumentId());
}

private PcrOutboundPayload createPcrOutboundPayload(final PcrEventPayload pcrEventPayload,
final UUID documentId) {
return PcrOutboundPayload.builder()
.eventId(pcrEventPayload.getEventId())
.eventType(pcrEventPayload.getEventType())
private EventNotificationPayload createEventNotificationPayload(final PcrEventPayload pcrEventPayload,
final UUID documentId) {
final PcrEventPayloadDefendant defendant = pcrEventPayload.getDefendant();
final List<EventNotificationPayloadCasesInner> cases = defendant.getCases().stream()
.map(c -> EventNotificationPayloadCasesInner.builder().urn(c.getUrn()).build())
.toList();
final String prisonEmailAddress = defendant.getCustodyEstablishmentDetails().getEmailAddress();
return EventNotificationPayload.builder()
.cases(cases)
.masterDefendantId(defendant.getMasterDefendantId())
.defendantName(defendant.getName())
.defendantDateOfBirth(defendant.getDateOfBirth())
.documentId(documentId)
.timestamp(pcrEventPayload.getTimestamp())
//TBD - post to api changes
.defendant(pcrEventPayload.getDefendant())
.documentGeneratedTimestamp(pcrEventPayload.getTimestamp())
.prisonEmailAddress(prisonEmailAddress)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.model.PcrOutboundPayload;

@Service
@Slf4j
Expand All @@ -13,7 +13,7 @@ public class CallbackService {

private final CallbackClient callbackClient;

public void sendToSubscriber(final String url, final PcrOutboundPayload pcrOutboundPayload) {
callbackClient.sendNotification(url, pcrOutboundPayload);
public void sendToSubscriber(final String url, final EventNotificationPayload eventNotificationPayload) {
callbackClient.sendNotification(url, eventNotificationPayload);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import uk.gov.hmcts.cp.openapi.model.ClientSubscription;
import uk.gov.hmcts.cp.openapi.model.ClientSubscriptionRequest;
import uk.gov.hmcts.cp.openapi.model.CreateClientSubscriptionRequest;
import uk.gov.hmcts.cp.subscription.entities.ClientSubscriptionEntity;
import uk.gov.hmcts.cp.subscription.mappers.SubscriptionMapper;
import uk.gov.hmcts.cp.subscription.model.EntityEventType;
Expand All @@ -25,8 +24,8 @@ public class SubscriptionService {
private final SubscriptionMapper mapper;

@Transactional
public ClientSubscription saveSubscription(final String callbackUrl, final CreateClientSubscriptionRequest request) {
final ClientSubscriptionEntity entity = mapper.mapCreateRequestToEntity(clockService, callbackUrl, request);
public ClientSubscription saveSubscription(final ClientSubscriptionRequest request) {
final ClientSubscriptionEntity entity = mapper.mapCreateRequestToEntity(clockService, request);
return mapper.mapEntityToResponse(clockService, subscriptionRepository.save(entity));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpEntity;
import org.springframework.web.client.RestTemplate;
import uk.gov.hmcts.cp.subscription.model.PcrOutboundPayload;
import uk.gov.hmcts.cp.openapi.model.EventNotificationPayload;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
Expand All @@ -26,7 +26,7 @@ class CallbackClientTest {

@Test
void client_should_post_to_subscriber() {
PcrOutboundPayload payload = PcrOutboundPayload.builder().build();
EventNotificationPayload payload = EventNotificationPayload.builder().build();
client.sendNotification("http://subscriber", payload);
verify(restTemplate).exchange(anyString(), eq(POST), any(HttpEntity.class), eq(String.class));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +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.bean.override.mockito.MockitoBean;
import org.wiremock.spring.ConfigureWireMock;
import org.wiremock.spring.EnableWireMock;
Expand Down Expand Up @@ -31,11 +32,16 @@


@EnableWireMock({@ConfigureWireMock(name = "material-client", baseUrlProperties = "material-client.url", port = 18081)})
@TestPropertySource(properties = {
"material-client.retry.timeoutMilliSecs=500",
"material-client.retry.intervalMilliSecs=100"
})
class NotificationControllerIntegrationTest extends IntegrationTestBase {

private static final String NOTIFICATION_PCR_URI = "/notifications/pcr";
private static final String CALLBACK_URL = "https://callback.example.com";
private static final UUID MATERIAL_ID = UUID.fromString("6c198796-08bb-4803-b456-fa0c29ca6021");
private static final String SUBSCRIPTION_DOCUEMNT_URI = "/client-subscriptions/{clientSubscriptionId}/documents/{documentId}";

@MockitoBean
private CallbackDeliveryService callbackDeliveryService;
Expand Down Expand Up @@ -90,14 +96,27 @@ void material_metadata_not_found_should_return_404() throws Exception {
.andExpect(status().isNotFound());
}

@Test
void material_metadata_timeout_should_return_504_via_global_exception_handler() throws Exception {
String pcrPayload = loadPcrPayload("stubs/requests/pcr-request-material-timeout.json");

mockMvc.perform(post(NOTIFICATION_PCR_URI)
.contentType(MediaType.APPLICATION_JSON)
.header("Accept", MediaType.APPLICATION_JSON_VALUE)
.content(pcrPayload))
.andDo(print())
.andExpect(status().isGatewayTimeout())
.andExpect(content().string("Material metadata not ready"));
}

@Test
void get_document_should_return_200_with_pdf_when_subscription_has_access() throws Exception {
ClientSubscriptionEntity subscription = insertSubscription(
CALLBACK_URL,
List.of(EntityEventType.PRISON_COURT_REGISTER_GENERATED));
DocumentMappingEntity document = insertDocument(MATERIAL_ID, EntityEventType.PRISON_COURT_REGISTER_GENERATED);

mockMvc.perform(get("/client-subscriptions/{clientSubscriptionId}/documents/{documentId}",
mockMvc.perform(get(SUBSCRIPTION_DOCUEMNT_URI,
subscription.getId(), document.getDocumentId()))
.andDo(print())
.andExpect(status().isOk())
Expand All @@ -114,7 +133,7 @@ void get_document_should_return_403_when_subscription_does_not_have_event_type()
List.of(EntityEventType.CUSTODIAL_RESULT));
DocumentMappingEntity document = insertDocument(MATERIAL_ID, EntityEventType.PRISON_COURT_REGISTER_GENERATED);

mockMvc.perform(get("/client-subscriptions/{clientSubscriptionId}/documents/{documentId}",
mockMvc.perform(get(SUBSCRIPTION_DOCUEMNT_URI,
subscription.getId(), document.getDocumentId()))
.andDo(print())
.andExpect(status().isForbidden());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

class SubscriptionControllerValidationTest extends IntegrationTestBase {

public static final String CLIENT_SUBSCRIPTIONS = "/client-subscriptions";
@Autowired
SubscriptionRepository subscriptionRepository;

Expand All @@ -34,22 +35,23 @@ class SubscriptionControllerValidationTest extends IntegrationTestBase {
void bad_event_type_should_return_400() throws Exception {
String body = new ObjectMapper().writeValueAsString(request)
.replace("PRISON_COURT_REGISTER_GENERATED", "BAD");
mockMvc.perform(post("/client-subscriptions")
.param("callbackUrl", "https://my-callback-url")
mockMvc.perform(post(CLIENT_SUBSCRIPTIONS)
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest())
.andExpect(content().string("JSON parse error: Cannot construct instance of `uk.gov.hmcts.cp.openapi.model.EventType`, problem: Unexpected value 'BAD'"));
}

@Test
void callback_url_should_return_400() throws Exception {
String body = new ObjectMapper().writeValueAsString(request);
mockMvc.perform(post("/client-subscriptions")
.param("callbackUrl", "not-a-url")
void callback_url_invalid_should_return_400() throws Exception {
ClientSubscriptionRequest invalidRequest = ClientSubscriptionRequest.builder()
.notificationEndpoint(NotificationEndpoint.builder().callbackUrl("not-a-url").build())
.eventTypes(List.of(PRISON_COURT_REGISTER_GENERATED, CUSTODIAL_RESULT))
.build();
String body = new ObjectMapper().writeValueAsString(invalidRequest);
mockMvc.perform(post(CLIENT_SUBSCRIPTIONS)
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest())
.andExpect(content().string("createClientSubscription.arg0: must match \"^https://.*$\""));
.andExpect(status().isBadRequest());
}
}
Loading
Loading