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
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
SERVER_PORT=4550
BASE_IMAGE=openjdk:21-jdk-slim
BASE_IMAGE=openjdk:21-jdk-slim
43 changes: 43 additions & 0 deletions .github/workflows/publish-pact-verification.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Run pact provider test and publish verification report to pact broker

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

jobs:
publish-provider-pacts:
runs-on: ubuntu-latest

env:
PACT_BROKER_URL: https://hmcts-dts.pactflow.io
PACT_BROKER_HOST: hmcts-dts.pactflow.io
PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
PACT_VERIFIER_PUBLISH_RESULTS: true
GIT_COMMIT: ${{ github.sha }}
GIT_BRANCH: ${{ github.ref_name }}
PACT_ENV: dev/pactTest


steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 21

- name: Install Pact CLI
run: npm install -g @pact-foundation/pact-cli

- name: Make provider script executable
run: chmod +x publish-pacts.sh

- name: Run provider publish script
run: ./publish-pacts.sh
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ applicationinsights-agent-*.jar
*.log

.DS_Store
*/.DS_Store
*/.DS_Store

.env
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

Further documentation see the [HMCTS Marketplace Springboot template readme](https://github.com/hmcts/service-hmcts-marketplace-springboot-template/blob/main/README.md).

### Run pact provider test and publish verification report to pact broker locally

update .env file with below details
PACT_BROKER_URL= <<PactFlow broker url>>
PACT_BROKER_HOST=<<PactFlow broker url excluding https://>>
PACT_BROKER_TOKEN= <<Your PactFlow broker token>>
PACT_ENV= << This is the environment in PactFlow broker to which we tag the contracts>
PACT_VERIFIER_PUBLISH_RESULTS=true
run ./publish-pacts.sh

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
25 changes: 24 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ plugins {
id 'maven-publish'
id "com.github.ben-manes.versions" version "0.52.0"
id "org.cyclonedx.bom" version "2.3.1"
id "au.com.dius.pact" version "4.6.17"
}

group = 'uk.gov.hmcts.cp'
Expand Down Expand Up @@ -85,7 +86,7 @@ tasks.named('test') {
// 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'
"-javaagent:${configurations.testRuntimeClasspath.find { it.name.contains('mockito-core') }}", '-Xshare:off'
]
testLogging {
events "passed", "skipped", "failed"
Expand Down Expand Up @@ -272,4 +273,26 @@ dependencies {
exclude group: 'junit', module: 'junit'
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}

testImplementation 'au.com.dius.pact.provider:junit5:4.6.17'
testImplementation 'au.com.dius.pact.provider:spring6:4.6.17'

test{
useJUnitPlatform {
excludeTags 'pact'
}
}

tasks.register('pactVerificationTest', Test) {
useJUnitPlatform {
includeTags 'pact'
}
systemProperty 'pact.broker.url', System.getenv('PACT_BROKER_URL')
systemProperty 'pact.broker.token', System.getenv('PACT_BROKER_TOKEN')
systemProperty 'pact.provider.version', System.getenv('GIT_COMMIT') ?: 'dev'
systemProperty 'pact.provider.branch', System.getenv('GIT_BRANCH')
systemProperty 'pact.verifier.publishResults', System.getenv('PACT_VERIFIER_PUBLISH_RESULTS')
systemProperty 'pact.broker.host', System.getenv('PACT_BROKER_HOST')
systemProperty 'pact.env', System.getenv('PACT_ENV')
}
}
35 changes: 35 additions & 0 deletions publish-pacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

# Load env vars from .env if available
if [ -f .env ]; then
set -a
source .env
set +a
fi

# Export Git metadata
export GIT_COMMIT=$(git rev-parse HEAD)

if [ -n "$GIT_BRANCH" ]; then
BRANCH_NAME="$GIT_BRANCH"
else
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
fi

export GIT_BRANCH="$BRANCH_NAME"

# Run provider verification with results published
gradle pactVerificationTest \
-Dpact.provider.version="$GIT_COMMIT" \
-Dpact.verifier.publishResults=true \
-Dpact.provider.branch="$GIT_BRANCH" \
-DPACT_BROKER_TOKEN="$PACT_BROKER_TOKEN" \
-DPACT_BROKER_HOST="$PACT_BROKER_URL"

# Tag provider in the broker
pact-broker create-version-tag \
--pacticipant "VPCourtScheduleProvider" \
--version "$GIT_COMMIT" \
--tag "$PACT_ENV" \
--broker-base-url "$PACT_BROKER_URL" \
--broker-token "$PACT_BROKER_TOKEN"
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import org.springframework.stereotype.Repository;
import uk.gov.hmcts.cp.openapi.model.CourtScheduleResponse;

@FunctionalInterface
@Repository
public interface CourtScheduleRepository {
public interface CourtScheduleRepository {

CourtScheduleResponse getCourtScheduleByCaseUrn(String caseUrn);
void saveCourtSchedule(String caseUrn, CourtScheduleResponse courtScheduleResponse);
void clearAll();

}
Original file line number Diff line number Diff line change
@@ -1,40 +1,64 @@
package uk.gov.hmcts.cp.repositories;

import org.springframework.stereotype.Component;
import uk.gov.hmcts.cp.openapi.model.CourtSchedule;
import uk.gov.hmcts.cp.openapi.model.CourtScheduleResponse;
import uk.gov.hmcts.cp.openapi.model.CourtSitting;
import uk.gov.hmcts.cp.openapi.model.CourtSchedule;
import uk.gov.hmcts.cp.openapi.model.Hearing;
import uk.gov.hmcts.cp.openapi.model.CourtSitting;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class InMemoryCourtScheduleRepositoryImpl implements CourtScheduleRepository {

private final Map<String, CourtScheduleResponse> courtScheduleResponseMap = new ConcurrentHashMap<>();

public void saveCourtSchedule(final String caseUrn, final CourtScheduleResponse courtScheduleResponse) {
courtScheduleResponseMap.put(caseUrn, courtScheduleResponse);
}

public CourtScheduleResponse getCourtScheduleByCaseUrn(final String caseUrn) {
final Hearing courtScheduleHearing = Hearing.builder()
if (!courtScheduleResponseMap.containsKey(caseUrn)) {
saveCourtSchedule(caseUrn, createCourtScheduleResponse());
}
return courtScheduleResponseMap.get(caseUrn);
}

public void clearAll() {
courtScheduleResponseMap.clear();
}

private CourtScheduleResponse createCourtScheduleResponse() {

final OffsetDateTime sittingStartTime = OffsetDateTime.now(ZoneOffset.UTC)
.truncatedTo(ChronoUnit.SECONDS);

final Hearing hearing = Hearing.builder()
.hearingId(UUID.randomUUID().toString())
.listNote("Requires interpreter")
.hearingDescription("Sentencing for theft case")
.hearingType("Trial")
.courtSittings(List.of(
CourtSitting.builder()
.courtHouse("Central Criminal Court")
.sittingStart(OffsetDateTime.now())
.sittingEnd(OffsetDateTime.now().plusMinutes(60))
.sittingStart(sittingStartTime)
.sittingEnd(sittingStartTime.plusMinutes(60))
.judiciaryId(UUID.randomUUID().toString())
.build())
).build();

return CourtScheduleResponse.builder()
.courtSchedule(List.of(
CourtSchedule.builder()
.hearings(List.of(courtScheduleHearing)
.hearings(List.of(hearing)
).build()
)
).build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.web.server.ResponseStatusException;
import uk.gov.hmcts.cp.openapi.model.CourtSchedule;
import uk.gov.hmcts.cp.openapi.model.CourtScheduleResponse;

import uk.gov.hmcts.cp.openapi.model.CourtSitting;
import uk.gov.hmcts.cp.openapi.model.Hearing;
import uk.gov.hmcts.cp.repositories.CourtScheduleRepository;
Expand Down Expand Up @@ -52,24 +53,24 @@ void getJudgeById_ShouldReturnJudgesWithOkStatus() {
assertNotNull(responseBody.getCourtSchedule());
assertEquals(1, responseBody.getCourtSchedule().size());

CourtSchedule schedule = responseBody.getCourtSchedule().get(0);
assertNotNull(schedule.getHearings());
assertEquals(1, schedule.getHearings().size());
CourtSchedule courtSchedule = responseBody.getCourtSchedule().get(0);
assertNotNull(courtSchedule.getHearings());
assertEquals(1, courtSchedule.getHearings().size());

Hearing hearing = schedule.getHearings().get(0);
Hearing hearing = courtSchedule.getHearings().get(0);
assertNotNull(hearing.getHearingId());
assertEquals("Requires interpreter", hearing.getListNote());
assertEquals("Sentencing for theft case", hearing.getHearingDescription());
assertEquals("Trial", hearing.getHearingType());
assertNotNull(hearing.getCourtSittings());
assertEquals(1, hearing.getCourtSittings().size());

CourtSitting sitting =
CourtSitting courtSitting =
hearing.getCourtSittings().get(0);
assertEquals("Central Criminal Court", sitting.getCourtHouse());
assertNotNull(sitting.getSittingStart());
assertTrue(sitting.getSittingEnd().isAfter(sitting.getSittingStart()));
assertNotNull(sitting.getJudiciaryId());
assertEquals("Central Criminal Court", courtSitting.getCourtHouse());
assertNotNull(courtSitting.getSittingStart());
assertTrue(courtSitting.getSittingEnd().isAfter(courtSitting.getSittingStart()));
assertNotNull(courtSitting.getJudiciaryId());

}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package uk.gov.hmcts.cp.pact.provider;

import au.com.dius.pact.provider.junit5.HttpTestTarget;
import au.com.dius.pact.provider.junit5.PactVerificationContext;
import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider;
import au.com.dius.pact.provider.junitsupport.Provider;
import au.com.dius.pact.provider.junitsupport.State;
import au.com.dius.pact.provider.junitsupport.loader.PactBroker;
import au.com.dius.pact.provider.junitsupport.loader.PactBrokerAuth;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import uk.gov.hmcts.cp.openapi.model.CourtScheduleResponse;
import uk.gov.hmcts.cp.openapi.model.CourtSchedule;
import uk.gov.hmcts.cp.openapi.model.Hearing;
import uk.gov.hmcts.cp.openapi.model.CourtSitting;
import uk.gov.hmcts.cp.repositories.CourtScheduleRepository;

import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith({SpringExtension.class, PactVerificationInvocationContextProvider.class})
@Provider("VPCourtScheduleProvider")
@PactBroker(
scheme = "https",
host = "${pact.broker.host}",
authentication = @PactBrokerAuth(token = "${pact.broker.token}")
)
@Tag("pact")
public class CourtScheduleProviderPactTest {

@Autowired
private CourtScheduleRepository courtScheduleRepository;

@LocalServerPort
private int port;

@BeforeEach
void setupTarget(PactVerificationContext context) {
System.out.println("Running test on port: " + port);
context.setTarget(new HttpTestTarget("localhost", port));
System.out.println("pact.verifier.publishResults: " + System.getProperty("pact.verifier.publishResults"));
}

@State("court schedule for case 456789 exists")
public void setupCourtSchedule() {
courtScheduleRepository.clearAll();
var courtSitting = CourtSitting.builder()
.courtHouse("Central Criminal Court")
.sittingStart(OffsetDateTime.now())
.sittingEnd(OffsetDateTime.now().plusMinutes(60))
.judiciaryId(UUID.randomUUID().toString())
.build();
var hearing = Hearing.builder()
.hearingId(UUID.randomUUID().toString())
.listNote("Requires interpreter")
.hearingDescription("Sentencing for theft case")
.hearingType("Trial")
.courtSittings(List.of(courtSitting))
.build();

var schedule = CourtSchedule.builder()
.hearings(List.of(hearing))
.build();

var response = CourtScheduleResponse.builder()
.courtSchedule(List.of(schedule))
.build();

courtScheduleRepository.saveCourtSchedule("456789", response);
}

@TestTemplate
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void getCourtScheduleByCaseUrn_shouldReturnCourtScheduleResponse() {
assertNotNull(hearing.getCourtSittings());
assertEquals(1, hearing.getCourtSittings().size());

CourtSitting sitting =
CourtSitting sitting =
hearing.getCourtSittings().get(0);
assertEquals("Central Criminal Court", sitting.getCourtHouse());
assertNotNull(sitting.getSittingStart());
Expand Down
Loading