diff --git a/Jenkinsfile_CNP b/Jenkinsfile_CNP index b84dbfb526..c70ee447d5 100644 --- a/Jenkinsfile_CNP +++ b/Jenkinsfile_CNP @@ -118,6 +118,9 @@ env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = "hmctspublic.azurecr.io/imported/" withPipeline(type, product, component) { onMaster { enableSlackNotifications('#ccd-master-builds') + enablePactAs([ + AppPipelineDsl.PactRoles.PROVIDER + ]) } if (env.BRANCH_NAME.equalsIgnoreCase(dataStoreApiDevelopPr)) { enableSlackNotifications('#ccd-develop-builds') @@ -128,6 +131,9 @@ withPipeline(type, product, component) { } onPR { enableSlackNotifications('#ccd-pr-builds') + enablePactAs([ + AppPipelineDsl.PactRoles.PROVIDER + ]) } // Check if the build should be wired to an environment higher than 'preview'. diff --git a/build.gradle b/build.gradle index 0635662e3d..bccb524053 100644 --- a/build.gradle +++ b/build.gradle @@ -21,14 +21,14 @@ plugins { id 'org.jetbrains.gradle.plugin.idea-ext' version '0.7' id 'info.solidsoft.pitest' version '1.15.0' id 'uk.gov.hmcts.java' version '0.12.57' - id 'au.com.dius.pact' version '4.1.0' + id 'au.com.dius.pact' version '4.3.12' id "org.jsonschema2pojo" version "1.2.1" } apply from: './gradle/suppress.gradle' def versions = [ - pact_version : '4.1.7', + pact_version : '4.3.4', ] ext['spring-security.version'] = '5.7.11' @@ -921,7 +921,7 @@ task runProviderPactVerification(type:Test) { systemProperty 'pact.verifier.publishResults', project.property('pact.verifier.publishResults') } systemProperty 'pact.provider.version', project.pactVersion - include "uk/gov/hmcts/ccd/v2/external/controller/**" + include "uk/gov/hmcts/reform/**" } runProviderPactVerification.finalizedBy pactVerify diff --git a/charts/ccd-data-store-api/Chart.yaml b/charts/ccd-data-store-api/Chart.yaml index 63e01b40f6..061385efc8 100644 --- a/charts/ccd-data-store-api/Chart.yaml +++ b/charts/ccd-data-store-api/Chart.yaml @@ -2,7 +2,7 @@ description: Helm chart for the HMCTS CCD Data Store name: ccd-data-store-api apiVersion: v2 home: https://github.com/hmcts/ccd-data-store-api -version: 2.0.30 +version: 2.0.31 maintainers: - name: HMCTS CCD Dev Team email: ccd-devops@HMCTS.NET diff --git a/charts/ccd-data-store-api/values.yaml b/charts/ccd-data-store-api/values.yaml index 9786145911..7ed7bebaab 100644 --- a/charts/ccd-data-store-api/values.yaml +++ b/charts/ccd-data-store-api/values.yaml @@ -90,3 +90,8 @@ java: DATA_STORE_CROSS_JURISDICTIONAL_ROLES: caseworker-caa,caseworker-approver,next-hearing-date-admin DATA_STORE_CITIZEN_ROLES: citizen,letter-holder DATA_STORE_SECURITY_LOGGING_FILTER_PATH_REGEX: 'DISABLED' + + PACT_BROKER_FULL_URL: https://pact-broker.platform.hmcts.net + PACT_BROKER_URL: pact-broker.platform.hmcts.net + PACT_BROKER_PORT: 443 + PACT_BROKER_SCHEME: https diff --git a/src/contractTest/java/uk/gov/hmcts/reform/ccd/pactprovider/cases/CasesProviderTest.java b/src/contractTest/java/uk/gov/hmcts/reform/ccd/pactprovider/cases/CasesProviderTest.java new file mode 100644 index 0000000000..9bcc028e8b --- /dev/null +++ b/src/contractTest/java/uk/gov/hmcts/reform/ccd/pactprovider/cases/CasesProviderTest.java @@ -0,0 +1,161 @@ +package uk.gov.hmcts.reform.ccd.pactprovider.cases; + +import au.com.dius.pact.provider.junit5.PactVerificationContext; +import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider; +import au.com.dius.pact.provider.junitsupport.IgnoreNoPactsToVerify; +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.VersionSelector; +import au.com.dius.pact.provider.spring.junit5.MockMvcTestTarget; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.TextNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.ccd.data.casedetails.SecurityClassification; +import uk.gov.hmcts.ccd.domain.model.callbacks.StartEventResult; +import uk.gov.hmcts.ccd.domain.model.definition.CaseDetails; +import uk.gov.hmcts.ccd.domain.model.std.CaseDataContent; +import uk.gov.hmcts.ccd.domain.service.createcase.CreateCaseOperation; +import uk.gov.hmcts.ccd.domain.service.createevent.CreateEventOperation; +import uk.gov.hmcts.ccd.domain.service.getcase.GetCaseOperation; +import uk.gov.hmcts.ccd.domain.service.startevent.StartEventOperation; +import uk.gov.hmcts.reform.ccd.pactprovider.cases.controller.CasesRestController; +import java.util.HashMap; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.when; + +@Provider("ccd_data_store_api_cases") +@PactBroker( + url = "${PACT_BROKER_FULL_URL:http://localhost:9292}", + consumerVersionSelectors = {@VersionSelector(tag = "master")}) +@IgnoreNoPactsToVerify +@ExtendWith(SpringExtension.class) +public class CasesProviderTest { + + @Mock + private GetCaseOperation getCaseOperation; + + @Mock + private StartEventOperation startEventOperation; + + @Mock + private CreateEventOperation createEventOperation; + + @Mock + private CreateCaseOperation createCaseOperation; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @BeforeEach + void beforeCreate(PactVerificationContext context) { + MockMvcTestTarget testTarget = new MockMvcTestTarget(); + testTarget.setControllers(new CasesRestController(getCaseOperation, startEventOperation, createEventOperation, + createCaseOperation)); + if (context != null) { + context.setTarget(testTarget); + } + } + + @TestTemplate + @ExtendWith(PactVerificationInvocationContextProvider.class) + void pactVerificationTestTemplate(PactVerificationContext context) { + if (context != null) { + context.verifyInteraction(); + } + } + + @State("A Get Case is requested") + public void getCase() throws JsonProcessingException { + when(getCaseOperation.execute(anyString())).thenReturn(Optional.of(mockCaseDetails())); + } + + @State("A Start event for a Citizen is requested") + public void startEventForCitizen() throws JsonProcessingException { + when(startEventOperation.triggerStartForCase(anyString(), anyString(), anyBoolean())) + .thenReturn(mockStartEventResult()); + } + + @State("A Start case for a Citizen is requested") + public void startCaseForCitizen() throws JsonProcessingException { + when(startEventOperation.triggerStartForCaseType(anyString(), anyString(), anyBoolean())) + .thenReturn(mockStartEventResult()); + } + + @State("A Submit event for a Citizen is requested") + public void createCaseEventForCitizen() throws JsonProcessingException { + when(createEventOperation.createCaseEvent(anyString(), nullable(CaseDataContent.class))) + .thenReturn(mockCaseDetails()); + } + + @State("A Submit case for a Citizen is requested") + public void saveCaseDetailsForCitizen() throws JsonProcessingException { + when(createCaseOperation.createCaseDetails(anyString(), nullable(CaseDataContent.class), anyBoolean())) + .thenReturn(mockCaseDetails()); + } + + private StartEventResult mockStartEventResult() throws JsonProcessingException { + StartEventResult startEventResult = new StartEventResult(); + startEventResult.setToken("someToken"); + startEventResult.setEventId("startAppeal"); + startEventResult.setCaseDetails(mockCaseDetails()); + return startEventResult; + } + + private CaseDetails mockCaseDetails() throws JsonProcessingException { + CaseDetails caseDetails = new CaseDetails(); + caseDetails.setReference(1L); + caseDetails.setCaseTypeId("ET_EnglandWales"); + caseDetails.setJurisdiction("EMPLOYMENT"); + caseDetails.setSecurityClassification(SecurityClassification.PUBLIC); + caseDetails.setAfterSubmitCallbackResponseEntity(new ResponseEntity<>(HttpStatus.OK)); + caseDetails.setState("appealStarted"); + caseDetails.setData(new HashMap() {{ + put("appealReferenceNumber", new TextNode("DRAFT")); + put("appealType", new TextNode("protection")); + put("appellantDateOfBirth", new TextNode("1990-12-07")); + put("appellantFamilyName", new TextNode("Smith")); + put("appellantGivenNames", new TextNode("Bob")); + put("appellantNameForDisplay", new TextNode("Bob Smith")); + put("appellantTitle", new TextNode("Mr")); + put("applicationOutOfTimeExplanation", new TextNode("test case")); + put("caseManagementLocation", objectMapper.readTree("{\"baseLocation\":\"765324\",\"region\":" + + "\"1\"}")); + put("currentCaseStateVisibleToLegalRepresentative", new TextNode("appealStarted")); + put("homeOfficeDecisionDate", new TextNode("2019-08-01")); + put("homeOfficeReferenceNumber", new TextNode("000123456")); + put("legalRepCompanyAddress", objectMapper.readTree("{\"AddressLine1\":\"\",\"AddressLine2\":" + + "\"\",\"AddressLine3\":\"\",\"Country\":\"\",\"PostCode\":\"\",\"PostTown\":\"\"}")); + put("legalRepCompanyName", new TextNode("")); + put("staffLocation", new TextNode("Taylor House")); + put("submissionOutOfTime", new TextNode("Yes")); + put("subscriptions", objectMapper.readTree("[{\"id\":\"1\",\"value\":{\"email\":" + + "\"test@example.com\",\"mobileNumber\":\"0111111111\",\"subscriber\":\"appellant\"," + + "\"wantsEmail\":\"Yes\",\"wantsSms\":\"Yes\"}}]")); + put("uploadAddendumEvidenceLegalRepActionAvailable", new TextNode("No")); + put("uploadAdditionalEvidenceActionAvailable", new TextNode("No")); + put("uploadTheNoticeOfDecisionDocs", objectMapper.readTree("[{\"id\":\"1\",\"value\":" + + "{\"description\":\"some notice of decision description\",\"document\":{\"document_binary_url\":" + + "\"http://dm-store-aat.service.core-compute-aat.internal/documents/" + + "7f63ca9b-c361-49ab-aa8c-8fbdb6bc2936\",\"document_filename\":" + + "\"some-notice-of-decision-letter.pdf\",\"document_url\":" + + "\"http://dm-store-aat.service.core-compute-aat.internal/documents/" + + "7f63ca9b-c361-49ab-aa8c-8fbdb6bc2936\"}}}]")); + put("caseSource", new TextNode("Manually Created")); + put("caseType", new TextNode("Single")); + } + }); + return caseDetails; + } +} diff --git a/src/contractTest/java/uk/gov/hmcts/reform/ccd/pactprovider/cases/controller/CasesRestController.java b/src/contractTest/java/uk/gov/hmcts/reform/ccd/pactprovider/cases/controller/CasesRestController.java new file mode 100644 index 0000000000..bf07c2e585 --- /dev/null +++ b/src/contractTest/java/uk/gov/hmcts/reform/ccd/pactprovider/cases/controller/CasesRestController.java @@ -0,0 +1,188 @@ +package uk.gov.hmcts.reform.ccd.pactprovider.cases.controller; + +import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import uk.gov.hmcts.ccd.domain.model.callbacks.StartEventResult; +import uk.gov.hmcts.ccd.domain.model.definition.CaseDetails; +import uk.gov.hmcts.ccd.domain.model.std.CaseDataContent; +import uk.gov.hmcts.ccd.domain.service.createcase.CreateCaseOperation; +import uk.gov.hmcts.ccd.domain.service.createevent.CreateEventOperation; +import uk.gov.hmcts.ccd.domain.service.getcase.CaseNotFoundException; +import uk.gov.hmcts.ccd.domain.service.getcase.GetCaseOperation; +import uk.gov.hmcts.ccd.domain.service.startevent.StartEventOperation; +import uk.gov.hmcts.ccd.v2.external.resource.CaseResource; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@RestController +public class CasesRestController { + + private final GetCaseOperation getCaseOperation; + private final StartEventOperation startEventOperation; + private final CreateEventOperation createEventOperation; + private final CreateCaseOperation createCaseOperation; + + public CasesRestController(final GetCaseOperation getCaseOperation, + final StartEventOperation startEventOperation, + final CreateEventOperation createEventOperation, + final CreateCaseOperation createCaseOperation) { + this.getCaseOperation = getCaseOperation; + this.startEventOperation = startEventOperation; + this.createEventOperation = createEventOperation; + this.createCaseOperation = createCaseOperation; + } + + /* + * Handle Pact State "A Get Case is requested". + */ + @Operation(description = "A Get Case is requested", + security = {@SecurityRequirement(name = "ServiceAuthorization"), @SecurityRequirement(name = "Authorization")}) + @ApiResponses({ + @ApiResponse( + responseCode = "200", description = "OK", + content = { + @Content(mediaType = "application/json") + }) + }) + @GetMapping(path = "/cases/{caseId}", produces = APPLICATION_JSON_VALUE) + public ResponseEntity getCase(@PathVariable("caseId") final String caseId) { + CaseDetails caseDetails = getCaseOperation.execute(caseId) + .orElseThrow(() -> new CaseNotFoundException(caseId)); + + return ResponseEntity.ok(new CaseResource(caseDetails)); + } + + /* + * Handle Pact State "A Start event for a Citizen is requested". + */ + @Operation(description = "A Start event for a Citizen is requested", + security = {@SecurityRequirement(name = "ServiceAuthorization"), @SecurityRequirement(name = "Authorization")}) + @ApiResponses({ + @ApiResponse( + responseCode = "200", description = "OK", + content = { + @Content(mediaType = "application/json") + }) + }) + @GetMapping( + path = "/citizens/{uid}/jurisdictions/{jid}/case-types/{ctid}/cases/{cid}/event-triggers/{etid}/token", + produces = APPLICATION_JSON_VALUE) + public ResponseEntity startEventForCitizen( + @ApiParam(value = "Idam user ID", required = true) + @PathVariable("uid") final String uid, + @ApiParam(value = "Jurisdiction ID", required = true) + @PathVariable("jid") final String jurisdictionId, + @ApiParam(value = "Case type ID", required = true) + @PathVariable("ctid") final String caseTypeId, + @ApiParam(value = "Case ID", required = true) + @PathVariable("cid") final String caseId, + @ApiParam(value = "Event ID", required = true) + @PathVariable("etid") final String eventId, + @ApiParam(value = "Should `AboutToStart` callback warnings be ignored") + @RequestParam(value = "ignore-warning", required = false, defaultValue = "false") final Boolean ignoreWarning) { + + StartEventResult startEventResult = startEventOperation.triggerStartForCase(caseId, eventId, ignoreWarning); + return ResponseEntity.ok(startEventResult); + } + + /* + * Handle Pact State "A Start case for a Citizen is requested". + */ + @Operation(description = "A Start case for a Citizen is requested", + security = {@SecurityRequirement(name = "ServiceAuthorization"), @SecurityRequirement(name = "Authorization")}) + @ApiResponses({ + @ApiResponse( + responseCode = "200", description = "OK", + content = { + @Content(mediaType = "application/json") + }) + }) + @GetMapping(path = "/citizens/{uid}/jurisdictions/{jid}/case-types/{ctid}/event-triggers/{etid}/token", + produces = APPLICATION_JSON_VALUE) + public ResponseEntity startCaseForCitizen( + @ApiParam(value = "Idam user ID", required = true) + @PathVariable("uid") final String uid, + @ApiParam(value = "Jurisdiction ID", required = true) + @PathVariable("jid") final String jurisdictionId, + @ApiParam(value = "Case type ID", required = true) + @PathVariable("ctid") final String caseTypeId, + @ApiParam(value = "Event ID", required = true) + @PathVariable("etid") final String eventId, + @ApiParam(value = "Should `AboutToStart` callback warnings be ignored") + @RequestParam(value = "ignore-warning", required = false, defaultValue = "false") final Boolean ignoreWarning) { + + StartEventResult startEventResult = startEventOperation.triggerStartForCaseType(caseTypeId, eventId, + ignoreWarning); + return ResponseEntity.ok(startEventResult); + } + + /* + * Handle Pact State "A Submit event for a Citizen is requested". + */ + @Operation(description = "A Submit event for a Citizen is requested", + security = {@SecurityRequirement(name = "ServiceAuthorization"), @SecurityRequirement(name = "Authorization")}) + @ApiResponses({ + @ApiResponse( + responseCode = "201", description = "Created", + content = { + @Content(mediaType = "application/json") + }) + }) + @PostMapping(path = "/citizens/{uid}/jurisdictions/{jid}/case-types/{ctid}/cases/{cid}/events", + produces = APPLICATION_JSON_VALUE) + public ResponseEntity createCaseEventForCitizen( + @ApiParam(value = "Idam user ID", required = true) + @PathVariable("uid") final String uid, + @ApiParam(value = "Jurisdiction ID", required = true) + @PathVariable("jid") final String jurisdictionId, + @ApiParam(value = "Case type ID", required = true) + @PathVariable("ctid") final String caseTypeId, + @ApiParam(value = "Case ID", required = true) + @PathVariable("cid") final String caseId, + @RequestBody(required = false) final CaseDataContent content) { + + CaseDetails caseDetails = createEventOperation.createCaseEvent(caseId, content); + return ResponseEntity.status(HttpStatus.CREATED).body(caseDetails); + } + + /* + * Handle Pact State "A Submit case for a Citizen is requested". + */ + @Operation(description = "A Submit case for a Citizen is requested", + security = {@SecurityRequirement(name = "ServiceAuthorization"), @SecurityRequirement(name = "Authorization")}) + @ApiResponses({ + @ApiResponse( + responseCode = "201", description = "Created", + content = { + @Content(mediaType = "application/json") + }) + }) + @PostMapping(path = "/citizens/{uid}/jurisdictions/{jid}/case-types/{ctid}/cases", + produces = APPLICATION_JSON_VALUE) + public ResponseEntity saveCaseDetailsForCitizen( + @ApiParam(value = "Idam user ID", required = true) + @PathVariable("uid") final String uid, + @ApiParam(value = "Jurisdiction ID", required = true) + @PathVariable("jid") final String jurisdictionId, + @ApiParam(value = "Case type ID", required = true) + @PathVariable("ctid") final String caseTypeId, + @ApiParam(value = "Should `AboutToSubmit` callback warnings be ignored") + @RequestParam(value = "ignore-warning", required = false, defaultValue = "false") final Boolean ignoreWarning, + @RequestBody(required = false) final CaseDataContent content) { + + CaseDetails caseDetails = createCaseOperation.createCaseDetails(caseTypeId, content, ignoreWarning); + return ResponseEntity.status(HttpStatus.CREATED).body(caseDetails); + } +}