Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
38 changes: 19 additions & 19 deletions .github/workflows/ci-draft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,22 @@ jobs:
BASE_IMAGE=openjdk:21-jdk-slim
JAR_FILENAME=${{ needs.Build.outputs.artefact_name }}.jar

# https://github.com/marketplace/actions/azure-pipelines-action
Deploy:
needs: [ Build, Artefact-Version ]
runs-on: ubuntu-latest
if: github.event_name == 'push'

steps:
- name: Trigger ADO pipeline
uses: Azure/pipelines@v1.2
with:
azure-devops-project-url: 'https://dev.azure.com/hmcts-cpp/cpp-apps'
azure-pipeline-name: 'cp-gh-artifact-to-acr'
azure-devops-token: ${{ secrets.HMCTS_ADO_PAT }}
azure-pipeline-variables: >-
{
"GROUP_ID" : "uk.gov.hmcts.cp",
"ARTIFACT_ID" : "${{ github.repository }}",
"ARTIFACT_VERSION" : "${{ needs.Artefact-Version.outputs.draft_version}}"
}
# # https://github.com/marketplace/actions/azure-pipelines-action
# Deploy:
# needs: [ Build, Artefact-Version ]
# runs-on: ubuntu-latest
# if: github.event_name == 'push'
#
# steps:
# - name: Trigger ADO pipeline
# uses: Azure/pipelines@v1.2
# with:
# azure-devops-project-url: 'https://dev.azure.com/hmcts-cpp/cpp-apps'
# azure-pipeline-name: 'cp-gh-artifact-to-acr'
# azure-devops-token: ${{ secrets.HMCTS_ADO_PAT }}
# azure-pipeline-variables: >-
# {
# "GROUP_ID" : "uk.gov.hmcts.cp",
# "ARTIFACT_ID" : "${{ github.repository }}",
# "ARTIFACT_VERSION" : "${{ needs.Artefact-Version.outputs.draft_version}}"
# }
54 changes: 45 additions & 9 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ configurations {

integrationTestImplementation.extendsFrom testImplementation
integrationTestRuntimeOnly.extendsFrom runtimeOnly

// Ensure testRuntimeClasspath can be resolved for agent injection
testRuntimeClasspath {
canBeResolved = true
}
}


Expand All @@ -78,6 +83,11 @@ tasks.named('test') {
useJUnitPlatform()
systemProperty 'API_SPEC_VERSION', project.version
failFast = true
// Mockito must be added as an agent, see:
// https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
jvmArgs += [
"-javaagent:${configurations.testRuntimeClasspath.find { it.name.contains('mockito-core') }}", '-Xshare:off'
]
testLogging {
events "passed", "skipped", "failed"
exceptionFormat = 'full'
Expand All @@ -94,14 +104,40 @@ tasks.register('functional', Test) {
group = "Verification"
testClassesDirs = sourceSets.functionalTest.output.classesDirs
classpath = sourceSets.functionalTest.runtimeClasspath
useJUnitPlatform()
failFast = true
testLogging {
events "passed", "skipped", "failed"
exceptionFormat = 'full'
showStandardStreams = true
}
reports {
junitXml.required.set(true) // For CI tools (e.g. Jenkins, GitHub Actions)
html.required.set(true) // Human-readable browser report
}
}

tasks.register('integration', Test) {
description = "Runs integration tests"
group = "Verification"
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
useJUnitPlatform()
failFast = true
testLogging {
events "passed", "skipped", "failed"
exceptionFormat = 'full'
showStandardStreams = true
}
reports {
junitXml.required.set(true) // For CI tools (e.g. Jenkins, GitHub Actions)
html.required.set(true) // Human-readable browser report
}
}

tasks.named('build') {
dependsOn tasks.named('test')
dependsOn tasks.named('integration')
// dependsOn tasks.named('functional') disabling until functional tests are implemented
}

tasks.named('jacocoTestReport') {
Expand All @@ -113,11 +149,6 @@ tasks.named('jacocoTestReport') {
}
}

tasks.named('check') {
dependsOn tasks.named('integration')
dependsOn tasks.named('functional')
}

// check dependencies upon release ONLY
tasks.named("dependencyUpdates").configure {
def isNonStable = { String version ->
Expand Down Expand Up @@ -200,7 +231,7 @@ application {
}

ext {
apiCourtScheduleVersion="0.3.0"
apiCourtScheduleVersion="0.3.3"
log4JVersion = "2.24.3"
logbackVersion = "1.5.18"
lombokVersion = "1.18.38"
Expand All @@ -216,8 +247,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-json'

implementation 'org.springframework.cloud:spring-cloud-starter-sleuth:3.1.11'
implementation 'com.azure.spring:spring-cloud-azure-trace-sleuth:4.20.0'
implementation platform('io.micrometer:micrometer-tracing-bom:latest.release')
implementation 'io.micrometer:micrometer-tracing'
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
implementation 'io.micrometer:micrometer-registry-azure-monitor'
implementation 'com.azure:azure-monitor-opentelemetry-exporter:1.0.0-beta.17'

implementation 'net.logstash.logback:logstash-logback-encoder:8.1'
implementation group: 'org.apache.logging.log4j', name: 'log4j-to-slf4j', version: log4JVersion
Expand All @@ -232,6 +266,8 @@ dependencies {
annotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombokVersion

testImplementation(platform('org.junit:junit-bom:5.12.2'))
testImplementation 'org.mockito:mockito-core:5.18.0'
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '3.5.0', {
exclude group: 'junit', module: 'junit'
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,31 +1,79 @@
package uk.gov.hmcts.cp.controllers;

import org.junit.jupiter.api.DisplayName;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import uk.gov.hmcts.cp.openapi.model.CourtScheduleResponse;
import uk.gov.hmcts.cp.services.CourtScheduleService;

import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
class CourtScheduleControllerITest {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this class name can be CourtScheduleControllerIT

private static final Logger log = LoggerFactory.getLogger(CourtScheduleControllerITest.class);

@Autowired
private MockMvc mockMvc;

@DisplayName("Should /case/{case_urn}/courtschedule request with 200 response code")
@Test
void shouldCallActuatorAndGet200() throws Exception {
mockMvc.perform(get("/case/123/courtschedule"))
.andDo(print())
.andExpect(status().isOk());
void shouldReturnOkWhenValidUrnIsProvided() throws Exception {
String caseUrn = "test-case-urn";
CourtScheduleResponse expectedResponse = CourtScheduleResponse.builder().build();

mockMvc.perform(get("/case/{case_urn}/courtschedule", caseUrn)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(result -> {
// You may need to adjust this depending on the actual fields in CourtScheduleResponse
String responseBody = result.getResponse().getContentAsString();
log.info("Response: {}", responseBody);
JsonNode jsonBody = new ObjectMapper().readTree(result.getResponse().getContentAsString());


assertEquals("courtSchedule", jsonBody.fieldNames().next());
JsonNode courtSchedule = jsonBody.get("courtSchedule");
assertTrue(courtSchedule.isArray());
assertEquals(1, courtSchedule.size());

JsonNode hearings = courtSchedule.get(0).get("hearings");
assertTrue(hearings.isArray());
assertEquals(1, hearings.size());

JsonNode hearing = hearings.get(0);
UUID hearingId = UUID.fromString(hearing.get("hearingId").asText());
assertNotNull(hearingId);
assertEquals("Requires interpreter", hearing.get("listNote").asText());
assertEquals("Sentencing for theft case", hearing.get("hearingDescription").asText());
assertEquals("Trial", hearing.get("hearingType").asText());

JsonNode courtSittings = hearing.get("courtSittings");
assertTrue(courtSittings.isArray());
assertEquals(1, courtSittings.size());

JsonNode sitting = courtSittings.get(0);
assertEquals("Central Criminal Court", sitting.get("courtHouse").asText());
assertNotNull(sitting.get("sittingStart").asText());
assertNotNull(sitting.get("sittingEnd").asText());
UUID judiciaryId = UUID.fromString(sitting.get("judiciaryId").asText());
assertNotNull(sitting.get("judiciaryId").asText());
log.info("Response Object: {}", jsonBody);
});
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
Expand All @@ -29,14 +30,16 @@ public ResponseEntity<CourtScheduleResponse> getCourtScheduleByCaseUrn(String ca
courtScheduleResponse = courtScheduleService.getCourtScheduleResponse(sanitizedCaseUrn);
} catch (ResponseStatusException e) {
log.error(e.getMessage());
return ResponseEntity.status(e.getStatusCode()).build();
throw e;
}
log.debug("Found court schedule for caseUrn: {}", sanitizedCaseUrn);
return new ResponseEntity<>(courtScheduleResponse, HttpStatus.OK);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(courtScheduleResponse);
}

private String sanitizeCaseUrn(String urn) {
if (urn == null) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "caseUrn is required");;
if (urn == null) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "caseUrn is required");
return StringEscapeUtils.escapeHtml4(urn);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package uk.gov.hmcts.cp.controllers;

import io.micrometer.tracing.Tracer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.server.ResponseStatusException;
import uk.gov.hmcts.cp.openapi.model.ErrorResponse;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Objects;

@RestControllerAdvice
public class GlobalExceptionHandler {

private final Tracer tracer;

public GlobalExceptionHandler(Tracer tracer) {
this.tracer = tracer;
}

@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<ErrorResponse> handleResponseStatusException(ResponseStatusException e) {
ErrorResponse error = ErrorResponse.builder()
.error(String.valueOf(e.getStatusCode().value()))
.message(e.getReason() != null ? e.getReason() : e.getMessage())
.timestamp(OffsetDateTime.now(ZoneOffset.UTC))
.traceId(Objects.requireNonNull(tracer.currentSpan()).context().traceId())
.build();

return ResponseEntity
.status(e.getStatusCode())
.body(error);
}
}
Loading