Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: "CodeQL config"
paths-ignore:
- "**/src/test/**"
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion apiTest/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> handleNotFoundException(final Exception exception) {
log.error("NotFoundException {}", exception.getMessage());
Expand Down Expand Up @@ -83,9 +86,14 @@ public ResponseEntity<String> handleUnsupportedOperation(final UnsupportedOperat

@ExceptionHandler(ConditionTimeoutException.class)
public ResponseEntity<String> handleConditionTimeout(final ConditionTimeoutException ex) {
log.error("Material metadata timed out: {}", ex.getMessage());
final 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,46 @@

import lombok.RequiredArgsConstructor;
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;
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 (RestClientException e) {
log.warn("Callback delivery failed for {}, retrying: {}", url, e.getMessage());
return false;
}
});
}
}

This file was deleted.

5 changes: 5 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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.CertificateException;
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) throws CertificateException {
if (chain == null || chain.length == 0) {
throw new CertificateException("No server certificates");
}
}

@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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> response =
Expand All @@ -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<String> response =
globalExceptionHandler.handleConditionTimeout(e);

assertErrorFields(response, HttpStatus.GATEWAY_TIMEOUT, "Callback is not ready");
}

private void assertErrorFields(ResponseEntity<String> errorResponse, HttpStatusCode httpStatusCode, String message) {
assertThat(errorResponse.getStatusCode()).isEqualTo(httpStatusCode);
assertThat(errorResponse.getBody()).isEqualTo(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +33,11 @@
@Slf4j
public abstract class IntegrationTestBase {

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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -31,11 +31,8 @@
import uk.gov.hmcts.cp.subscription.services.CallbackDeliveryService;


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

private static final String NOTIFICATION_PCR_URI = "/notifications/pcr";
Expand All @@ -54,7 +51,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-request-prison-court-register.json");

mockMvc.perform(post(NOTIFICATION_PCR_URI)
.contentType(MediaType.APPLICATION_JSON)
Expand All @@ -68,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/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));
Expand All @@ -86,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/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)
Expand All @@ -98,7 +95,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-request-material-timeout.json");


mockMvc.perform(post(NOTIFICATION_PCR_URI)
.contentType(MediaType.APPLICATION_JSON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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-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}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading
Loading