diff --git a/README.md b/README.md index 0b1bc35a92..e8e02c4ed9 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,16 @@ There are also several custom test scripts available: - `test:chrome` - runs the full E2E suite in Chrome - `test:firefox` - runs the full E2E suite in Firefox +### Running pcs-api with local CCD + +```bash +./gradlew bootWithCCD +``` +Above command starts PCS API + CCD & all dependencies + +Once successfully loaded open XUI at http://localhost:3000 +See CftlibConfig.java for users and login details. + ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details diff --git a/build.gradle b/build.gradle index 3105bc3ce4..428fc2d985 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,6 @@ +import uk.gov.hmcts.rse.AuthMode +import uk.gov.hmcts.rse.CftlibExec + plugins { id 'application' id 'jacoco' @@ -6,6 +9,9 @@ plugins { id 'com.github.ben-manes.versions' version '0.52.0' id 'org.sonarqube' version '6.0.1.5171' id 'net.serenity-bdd.serenity-gradle-plugin' version '4.2.16' + id 'hmcts.ccd.sdk' version '5.5.16' + id 'com.github.hmcts.rse-cft-lib' version '0.19.1600' + id 'io.freefair.lombok' version '8.12.1' id 'au.com.dius.pact' version '4.6.17' /* Applies analysis tools including checkstyle and OWASP Dependency checker. @@ -70,6 +76,9 @@ configurations { smokeTestImplementation.extendsFrom testImplementation smokeTestRuntimeOnly.extendsFrom runtimeOnly + + cftlibTestImplementation.extendsFrom testImplementation + cftlibTestRuntime.extendsFrom testRuntime } tasks.withType(JavaCompile) { @@ -201,7 +210,7 @@ def getCheckedOutGitCommitHash() { } jacocoTestReport { - executionData(test, integration) + executionData(test, integration, cftlibTest) reports { xml.required = true csv.required = false @@ -264,12 +273,15 @@ dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-jdbc' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security' + implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.8.5' implementation group: 'uk.gov.service.notify', name: 'notifications-java-client', version: '5.2.1-RELEASE' implementation platform(group: 'com.azure.spring', name :'spring-cloud-azure-dependencies', version :'5.20.1') implementation "com.azure.spring:spring-cloud-azure-starter-servicebus-jms" implementation group: 'com.github.hmcts.java-logging', name: 'logging', version: '6.1.8' + implementation group: 'com.github.hmcts', name: 'ccd-client', version: '5.0.3' + implementation group: 'com.github.hmcts', name: 'idam-java-client', version: '3.0.3' implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4JVersion implementation group: 'org.apache.logging.log4j', name: 'log4j-to-slf4j', version: log4JVersion @@ -303,6 +315,8 @@ dependencies { exclude group: 'junit', module: 'junit' exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } + // Allow fast reloading during dev; recompile a class to trigger fast reload + definition reimport. + cftlibImplementation 'org.springframework.boot:spring-boot-devtools' } mainClassName = 'uk.gov.hmcts.reform.pcs.Application' @@ -316,10 +330,19 @@ bootJar { } // Gradle 7.x issue, workaround from: https://github.com/gradle/gradle/issues/17236#issuecomment-894768083 -rootProject.tasks.named("processSmokeTestResources") { +project.tasks.named("processSmokeTestResources") { duplicatesStrategy = 'include' } +tasks.withType(CftlibExec).configureEach { + group = 'ccd tasks' + authMode = AuthMode.Local + environment 'SPRING_PROFILES_ACTIVE', 'dev' +} + +// Ensure we cover Cftlib dev & test with our CI checks +check.dependsOn cftlibTest + rootProject.tasks.named("processContractTestResources") { duplicatesStrategy = 'include' } diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..c49ebda762 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url "https://jitpack.io" + } + } +} diff --git a/src/cftlib/java/uk/gov/hmcts/reform/pcs/CftlibConfig.java b/src/cftlib/java/uk/gov/hmcts/reform/pcs/CftlibConfig.java new file mode 100644 index 0000000000..e893a10c69 --- /dev/null +++ b/src/cftlib/java/uk/gov/hmcts/reform/pcs/CftlibConfig.java @@ -0,0 +1,47 @@ +package uk.gov.hmcts.reform.pcs; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import uk.gov.hmcts.ccd.sdk.CCDDefinitionGenerator; +import uk.gov.hmcts.reform.pcs.ccd.domain.State; +import uk.gov.hmcts.rse.ccd.lib.api.CFTLib; +import uk.gov.hmcts.rse.ccd.lib.api.CFTLibConfigurer; + +import java.io.File; +import java.util.List; +import java.util.Map; + +/** + * Configures the CFTLib with the required users, roles and CCD definitions. + * The Cftlib will find and execute this configuration class once all services are ready. + */ +@Component +public class CftlibConfig implements CFTLibConfigurer { + @Autowired + @Lazy + CCDDefinitionGenerator configWriter; + + @Override + public void configure(CFTLib lib) throws Exception { + var users = Map.of( + "caseworker@pcs.com", List.of("caseworker", "caseworker-civil")); + + // Create users and roles including in idam simulator + for (var entry : users.entrySet()) { + lib.createIdamUser(entry.getKey(), entry.getValue().toArray(new String[0])); + lib.createProfile(entry.getKey(), "CIVIL", "PCS", State.Open.name()); + } + + lib.createRoles( + "caseworker", + "caseworker-civil" + ); + + // Generate CCD definitions + configWriter.generateAllCaseTypesToJSON(new File("build/definitions")); + + // Import CCD definitions + lib.importJsonDefinition(new File("build/definitions/PCS")); + } +} diff --git a/src/cftlibTest/java/uk/gov/hmcts/reform/pcs/TestWithCCD.java b/src/cftlibTest/java/uk/gov/hmcts/reform/pcs/TestWithCCD.java new file mode 100644 index 0000000000..71ea2e00ca --- /dev/null +++ b/src/cftlibTest/java/uk/gov/hmcts/reform/pcs/TestWithCCD.java @@ -0,0 +1,56 @@ +package uk.gov.hmcts.reform.pcs; + + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import uk.gov.hmcts.reform.ccd.client.CoreCaseDataApi; +import uk.gov.hmcts.reform.ccd.client.model.CaseDataContent; +import uk.gov.hmcts.reform.ccd.client.model.CaseDetails; +import uk.gov.hmcts.reform.ccd.client.model.Event; +import uk.gov.hmcts.reform.idam.client.IdamClient; +import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.rse.ccd.lib.test.CftlibTest; + +import static org.assertj.core.api.Assertions.assertThat; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestWithCCD extends CftlibTest { + + @Autowired + private CoreCaseDataApi ccdApi; + + @Autowired + private IdamClient idamClient; + + CaseDetails caseDetails; + String idamToken; + String s2sToken; + String userId; + + @BeforeAll + public void setup() { + idamToken = idamClient.getAccessToken("caseworker@pcs.com", "password"); + s2sToken = generateDummyS2SToken("ccd_gw"); + userId = idamClient.getUserInfo(idamToken).getUid(); + } + + @Order(1) + @Test + public void createsTestCase() { + var r = ccdApi.startCase(idamToken, s2sToken, "PCS", "createTestApplication"); + var content = CaseDataContent.builder() + .data(PCSCase.builder().applicantForename("Foo").build()) + .event(Event.builder().id("createTestApplication").build()) + .eventToken(r.getToken()) + .build(); + caseDetails = ccdApi.submitForCaseworker(idamToken, s2sToken, userId, + "CIVIL", "PCS", false, content); + assertThat(caseDetails.getId()).isNotNull(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/Application.java b/src/main/java/uk/gov/hmcts/reform/pcs/Application.java index e20a605d60..2c76dea879 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/Application.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/Application.java @@ -3,17 +3,21 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; +import uk.gov.hmcts.reform.idam.client.IdamApi; import org.springframework.jms.annotation.EnableJms; import uk.gov.hmcts.reform.pcs.hearings.service.api.HmcHearingApi; @SpringBootApplication( - scanBasePackages = {"uk.gov.hmcts.reform.pcs", + scanBasePackages = { + "uk.gov.hmcts.reform.pcs", + "uk.gov.hmcts.ccd.sdk", "uk.gov.hmcts.reform.pcs.hearings", }) @SuppressWarnings("HideUtilityClassConstructor") // Spring needs a constructor, its not a utility class @EnableFeignClients( clients = { - HmcHearingApi.class + HmcHearingApi.class, + IdamApi.class } ) @EnableJms diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/CaseType.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/CaseType.java new file mode 100644 index 0000000000..51e163101b --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/CaseType.java @@ -0,0 +1,40 @@ +package uk.gov.hmcts.reform.pcs.ccd; + +import org.springframework.stereotype.Component; +import uk.gov.hmcts.ccd.sdk.api.CCDConfig; +import uk.gov.hmcts.ccd.sdk.api.ConfigBuilder; +import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.reform.pcs.ccd.domain.State; +import uk.gov.hmcts.reform.pcs.ccd.domain.UserRole; + +/** + * Setup some common possessions case type configuration. + */ +@Component +public class CaseType implements CCDConfig { + + @Override + public void configure(final ConfigBuilder builder) { + builder.setCallbackHost("http://localhost:3206"); + + builder.caseType("PCS", "Civil Possessions", "Possessions"); + builder.jurisdiction("CIVIL", "Civil Possessions", "The new one"); + + var label = "Applicant Forename"; + builder.searchInputFields() + .field(PCSCase::getApplicantForename, label); + builder.searchCasesFields() + .field(PCSCase::getApplicantForename, label); + + builder.searchResultFields() + .field(PCSCase::getApplicantForename, label); + builder.workBasketInputFields() + .field(PCSCase::getApplicantForename, label); + builder.workBasketResultFields() + .field(PCSCase::getApplicantForename, label); + + builder.tab("Example", "Example Tab") + .field(PCSCase::getApplicantForename) + .field(PCSCase::getPartyA); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/DefaultStateAccess.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/DefaultStateAccess.java new file mode 100644 index 0000000000..15a7e2acb6 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/DefaultStateAccess.java @@ -0,0 +1,22 @@ +package uk.gov.hmcts.reform.pcs.ccd.domain; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; +import uk.gov.hmcts.ccd.sdk.api.HasAccessControl; +import uk.gov.hmcts.ccd.sdk.api.HasRole; +import uk.gov.hmcts.ccd.sdk.api.Permission; + +import static uk.gov.hmcts.reform.pcs.ccd.domain.UserRole.CASE_WORKER; + + +/** + * Placeholder access control configuration granting caseworker CRU. + */ +public class DefaultStateAccess implements HasAccessControl { + @Override + public SetMultimap getGrants() { + SetMultimap grants = HashMultimap.create(); + grants.putAll(CASE_WORKER, Permission.CRU); + return grants; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java new file mode 100644 index 0000000000..e4dfdabf1c --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java @@ -0,0 +1,18 @@ +package uk.gov.hmcts.reform.pcs.ccd.domain; + +import lombok.Builder; +import lombok.Data; +import uk.gov.hmcts.ccd.sdk.api.CCD; + +/** + * The main domain model representing a possessions case. + */ +@Builder +@Data +public class PCSCase { + @CCD(label = "Applicant's first name") + private String applicantForename; + + @CCD(label = "Party A") + private Party partyA; +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/Party.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/Party.java new file mode 100644 index 0000000000..75ba1ed016 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/Party.java @@ -0,0 +1,14 @@ +package uk.gov.hmcts.reform.pcs.ccd.domain; + +import lombok.Builder; +import lombok.Data; +import uk.gov.hmcts.ccd.sdk.api.CCD; + +@Builder +@Data +public class Party { + @CCD(label = "Party's forename") + private String forename; + @CCD(label = "Party's surname") + private String surname; +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java new file mode 100644 index 0000000000..8127010ea3 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java @@ -0,0 +1,21 @@ +package uk.gov.hmcts.reform.pcs.ccd.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import uk.gov.hmcts.ccd.sdk.api.CCD; + +/** + * All possible PCS case states. + * Converted into CCD states. + */ +@RequiredArgsConstructor +@Getter +public enum State { + + @CCD( + label = "Open", + access = {DefaultStateAccess.class} + ) + Open; +} + diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/UserRole.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/UserRole.java new file mode 100644 index 0000000000..4e7efffa16 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/UserRole.java @@ -0,0 +1,27 @@ +package uk.gov.hmcts.reform.pcs.ccd.domain; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; +import uk.gov.hmcts.ccd.sdk.api.HasRole; +import uk.gov.hmcts.ccd.sdk.api.Permission; + +import java.util.Set; + +/** + * All the different roles for a PCS case. + */ +@AllArgsConstructor +@Getter +public enum UserRole implements HasRole { + + CASE_WORKER("caseworker-civil", Permission.CRU); + + @JsonValue + private final String role; + private final Set caseTypePermissions; + + public String getCaseTypePermissions() { + return Permission.toString(caseTypePermissions); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/CreateTestCase.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/CreateTestCase.java new file mode 100644 index 0000000000..e2ef2bfc61 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/CreateTestCase.java @@ -0,0 +1,48 @@ +package uk.gov.hmcts.reform.pcs.ccd.event; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import uk.gov.hmcts.ccd.sdk.api.CCDConfig; +import uk.gov.hmcts.ccd.sdk.api.CaseDetails; +import uk.gov.hmcts.ccd.sdk.api.ConfigBuilder; +import uk.gov.hmcts.ccd.sdk.api.Permission; +import uk.gov.hmcts.ccd.sdk.api.callback.AboutToStartOrSubmitResponse; +import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.reform.pcs.ccd.domain.State; +import uk.gov.hmcts.reform.pcs.ccd.domain.UserRole; + +@Profile("dev") // Non-prod event +@Component +public class CreateTestCase implements CCDConfig { + @Override + public void configure(ConfigBuilder configBuilder) { + configBuilder + .event("createTestApplication") + .initialState(State.Open) + .name("Create test case") + .aboutToStartCallback(this::start) + .aboutToSubmitCallback(this::aboutToSubmit) + .grant(Permission.CRUD, UserRole.CASE_WORKER) + .fields() + .page("Create test case") + .mandatory(PCSCase::getApplicantForename) + .done(); + } + + private AboutToStartOrSubmitResponse start(CaseDetails caseDetails) { + PCSCase data = caseDetails.getData(); + data.setApplicantForename("Preset value"); + + return AboutToStartOrSubmitResponse.builder() + .data(caseDetails.getData()) + .build(); + } + + public AboutToStartOrSubmitResponse aboutToSubmit(CaseDetails details, + CaseDetails beforeDetails) { + // TODO: Whatever you need. + return AboutToStartOrSubmitResponse.builder() + .data(details.getData()) + .build(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/config/JacksonConfiguration.java b/src/main/java/uk/gov/hmcts/reform/pcs/config/JacksonConfiguration.java new file mode 100644 index 0000000000..d2f15b7b75 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/config/JacksonConfiguration.java @@ -0,0 +1,41 @@ +package uk.gov.hmcts.reform.pcs.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import static com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS; +import static com.fasterxml.jackson.databind.MapperFeature.INFER_BUILDER_TYPE_BINDINGS; + +@Configuration +public class JacksonConfiguration { + + @Primary + @Bean + public ObjectMapper getMapper() { + ObjectMapper mapper = JsonMapper.builder() + .configure(ACCEPT_CASE_INSENSITIVE_ENUMS, true) + .enable(INFER_BUILDER_TYPE_BINDINGS) + .serializationInclusion(JsonInclude.Include.NON_NULL) + .build(); + + JavaTimeModule datetime = new JavaTimeModule(); + datetime.addSerializer(LocalDateSerializer.INSTANCE); + mapper.registerModule(datetime); + + mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + mapper.registerModules(new Jdk8Module(), new JavaTimeModule(), new ParameterNamesModule()); + + return mapper; + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 0fead9843d..f9094d4e8f 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -67,12 +67,14 @@ spring: hearings-topic: ${HEARINGS_SERVICEBUS_TOPIC:hmc-to-cft-aat} hearings-subscription: ${HEARINGS_SERVICEBUS_SUBSCRIPTION:hmc-to-pcs-subscription-aat} idam: + api: + url: ${IDAM_API_URL:http://localhost:5062} s2s-auth: url: ${IDAM_S2S_AUTH_URL:http://localhost:4502} totp_secret: ${PCS_API_S2S_SECRET:AAAAAAAAAAAAAAAA} microservice: ${S2S_SERVICE_NAME:pcs_api} s2s-authorised: - services: ${S2S_NAMES_WHITELIST:pcs_api,pcs_frontend} + services: ${S2S_NAMES_WHITELIST:pcs_api,pcs_frontend,ccd_data} azure: application-insights: