diff --git a/build.gradle b/build.gradle index 7e66865d..9a2ff365 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,6 @@ plugins { id 'application' id 'checkstyle' - id 'pmd' id 'jacoco' id 'io.spring.dependency-management' version '1.1.0' id 'org.springframework.boot' version '2.7.9' @@ -15,20 +14,15 @@ version = '0.0.1' java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(11) } } -configurations { - functionalTestImplementation.extendsFrom testImplementation - functionalTestRuntimeOnly.extendsFrom runtimeOnly - - integrationTestImplementation.extendsFrom testImplementation - integrationTestRuntimeOnly.extendsFrom runtimeOnly - - smokeTestImplementation.extendsFrom testImplementation - smokeTestRuntimeOnly.extendsFrom runtimeOnly -} +def versions = [ + junit : '5.7.0', + junitPlatform: '1.8.1', + lombok : '1.18.26' +] tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" << "-Werror" @@ -57,15 +51,6 @@ checkstyle { getConfigDirectory().set(new File(rootDir, 'config/checkstyle')) } -pmd { - toolVersion = "6.55.0" - sourceSets = [sourceSets.main, sourceSets.test] - reportsDir = file("$project.buildDir/reports/pmd") - // https://github.com/pmd/pmd/issues/876 - ruleSets = [] - ruleSetFiles = files("config/pmd/ruleset.xml") -} - jacocoTestReport { executionData(test) reports { @@ -96,6 +81,16 @@ dependencyUpdates { } } +dependencyManagement { + dependencies { + + // Solves CVE-2023-24998 + dependency group: 'commons-fileupload', name: 'commons-fileupload', version: '1.5' + } + imports { + mavenBom 'org.springframework.cloud:spring-cloud-dependencies:2020.0.6' + } +} // https://jeremylong.github.io/DependencyCheck/dependency-check-gradle/configuration.html dependencyCheck { // Specifies if the build should be failed if a CVSS score above a specified level is identified. @@ -110,14 +105,22 @@ dependencyCheck { skipConfigurations = [ "checkstyle", "compileOnly", - "pmd" ] } repositories { mavenLocal() mavenCentral() - maven { url 'https://jitpack.io' } + jcenter() + maven { + url "https://jitpack.io" + } + maven { + url "https://repo.spring.io/release" + } + maven { + url "https://dl.bintray.com/hmcts/hmcts-maven" + } } ext { @@ -125,38 +128,67 @@ ext { } ext['snakeyaml.version'] = '1.33' +ext.libraries = [ + junit5: [ + "org.junit.jupiter:junit-jupiter-api:${versions.junit}", + "org.junit.jupiter:junit-jupiter-engine:${versions.junit}", + "org.junit.jupiter:junit-jupiter-params:${versions.junit}", + "org.junit.platform:junit-platform-commons:${versions.junitPlatform}", + "org.junit.platform:junit-platform-engine:${versions.junitPlatform}" + ] +] dependencies { + implementation group: 'org.springframework.boot', name: 'spring-boot-starter' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web' - implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator' - implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-json' + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation' + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache' + + implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '3.1.6' + implementation group: 'org.springframework.cloud', name: 'spring-cloud-openfeign-core', version: '3.1.6' + implementation group: 'io.github.openfeign', name: 'feign-httpclient', version: '11.10' + + implementation group: 'org.springframework.retry', name: 'spring-retry' + implementation group: 'com.sendgrid', name: 'sendgrid-java', version: '4.9.3' + implementation group: 'com.github.hmcts', name: 'ccd-client', version: '4.9.1' + + implementation group: 'commons-io', name: 'commons-io', version: '2.11.0' + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' - implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.15' + implementation group: 'uk.gov.service.notify', name: 'notifications-java-client', version: '3.19.1-RELEASE' + implementation group: 'uk.gov.hmcts.reform', name: 'service-auth-provider-client', version: '4.0.0' + implementation group: 'uk.gov.hmcts.reform', name: 'idam-client', version: '2.0.0' + implementation group: 'uk.gov.hmcts.reform', name: 'send-letter-client', version: '3.0.3' - implementation group: 'com.github.hmcts.java-logging', name: 'logging', version: '6.0.1' + implementation group: 'uk.gov.hmcts.reform', name: 'document-management-client', version: '7.0.0' + implementation group: 'com.github.hmcts', name: 'ccd-case-document-am-client', version: '1.7.1' - implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4JVersion - implementation group: 'org.apache.logging.log4j', name: 'log4j-to-slf4j', version: log4JVersion + implementation group: 'com.launchdarkly', name: 'launchdarkly-java-server-sdk', version: '5.10.7' + implementation group: 'com.google.guava', name: 'guava', version: '31.1-jre' - implementation group: 'io.rest-assured', name: 'rest-assured' + annotationProcessor group: 'org.projectlombok', name: 'lombok', version: versions.lombok + compileOnly group: 'org.projectlombok', name: 'lombok', version: versions.lombok - testImplementation(platform('org.junit:junit-bom:5.9.2')) - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: versions.lombok + testCompileOnly group: 'org.projectlombok', name: 'lombok', version: versions.lombok + + testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.2.0' + testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '4.8.1' + testImplementation group: 'org.mockito', name: 'mockito-inline', version: '4.8.1' + + testImplementation libraries.junit5 testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', { exclude group: 'junit', module: 'junit' - exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } } -mainClassName = 'uk.gov.hmcts.reform.demo.Application' - bootJar { - archiveFileName = "spring-boot-template.jar" + enabled = false +} - manifest { - attributes('Implementation-Version': project.version.toString()) - } +jar { + enabled = true } // Gradle 7.x issue, workaround from: https://github.com/gradle/gradle/issues/17236#issuecomment-894768083 diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index df6ac304..14c12748 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -26,10 +26,15 @@ - + + + + + + - + @@ -72,6 +77,9 @@ value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, INSTANCE_INIT"/> + + + @@ -86,11 +94,15 @@ - + + + + + @@ -185,16 +197,15 @@ - - + - - - - - + + + + + CVE-2016-1000027 + + False positive. We don't have any reference to json-java_project:json-java + nor any reference to hutool:hutool:5.8.10. Suppressed long-term to re-assess and possibly delete. + CVE-2022-45688 + + + CVE-2020-8908 + + + + + ^pkg:maven/org\.springframework\.security/spring\-security\-crypto@.+?$ + CVE-2020-5408 + diff --git a/config/pmd/ruleset.xml b/config/pmd/ruleset.xml deleted file mode 100644 index 03b66c19..00000000 --- a/config/pmd/ruleset.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - HMCTS PMD rule set - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/client/CaseAccessDataStoreApi.java b/src/main/java/uk/gov/hmcts/reform/ccd/client/CaseAccessDataStoreApi.java new file mode 100644 index 00000000..dc9653cf --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/client/CaseAccessDataStoreApi.java @@ -0,0 +1,58 @@ +package uk.gov.hmcts.reform.ccd.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import uk.gov.hmcts.reform.ccd.model.AddCaseAssignedUserRolesRequest; +import uk.gov.hmcts.reform.ccd.model.AddCaseAssignedUserRolesResponse; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRolesRequest; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRolesResource; + +import java.util.List; + +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static uk.gov.hmcts.reform.ccd.client.CoreCaseDataApi.SERVICE_AUTHORIZATION; + +@FeignClient(name = "ccd-access-data-store-api", url = "${core_case_data.api.url}", + configuration = CoreCaseDataConfiguration.class) +public interface CaseAccessDataStoreApi { + + @PostMapping( + value = "/case-users", + consumes = MediaType.APPLICATION_JSON_VALUE + ) + @ResponseBody + AddCaseAssignedUserRolesResponse addCaseUserRoles( + @RequestHeader(AUTHORIZATION) String authorisation, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization, + @RequestBody AddCaseAssignedUserRolesRequest caseRoleRequest + ); + + @GetMapping( + value = "/case-users", + produces = MediaType.APPLICATION_JSON_VALUE + ) + @ResponseBody + CaseAssignedUserRolesResource getUserRoles( + @RequestHeader(AUTHORIZATION) String authorisation, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization, + @RequestParam("case_ids") List caseIds + ); + + @DeleteMapping( + value = "/case-users", + consumes = MediaType.APPLICATION_JSON_VALUE + ) + @ResponseBody + AddCaseAssignedUserRolesResponse removeCaseUserRoles( + @RequestHeader(AUTHORIZATION) String authorisation, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization, + @RequestBody CaseAssignedUserRolesRequest caseRoleRequest + ); +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/AddCaseAssignedUserRolesRequest.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/AddCaseAssignedUserRolesRequest.java new file mode 100644 index 00000000..93f65a00 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/AddCaseAssignedUserRolesRequest.java @@ -0,0 +1,15 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class AddCaseAssignedUserRolesRequest { + + @JsonProperty("case_users") + private List caseAssignedUserRoles; +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/AddCaseAssignedUserRolesResponse.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/AddCaseAssignedUserRolesResponse.java new file mode 100644 index 00000000..810f0380 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/AddCaseAssignedUserRolesResponse.java @@ -0,0 +1,13 @@ +package uk.gov.hmcts.reform.ccd.model; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +public class AddCaseAssignedUserRolesResponse { + + private String status; +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/Address.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/Address.java new file mode 100644 index 00000000..534c3485 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/Address.java @@ -0,0 +1,24 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder(toBuilder = true) +@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class) +@NoArgsConstructor +@AllArgsConstructor +public class Address { + + private String addressLine1; + private String addressLine2; + private String addressLine3; + private String postTown; + private String county; + private String country; + private String postCode; +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRole.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRole.java new file mode 100644 index 00000000..7c9126b8 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRole.java @@ -0,0 +1,21 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +@Builder(toBuilder = true) +public class CaseAssignedUserRole { + + @JsonProperty("case_id") + private String caseDataId; + + @JsonProperty("user_id") + private String userId; + + @JsonProperty("case_role") + private String caseRole; +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRoleWithOrganisation.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRoleWithOrganisation.java new file mode 100644 index 00000000..c4905444 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRoleWithOrganisation.java @@ -0,0 +1,26 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder(toBuilder = true) +@Jacksonized +public class CaseAssignedUserRoleWithOrganisation { + + @JsonProperty("organisation_id") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String organisationId; + + @JsonProperty("case_id") + private String caseDataId; + + @JsonProperty("user_id") + private String userId; + + @JsonProperty("case_role") + private String caseRole; +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRolesRequest.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRolesRequest.java new file mode 100644 index 00000000..fab152b3 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRolesRequest.java @@ -0,0 +1,16 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class CaseAssignedUserRolesRequest { + + @JsonProperty("case_users") + private List caseAssignedUserRoles; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRolesResource.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRolesResource.java new file mode 100644 index 00000000..64a7d4ae --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/CaseAssignedUserRolesResource.java @@ -0,0 +1,17 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +import java.util.List; + +@Data +@Builder(toBuilder = true) +@Jacksonized +public class CaseAssignedUserRolesResource { + + @JsonProperty("case_users") + private List caseAssignedUserRoles; +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/ChangeOrganisationApprovalStatus.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/ChangeOrganisationApprovalStatus.java new file mode 100644 index 00000000..4fca235c --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/ChangeOrganisationApprovalStatus.java @@ -0,0 +1,21 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum ChangeOrganisationApprovalStatus { + + PENDING("0"), + APPROVED("1"), + REJECTED("2"); + + String value; + + ChangeOrganisationApprovalStatus(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/Organisation.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/Organisation.java new file mode 100644 index 00000000..d81f2681 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/Organisation.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +@NoArgsConstructor +@AllArgsConstructor +public class Organisation { + + @JsonProperty("OrganisationID") + private String organisationID; +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/OrganisationPolicy.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/OrganisationPolicy.java new file mode 100644 index 00000000..0e1957e7 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/OrganisationPolicy.java @@ -0,0 +1,31 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder(toBuilder = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class OrganisationPolicy { + + @JsonProperty("Organisation") + private Organisation organisation; + + @JsonProperty("OrgPolicyReference") + private String orgPolicyReference; + + @JsonProperty("OrgPolicyCaseAssignedRole") + private String orgPolicyCaseAssignedRole; + + @JsonProperty("PreviousOrganisations") + private List previousOrganisations; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/PreviousOrganisation.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/PreviousOrganisation.java new file mode 100644 index 00000000..fa3fa50a --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/PreviousOrganisation.java @@ -0,0 +1,27 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder(toBuilder = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class PreviousOrganisation { + + @JsonProperty("OrganisationName") + private String organisationName; + @JsonProperty("FromTimestamp") + private LocalDateTime fromTimestamp; + @JsonProperty("ToTimestamp") + private LocalDateTime toTimestamp; + @JsonProperty("OrganisationAddress") + private Address organisationAddress; +} diff --git a/src/main/java/uk/gov/hmcts/reform/ccd/model/PreviousOrganisationCollectionItem.java b/src/main/java/uk/gov/hmcts/reform/ccd/model/PreviousOrganisationCollectionItem.java new file mode 100644 index 00000000..2d8f73d0 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/ccd/model/PreviousOrganisationCollectionItem.java @@ -0,0 +1,20 @@ +package uk.gov.hmcts.reform.ccd.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder(toBuilder = true) +@AllArgsConstructor +@NoArgsConstructor +public class PreviousOrganisationCollectionItem { + + @JsonProperty("id") + private String id; + + @JsonProperty("value") + private PreviousOrganisation value; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/BankHolidays.java b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/BankHolidays.java new file mode 100644 index 00000000..8ccaafd4 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/BankHolidays.java @@ -0,0 +1,38 @@ +package uk.gov.hmcts.reform.civil.bankholidays; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; + +import java.time.LocalDate; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BankHolidays { + + @JsonProperty(Countries.ENGLAND_AND_WALES) + public Division englandAndWales; + + static final class Countries { + + static final String ENGLAND_AND_WALES = "england-and-wales"; + + private Countries() { + // NO-OP + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Division { + @JsonProperty("events") + public List events; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class EventDate { + @JsonProperty("date") + @JsonDeserialize(using = LocalDateDeserializer.class) + public LocalDate date; + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/BankHolidaysApi.java b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/BankHolidaysApi.java new file mode 100644 index 00000000..570decfb --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/BankHolidaysApi.java @@ -0,0 +1,11 @@ +package uk.gov.hmcts.reform.civil.bankholidays; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +@FeignClient(name = "bank-holidays-api", url = "${bankHolidays.api.url}") +public interface BankHolidaysApi { + + @GetMapping(path = "/bank-holidays.json") + BankHolidays retrieveAll(); +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/NonWorkingDaysCollection.java b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/NonWorkingDaysCollection.java new file mode 100644 index 00000000..1fb6cbe8 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/NonWorkingDaysCollection.java @@ -0,0 +1,33 @@ +package uk.gov.hmcts.reform.civil.bankholidays; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.civil.helpers.ResourceReader; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.function.Predicate; + +@Service +public class NonWorkingDaysCollection { + + private final String dataResource; + + public NonWorkingDaysCollection(@Value("${nonworking-days.datafile}") String dataSource) { + this.dataResource = dataSource; + } + + public boolean contains(LocalDate date) { + final String isoDate = date.format(DateTimeFormatter.ISO_LOCAL_DATE); + try { + String data = ResourceReader.readString(dataResource); + return Arrays.stream(data.split("[\r\n]+")) + .map(String::trim) + .anyMatch(Predicate.isEqual(isoDate)); + } catch (IllegalStateException e) { + // thrown from ResourceReader#readString + return false; + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/PublicHolidaysCollection.java b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/PublicHolidaysCollection.java new file mode 100644 index 00000000..d1d1d204 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/PublicHolidaysCollection.java @@ -0,0 +1,38 @@ +package uk.gov.hmcts.reform.civil.bankholidays; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Stores all public holidays retrieved from Gov uk API: https://www.gov.uk/bank-holidays.json + */ +@Service +public class PublicHolidaysCollection { + + private final BankHolidaysApi bankHolidaysApi; + private Set cachedPublicHolidays; + + @Autowired + public PublicHolidaysCollection(BankHolidaysApi bankHolidaysApi) { + this.bankHolidaysApi = bankHolidaysApi; + } + + private Set retrieveAllPublicHolidays() { + BankHolidays bankHolidays = bankHolidaysApi.retrieveAll(); + + return bankHolidays.englandAndWales.events.stream() + .map(item -> item.date) + .collect(Collectors.toSet()); + } + + public Set getPublicHolidays() { + if (cachedPublicHolidays == null) { + cachedPublicHolidays = retrieveAllPublicHolidays(); + } + return cachedPublicHolidays; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/WorkingDayIndicator.java b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/WorkingDayIndicator.java new file mode 100644 index 00000000..60df6792 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/bankholidays/WorkingDayIndicator.java @@ -0,0 +1,53 @@ +package uk.gov.hmcts.reform.civil.bankholidays; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.civil.bankholidays.NonWorkingDaysCollection; +import uk.gov.hmcts.reform.civil.bankholidays.PublicHolidaysCollection; + +import java.time.LocalDate; + +import static java.time.DayOfWeek.SATURDAY; +import static java.time.DayOfWeek.SUNDAY; +import static java.util.Objects.requireNonNull; + +@Service +@RequiredArgsConstructor +public class WorkingDayIndicator { + + private final PublicHolidaysCollection publicHolidaysCollection; + private final NonWorkingDaysCollection nonWorkingDaysCollection; + + /** + * Verifies if given date is a working day in UK (England and Wales only). + */ + public boolean isWorkingDay(LocalDate date) { + return !isWeekend(date) + && !isPublicHoliday(date) + && !isCustomNonWorkingDay(date); + } + + public boolean isWeekend(LocalDate date) { + return date.getDayOfWeek() == SATURDAY || date.getDayOfWeek() == SUNDAY; + } + + public boolean isPublicHoliday(LocalDate date) { + return publicHolidaysCollection.getPublicHolidays().contains(date); + } + + public boolean isCustomNonWorkingDay(LocalDate date) { + return nonWorkingDaysCollection.contains(date); + } + + public LocalDate getNextWorkingDay(LocalDate date) { + requireNonNull(date); + + return isWorkingDay(date) ? date : getNextWorkingDay(date.plusDays(1)); + } + + public LocalDate getPreviousWorkingDay(LocalDate date) { + requireNonNull(date); + + return isWorkingDay(date) ? date : getPreviousWorkingDay(date.minusDays(1)); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/cas/client/CaseAssignmentApi.java b/src/main/java/uk/gov/hmcts/reform/civil/cas/client/CaseAssignmentApi.java new file mode 100644 index 00000000..5cad6e42 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/cas/client/CaseAssignmentApi.java @@ -0,0 +1,42 @@ +package uk.gov.hmcts.reform.civil.cas.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.FeignClientProperties; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.ResponseBody; +import uk.gov.hmcts.reform.civil.cas.model.DecisionRequest; +import uk.gov.hmcts.reform.ccd.client.model.AboutToStartOrSubmitCallbackResponse; +import uk.gov.hmcts.reform.ccd.client.model.CallbackRequest; +import uk.gov.hmcts.reform.ccd.client.model.SubmittedCallbackResponse; + +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static uk.gov.hmcts.reform.ccd.client.CoreCaseDataApi.SERVICE_AUTHORIZATION; + +@FeignClient( + name = "case-assignment-api", + url = "${aca.api.baseurl}", + configuration = FeignClientProperties.FeignClientConfiguration.class +) +public interface CaseAssignmentApi { + + @PostMapping( + value = "/noc/apply-decision", + consumes = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + AboutToStartOrSubmitCallbackResponse applyDecision( + @RequestHeader(AUTHORIZATION) String authorisation, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization, + @RequestBody DecisionRequest decisionRequest); + + @PostMapping( + value = "/noc/check-noc-approval", + consumes = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + SubmittedCallbackResponse checkNocApproval( + @RequestHeader(AUTHORIZATION) String authorisation, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization, + @RequestBody CallbackRequest callbackRequest); +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/cas/model/DecisionRequest.java b/src/main/java/uk/gov/hmcts/reform/civil/cas/model/DecisionRequest.java new file mode 100644 index 00000000..8508c8fe --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/cas/model/DecisionRequest.java @@ -0,0 +1,26 @@ +package uk.gov.hmcts.reform.civil.cas.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import uk.gov.hmcts.reform.ccd.client.model.CaseDetails; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@JsonIgnoreProperties(ignoreUnknown = true) +public class DecisionRequest { + + @JsonProperty("case_details") + private CaseDetails caseDetails; + + public static DecisionRequest decisionRequest(CaseDetails caseDetails) { + return DecisionRequest.builder().caseDetails(caseDetails).build(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/config/AopConfig.java b/src/main/java/uk/gov/hmcts/reform/civil/config/AopConfig.java new file mode 100644 index 00000000..7bfaedf6 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/config/AopConfig.java @@ -0,0 +1,12 @@ +package uk.gov.hmcts.reform.civil.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@Configuration +@EnableAspectJAutoProxy +@ComponentScan("uk.gov.hmcts.reform.civil") +public class AopConfig { + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/config/CacheConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/config/CacheConfiguration.java new file mode 100644 index 00000000..04459df1 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/config/CacheConfiguration.java @@ -0,0 +1,17 @@ +package uk.gov.hmcts.reform.civil.config; + +import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +public class CacheConfiguration { + + @Bean + public CacheManagerCustomizer cacheManagerCustomizer() { + return cacheManager -> cacheManager.setAllowNullValues(false); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/config/CrossAccessUserConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/config/CrossAccessUserConfiguration.java new file mode 100644 index 00000000..8de1e8de --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/config/CrossAccessUserConfiguration.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.civil.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +public class CrossAccessUserConfiguration { + + private final String userName; + private final String password; + + public CrossAccessUserConfiguration(@Value("${civil.cross-access.username}") String userName, + @Value("${civil.cross-access.password}") String password) { + this.userName = userName; + this.password = password; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/config/LaunchDarklyConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/config/LaunchDarklyConfiguration.java new file mode 100644 index 00000000..12b70041 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/config/LaunchDarklyConfiguration.java @@ -0,0 +1,105 @@ +package uk.gov.hmcts.reform.civil.config; + +import com.launchdarkly.sdk.server.LDClient; +import com.launchdarkly.sdk.server.LDConfig; +import com.launchdarkly.sdk.server.integrations.FileData; +import com.launchdarkly.sdk.server.interfaces.DataSourceFactory; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.stream.Stream; + +@Configuration +@Slf4j +public class LaunchDarklyConfiguration { + + /** + * Builds the client for Launch Darkly. + * + * @param sdkKey sdk key to connect to launchdarkly. + * @param offlineMode true to use launchdarkly offline mode. + * @param flagFiles (optional) a list of paths to json or yaml files containing flags for launchdarkly. + * If there are duplicate keys, the first files have precedence. + * @return Launch Darkly Client. + */ + @Bean + public LDClient ldClient(@Value("${launchdarkly.sdk-key}") String sdkKey, + @Value("${launchdarkly.offline-mode:false}") Boolean offlineMode, + @Value("${launchdarkly.file:''}") String[] flagFiles) { + LDConfig.Builder builder = new LDConfig.Builder().offline(offlineMode); + getExistingFiles(flagFiles) + .map(this::getDataSource) + .ifPresent(builder::dataSource); + return new LDClient(sdkKey, builder.build()); + } + + /** + * Converts an array of paths in a datasource suitable for Launch Darkly. + * + * @param flagFilePaths an array of files that exist and that have to be used to create the launch darkly flags + * @return a datasource able to combine the contents of all the files. If there are duplicated keys, the values + * on the first file have precedence + */ + private DataSourceFactory getDataSource(Path[] flagFilePaths) { + return FileData.dataSource() + .filePaths(flagFilePaths) + .duplicateKeysHandling(FileData.DuplicateKeysHandling.IGNORE); + } + + /** + * Filters the array of files looking for the ones that can actually be found, and converts those to Path. + * + * @param files an array of strings describing files. Each starts with / if absolute. + * Otherwise, it's considered relative to the working directory. + * @return an Optional containing those files describes which actually exist. Empty if files is null, + * empty or if none can be found. + */ + private Optional getExistingFiles(String[] files) { + if (files == null || files.length < 1) { + return Optional.empty(); + } else { + Path[] existing = Stream.of(files).map(this::getPathIfExists) + .filter(Optional::isPresent) + .map(Optional::get) + .toArray(Path[]::new); + if (existing.length > 0) { + return Optional.of(existing); + } else { + return Optional.empty(); + } + } + } + + /** + * Tries to convert file into the Path of a file that can be found in the filesystem. + * + * @param file a string describing a file. Start with / if absolute path. + * @return if it exists the described file, an Optional containing the path. + * An empty Optional otherwise. + */ + private Optional getPathIfExists(String file) { + if (StringUtils.isNotBlank(file)) { + Path flagFile; + if (file.startsWith("/")) { + flagFile = Paths.get(file); + } else { + flagFile = Paths.get("").resolve(file); + } + if (Files.exists(flagFile)) { + return Optional.of(flagFile); + } else { + log.debug("Could not find files defined by " + file); + return Optional.empty(); + } + } else { + return Optional.empty(); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/config/PrdAdminUserConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/config/PrdAdminUserConfiguration.java new file mode 100644 index 00000000..faab40a6 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/config/PrdAdminUserConfiguration.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.civil.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +public class PrdAdminUserConfiguration { + + private final String username; + private final String password; + + public PrdAdminUserConfiguration(@Value("${civil.prd-admin.username}") String username, + @Value("${civil.prd-admin.password}") String password) { + this.username = username; + this.password = password; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/crd/client/ListOfValuesApi.java b/src/main/java/uk/gov/hmcts/reform/civil/crd/client/ListOfValuesApi.java new file mode 100644 index 00000000..45de0c75 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/crd/client/ListOfValuesApi.java @@ -0,0 +1,23 @@ +package uk.gov.hmcts.reform.civil.crd.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import uk.gov.hmcts.reform.civil.crd.model.CategorySearchResult; + +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static uk.gov.hmcts.reform.ccd.client.CoreCaseDataApi.SERVICE_AUTHORIZATION; + +@FeignClient(name = "rd-commondata-api", url = "${rd_commondata.api.url}") +public interface ListOfValuesApi { + + @GetMapping("/refdata/commondata/lov/categories/{category-id}") + CategorySearchResult findCategoryByCategoryIdAndServiceId( + @PathVariable("category-id") String categoryId, + @RequestParam("serviceId") String serviceId, + @RequestHeader(AUTHORIZATION) String authorization, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization + ); +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/crd/model/Category.java b/src/main/java/uk/gov/hmcts/reform/civil/crd/model/Category.java new file mode 100644 index 00000000..bec24467 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/crd/model/Category.java @@ -0,0 +1,49 @@ +package uk.gov.hmcts.reform.civil.crd.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Category { + + @JsonProperty("active_flag") + private String activeFlag; + + @JsonProperty("category_key") + private String categoryKey; + + @JsonProperty("hint_text_cy") + private String hintTextCy; + + @JsonProperty("hint_text_en") + private String hintTextEn; + + @JsonProperty("key") + private String key; + + @JsonProperty("lov_order") + private int lovOrder; + + @JsonProperty("parent_category") + private String parentCategory; + + @JsonProperty("parent_key") + private String parentKey; + + @JsonProperty("value_cy") + private String valueCy; + + @JsonProperty("value_en") + private String valueEn; + + @JsonProperty("child_nodes") + private List childNodes; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/crd/model/CategorySearchResult.java b/src/main/java/uk/gov/hmcts/reform/civil/crd/model/CategorySearchResult.java new file mode 100644 index 00000000..e67ee317 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/crd/model/CategorySearchResult.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.civil.crd.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CategorySearchResult { + + @JsonProperty("list_of_values") + private List categories; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentDownloadException.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentDownloadException.java new file mode 100644 index 00000000..1445e92b --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentDownloadException.java @@ -0,0 +1,10 @@ +package uk.gov.hmcts.reform.civil.documentmanagement; + +public class DocumentDownloadException extends RuntimeException { + + public static final String MESSAGE_TEMPLATE = "Unable to download document %s from document management."; + + public DocumentDownloadException(String fileName, Throwable t) { + super(String.format(MESSAGE_TEMPLATE, fileName), t); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentManagementConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentManagementConfiguration.java new file mode 100644 index 00000000..22989c4f --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentManagementConfiguration.java @@ -0,0 +1,18 @@ +package uk.gov.hmcts.reform.civil.documentmanagement; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Data +@Configuration +public class DocumentManagementConfiguration { + + private final List userRoles; + + public DocumentManagementConfiguration(@Value("${document_management.userRoles}") List userRoles) { + this.userRoles = userRoles; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentManagementService.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentManagementService.java new file mode 100644 index 00000000..e2c1754a --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentManagementService.java @@ -0,0 +1,12 @@ +package uk.gov.hmcts.reform.civil.documentmanagement; + +import uk.gov.hmcts.reform.civil.documentmanagement.model.CaseDocument; +import uk.gov.hmcts.reform.civil.documentmanagement.model.PDF; + +public interface DocumentManagementService { + + CaseDocument uploadDocument(String authorisation, PDF pdf); + + byte[] downloadDocument(String authorisation, String documentPath); + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentUploadException.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentUploadException.java new file mode 100644 index 00000000..7af7bc70 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/DocumentUploadException.java @@ -0,0 +1,14 @@ +package uk.gov.hmcts.reform.civil.documentmanagement; + +public class DocumentUploadException extends RuntimeException { + + public static final String MESSAGE_TEMPLATE = "Unable to upload document %s to document management."; + + public DocumentUploadException(String fileName) { + super(String.format(MESSAGE_TEMPLATE, fileName)); + } + + public DocumentUploadException(String fileName, Throwable t) { + super(String.format(MESSAGE_TEMPLATE, fileName), t); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/SecuredDocumentManagementService.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/SecuredDocumentManagementService.java new file mode 100644 index 00000000..dc3562ab --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/SecuredDocumentManagementService.java @@ -0,0 +1,155 @@ +package uk.gov.hmcts.reform.civil.documentmanagement; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.ccd.document.am.feign.CaseDocumentClientApi; +import uk.gov.hmcts.reform.ccd.document.am.model.Classification; +import uk.gov.hmcts.reform.ccd.document.am.model.Document; +import uk.gov.hmcts.reform.ccd.document.am.model.DocumentUploadRequest; +import uk.gov.hmcts.reform.ccd.document.am.model.UploadResponse; +import uk.gov.hmcts.reform.civil.documentmanagement.DocumentManagementConfiguration; +import uk.gov.hmcts.reform.civil.helpers.LocalDateTimeHelper; +import uk.gov.hmcts.reform.civil.documentmanagement.model.CaseDocument; +import uk.gov.hmcts.reform.civil.documentmanagement.model.PDF; +import uk.gov.hmcts.reform.civil.service.UserService; +import uk.gov.hmcts.reform.document.DocumentDownloadClientApi; +import uk.gov.hmcts.reform.document.utils.InMemoryMultipartFile; +import uk.gov.hmcts.reform.idam.client.models.UserInfo; + +import java.net.URI; +import java.time.ZoneId; +import java.util.Collections; +import java.util.Optional; +import java.util.UUID; + +import static org.springframework.http.MediaType.APPLICATION_PDF_VALUE; + +@Slf4j +@Service("documentManagementService") +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "document_management", name = "secured", havingValue = "true") +public class SecuredDocumentManagementService implements DocumentManagementService { + + protected static final int DOC_UUID_LENGTH = 36; + public static final String CREATED_BY = "Civil"; + protected static final String FILES_NAME = "files"; + + private final DocumentDownloadClientApi documentDownloadClientApi; + private final AuthTokenGenerator authTokenGenerator; + private final UserService userService; + private final DocumentManagementConfiguration documentManagementConfiguration; + private final CaseDocumentClientApi caseDocumentClientApi; + + @Retryable(value = {DocumentUploadException.class}, backoff = @Backoff(delay = 200)) + @Override + public CaseDocument uploadDocument(String authorisation, PDF pdf) { + String originalFileName = pdf.getFileBaseName(); + log.info("Uploading file {}", originalFileName); + try { + MultipartFile file + = new InMemoryMultipartFile(FILES_NAME, originalFileName, APPLICATION_PDF_VALUE, pdf.getBytes() + ); + + DocumentUploadRequest documentUploadRequest = new DocumentUploadRequest( + Classification.RESTRICTED.toString(), + "CIVIL", + "CIVIL", + Collections.singletonList(file) + ); + + UploadResponse response = caseDocumentClientApi.uploadDocuments( + authorisation, + authTokenGenerator.generate(), + documentUploadRequest + ); + + Document document = response.getDocuments().stream() + .findFirst() + .orElseThrow(() -> new DocumentUploadException(originalFileName)); + + return CaseDocument.builder() + .documentLink(uk.gov.hmcts.reform.civil.documentmanagement.model.Document.builder() + .documentUrl(document.links.self.href) + .documentBinaryUrl(document.links.binary.href) + .documentFileName(originalFileName) + .documentHash(document.hashToken) + .build()) + .documentName(originalFileName) + .documentType(pdf.getDocumentType()) + .createdDatetime(LocalDateTimeHelper.fromUTC(document.createdOn + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime())) + .documentSize(document.size) + .createdBy(CREATED_BY) + .build(); + } catch (Exception ex) { + log.error("Failed uploading file {}", originalFileName, ex); + throw new DocumentUploadException(originalFileName, ex); + } + } + + @Retryable(value = DocumentDownloadException.class, backoff = @Backoff(delay = 200)) + @Override + public byte[] downloadDocument(String authorisation, String documentPath) { + log.info("Downloading document {}", documentPath); + try { + UserInfo userInfo = userService.getUserInfo(authorisation); + String userRoles = String.join(",", this.documentManagementConfiguration.getUserRoles()); + + ResponseEntity responseEntity = caseDocumentClientApi.getDocumentBinary( + authorisation, + authTokenGenerator.generate(), + UUID.fromString(documentPath.substring(documentPath.lastIndexOf("/") + 1)) + ); + + if (responseEntity == null) { + Document documentMetadata = getDocumentMetaData(authorisation, documentPath); + responseEntity = documentDownloadClientApi.downloadBinary( + authorisation, + authTokenGenerator.generate(), + userRoles, + userInfo.getUid(), + URI.create(documentMetadata.links.binary.href).getPath().replaceFirst("/", "") + ); + } + + return Optional.ofNullable(responseEntity.getBody()) + .map(ByteArrayResource.class::cast) + .map(ByteArrayResource::getByteArray) + .orElseThrow(RuntimeException::new); + } catch (Exception ex) { + log.error("Failed downloading document {}", documentPath, ex); + throw new DocumentDownloadException(documentPath, ex); + } + } + + public Document getDocumentMetaData(String authorisation, String documentPath) { + log.info("Getting metadata for file {}", documentPath); + + try { + return caseDocumentClientApi.getMetadataForDocument( + authorisation, + authTokenGenerator.generate(), + getDocumentIdFromSelfHref(documentPath) + ); + + } catch (Exception ex) { + log.error("Failed getting metadata for {}", documentPath, ex); + throw new DocumentDownloadException(documentPath, ex); + } + } + + private UUID getDocumentIdFromSelfHref(String selfHref) { + return UUID.fromString(selfHref.substring(selfHref.length() - DOC_UUID_LENGTH)); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/UnsecuredDocumentManagementService.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/UnsecuredDocumentManagementService.java new file mode 100644 index 00000000..be2b5ce2 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/UnsecuredDocumentManagementService.java @@ -0,0 +1,139 @@ +package uk.gov.hmcts.reform.civil.documentmanagement; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.helpers.LocalDateTimeHelper; +import uk.gov.hmcts.reform.civil.documentmanagement.model.CaseDocument; +import uk.gov.hmcts.reform.civil.documentmanagement.model.PDF; +import uk.gov.hmcts.reform.civil.service.UserService; +import uk.gov.hmcts.reform.document.DocumentDownloadClientApi; +import uk.gov.hmcts.reform.document.DocumentMetadataDownloadClientApi; +import uk.gov.hmcts.reform.document.DocumentUploadClientApi; +import uk.gov.hmcts.reform.document.domain.Classification; +import uk.gov.hmcts.reform.document.domain.Document; +import uk.gov.hmcts.reform.document.domain.UploadResponse; +import uk.gov.hmcts.reform.document.utils.InMemoryMultipartFile; +import uk.gov.hmcts.reform.idam.client.models.UserInfo; + +import java.net.URI; +import java.time.ZoneId; +import java.util.Optional; + +import static java.util.Collections.singletonList; +import static org.springframework.http.MediaType.APPLICATION_PDF_VALUE; + +@Slf4j +@Service("documentManagementService") +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "document_management", name = "secured", havingValue = "false") +public class UnsecuredDocumentManagementService implements DocumentManagementService { + + public static final String CREATED_BY = "Civil"; + protected static final String FILES_NAME = "files"; + + private final DocumentUploadClientApi documentUploadClientApi; + private final DocumentDownloadClientApi documentDownloadClientApi; + private final DocumentMetadataDownloadClientApi documentMetadataDownloadClient; + private final AuthTokenGenerator authTokenGenerator; + private final UserService userService; + private final DocumentManagementConfiguration documentManagementConfiguration; + + @Retryable(value = {DocumentUploadException.class}, backoff = @Backoff(delay = 200)) + @Override + public CaseDocument uploadDocument(String authorisation, PDF pdf) { + String originalFileName = pdf.getFileBaseName(); + log.info("Uploading file {}", originalFileName); + try { + MultipartFile file + = new InMemoryMultipartFile(FILES_NAME, originalFileName, APPLICATION_PDF_VALUE, pdf.getBytes()); + + UserInfo userInfo = userService.getUserInfo(authorisation); + UploadResponse response = documentUploadClientApi.upload( + authorisation, + authTokenGenerator.generate(), + userInfo.getUid(), + documentManagementConfiguration.getUserRoles(), + Classification.RESTRICTED, + singletonList(file) + ); + + Document document = response.getEmbedded().getDocuments().stream() + .findFirst() + .orElseThrow(() -> new DocumentUploadException(originalFileName)); + + return CaseDocument.builder() + .documentLink(uk.gov.hmcts.reform.civil.documentmanagement.model.Document.builder() + .documentUrl(document.links.self.href) + .documentBinaryUrl(document.links.binary.href) + .documentFileName(originalFileName) + .build()) + .documentName(originalFileName) + .documentType(pdf.getDocumentType()) + .createdDatetime(LocalDateTimeHelper.fromUTC(document.createdOn + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime())) + .documentSize(document.size) + .createdBy(CREATED_BY) + .build(); + } catch (Exception ex) { + log.error("Failed uploading file {}", originalFileName, ex); + throw new DocumentUploadException(originalFileName, ex); + } + } + + @Retryable(value = DocumentDownloadException.class, backoff = @Backoff(delay = 200)) + @Override + public byte[] downloadDocument(String authorisation, String documentPath) { + log.info("Downloading document {}", documentPath); + try { + UserInfo userInfo = userService.getUserInfo(authorisation); + String userRoles = String.join(",", this.documentManagementConfiguration.getUserRoles()); + Document documentMetadata = getDocumentMetaData(authorisation, documentPath); + + ResponseEntity responseEntity = documentDownloadClientApi.downloadBinary( + authorisation, + authTokenGenerator.generate(), + userRoles, + userInfo.getUid(), + URI.create(documentMetadata.links.binary.href).getPath() + ); + + return Optional.ofNullable(responseEntity.getBody()) + .map(ByteArrayResource.class::cast) + .map(ByteArrayResource::getByteArray) + .orElseThrow(RuntimeException::new); + } catch (Exception ex) { + log.error("Failed downloading document {}", documentPath, ex); + throw new DocumentDownloadException(documentPath, ex); + } + } + + public Document getDocumentMetaData(String authorisation, String documentPath) { + log.info("Getting metadata for file {}", documentPath); + + try { + UserInfo userInfo = userService.getUserInfo(authorisation); + String userRoles = String.join(",", this.documentManagementConfiguration.getUserRoles()); + return documentMetadataDownloadClient.getDocumentMetadata( + authorisation, + authTokenGenerator.generate(), + userRoles, + userInfo.getUid(), + documentPath + ); + } catch (Exception ex) { + log.error("Failed getting metadata for {}", documentPath, ex); + throw new DocumentDownloadException(documentPath, ex); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/CaseDocument.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/CaseDocument.java new file mode 100644 index 00000000..df93fd99 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/CaseDocument.java @@ -0,0 +1,20 @@ +package uk.gov.hmcts.reform.civil.documentmanagement.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class CaseDocument { + + private final Document documentLink; + private final String documentName; + private final DocumentType documentType; + private final long documentSize; + private final LocalDateTime createdDatetime; + private final String createdBy; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/Document.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/Document.java new file mode 100644 index 00000000..aa0ad511 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/Document.java @@ -0,0 +1,31 @@ +package uk.gov.hmcts.reform.civil.documentmanagement.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +public class Document { + + String documentUrl; + String documentBinaryUrl; + String documentFileName; + String documentHash; + String categoryID; + + @JsonCreator + public Document(@JsonProperty("document_url") String documentUrl, + @JsonProperty("document_binary_url") String documentBinaryUrl, + @JsonProperty("document_filename") String documentFileName, + @JsonProperty("document_hash") String documentHash, + @JsonProperty("category_id") String categoryID) { + this.documentUrl = documentUrl; + this.documentBinaryUrl = documentBinaryUrl; + this.documentFileName = documentFileName; + this.documentHash = documentHash; + this.categoryID = categoryID; + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/DocumentType.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/DocumentType.java new file mode 100644 index 00000000..60ecb990 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/DocumentType.java @@ -0,0 +1,28 @@ +package uk.gov.hmcts.reform.civil.documentmanagement.model; + +public enum DocumentType { + SEALED_CLAIM, + ACKNOWLEDGEMENT_OF_CLAIM, + ACKNOWLEDGEMENT_OF_SERVICE, + DIRECTIONS_QUESTIONNAIRE, + DEFENDANT_DEFENCE, + DEFENDANT_DRAFT_DIRECTIONS, + DEFAULT_JUDGMENT, + CLAIMANT_DEFENCE, + CLAIMANT_DRAFT_DIRECTIONS, + DEFAULT_JUDGMENT_SDO_ORDER, + LITIGANT_IN_PERSON_CLAIM_FORM, + SDO_ORDER, + HEARING_FORM, + PIP_LETTER, + + //General Application Document Type + GENERAL_ORDER, + DIRECTION_ORDER, + DISMISSAL_ORDER, + REQUEST_FOR_INFORMATION, + HEARING_ORDER, + WRITTEN_REPRESENTATION_SEQUENTIAL, + WRITTEN_REPRESENTATION_CONCURRENT, + HEARING_NOTICE; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/PDF.java b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/PDF.java new file mode 100644 index 00000000..84384ae3 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/documentmanagement/model/PDF.java @@ -0,0 +1,11 @@ +package uk.gov.hmcts.reform.civil.documentmanagement.model; + +import lombok.Data; + +@Data +public class PDF { + + private final String fileBaseName; + private final byte[] bytes; + private final DocumentType documentType; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/enums/CaseRole.java b/src/main/java/uk/gov/hmcts/reform/civil/enums/CaseRole.java new file mode 100644 index 00000000..17fb0c08 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/enums/CaseRole.java @@ -0,0 +1,17 @@ +package uk.gov.hmcts.reform.civil.enums; + +import lombok.Getter; + +@Getter +public enum CaseRole { + CREATOR, + APPLICANTSOLICITORONE, + RESPONDENTSOLICITORONE, + RESPONDENTSOLICITORTWO; + + private String formattedName; + + CaseRole() { + this.formattedName = String.format("[%s]", name()); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/helpers/DateFormatHelper.java b/src/main/java/uk/gov/hmcts/reform/civil/helpers/DateFormatHelper.java new file mode 100644 index 00000000..379b8496 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/helpers/DateFormatHelper.java @@ -0,0 +1,24 @@ +package uk.gov.hmcts.reform.civil.helpers; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public class DateFormatHelper { + + public static final String DATE_TIME_AT = "h:mma 'on' d MMMM yyyy"; + public static final String DATE = "d MMMM yyyy"; + + private DateFormatHelper() { + //NO-OP + } + + public static String formatLocalDateTime(LocalDateTime dateTime, String format) { + return dateTime.format(DateTimeFormatter.ofPattern(format, Locale.UK)); + } + + public static String formatLocalDate(LocalDate date, String format) { + return date.format(DateTimeFormatter.ofPattern(format, Locale.UK)); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/helpers/LocalDateTimeHelper.java b/src/main/java/uk/gov/hmcts/reform/civil/helpers/LocalDateTimeHelper.java new file mode 100644 index 00000000..341edc60 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/helpers/LocalDateTimeHelper.java @@ -0,0 +1,20 @@ +package uk.gov.hmcts.reform.civil.helpers; + +import java.time.LocalDateTime; +import java.time.ZoneId; + +public class LocalDateTimeHelper { + + public static final ZoneId UTC_ZONE = ZoneId.of("UTC"); + public static final ZoneId LOCAL_ZONE = ZoneId.of("Europe/London"); + + private LocalDateTimeHelper() { + } + + public static LocalDateTime fromUTC(LocalDateTime input) { + return input.atZone(UTC_ZONE) + .withZoneSameInstant(LOCAL_ZONE) + .toLocalDateTime(); + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/helpers/ResourceReader.java b/src/main/java/uk/gov/hmcts/reform/civil/helpers/ResourceReader.java new file mode 100644 index 00000000..72a86621 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/helpers/ResourceReader.java @@ -0,0 +1,34 @@ +package uk.gov.hmcts.reform.civil.helpers; + +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class ResourceReader { + + private ResourceReader() { + // Utility class, no instances + } + + public static String readString(String resourcePath) { + return new String( + readBytes(resourcePath), + StandardCharsets.UTF_8 + ); + } + + public static byte[] readBytes(String resourcePath) { + try (InputStream inputStream = ResourceReader.class.getResourceAsStream(resourcePath)) { + if (inputStream == null) { + throw new IllegalStateException("Unable to read resource: " + resourcePath); + } + return IOUtils.toByteArray(inputStream); + } catch (IOException e) { + throw new IllegalStateException(e); + } catch (NullPointerException e) { + throw new IllegalStateException("Unable to read resource: " + resourcePath, e); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggle.java b/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggle.java new file mode 100644 index 00000000..a45c4d6e --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggle.java @@ -0,0 +1,39 @@ +package uk.gov.hmcts.reform.civil.launchdarkly; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The FeatureToggle annotation is used to define the conditional execution of the business method. + * The condition is defined by the launchdarkly feature toggle. + * For example: + * To execute a business method only when feature toggle is enabled (using the default value=true). + *
+ * @FeatureToggle(feature="my-new-feature")
+ * public void businessMethod(Object param);
+ *
+ * To execute a business method only when feature toggle is disabled.
+ * @FeatureToggle(feature="my-new-feature", value=false)
+ *  * public void businessMethod(Object param);
+ * 
+ */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface FeatureToggle { + + /** + * The name of the feature toggle to be checked. + * + * @return String The feature name + */ + String feature(); + + /** + * Indicates the boolean value of feature toggle, for which to invoke the execution of the business method. + * + * @return boolean value + */ + boolean value() default true; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleApi.java b/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleApi.java new file mode 100644 index 00000000..d7080c2b --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleApi.java @@ -0,0 +1,55 @@ +package uk.gov.hmcts.reform.civil.launchdarkly; + +import com.launchdarkly.sdk.LDUser; +import com.launchdarkly.sdk.server.interfaces.LDClientInterface; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Slf4j +@Service +public class FeatureToggleApi { + + private final LDClientInterface internalClient; + private final String environment; + + @Autowired + public FeatureToggleApi(LDClientInterface internalClient, @Value("${launchdarkly.env}") String environment) { + this.internalClient = internalClient; + this.environment = environment; + Runtime.getRuntime().addShutdownHook(new Thread(this::close)); + } + + public boolean isFeatureEnabled(String feature) { + return internalClient.boolVariation(feature, createLDUser().build(), false); + } + + public boolean isFeatureEnabled(String feature, boolean defaultValue) { + return internalClient.boolVariation(feature, createLDUser().build(), defaultValue); + } + + public boolean isFeatureEnabled(String feature, LDUser user) { + return internalClient.boolVariation(feature, user, false); + } + + public boolean isFeatureEnabled(String feature, LDUser user, boolean defaultValue) { + return internalClient.boolVariation(feature, user, defaultValue); + } + + public LDUser.Builder createLDUser() { + return new LDUser.Builder("civil-service") + .custom("timestamp", String.valueOf(System.currentTimeMillis())) + .custom("environment", environment); + } + + private void close() { + try { + internalClient.close(); + } catch (IOException e) { + log.error("Error in closing the Launchdarkly client::", e); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleAspect.java b/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleAspect.java new file mode 100644 index 00000000..60d80e41 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleAspect.java @@ -0,0 +1,33 @@ +package uk.gov.hmcts.reform.civil.launchdarkly; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class FeatureToggleAspect { + + private final FeatureToggleApi featureToggleApi; + + @Around("execution(* *(*)) && @annotation(featureToggle)") + public void checkFeatureEnabled(ProceedingJoinPoint joinPoint, FeatureToggle featureToggle) throws Throwable { + + if (featureToggle.value() && featureToggleApi.isFeatureEnabled(featureToggle.feature())) { + joinPoint.proceed(); + } else if (!featureToggle.value() && !featureToggleApi.isFeatureEnabled(featureToggle.feature())) { + joinPoint.proceed(); + } else { + log.warn( + "Feature %s is not enabled for method %s", + featureToggle.feature(), + joinPoint.getSignature().getName() + ); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationException.java b/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationException.java new file mode 100644 index 00000000..31ee715f --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationException.java @@ -0,0 +1,9 @@ +package uk.gov.hmcts.reform.civil.notify; + +public class NotificationException extends RuntimeException { + + public NotificationException(Exception cause) { + super(cause); + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationService.java b/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationService.java new file mode 100644 index 00000000..c2bc889c --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationService.java @@ -0,0 +1,39 @@ +package uk.gov.hmcts.reform.civil.notify; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import uk.gov.service.notify.NotificationClient; +import uk.gov.service.notify.NotificationClientException; + +import java.util.Map; + +@Service +@Slf4j +@RequiredArgsConstructor +public class NotificationService { + + private final NotificationClient notificationClient; + + public void sendMail( + String targetEmail, + String emailTemplate, + Map parameters, + String reference + ) { + try { + notificationClient.sendEmail(emailTemplate, targetEmail, parameters, reference); + } catch (NotificationClientException e) { + log.info("Notification Service error {}", e.getMessage()); + throw new NotificationException(e); + } + } + + public void sendLetter(String letterTemplate, Map personalisation, String reference) { + try { + notificationClient.sendLetter(letterTemplate, personalisation, reference); + } catch (NotificationClientException e) { + throw new NotificationException(e); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationsConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationsConfiguration.java new file mode 100644 index 00000000..993aa283 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationsConfiguration.java @@ -0,0 +1,24 @@ +package uk.gov.hmcts.reform.civil.notify; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.retry.annotation.EnableRetry; +import uk.gov.service.notify.NotificationClient; + +@Configuration +@EnableRetry +public class NotificationsConfiguration { + + @Bean + @ConfigurationProperties(prefix = "notifications") + public NotificationsProperties notificationsProperties() { + return new NotificationsProperties(); + } + + @Bean + public NotificationClient notificationClient(NotificationsProperties notificationsProperties) { + return new NotificationClient(notificationsProperties.getGovNotifyApiKey()); + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationsProperties.java b/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationsProperties.java new file mode 100644 index 00000000..9d9b934a --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/notify/NotificationsProperties.java @@ -0,0 +1,227 @@ +package uk.gov.hmcts.reform.civil.notify; + +import lombok.Data; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotEmpty; + +@Validated +@Data +public class NotificationsProperties { + + @NotEmpty + private String govNotifyApiKey; + + @NotEmpty + private String respondentSolicitorClaimIssueMultipartyEmailTemplate; + + @NotEmpty + private String respondentSolicitorClaimDetailsEmailTemplate; + + @NotEmpty + private String solicitorDefendantResponseCaseTakenOffline; + + @NotEmpty + private String solicitorDefendantResponseCaseTakenOfflineMultiparty; + + @NotEmpty + private String claimantSolicitorDefendantResponseFullDefence; + + @NotEmpty + private String respondentSolicitorAcknowledgeClaim; + + @NotEmpty + private String respondentSolicitorAcknowledgeClaimForSpec; + + @NotEmpty + private String applicantSolicitorAcknowledgeClaimForSpec; + + @NotEmpty + private String failedPayment; + + @NotEmpty + private String failedPaymentForSpec; + + @NotEmpty + private String solicitorClaimDismissedWithin4Months; + + @NotEmpty + private String solicitorClaimDismissedWithin14Days; + + @NotEmpty + private String solicitorClaimDismissedWithinDeadline; + + @NotEmpty String applicantHearingFeeUnpaid; + @NotEmpty String respondentHearingFeeUnpaid; + + @NotEmpty + private String claimantSolicitorCaseWillProgressOffline; + + @NotEmpty + private String claimantSolicitorSpecCaseWillProgressOffline; + + @NotEmpty + private String claimantSolicitorAgreedExtensionDate; + + @NotEmpty + private String claimantSolicitorAgreedExtensionDateForSpec; + + @NotEmpty + private String respondentSolicitorAgreedExtensionDateForSpec; + + @NotEmpty + private String claimantSolicitorConfirmsToProceed; + + @NotEmpty + private String claimantSolicitorConfirmsNotToProceed; + + @NotEmpty + private String claimantSolicitorClaimContinuingOnline; + + @NotEmpty + private String claimantSolicitorClaimContinuingOnlineForSpec; + + @NotEmpty + private String claimantSolicitorClaimContinuingOnline1v2ForSpec; + + @NotEmpty + private String claimantClaimContinuingOnlineForSpec; + + @NotEmpty + private String respondentSolicitorClaimContinuingOnlineForSpec; + + @NotEmpty + private String solicitorCaseTakenOffline; + + @NotEmpty + private String solicitorTrialReady; + + @NotEmpty + private String solicitorCaseTakenOfflineForSpec; + + @NotEmpty + private String solicitorLitigationFriendAdded; + + @NotEmpty + private String claimantSolicitorDefendantResponseForSpec; + + @NotEmpty + private String respondentSolicitorDefendantResponseForSpec; + + @NotEmpty + private String respondentDefendantResponseForSpec; + + @NotEmpty + private String sdoOrdered; + + @NotEmpty + private String sdoOrderedSpec; + + @NotEmpty + private String claimantSolicitorConfirmsNotToProceedSpec; + + @NotEmpty + private String respondentSolicitorNotifyNotToProceedSpec; + + @NotEmpty + private String claimantSolicitorConfirmsToProceedSpec; + + @NotEmpty + private String respondentSolicitorNotifyToProceedSpec; + + @NotEmpty + private String applicantSolicitor1DefaultJudgmentReceived; + + @NotEmpty + private String respondentSolicitor1DefaultJudgmentReceived; + + @NotEmpty + private String breathingSpaceEnterDefendantEmailTemplate; + + @NotEmpty + private String breathingSpaceEnterApplicantEmailTemplate; + + @NotEmpty + private String breathingSpaceLiftedApplicantEmailTemplate; + + @NotEmpty + private String breathingSpaceLiftedRespondentEmailTemplate; + + @NotEmpty + private String claimantSolicitorCounterClaimForSpec; + + @NotEmpty + private String respondentSolicitorCounterClaimForSpec; + + @NotEmpty + private String respondentDeadlineExtension; + + @NotEmpty + private String claimantDeadlineExtension; + + @NotEmpty + private String respondentSolicitor1DefaultJudgmentRequested; + + @NotEmpty + private String applicantSolicitor1DefaultJudgmentRequested; + + @NotEmpty + private String interimJudgmentRequestedClaimant; + + @NotEmpty + private String interimJudgmentApprovalClaimant; + + @NotEmpty + private String interimJudgmentRequestedDefendant; + + @NotEmpty + private String interimJudgmentApprovalDefendant; + + @NotEmpty + private String standardDirectionOrderDJTemplate; + + @NotEmpty + private String caseworkerDefaultJudgmentRequested; + + private String respondentChangeOfAddressNotificationTemplate; + + @NotEmpty + private String respondentLipFullAdmitOrPartAdmitTemplate; + + @NotEmpty + private String respondentLipFullDefenceWithMediationTemplate; + + @NotEmpty + private String respondentLipFullDefenceNoMediationTemplate; + + @NotEmpty + private String respondentLipResponseSubmissionTemplate; + + @NotEmpty + private String hearingListedFeeClaimantLrTemplate; + + @NotEmpty + private String hearingListedNoFeeClaimantLrTemplate; + + @NotEmpty + private String hearingListedNoFeeDefendantLrTemplate; + + @NotEmpty + private String noticeOfChangeFormerSolicitor; + + @NotEmpty + private String noticeOfChangeOtherParties; + + @NotEmpty + private String claimantSolicitorClaimContinuingOnlineCos; + + @NotEmpty + private String evidenceUploadTemplate; + + @NotEmpty + private String respondentCcjNotificationTemplate; + + @NotEmpty + private String respondentSolicitorCcjNotificationTemplate; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/postcode/CountriesAllowed.java b/src/main/java/uk/gov/hmcts/reform/civil/postcode/CountriesAllowed.java new file mode 100644 index 00000000..e5984c01 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/postcode/CountriesAllowed.java @@ -0,0 +1,8 @@ +package uk.gov.hmcts.reform.civil.postcode; + +public enum CountriesAllowed { + ENGLAND, + SCOTLAND, + WALES, + NOT_FOUND +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupConfiguration.java new file mode 100644 index 00000000..1883268a --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupConfiguration.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.civil.postcode; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +public class PostcodeLookupConfiguration { + + private final String url; + private final String accessKey; + + public PostcodeLookupConfiguration(@Value("${os-postcode-lookup.url}") String url, + @Value("${os-postcode-lookup.key}") String accessKey) { + this.url = url; + this.accessKey = accessKey; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupService.java b/src/main/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupService.java new file mode 100644 index 00000000..541502ab --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupService.java @@ -0,0 +1,103 @@ +package uk.gov.hmcts.reform.civil.postcode; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +@Service +@Slf4j +@SuppressWarnings("unchecked") +public class PostcodeLookupService { + + private static final Logger LOG = LoggerFactory.getLogger(PostcodeLookupService.class); + + private final RestTemplate restTemplate; + private final PostcodeLookupConfiguration configuration; + + public PostcodeLookupService(RestTemplate restTemplate, PostcodeLookupConfiguration configuration) { + this.restTemplate = restTemplate; + this.configuration = configuration; + } + + public boolean validatePostCodeForDefendant(String postcode) { + String countryName = fetchCountryFromPostCode(postcode.toUpperCase(Locale.UK)); + return (countryName != null + && (CountriesAllowed.ENGLAND.name().equals(countryName.toUpperCase(Locale.UK)) + || CountriesAllowed.WALES.name().equals(countryName.toUpperCase(Locale.UK)))); + } + + private String fetchCountryFromPostCode(String postcode) { + String countryName = null; + String postcodeFromApilookup = null; + HttpEntity response = null; + try { + + Map params = new HashMap<>(); + String postcodeQueryParam = StringUtils.deleteWhitespace(postcode) + "+localtype=postcode"; + params.put("query", postcodeQueryParam); + params.put("maxresults", "1"); + String url = configuration.getUrl(); + String key = configuration.getAccessKey(); + params.put("key", key); + if (url == null) { + throw new RuntimeException("Postcode URL is null"); + } + if (key == null || key.equals("")) { + throw new RuntimeException("Postcode API Key is null"); + } + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url); + for (Map.Entry entry : params.entrySet()) { + builder.queryParam(entry.getKey(), entry.getValue()); + } + + HttpHeaders headers = new HttpHeaders(); + headers.set("Accept", "application/json"); + + response = restTemplate.exchange( + builder.toUriString(), + HttpMethod.GET, + new HttpEntity(headers), + String.class + ); + + HttpStatus responseStatus = ((ResponseEntity) response).getStatusCode(); + + if (responseStatus.value() == org.apache.http.HttpStatus.SC_OK) { + JSONObject jsonObj = new JSONObject(response.getBody()); + + if (jsonObj.has("results")) { + JSONObject gazeteerEntry = new JSONObject(new JSONObject(((JSONArray) jsonObj + .get("results")).get(0).toString()).get("GAZETTEER_ENTRY").toString()); + postcodeFromApilookup = StringUtils.deleteWhitespace(gazeteerEntry.get("NAME1").toString()); + if (postcodeFromApilookup.equals(StringUtils.deleteWhitespace(postcode))) { + countryName = gazeteerEntry.get("COUNTRY").toString(); + } + } + } else if (responseStatus.value() == org.apache.http.HttpStatus.SC_NOT_FOUND) { + LOG.info("Postcode " + postcode + " not found"); + } else { + LOG.info("Postcode lookup failed with status ", responseStatus.value()); + } + + } catch (Exception e) { + LOG.error("Postcode Lookup Failed - ", e.getMessage()); + throw new RuntimeException(e); + } + return countryName; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/prd/client/OrganisationApi.java b/src/main/java/uk/gov/hmcts/reform/civil/prd/client/OrganisationApi.java new file mode 100644 index 00000000..1ca47570 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/prd/client/OrganisationApi.java @@ -0,0 +1,27 @@ +package uk.gov.hmcts.reform.civil.prd.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import uk.gov.hmcts.reform.civil.prd.model.Organisation; + +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static uk.gov.hmcts.reform.ccd.client.CoreCaseDataApi.SERVICE_AUTHORIZATION; + +@FeignClient(name = "rd-professional-api", url = "${rd_professional.api.url}") +public interface OrganisationApi { + + @GetMapping("/refdata/external/v1/organisations") + Organisation findUserOrganisation( + @RequestHeader(AUTHORIZATION) String authorisation, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization + ); + + @GetMapping("/refdata/internal/v1/organisations") + Organisation findOrganisationById( + @RequestHeader(AUTHORIZATION) String authorisation, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization, + @RequestParam("id") String organisationId + ); +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/prd/model/ContactInformation.java b/src/main/java/uk/gov/hmcts/reform/civil/prd/model/ContactInformation.java new file mode 100644 index 00000000..018c1a10 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/prd/model/ContactInformation.java @@ -0,0 +1,24 @@ +package uk.gov.hmcts.reform.civil.prd.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +public class ContactInformation { + + private String addressLine1; + private String addressLine2; + private String addressLine3; + private String country; + private String county; + private List dxAddress; + private String postCode; + private String townCity; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/prd/model/DxAddress.java b/src/main/java/uk/gov/hmcts/reform/civil/prd/model/DxAddress.java new file mode 100644 index 00000000..aa7fb516 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/prd/model/DxAddress.java @@ -0,0 +1,16 @@ +package uk.gov.hmcts.reform.civil.prd.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DxAddress { + + private String dxExchange; + private String dxNumber; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/prd/model/Organisation.java b/src/main/java/uk/gov/hmcts/reform/civil/prd/model/Organisation.java new file mode 100644 index 00000000..0898221c --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/prd/model/Organisation.java @@ -0,0 +1,26 @@ +package uk.gov.hmcts.reform.civil.prd.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Organisation { + + private String companyNumber; + private String companyUrl; + private List contactInformation; + private String name; + private String organisationIdentifier; + private List paymentAccount; + private String sraId; + private boolean sraRegulated; + private String status; + private SuperUser superUser; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/prd/model/SuperUser.java b/src/main/java/uk/gov/hmcts/reform/civil/prd/model/SuperUser.java new file mode 100644 index 00000000..4b847ce5 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/prd/model/SuperUser.java @@ -0,0 +1,17 @@ +package uk.gov.hmcts.reform.civil.prd.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SuperUser { + + private String email; + private String firstName; + private String lastName; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/ras/client/RoleAssignmentsApi.java b/src/main/java/uk/gov/hmcts/reform/civil/ras/client/RoleAssignmentsApi.java new file mode 100644 index 00000000..04a10631 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/ras/client/RoleAssignmentsApi.java @@ -0,0 +1,29 @@ +package uk.gov.hmcts.reform.civil.ras.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import uk.gov.hmcts.reform.civil.ras.model.RoleAssignmentServiceResponse; + +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@FeignClient(name = "am-role-assignment-service-api", url = "${role-assignment-service.api.url}") +public interface RoleAssignmentsApi { + + String SERVICE_AUTHORIZATION = "ServiceAuthorization"; + String ACTOR_ID = "actorId"; + + @GetMapping( + value = "/am/role-assignments/actors/{actorId}", + consumes = APPLICATION_JSON_VALUE, + headers = CONTENT_TYPE + "=" + APPLICATION_JSON_VALUE + ) + RoleAssignmentServiceResponse getRoleAssignments( + @RequestHeader(AUTHORIZATION) String authorization, + @RequestHeader(SERVICE_AUTHORIZATION) String serviceAuthorization, + @PathVariable(ACTOR_ID) String actorId); + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/ras/model/Attributes.java b/src/main/java/uk/gov/hmcts/reform/civil/ras/model/Attributes.java new file mode 100644 index 00000000..0725a0f7 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/ras/model/Attributes.java @@ -0,0 +1,19 @@ +package uk.gov.hmcts.reform.civil.ras.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Attributes { + + private String substantive; + private String caseId; + private String jurisdiction; + private String caseType; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/ras/model/RoleAssignmentResponse.java b/src/main/java/uk/gov/hmcts/reform/civil/ras/model/RoleAssignmentResponse.java new file mode 100644 index 00000000..a0d6c60b --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/ras/model/RoleAssignmentResponse.java @@ -0,0 +1,28 @@ +package uk.gov.hmcts.reform.civil.ras.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RoleAssignmentResponse { + + private String actorId; + private String actorIdType; + private String roleType; + private String roleName; + private String classification; + private String grantType; + private String roleCategory; + private Boolean readOnly; + private LocalDate beginTime; + private LocalDate created; + private Attributes attributes; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/ras/model/RoleAssignmentServiceResponse.java b/src/main/java/uk/gov/hmcts/reform/civil/ras/model/RoleAssignmentServiceResponse.java new file mode 100644 index 00000000..cc305264 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/ras/model/RoleAssignmentServiceResponse.java @@ -0,0 +1,18 @@ +package uk.gov.hmcts.reform.civil.ras.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RoleAssignmentServiceResponse { + + private List roleAssignmentResponse; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/referencedata/JRDConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/JRDConfiguration.java new file mode 100644 index 00000000..19107af7 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/JRDConfiguration.java @@ -0,0 +1,20 @@ +package uk.gov.hmcts.reform.civil.referencedata; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +public class JRDConfiguration { + + private final String url; + private final String endpoint; + + public JRDConfiguration( + @Value("${genApp.jrd.url}") String url, + @Value("${genApp.jrd.endpoint}") String endpoint) { + this.url = url; + this.endpoint = endpoint; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/referencedata/JudicialRefDataService.java b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/JudicialRefDataService.java new file mode 100644 index 00000000..2e23a34a --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/JudicialRefDataService.java @@ -0,0 +1,72 @@ +package uk.gov.hmcts.reform.civil.referencedata; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.referencedata.model.JudgeRefData; +import uk.gov.hmcts.reform.civil.referencedata.model.JudgeSearchRequest; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class JudicialRefDataService { + + private final RestTemplate restTemplate; + private final JRDConfiguration jrdConfiguration; + private final AuthTokenGenerator authTokenGenerator; + + public List getJudgeReferenceData(String searchString, String authToken) { + JudgeSearchRequest jsr = JudgeSearchRequest.builder() + .searchString(searchString) + .build(); + + HttpEntity request = new HttpEntity<>(jsr, getHeaders(authToken)); + + try { + ResponseEntity> responseEntity = restTemplate.exchange( + buildURI(), + HttpMethod.POST, + request, + new ParameterizedTypeReference>() { + } + ); + + return responseEntity.getBody(); + } catch (Exception e) { + log.error("Judicial Reference Data Lookup Failed - " + e.getMessage(), e); + } + + return new ArrayList<>(); + } + + private URI buildURI() { + String queryURL = jrdConfiguration.getUrl() + jrdConfiguration.getEndpoint(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(queryURL); + + return builder.buildAndExpand(new HashMap<>()).toUri(); + } + + private HttpHeaders getHeaders(String authToken) { + HttpHeaders headers = new HttpHeaders(); + + headers.setContentType(MediaType.APPLICATION_JSON); + headers.add("Authorization", authToken); + headers.add("ServiceAuthorization", authTokenGenerator.generate()); + + return headers; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LRDConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LRDConfiguration.java new file mode 100644 index 00000000..ae20db8b --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LRDConfiguration.java @@ -0,0 +1,20 @@ +package uk.gov.hmcts.reform.civil.referencedata; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +public class LRDConfiguration { + + private final String url; + private final String endpoint; + + public LRDConfiguration( + @Value("${genApp.lrd.url}") String url, + @Value("${genApp.lrd.endpoint}") String endpoint) { + this.url = url; + this.endpoint = endpoint; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LocationRefDataException.java b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LocationRefDataException.java new file mode 100644 index 00000000..7dbf7fd6 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LocationRefDataException.java @@ -0,0 +1,13 @@ +package uk.gov.hmcts.reform.civil.referencedata; + +public class LocationRefDataException extends RuntimeException { + + public LocationRefDataException(String message) { + super(message); + } + + public LocationRefDataException(Exception cause) { + super(cause); + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LocationRefDataService.java b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LocationRefDataService.java new file mode 100644 index 00000000..454333ff --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/LocationRefDataService.java @@ -0,0 +1,232 @@ +package uk.gov.hmcts.reform.civil.referencedata; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.referencedata.model.LocationRefData; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.apache.logging.log4j.util.Strings.concat; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LocationRefDataService { + + private final RestTemplate restTemplate; + private final LRDConfiguration lrdConfiguration; + private final AuthTokenGenerator authTokenGenerator; + + public LocationRefData getCcmccLocation(String authToken) { + try { + ResponseEntity> responseEntity = restTemplate.exchange( + buildURIforCcmcc(), + HttpMethod.GET, + getHeaders(authToken), + new ParameterizedTypeReference>() { + } + ); + List ccmccLocations = responseEntity.getBody(); + if (ccmccLocations == null || ccmccLocations.isEmpty()) { + log.warn("Location Reference Data Lookup did not return any CCMCC location"); + return LocationRefData.builder().build(); + } else { + if (ccmccLocations.size() > 1) { + log.warn("Location Reference Data Lookup returned more than one CCMCC location"); + } + return ccmccLocations.get(0); + } + } catch (Exception e) { + log.error("Location Reference Data Lookup Failed - " + e.getMessage(), e); + } + return LocationRefData.builder().build(); + } + + public List getCourtLocationsForDefaultJudgments(String authToken) { + try { + ResponseEntity> responseEntity = restTemplate.exchange( + buildURIForDefaultJudgments(), + HttpMethod.GET, + getHeaders(authToken), + new ParameterizedTypeReference<>() { + } + ); + return responseEntity.getBody(); + } catch (Exception e) { + log.error("Location Reference Data Lookup Failed - " + e.getMessage(), e); + } + return new ArrayList<>(); + } + + public List getCourtLocationsForGeneralApplication(String authToken) { + try { + ResponseEntity> responseEntity = restTemplate.exchange( + buildURI(), + HttpMethod.GET, + getHeaders(authToken), + new ParameterizedTypeReference<>() { + } + ); + return onlyEnglandAndWalesLocations(responseEntity.getBody()) + .stream().sorted(Comparator.comparing(LocationRefData::getSiteName)).collect(Collectors.toList()); + } catch (Exception e) { + log.error("Location Reference Data Lookup Failed - " + e.getMessage(), e); + } + return new ArrayList<>(); + } + + public List getCourtLocationsByEpimmsId(String authToken, String epimmsId) { + try { + ResponseEntity> responseEntity = restTemplate.exchange( + buildURIforCourtLocation(epimmsId), + HttpMethod.GET, + getHeaders(authToken), + new ParameterizedTypeReference<>() { + } + ); + return responseEntity.getBody(); + } catch (Exception e) { + log.error("Location Reference Data Lookup Failed - " + e.getMessage(), e); + } + return new ArrayList<>(); + } + + private URI buildURI() { + String queryURL = lrdConfiguration.getUrl() + lrdConfiguration.getEndpoint(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(queryURL) + .queryParam("is_hearing_location", "Y") + .queryParam("is_case_management_location", "Y") + .queryParam("court_type_id", "10") + .queryParam("location_type", "Court"); + return builder.buildAndExpand(new HashMap<>()).toUri(); + } + + private URI buildURIforCcmcc() { + String queryURL = lrdConfiguration.getUrl() + lrdConfiguration.getEndpoint(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(queryURL) + .queryParam("court_venue_name", "County Court Money Claims Centre"); + return builder.buildAndExpand(new HashMap<>()).toUri(); + } + + private URI buildURIForDefaultJudgments() { + String queryURL = lrdConfiguration.getUrl() + lrdConfiguration.getEndpoint(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(queryURL) + .queryParam("is_hearing_location", "Y") + .queryParam("is_case_management_location", "Y") + .queryParam("court_type_id", "10") + .queryParam("location_type", "Court"); + return builder.buildAndExpand(new HashMap<>()).toUri(); + } + + private URI buildURIforCourtLocation(String epimmsId) { + String queryURL = lrdConfiguration.getUrl() + lrdConfiguration.getEndpoint(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(queryURL) + .queryParam("epimms_id", epimmsId); + return builder.buildAndExpand(new HashMap<>()).toUri(); + } + + private HttpEntity getHeaders(String authToken) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", authToken); + headers.add("ServiceAuthorization", authTokenGenerator.generate()); + return new HttpEntity<>(headers); + } + + private List onlyEnglandAndWalesLocations(List locationRefData) { + return locationRefData == null + ? new ArrayList<>() + : locationRefData.stream().filter(location -> !"Scotland".equals(location.getRegion())) + .collect(Collectors.toList()); + } + + public Optional getLocationMatchingLabel(String label, String bearerToken) { + if (StringUtils.isBlank(label)) { + return Optional.empty(); + } + + List locations = getCourtLocationsForDefaultJudgments(bearerToken); + return locations.stream().filter(loc -> LocationRefDataService.getDisplayEntry(loc) + .equals(label)) + .findFirst(); + } + + /** + * Label is siteName - courtAddress - postCode. + * + * @param location a location + * @return string to serve as label + */ + public static String getDisplayEntry(LocationRefData location) { + return concat( + concat(concat(location.getSiteName(), " - "), concat(location.getCourtAddress(), " - ")), + location.getPostcode() + ); + } + + public LocationRefData getCourtLocation(String authToken, String threeDigitCode) { + try { + ResponseEntity> responseEntity = restTemplate.exchange( + buildURIforCourtCode(threeDigitCode), + HttpMethod.GET, + getHeaders(authToken), + new ParameterizedTypeReference>() { + } + ); + List locations = responseEntity.getBody(); + if (locations == null || locations.isEmpty()) { + return LocationRefData.builder().build(); + } else { + return filterCourtLocation(locations, threeDigitCode); + + } + } catch (Exception e) { + log.error("Location Reference Data Lookup Failed - " + e.getMessage(), e); + throw e; + } + + } + + private URI buildURIforCourtCode(String courtCode) { + String queryURL = lrdConfiguration.getUrl() + lrdConfiguration.getEndpoint(); + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(queryURL) + .queryParam("court_type_id", "10") + .queryParam("is_case_management_location", "Y") + .queryParam("court_location_code", courtCode) + .queryParam("court_status", "Open"); + + return builder.buildAndExpand(new HashMap<>()).toUri(); + } + + private LocationRefData filterCourtLocation(List locations, String courtCode) { + List filteredLocations = locations.stream().filter(location -> location.getCourtLocationCode() + .equals(courtCode)) + .collect(Collectors.toList()); + if (filteredLocations.isEmpty()) { + log.warn("No court Location Found for three digit court code : {}", courtCode); + throw new LocationRefDataException("No court Location Found for three digit court code : " + courtCode); + } else if (filteredLocations.size() > 1) { + log.warn("More than one court location found : {}", courtCode); + throw new LocationRefDataException("More than one court location found : " + courtCode); + } + + return filteredLocations.get(0); + + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/JudgeRefData.java b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/JudgeRefData.java new file mode 100644 index 00000000..9ac3b847 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/JudgeRefData.java @@ -0,0 +1,45 @@ +package uk.gov.hmcts.reform.civil.referencedata.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +public class JudgeRefData { + + private String title; + private String knownAs; + private String surname; + private String fullName; + private String emailId; + private String idamId; + private String personalCode; + private String isJudge; + private String isPanelMember; + private String isMagistrate; + + @JsonCreator + public JudgeRefData(@JsonProperty("post_nominals") String title, + @JsonProperty("known_as") String knownAs, + @JsonProperty("surname") String surname, + @JsonProperty("full_name") String fullName, + @JsonProperty("ejudiciary_email") String emailId, + @JsonProperty("sidam_id") String idamId, + @JsonProperty("personal_code") String personalCode, + @JsonProperty("is_judge") String isJudge, + @JsonProperty("is_panel_member") String isPanelMember, + @JsonProperty("is_magistrate") String isMagistrate) { + this.title = title; + this.knownAs = knownAs; + this.surname = surname; + this.fullName = fullName; + this.emailId = emailId; + this.idamId = idamId; + this.personalCode = personalCode; + this.isJudge = isJudge; + this.isPanelMember = isPanelMember; + this.isMagistrate = isMagistrate; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/JudgeSearchRequest.java b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/JudgeSearchRequest.java new file mode 100644 index 00000000..1d980fbe --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/JudgeSearchRequest.java @@ -0,0 +1,46 @@ +package uk.gov.hmcts.reform.civil.referencedata.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class JudgeSearchRequest { + + @JsonProperty("searchString") + @NotNull(message = "cannot be null") + @NotEmpty(message = "cannot be empty") + @Size(min = 3, message = "should have atleast {min} characters") + @Pattern(regexp = "[a-zA-Z]+", message = "should contains letters only") + private String searchString; + + @JsonProperty("serviceCode") + @Pattern(regexp = "[a-zA-Z0-9]+", message = "should not be empty or contain special characters") + private String serviceCode; + + @JsonProperty("location") + @Pattern(regexp = "[a-zA-Z0-9]+", message = "should not be empty or contain special characters") + private String location; + + public void setSearchString(String searchString) { + this.searchString = searchString.trim(); + } + + public void setServiceCode(String serviceCode) { + this.serviceCode = serviceCode != null ? serviceCode.trim().toLowerCase() : null; + } + + public void setLocation(String location) { + this.location = location != null ? location.trim().toLowerCase() : null; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/LocationRefData.java b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/LocationRefData.java new file mode 100644 index 00000000..0bc32737 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/referencedata/model/LocationRefData.java @@ -0,0 +1,63 @@ +package uk.gov.hmcts.reform.civil.referencedata.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +public class LocationRefData { + + private String courtVenueId; + private String epimmsId; + private String siteName; + private String regionId; + private String region; + private String courtType; + private String courtTypeId; + private String courtAddress; + private String postcode; + private String phoneNumber; + private String courtLocationCode; + private String courtStatus; + private String courtName; + private String venueName; + private String locationType; + private String parentLocation; + + @JsonCreator + LocationRefData(@JsonProperty("court_venue_id") String courtVenueId, + @JsonProperty("epimms_id") String epimmsId, + @JsonProperty("site_name") String siteName, + @JsonProperty("region_id") String regionId, + @JsonProperty("region") String region, + @JsonProperty("court_type") String courtType, + @JsonProperty("court_type_id") String courtTypeId, + @JsonProperty("court_address") String courtAddress, + @JsonProperty("postcode") String postcode, + @JsonProperty("phone_number") String phoneNumber, + @JsonProperty("court_location_code") String courtLocationCode, + @JsonProperty("court_status") String courtStatus, + @JsonProperty("court_name") String courtName, + @JsonProperty("venue_name") String venueName, + @JsonProperty("location_type") String locationType, + @JsonProperty("parent_location") String parentLocation) { + this.courtVenueId = courtVenueId; + this.epimmsId = epimmsId; + this.siteName = siteName; + this.regionId = regionId; + this.region = region; + this.courtType = courtType; + this.courtTypeId = courtTypeId; + this.courtAddress = courtAddress; + this.postcode = postcode; + this.phoneNumber = phoneNumber; + this.courtLocationCode = courtLocationCode; + this.courtStatus = courtStatus; + this.courtName = courtName; + this.venueName = venueName; + this.locationType = locationType; + this.parentLocation = parentLocation; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailAttachment.java b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailAttachment.java new file mode 100644 index 00000000..2f854c77 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailAttachment.java @@ -0,0 +1,42 @@ +package uk.gov.hmcts.reform.civil.sendgrid; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.InputStreamSource; + +import static java.util.Objects.requireNonNull; + +@Getter +@EqualsAndHashCode +public class EmailAttachment { + + private final InputStreamSource data; + private final String contentType; + private final String filename; + + public EmailAttachment(InputStreamSource data, String contentType, String filename) { + requireNonNull(data); + requireNonNull(contentType); + requireNonNull(filename); + this.data = data; + this.contentType = contentType; + this.filename = filename; + } + + public static EmailAttachment pdf(byte[] content, String fileName) { + return create("application/pdf", content, fileName); + } + + public static EmailAttachment json(byte[] content, String fileName) { + return create("application/json", content, fileName); + } + + private static EmailAttachment create(String contentType, byte[] content, String filename) { + return new EmailAttachment( + new ByteArrayResource(content), + contentType, + filename + ); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailData.java b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailData.java new file mode 100644 index 00000000..1b95fac2 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailData.java @@ -0,0 +1,36 @@ +package uk.gov.hmcts.reform.civil.sendgrid; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.List; + +import static java.util.Collections.unmodifiableList; + +@EqualsAndHashCode +@Getter +public class EmailData { + + private final String to; + private final String subject; + private final String message; + private final List attachments; + + @Builder + public EmailData( + String to, + String subject, + String message, + List attachments + ) { + this.to = to; + this.subject = subject; + this.message = message; + this.attachments = attachments != null ? unmodifiableList(attachments) : List.of(); + } + + public boolean hasAttachments() { + return this.attachments != null && !this.attachments.isEmpty(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailSendFailedException.java b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailSendFailedException.java new file mode 100644 index 00000000..cf3ae658 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/EmailSendFailedException.java @@ -0,0 +1,12 @@ +package uk.gov.hmcts.reform.civil.sendgrid; + +public class EmailSendFailedException extends RuntimeException { + + public EmailSendFailedException(String message, Throwable cause) { + super(message, cause); + } + + public EmailSendFailedException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/SendGridClient.java b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/SendGridClient.java new file mode 100644 index 00000000..a16b25b1 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/SendGridClient.java @@ -0,0 +1,120 @@ +package uk.gov.hmcts.reform.civil.sendgrid; + +import com.sendgrid.Method; +import com.sendgrid.Request; +import com.sendgrid.Response; +import com.sendgrid.SendGrid; +import com.sendgrid.helpers.mail.Mail; +import com.sendgrid.helpers.mail.objects.Attachments; +import com.sendgrid.helpers.mail.objects.Content; +import com.sendgrid.helpers.mail.objects.Email; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpException; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SendGridClient { + + private static final String TEXT_PLAIN_VALUE = "text/plain"; + + private final SendGrid sendGrid; + + @Retryable(value = EmailSendFailedException.class, backoff = @Backoff(delay = 100, maxDelay = 500)) + public void sendEmail(String from, EmailData emailData) { + verifyData(from, emailData); + try { + Email sender = new Email(from); + String subject = emailData.getSubject(); + Email recipient = new Email(emailData.getTo()); + Content content = new Content(TEXT_PLAIN_VALUE, getMessage(emailData)); + Mail mail = new Mail(sender, subject, recipient, content); + emailData.getAttachments().stream() + .map(SendGridClient::toSendGridAttachments) + .forEach(mail::addAttachments); + + Request request = new Request(); + request.setMethod(Method.POST); + request.setEndpoint("mail/send"); + request.setBody(mail.build()); + + Response response = sendGrid.api(request); + if (!is2xxSuccessful(response)) { + log.error("EMAIl SEND FAILED:---------" + subject); + throw new EmailSendFailedException(new HttpException(String.format( + "SendGrid returned a non-success response (%d); body: %s", + response.getStatusCode(), + response.getBody() + ))); + } + log.info("EMAIl SENT:---------" + subject); + } catch (IOException exception) { + throw new EmailSendFailedException(exception); + } + } + + private boolean is2xxSuccessful(Response response) { + return response.getStatusCode() / 100 == 2; + } + + @SuppressWarnings("unused") // it's a recovery handler and will not have explicit call + @Recover + public void logSendMessageWithAttachmentFailure( + EmailSendFailedException exception, + String from, + EmailData emailData + ) { + String errorMessage = String.format( + "sendEmail failure: failed to send email with details: %s due to %s", + emailData.toString(), exception.getMessage() + ); + log.error(errorMessage, exception); + } + + private static String getMessage(EmailData emailData) { + // SendGrid will not allow empty messages, but it's fine with blank messages. + String message = emailData.getMessage(); + if (message == null || message.isBlank()) { + message = " "; + } + return message; + } + + private static Attachments toSendGridAttachments(EmailAttachment attachment) { + try { + return new Attachments.Builder(attachment.getFilename(), attachment.getData().getInputStream()) + .withType(attachment.getContentType()) + .withDisposition("attachment") + .build(); + } catch (IOException ioException) { + throw new EmailSendFailedException( + "Could not open input stream for attachment " + attachment.getFilename(), + ioException + ); + } + } + + private void verifyData(String from, EmailData emailData) { + if (from == null || from.isBlank()) { + throw new IllegalArgumentException("from cannot be null or blank"); + } + if (emailData == null) { + throw new IllegalArgumentException("emailData cannot be null"); + } + String to = emailData.getTo(); + if (to == null || to.isBlank()) { + throw new IllegalArgumentException("emailData.to cannot be null or blank"); + } + String subject = emailData.getSubject(); + if (subject == null || subject.isBlank()) { + throw new IllegalArgumentException("emailData.subject cannot be null or blank"); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridAutoConfiguration.java b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridAutoConfiguration.java new file mode 100644 index 00000000..0a0c6229 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridAutoConfiguration.java @@ -0,0 +1,38 @@ +package uk.gov.hmcts.reform.civil.sendgrid.config; + +import com.sendgrid.SendGrid; +import com.sendgrid.SendGridAPI; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(SendGrid.class) +@ConditionalOnProperty(prefix = "sendgrid", value = "api-key") +@EnableConfigurationProperties(SendGridProperties.class) +public class SendGridAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(SendGridAPI.class) + public SendGrid sendGrid(SendGridProperties properties) { + SendGrid sendGrid = createSendGrid(properties); + if (properties.getHost() != null) { + sendGrid.setHost(properties.getHost()); + } + if (properties.getVersion() != null) { + sendGrid.setVersion(properties.getVersion()); + } + + return sendGrid; + } + + private SendGrid createSendGrid(SendGridProperties properties) { + if (properties.getTest() != null && properties.getTest()) { + return new SendGrid(properties.getApiKey(), properties.getTest()); + } + return new SendGrid(properties.getApiKey()); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridProperties.java b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridProperties.java new file mode 100644 index 00000000..32ad5ed5 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridProperties.java @@ -0,0 +1,14 @@ +package uk.gov.hmcts.reform.civil.sendgrid.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Data +@ConfigurationProperties("sendgrid") +public class SendGridProperties { + + private String apiKey; + private Boolean test; + private String host; + private String version; +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/service/AssignCaseService.java b/src/main/java/uk/gov/hmcts/reform/civil/service/AssignCaseService.java new file mode 100644 index 00000000..1f93896a --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/service/AssignCaseService.java @@ -0,0 +1,31 @@ +package uk.gov.hmcts.reform.civil.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.civil.enums.CaseRole; +import uk.gov.hmcts.reform.civil.prd.model.Organisation; + +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AssignCaseService { + + private final CoreCaseUserService coreCaseUserService; + private final UserService userService; + private final OrganisationService organisationService; + + public void assignCase(String authorisation, String caseId, Optional caseRole) { + String userId = userService.getUserInfo(authorisation).getUid(); + String organisationId = organisationService.findOrganisation(authorisation) + .map(Organisation::getOrganisationIdentifier).orElse(null); + coreCaseUserService.assignCase( + caseId, + userId, + organisationId, + caseRole.orElse(CaseRole.RESPONDENTSOLICITORONE) + ); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/service/BulkPrintService.java b/src/main/java/uk/gov/hmcts/reform/civil/service/BulkPrintService.java new file mode 100644 index 00000000..045f39cd --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/service/BulkPrintService.java @@ -0,0 +1,55 @@ +package uk.gov.hmcts.reform.civil.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.sendletter.api.LetterWithPdfsRequest; +import uk.gov.hmcts.reform.sendletter.api.SendLetterApi; +import uk.gov.hmcts.reform.sendletter.api.SendLetterResponse; + +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class BulkPrintService { + + public static final String XEROX_TYPE_PARAMETER = "CMC001"; + protected static final String ADDITIONAL_DATA_LETTER_TYPE_KEY = "letterType"; + protected static final String ADDITIONAL_DATA_CASE_IDENTIFIER_KEY = "caseIdentifier"; + protected static final String ADDITIONAL_DATA_CASE_REFERENCE_NUMBER_KEY = "caseReferenceNumber"; + + private final SendLetterApi sendLetterApi; + private final AuthTokenGenerator authTokenGenerator; + + @Retryable( + value = RuntimeException.class, + backoff = @Backoff(delay = 200) + ) + public SendLetterResponse printLetter(byte[] letterContent, String claimId, + String claimReference, String letterType) { + String authorisation = authTokenGenerator.generate(); + LetterWithPdfsRequest letter = + generateLetter(additionalInformation(claimId, claimReference, letterType), letterContent); + return sendLetterApi.sendLetter(authorisation, letter); + } + + private LetterWithPdfsRequest generateLetter(Map letterParams, byte[] letterContent) { + String templateLetter = Base64.getEncoder().encodeToString(letterContent); + return new LetterWithPdfsRequest(List.of(templateLetter), XEROX_TYPE_PARAMETER, letterParams); + } + + private Map additionalInformation(String claimId, String claimReference, String letterType) { + Map additionalData = new HashMap<>(); + additionalData.put(ADDITIONAL_DATA_LETTER_TYPE_KEY, letterType); + additionalData.put(ADDITIONAL_DATA_CASE_IDENTIFIER_KEY, claimId); + additionalData.put(ADDITIONAL_DATA_CASE_REFERENCE_NUMBER_KEY, claimReference); + return additionalData; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/service/CategoryService.java b/src/main/java/uk/gov/hmcts/reform/civil/service/CategoryService.java new file mode 100644 index 00000000..f2ce5778 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/service/CategoryService.java @@ -0,0 +1,33 @@ +package uk.gov.hmcts.reform.civil.service; + +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.crd.client.ListOfValuesApi; +import uk.gov.hmcts.reform.civil.crd.model.CategorySearchResult; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CategoryService { + + private final ListOfValuesApi listOfValuesApi; + private final AuthTokenGenerator authTokenGenerator; + + public Optional findCategoryByCategoryIdAndServiceId(String authToken, String categoryId, String serviceId) { + try { + return Optional.ofNullable(listOfValuesApi.findCategoryByCategoryIdAndServiceId(categoryId, serviceId, authToken, + authTokenGenerator.generate())); + } catch (FeignException.NotFound ex) { + log.error("Category not found", ex); + return Optional.empty(); + } catch (FeignException.Forbidden ex) { + log.error("Access forbidden for this user", ex); + return Optional.empty(); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/service/CoreCaseUserService.java b/src/main/java/uk/gov/hmcts/reform/civil/service/CoreCaseUserService.java new file mode 100644 index 00000000..07649172 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/service/CoreCaseUserService.java @@ -0,0 +1,117 @@ +package uk.gov.hmcts.reform.civil.service; + +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.ccd.client.CaseAccessDataStoreApi; +import uk.gov.hmcts.reform.ccd.model.AddCaseAssignedUserRolesRequest; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRoleWithOrganisation; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRolesRequest; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRolesResource; +import uk.gov.hmcts.reform.civil.config.CrossAccessUserConfiguration; +import uk.gov.hmcts.reform.civil.enums.CaseRole; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class CoreCaseUserService { + + Logger log = LoggerFactory.getLogger(CoreCaseUserService.class); + + private final CaseAccessDataStoreApi caseAccessDataStoreApi; + private final UserService userService; + private final CrossAccessUserConfiguration crossAccessUserConfiguration; + private final AuthTokenGenerator authTokenGenerator; + + public List getUserCaseRoles(String caseId, String userId) { + return caseAccessDataStoreApi.getUserRoles(getCaaAccessToken(), authTokenGenerator.generate(), List.of(caseId)) + .getCaseAssignedUserRoles().stream() + .filter(c -> c.getUserId().equals(userId)).distinct() + .map(c -> c.getCaseRole()).collect(Collectors.toList()); + } + + public void assignCase(String caseId, String userId, String organisationId, CaseRole caseRole) { + String caaAccessToken = getCaaAccessToken(); + + if (!userWithCaseRoleExistsOnCase(caseId, caaAccessToken, caseRole)) { + assignUserToCaseForRole(caseId, userId, organisationId, caseRole, caaAccessToken); + } else { + log.info("Case already have the user with {} role", caseRole.getFormattedName()); + } + } + + public void removeCreatorRoleCaseAssignment(String caseId, String userId, String organisationId) { + + String caaAccessToken = getCaaAccessToken(); + + if (userWithCaseRoleExistsOnCase(caseId, caaAccessToken, CaseRole.CREATOR)) { + removeCreatorAccess(caseId, userId, organisationId, caaAccessToken); + } else { + log.info("User doesn't have {} role", CaseRole.CREATOR.getFormattedName()); + } + } + + public boolean userHasCaseRole(String caseId, String userId, CaseRole caseRole) { + return getUserCaseRoles(caseId, userId).stream() + .anyMatch(c -> c.equals(caseRole.getFormattedName())); + } + + private String getCaaAccessToken() { + return userService.getAccessToken( + crossAccessUserConfiguration.getUserName(), + crossAccessUserConfiguration.getPassword() + ); + } + + private void assignUserToCaseForRole(String caseId, String userId, String organisationId, + CaseRole caseRole, String caaAccessToken) { + CaseAssignedUserRoleWithOrganisation caseAssignedUserRoleWithOrganisation + = CaseAssignedUserRoleWithOrganisation.builder() + .caseDataId(caseId) + .userId(userId) + .caseRole(caseRole.getFormattedName()) + .organisationId(organisationId) + .build(); + + caseAccessDataStoreApi.addCaseUserRoles( + caaAccessToken, + authTokenGenerator.generate(), + AddCaseAssignedUserRolesRequest.builder() + .caseAssignedUserRoles(List.of(caseAssignedUserRoleWithOrganisation)) + .build() + ); + } + + private void removeCreatorAccess(String caseId, String userId, String organisationId, String caaAccessToken) { + CaseAssignedUserRoleWithOrganisation caseAssignedUserRoleWithOrganisation + = CaseAssignedUserRoleWithOrganisation.builder() + .caseDataId(caseId) + .userId(userId) + .caseRole(CaseRole.CREATOR.getFormattedName()) + .organisationId(organisationId) + .build(); + + caseAccessDataStoreApi.removeCaseUserRoles( + caaAccessToken, + authTokenGenerator.generate(), + CaseAssignedUserRolesRequest.builder() + .caseAssignedUserRoles(List.of(caseAssignedUserRoleWithOrganisation)) + .build() + ); + } + + private boolean userWithCaseRoleExistsOnCase(String caseId, String accessToken, CaseRole caseRole) { + CaseAssignedUserRolesResource userRoles = caseAccessDataStoreApi.getUserRoles( + accessToken, + authTokenGenerator.generate(), + List.of(caseId) + ); + + return userRoles.getCaseAssignedUserRoles().stream() + .anyMatch(c -> c.getCaseRole().equals(caseRole.getFormattedName())); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/service/OrganisationService.java b/src/main/java/uk/gov/hmcts/reform/civil/service/OrganisationService.java new file mode 100644 index 00000000..2987903d --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/service/OrganisationService.java @@ -0,0 +1,50 @@ +package uk.gov.hmcts.reform.civil.service; + +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.config.PrdAdminUserConfiguration; +import uk.gov.hmcts.reform.civil.prd.client.OrganisationApi; +import uk.gov.hmcts.reform.civil.prd.model.Organisation; + +import java.util.Optional; + +import static java.util.Optional.ofNullable; + +@Service +@RequiredArgsConstructor +@Slf4j +public class OrganisationService { + + private final OrganisationApi organisationApi; + private final AuthTokenGenerator authTokenGenerator; + private final UserService userService; + private final PrdAdminUserConfiguration userConfig; + + //WARNING! below function findOrganisation is being used by both damages and specified claims, + // changes to this code may break one of the claim journeys, check with respective teams before changing it + + public Optional findOrganisation(String authToken) { + try { + return ofNullable(organisationApi.findUserOrganisation(authToken, authTokenGenerator.generate())); + + } catch (FeignException.NotFound | FeignException.Forbidden ex) { + log.error("User not registered in MO", ex); + return Optional.empty(); + } + } + + //WARNING! below function findOrganisationById is being used by both damages and specified claims, + // changes to this code may break one of the claim journeys, check with respective teams before changing it + public Optional findOrganisationById(String id) { + String authToken = userService.getAccessToken(userConfig.getUsername(), userConfig.getPassword()); + try { + return ofNullable(organisationApi.findOrganisationById(authToken, authTokenGenerator.generate(), id)); + } catch (FeignException.NotFound ex) { + log.error("Organisation not found", ex); + return Optional.empty(); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/service/RoleAssignmentsService.java b/src/main/java/uk/gov/hmcts/reform/civil/service/RoleAssignmentsService.java new file mode 100644 index 00000000..3d9a2a45 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/service/RoleAssignmentsService.java @@ -0,0 +1,31 @@ +package uk.gov.hmcts.reform.civil.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.ras.client.RoleAssignmentsApi; +import uk.gov.hmcts.reform.civil.ras.model.RoleAssignmentServiceResponse; + +@Service +@RequiredArgsConstructor +@Slf4j +public class RoleAssignmentsService { + + private final RoleAssignmentsApi roleAssignmentApi; + private final AuthTokenGenerator authTokenGenerator; + + public RoleAssignmentServiceResponse getRoleAssignments(String actorId, + String authorization) { + + if (log.isDebugEnabled()) { + log.debug(actorId, "Getting Role assignments for actorId {0}"); + } + + return roleAssignmentApi.getRoleAssignments( + authorization, + authTokenGenerator.generate(), + actorId + ); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/service/UserService.java b/src/main/java/uk/gov/hmcts/reform/civil/service/UserService.java new file mode 100644 index 00000000..5d0c26c5 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/service/UserService.java @@ -0,0 +1,28 @@ +package uk.gov.hmcts.reform.civil.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.idam.client.IdamClient; +import uk.gov.hmcts.reform.idam.client.models.UserInfo; + +@Service +public class UserService { + + private final IdamClient idamClient; + + @Autowired + public UserService(IdamClient idamClient) { + this.idamClient = idamClient; + } + + @Cacheable(value = "userInfoCache") + public UserInfo getUserInfo(String bearerToken) { + return idamClient.getUserInfo(bearerToken); + } + + @Cacheable(value = "accessTokenCache") + public String getAccessToken(String username, String password) { + return idamClient.getAccessToken(username, password); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/utils/CaseDataContentConverter.java b/src/main/java/uk/gov/hmcts/reform/civil/utils/CaseDataContentConverter.java new file mode 100644 index 00000000..445b595a --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/utils/CaseDataContentConverter.java @@ -0,0 +1,29 @@ +package uk.gov.hmcts.reform.civil.utils; + +import uk.gov.hmcts.reform.ccd.client.model.CaseDataContent; +import uk.gov.hmcts.reform.ccd.client.model.Event; +import uk.gov.hmcts.reform.ccd.client.model.StartEventResponse; + +import java.util.HashMap; +import java.util.Map; + +public class CaseDataContentConverter { + + private CaseDataContentConverter() { + + } + + public static CaseDataContent caseDataContentFromStartEventResponse( + StartEventResponse startEventResponse, Map contentModified) { + var payload = new HashMap<>(startEventResponse.getCaseDetails().getData()); + payload.putAll(contentModified); + + return CaseDataContent.builder() + .eventToken(startEventResponse.getToken()) + .event(Event.builder() + .id(startEventResponse.getEventId()) + .build()) + .data(payload) + .build(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/utils/MonetaryConversions.java b/src/main/java/uk/gov/hmcts/reform/civil/utils/MonetaryConversions.java new file mode 100644 index 00000000..2b6372a5 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/utils/MonetaryConversions.java @@ -0,0 +1,26 @@ +package uk.gov.hmcts.reform.civil.utils; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +import static java.util.Objects.requireNonNull; + +public class MonetaryConversions { + + public static final BigDecimal HUNDRED = BigDecimal.valueOf(100); + + private MonetaryConversions() { + // Utilities class, no instances + } + + public static BigDecimal penniesToPounds(BigDecimal amountInPennies) { + requireNonNull(amountInPennies); + return amountInPennies.divide(HUNDRED, 2, RoundingMode.UNNECESSARY); + } + + public static BigInteger poundsToPennies(BigDecimal amountInPounds) { + requireNonNull(amountInPounds); + return amountInPounds.multiply(HUNDRED).toBigInteger(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/utils/ObjectUtils.java b/src/main/java/uk/gov/hmcts/reform/civil/utils/ObjectUtils.java new file mode 100644 index 00000000..13ecf876 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/utils/ObjectUtils.java @@ -0,0 +1,49 @@ +package uk.gov.hmcts.reform.civil.utils; + +public class ObjectUtils { + + private ObjectUtils() { + //NO-OP + } + + @SafeVarargs + public static T firstNonNull(final T... values) { + return nonNull(1, values); + } + + @SafeVarargs + public static T secondNonNull(final T... values) { + return nonNull(2, values); + } + + @SafeVarargs + public static T thirdNonNull(final T... values) { + return nonNull(3, values); + } + + @SafeVarargs + public static T fourthNonNull(final T... values) { + return nonNull(4, values); + } + + @SafeVarargs + public static T fifthNonNull(final T... values) { + return nonNull(5, values); + } + + @SafeVarargs + private static T nonNull(int position, final T... values) { + if (values != null) { + int nonNullCounter = 0; + for (final T val : values) { + if (val != null) { + nonNullCounter++; + if (nonNullCounter == position) { + return val; + } + } + } + } + return null; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/utils/ResourceReader.java b/src/main/java/uk/gov/hmcts/reform/civil/utils/ResourceReader.java new file mode 100644 index 00000000..9311a442 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/utils/ResourceReader.java @@ -0,0 +1,28 @@ +package uk.gov.hmcts.reform.civil.utils; + +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; + +public class ResourceReader { + + private ResourceReader() { + // NO-OP + } + + public static String readString(String resourcePath) { + return new String(ResourceReader.readBytes(resourcePath)); + } + + public static byte[] readBytes(String resourcePath) { + try (InputStream inputStream = ResourceReader.class.getClassLoader().getResourceAsStream(resourcePath)) { + if (inputStream == null) { + throw new IllegalArgumentException("Resource does not exist"); + } + return IOUtils.toByteArray(inputStream); + } catch (IOException ex) { + throw new IllegalArgumentException(ex); + } + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/utils/StringUtils.java b/src/main/java/uk/gov/hmcts/reform/civil/utils/StringUtils.java new file mode 100644 index 00000000..461a15a1 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/utils/StringUtils.java @@ -0,0 +1,24 @@ +package uk.gov.hmcts.reform.civil.utils; + +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class StringUtils { + + private StringUtils() { + //NO-OP + } + + public static String joinNonNull(String delimiter, String... values) { + if (Arrays.stream(values).allMatch(Objects::isNull)) { + return null; + } + return Stream.of(values) + .filter(Objects::nonNull) + .filter(Predicate.not(String::isBlank)) + .collect(Collectors.joining(delimiter)); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/validation/PostcodeValidator.java b/src/main/java/uk/gov/hmcts/reform/civil/validation/PostcodeValidator.java new file mode 100644 index 00000000..9bf81e7f --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/validation/PostcodeValidator.java @@ -0,0 +1,40 @@ +package uk.gov.hmcts.reform.civil.validation; + +import org.springframework.stereotype.Component; +import uk.gov.hmcts.reform.civil.postcode.PostcodeLookupService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +@Component +public class PostcodeValidator { + + private final PostcodeLookupService postcodeLookupService; + + public PostcodeValidator(PostcodeLookupService postcodeLookupService) { + this.postcodeLookupService = postcodeLookupService; + } + + public List validate(String postcode) { + List errors = new ArrayList<>(); + if (postcode != null) { + /* + Lookup to the PostCode service. Currently, the Postcode lookup service + returns England for Northern Ireland Postcodes starting with BT. + Hence, added below check for Northern Ireland postcodes. + If new regions apart from BT are added for Northern Ireland, + then this check needs to be modified accordingly. + For more details, refer to https://tools.hmcts.net/jira/browse/ROC-9113 + */ + if (postcode.toUpperCase(Locale.UK).trim().startsWith("BT") + || !(postcodeLookupService.validatePostCodeForDefendant( + postcode))) { + errors.add("Postcode must be in England or Wales"); + } + } else { + errors.add("Please enter Postcode"); + } + return errors; + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/civil/validation/ValidateEmailService.java b/src/main/java/uk/gov/hmcts/reform/civil/validation/ValidateEmailService.java new file mode 100644 index 00000000..5d53c118 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/civil/validation/ValidateEmailService.java @@ -0,0 +1,135 @@ +package uk.gov.hmcts.reform.civil.validation; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.lang.String.format; +import static java.net.IDN.toASCII; +import static java.net.IDN.toUnicode; +import static java.util.Collections.emptyList; +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static java.util.regex.Pattern.compile; +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.apache.commons.lang3.StringUtils.split; + +@Slf4j +@Service +public class ValidateEmailService { + + private static final int EMAIL_MAX_LENGTH = 320; + private static final int HOST_MAX_LENGTH = 253; + private static final int HOST_PART_MAX_LENGTH = 63; + private static final int USERNAME_MAX_LENGTH = 64; + + private static final String ERROR_MESSAGE = "Enter an email address in the correct format," + + " for example john.smith@example.com"; + private static final String LOCAL_CHARS = "a-zA-Z0-9.!#$%&'*+/=?^_`{|}~\\-"; + private static final Pattern HOSTNAME_PATTERN = compile(format( + "^(xn-|[a-z0-9]{1,%1$d})(-[a-z0-9]{1,%1$d}){0,%1$d}$", HOST_PART_MAX_LENGTH), CASE_INSENSITIVE); + private static final Pattern TLD_PATTERN = compile(format( + "^([a-z]{2,%1$d}|xn--([a-z0-9]{1,%1$d}-){0,%1$d}[a-z0-9]{1,%1$d})$", HOST_PART_MAX_LENGTH), CASE_INSENSITIVE); + private static final Pattern EMAIL_PATTERN = compile(format( + "^[%s]{1,%2$d}@([^.@][^@\\s]{2,%2$d})$", LOCAL_CHARS, EMAIL_MAX_LENGTH)); + + public List validate(String email) { + return isValid(email) ? emptyList() : List.of(ERROR_MESSAGE); + } + + /* + Mimic gov notify validation + see https://github.com/alphagov/notifications-utils/blob/master/notifications_utils/recipients.py#L494-L534 + */ + private boolean isValid(String email) { + + if (isEmpty(email)) { + log.warn("Email is null or empty"); + return false; + } + + final String emailAddress = StringUtils.trim(email); + + if (emailAddress.startsWith(".")) { + log.warn("Email begins with ."); + return false; + } + + final String username = emailAddress.split("@")[0]; + + if (username.endsWith(".")) { + log.warn("Username ends with ."); + return false; + } + + if (username.length() > USERNAME_MAX_LENGTH) { + log.warn("Email username is longer than {} characters", USERNAME_MAX_LENGTH); + return false; + } + + if (emailAddress.length() > EMAIL_MAX_LENGTH) { + log.warn("Email is longer than {} characters", EMAIL_MAX_LENGTH); + return false; + } + + if (emailAddress.contains("..")) { + log.warn("Email contains .."); + return false; + } + + if (emailAddress.contains("'")) { + log.warn("Email contains apostrophe"); + return false; + } + + final Matcher emailMatcher = EMAIL_PATTERN.matcher(emailAddress); + + if (!emailMatcher.matches()) { + log.warn("Email does not match pattern"); + return false; + } + + String hostname = emailMatcher.group(1); + + try { + hostname = toASCII(toUnicode(hostname)); + } catch (Exception e) { + log.warn("Email hostname can not be converted to ascii"); + return false; + } + + final String[] hostParts = split(hostname, "."); + + if (hostname.length() > HOST_MAX_LENGTH) { + log.warn("Email hostname is longer than {} characters", HOST_MAX_LENGTH); + return false; + } + + if (hostParts.length < 2) { + log.warn("Email hostname parts is {}", hostParts.length); + return false; + } + + for (String hostPart : hostParts) { + if (hostPart.length() > HOST_PART_MAX_LENGTH) { + log.warn("Email hostname part is longer than {}", HOST_PART_MAX_LENGTH); + return false; + } + + if (!HOSTNAME_PATTERN.matcher(hostPart).matches()) { + log.warn("Email hostname part does not match pattern"); + return false; + } + } + + if (!TLD_PATTERN.matcher(hostParts[hostParts.length - 1]).matches()) { + log.warn("Email top level domain does not match pattern"); + return false; + } + + return true; + } +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..f28a3b9a --- /dev/null +++ b/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + uk.gov.hmcts.reform.civil.sendgrid.config.SendGridAutoConfiguration diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..f5340066 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,44 @@ + +bankHolidays: + api: + url: https://www.gov.uk + +nonworking-days: + datafile: /data/non-working_days.dat + +# Postcode Lookup Service +os-postcode-lookup: + url: https://api.os.uk/search/names/v1/find + key: ${OS_POSTCODE_LOOKUP_API_KEY:} + offline-mode: false + +notifications: + govNotifyApiKey: ${GOV_NOTIFY_API_KEY:some-gov-uk-notify-api-key} + +launchdarkly: + sdk-key: ${LAUNCH_DARKLY_SDK_KEY:} + offline-mode: false + env: ${LAUNCH_DARKLY_ENV:default} + +role-assignment-service: + api: + url: http://localhost:4096 + +fees: + api: + service: civil money claims + jurisdiction1: civil + jurisdiction2: county court + channel: default + event: issue + hearingEvent: hearing + +pin-in-post: + cui-respond-to-claim: + url: ${CUI_URL_RESPOND_TO_CLAIM:http://localhost:3001/first-contact/start} + cui-front-end: + url: ${CUI_URL:http://localhost:3001} + +document_management: + userRoles: "caseworker-civil,caseworker-civil-solicitor" + secured: ${DOCUMENT_MANAGEMENT_SECURED:true} diff --git a/src/main/resources/application.yaml b/src/main/resources/data/non-working_days.dat similarity index 100% rename from src/main/resources/application.yaml rename to src/main/resources/data/non-working_days.dat diff --git a/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/NonWorkingDaysCollectionTest.java b/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/NonWorkingDaysCollectionTest.java new file mode 100644 index 00000000..4e6b3c93 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/NonWorkingDaysCollectionTest.java @@ -0,0 +1,39 @@ +package uk.gov.hmcts.reform.civil.bankholidays; + +import org.junit.jupiter.api.Test; +import uk.gov.hmcts.reform.civil.bankholidays.NonWorkingDaysCollection; + +import java.time.LocalDate; +import java.time.Month; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class NonWorkingDaysCollectionTest { + + private NonWorkingDaysCollection collection; + + @Test + void shouldReturnTrue_whenMatchingNonWorkingDays() { + collection = new NonWorkingDaysCollection("/non-working-days/nwd-valid.dat"); + assertTrue(collection.contains(LocalDate.of(2020, Month.DECEMBER, 2))); + } + + @Test + void shouldReturnFalse_whenNoNonWorkingDays() { + collection = new NonWorkingDaysCollection("/non-working-days/nwd-empty-file.dat"); + assertFalse(collection.contains(LocalDate.now())); + } + + @Test + void shouldReturnFalse_whenNonMatchingNonWorkingDays() { + collection = new NonWorkingDaysCollection("/non-working-days/nwd-valid.dat"); + assertFalse(collection.contains(LocalDate.of(2020, Month.DECEMBER, 3))); + } + + @Test + void shouldReturnFalse_whenIncoherentNonWorkingDays() { + collection = new NonWorkingDaysCollection("/non-working-days/nwd-invalid.dat"); + assertFalse(collection.contains(LocalDate.now())); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/PublicHolidaysCollectionTest.java b/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/PublicHolidaysCollectionTest.java new file mode 100644 index 00000000..711034c8 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/PublicHolidaysCollectionTest.java @@ -0,0 +1,73 @@ +package uk.gov.hmcts.reform.civil.bankholidays; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.civil.bankholidays.BankHolidays; +import uk.gov.hmcts.reform.civil.bankholidays.BankHolidaysApi; +import uk.gov.hmcts.reform.civil.bankholidays.PublicHolidaysCollection; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +class PublicHolidaysCollectionTest { + + private static final LocalDate BANK_HOLIDAY_1 = LocalDate.of(2020, 12, 24); + private static final LocalDate BANK_HOLIDAY_2 = LocalDate.of(2020, 12, 25); + + @Mock + private BankHolidaysApi bankHolidaysApi; + + @Test + void shouldReturnAllBankHolidays_whenSuccessfulResponse() { + when(bankHolidaysApi.retrieveAll()).thenReturn(createExpectedResponse()); + PublicHolidaysCollection publicHolidaysCollection = new PublicHolidaysCollection(bankHolidaysApi); + + Set response = publicHolidaysCollection.getPublicHolidays(); + + assertAll( + "Bank holidays", + () -> assertThat(response).contains(BANK_HOLIDAY_1), + () -> assertThat(response).contains(BANK_HOLIDAY_2) + ); + } + + @Test + void shouldMakeSingleExternalApiCall_whenCacheIsNotPopulated() { + when(bankHolidaysApi.retrieveAll()).thenReturn(createExpectedResponse()); + PublicHolidaysCollection publicHolidaysCollection = new PublicHolidaysCollection(bankHolidaysApi); + + Set resultFromApi = publicHolidaysCollection.getPublicHolidays(); + Set resultFromCache = publicHolidaysCollection.getPublicHolidays(); + Set resultFromCacheAgain = publicHolidaysCollection.getPublicHolidays(); + + verify(bankHolidaysApi).retrieveAll(); + assertThat(resultFromApi).isSameAs(resultFromCache).isSameAs(resultFromCacheAgain); + } + + private static BankHolidays createExpectedResponse() { + BankHolidays expResponse = new BankHolidays(); + expResponse.englandAndWales = new BankHolidays.Division(); + BankHolidays.Division.EventDate item1 = createItem(BANK_HOLIDAY_1); + BankHolidays.Division.EventDate item2 = createItem(BANK_HOLIDAY_2); + expResponse.englandAndWales.events = new ArrayList<>(Arrays.asList(item1, item2)); + + return expResponse; + } + + private static BankHolidays.Division.EventDate createItem(LocalDate date) { + BankHolidays.Division.EventDate item = new BankHolidays.Division.EventDate(); + item.date = date; + + return item; + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/WorkingDayIndicatorTest.java b/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/WorkingDayIndicatorTest.java new file mode 100644 index 00000000..ed1c2393 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/bankholidays/WorkingDayIndicatorTest.java @@ -0,0 +1,198 @@ +package uk.gov.hmcts.reform.civil.bankholidays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.civil.bankholidays.NonWorkingDaysCollection; +import uk.gov.hmcts.reform.civil.bankholidays.PublicHolidaysCollection; +import uk.gov.hmcts.reform.civil.bankholidays.WorkingDayIndicator; + +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +class WorkingDayIndicatorTest { + + private static final LocalDate BANK_HOLIDAY = LocalDate.of(2017, 5, 29); + private static final LocalDate NEXT_WORKING_DAY_AFTER_BANK_HOLIDAY = LocalDate.of(2017, 5, 30); + private static final LocalDate PREVIOUS_WORKING_DAY_BEFORE_BANK_HOLIDAY = LocalDate.of(2017, 5, 26); + private static final LocalDate SATURDAY_WEEK_BEFORE = LocalDate.of(2017, 6, 3); + private static final LocalDate SUNDAY_WEEK_BEFORE = LocalDate.of(2017, 6, 4); + private static final LocalDate MONDAY = LocalDate.of(2017, 6, 5); + private static final LocalDate TUESDAY = LocalDate.of(2017, 6, 6); + private static final LocalDate WEDNESDAY = LocalDate.of(2017, 6, 7); + private static final LocalDate THURSDAY = LocalDate.of(2017, 6, 8); + private static final LocalDate FRIDAY = LocalDate.of(2017, 6, 9); + private static final LocalDate SATURDAY = LocalDate.of(2017, 6, 10); + private static final LocalDate SUNDAY = LocalDate.of(2017, 6, 11); + + static class WeekDaysArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(MONDAY), + Arguments.of(TUESDAY), + Arguments.of(WEDNESDAY), + Arguments.of(THURSDAY), + Arguments.of(FRIDAY) + ); + } + } + + static class WeekendArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(SATURDAY), + Arguments.of(SUNDAY) + ); + } + } + + private WorkingDayIndicator service; + + @Mock + private PublicHolidaysCollection publicHolidaysApiClient; + @Mock + private NonWorkingDaysCollection nonWorkingDaysCollection; + + @BeforeEach + void setup() { + service = new WorkingDayIndicator(publicHolidaysApiClient, nonWorkingDaysCollection); + } + + @Nested + class ForWeekdays { + + @ParameterizedTest + @ArgumentsSource(WeekDaysArgumentProvider.class) + void shouldReturnTrue_whenWeekday(LocalDate weekday) { + when(publicHolidaysApiClient.getPublicHolidays()).thenReturn(Collections.emptySet()); + + assertTrue(service.isWorkingDay(weekday)); + } + } + + @Nested + class ForWeekend { + + @ParameterizedTest + @ArgumentsSource(WeekendArgumentsProvider.class) + void shouldReturnFalse_whenWeekend(LocalDate weekend) { + assertFalse(service.isWorkingDay(weekend)); + } + } + + @Nested + class IsWorkingDay { + + @Test + void shouldReturnFalseForOneBankHoliday_whenThereIsOneBankHolidayInCollection() { + when(publicHolidaysApiClient.getPublicHolidays()) + .thenReturn(new HashSet<>(singletonList(BANK_HOLIDAY))); + + assertFalse(service.isWorkingDay(BANK_HOLIDAY)); + } + + @Test + void shouldReturnFalseForPublicHoliday_WhenThereIsMoreDatesInPublicHolidaysCollection() { + Set publicHolidays = new HashSet<>(Arrays.asList(MONDAY, TUESDAY, WEDNESDAY, THURSDAY)); + when(publicHolidaysApiClient.getPublicHolidays()).thenReturn(publicHolidays); + + assertAll( + "Public holidays", + () -> assertFalse(service.isWorkingDay(MONDAY)), + () -> assertFalse(service.isWorkingDay(TUESDAY)), + () -> assertFalse(service.isWorkingDay(WEDNESDAY)), + () -> assertFalse(service.isWorkingDay(THURSDAY)) + ); + } + + @Test + void shouldReturnFalse_whenWorkingDayExcludedByNonWorkingDaysCollection() { + when(nonWorkingDaysCollection.contains(MONDAY)).thenReturn(true); + + assertFalse(service.isWorkingDay(MONDAY)); + } + } + + @Nested + class NextWorkingDay { + + @Test + void shouldReturnFollowingMonday_whenDayGivenAsSunday() { + LocalDate nextWorkingDay = service.getNextWorkingDay(SUNDAY_WEEK_BEFORE); + + assertEquals(MONDAY, nextWorkingDay); + } + + @Test + void shouldReturnFollowingMonday_whenDayGivenAsSaturday() { + LocalDate nextWorkingDay = service.getNextWorkingDay(SATURDAY_WEEK_BEFORE); + + assertEquals(MONDAY, nextWorkingDay); + } + + @Test + void shouldReturnFollowingTuesday_whenNextWorkingDayGivenABankHolidayFridayAndMonday() { + when(publicHolidaysApiClient.getPublicHolidays()).thenReturn( + new HashSet<>(singletonList(BANK_HOLIDAY)) + ); + + LocalDate nextWorkingDay = service.getNextWorkingDay(BANK_HOLIDAY); + + assertEquals(NEXT_WORKING_DAY_AFTER_BANK_HOLIDAY, nextWorkingDay); + } + + } + + @Nested + class PreviousWorkingDay { + + @Test + void shouldReturnPreviousFriday_whenNextWorkingDayGivenAsSunday() { + LocalDate previousWorkingDay = service.getPreviousWorkingDay(SUNDAY); + + assertEquals(FRIDAY, previousWorkingDay); + } + + @Test + void shouldReturnPreviousFriday_whenNextWorkingDayGivenAsSaturday() { + LocalDate previousWorkingDay = service.getPreviousWorkingDay(SATURDAY); + + assertEquals(FRIDAY, previousWorkingDay); + } + + @Test + void shouldReturnPreviousThursday_whenNextWorkingDayGivenABankHolidayFridayAndMonday() { + when(publicHolidaysApiClient.getPublicHolidays()).thenReturn( + new HashSet<>(singletonList(BANK_HOLIDAY)) + ); + + LocalDate previousWorkingDay = service.getPreviousWorkingDay(BANK_HOLIDAY); + + assertEquals(PREVIOUS_WORKING_DAY_BEFORE_BANK_HOLIDAY, previousWorkingDay); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/config/LaunchDarklyConfigurationTest.java b/src/test/java/uk/gov/hmcts/reform/civil/config/LaunchDarklyConfigurationTest.java new file mode 100644 index 00000000..753e6f92 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/config/LaunchDarklyConfigurationTest.java @@ -0,0 +1,50 @@ +package uk.gov.hmcts.reform.civil.config; + +import com.launchdarkly.sdk.server.LDClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Paths; + +public class LaunchDarklyConfigurationTest { + + private LaunchDarklyConfiguration configuration = new LaunchDarklyConfiguration(); + + /** + * LDClient build test without flag files. + */ + @Test + public void streamingLD() { + String key = "sdkkey"; + Boolean offline = false; + LDClient client = configuration.ldClient(key, offline, null); + Assertions.assertEquals(client.isOffline(), offline.booleanValue()); + + client = configuration.ldClient(key, offline, new String[0]); + Assertions.assertEquals(client.isOffline(), offline.booleanValue()); + } + + @Test + public void unexistentFiles() { + String key = "sdkkey"; + Boolean offline = false; + LDClient client = configuration.ldClient(key, offline, new String[]{ + "AFileThatDoesNotExist" + }); + Assertions.assertEquals(client.isOffline(), offline.booleanValue()); + } + + @Test + public void withFlags() { + String path = "./bin/utils/launchdarkly-flags.json"; + if (Files.exists(Paths.get(path))) { + String key = "sdkkey"; + Boolean offline = false; + LDClient client = configuration.ldClient(key, offline, new String[]{ + "AFileThatDoesNotExist" + }); + Assertions.assertEquals(client.isOffline(), offline.booleanValue()); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/documentmanagement/SecuredDocumentManagementServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/documentmanagement/SecuredDocumentManagementServiceTest.java new file mode 100644 index 00000000..7cc82ab1 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/documentmanagement/SecuredDocumentManagementServiceTest.java @@ -0,0 +1,296 @@ +package uk.gov.hmcts.reform.civil.documentmanagement; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.ccd.document.am.feign.CaseDocumentClientApi; +import uk.gov.hmcts.reform.ccd.document.am.model.Document; +import uk.gov.hmcts.reform.ccd.document.am.model.DocumentUploadRequest; +import uk.gov.hmcts.reform.ccd.document.am.model.UploadResponse; +import uk.gov.hmcts.reform.civil.documentmanagement.model.CaseDocument; +import uk.gov.hmcts.reform.civil.documentmanagement.model.PDF; +import uk.gov.hmcts.reform.civil.service.UserService; +import uk.gov.hmcts.reform.civil.utils.ResourceReader; +import uk.gov.hmcts.reform.document.DocumentDownloadClientApi; +import uk.gov.hmcts.reform.idam.client.models.UserInfo; + +import java.net.URI; +import java.util.List; +import java.util.UUID; + +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static uk.gov.hmcts.reform.civil.documentmanagement.DocumentDownloadException.MESSAGE_TEMPLATE; +import static uk.gov.hmcts.reform.civil.documentmanagement.SecuredDocumentManagementService.DOC_UUID_LENGTH; +import static uk.gov.hmcts.reform.civil.documentmanagement.model.DocumentType.SEALED_CLAIM; + +@SpringBootTest(classes = { + SecuredDocumentManagementService.class, + JacksonAutoConfiguration.class, + DocumentManagementConfiguration.class}, + properties = {"document_management.secured=true"}) +class SecuredDocumentManagementServiceTest { + + private static final String USER_ROLES_JOINED = "caseworker-civil,caseworker-civil-solicitor"; + public static final String BEARER_TOKEN = "Bearer Token"; + + @MockBean + private CaseDocumentClientApi caseDocumentClientApi; + @MockBean + private DocumentDownloadClientApi documentDownloadClient; + @MockBean + private AuthTokenGenerator authTokenGenerator; + @MockBean + private UserService userService; + + @Autowired + private ObjectMapper mapper; + + @Autowired + private SecuredDocumentManagementService documentManagementService; + + @Mock + private ResponseEntity responseEntity; + private final UserInfo userInfo = UserInfo.builder() + .roles(List.of("role")) + .uid("id") + .givenName("userFirstName") + .familyName("userLastName") + .sub("mail@mail.com") + .build(); + + @BeforeEach + public void setUp() { + when(authTokenGenerator.generate()).thenReturn(BEARER_TOKEN); + when(userService.getUserInfo(anyString())).thenReturn(userInfo); + } + + @Nested + class UploadDocument { + + @Test + void shouldUploadToDocumentManagement() throws JsonProcessingException { + PDF document = new PDF("0000-claim.pdf", "test".getBytes(), SEALED_CLAIM); + + uk.gov.hmcts.reform.ccd.document.am.model.UploadResponse uploadResponse = mapper.readValue( + ResourceReader.readString("document-management/secured.response.success.json"), + uk.gov.hmcts.reform.ccd.document.am.model.UploadResponse.class + ); + + when(caseDocumentClientApi.uploadDocuments(anyString(), anyString(), any(DocumentUploadRequest.class))) + .thenReturn(uploadResponse); + + CaseDocument caseDocument = documentManagementService.uploadDocument(BEARER_TOKEN, document); + assertNotNull(caseDocument.getDocumentLink()); + Assertions.assertEquals( + uploadResponse.getDocuments().get(0).links.self.href, + caseDocument.getDocumentLink().getDocumentUrl() + ); + + verify(caseDocumentClientApi).uploadDocuments(anyString(), anyString(), any(DocumentUploadRequest.class)); + } + + @Test + void shouldThrow_whenUploadDocumentFails() throws JsonProcessingException { + PDF document = new PDF("0000-failed-claim.pdf", "failed-test".getBytes(), SEALED_CLAIM); + + UploadResponse uploadResponse = mapper.readValue( + ResourceReader.readString("document-management/secured.response.failure.json"), + UploadResponse.class + ); + + when(caseDocumentClientApi.uploadDocuments(anyString(), anyString(), any(DocumentUploadRequest.class))) + .thenReturn(uploadResponse); + + DocumentUploadException documentManagementException = assertThrows( + DocumentUploadException.class, + () -> documentManagementService.uploadDocument(BEARER_TOKEN, document) + ); + + assertEquals( + "Unable to upload document 0000-failed-claim.pdf to document management.", + documentManagementException.getMessage() + ); + + verify(caseDocumentClientApi).uploadDocuments(anyString(), anyString(), any(DocumentUploadRequest.class)); + } + } + + @Nested + class DownloadDocument { + + @Test + void shouldDownloadDocumentFromDocumentManagement() throws JsonProcessingException { + + Document document = mapper.readValue( + ResourceReader.readString("document-management/download.success.json"), + Document.class + ); + String documentPath = URI.create(document.links.self.href).getPath(); + String documentBinary = URI.create(document.links.binary.href).getPath().replaceFirst("/", ""); + UUID documentId = getDocumentIdFromSelfHref(documentPath); + + when(caseDocumentClientApi.getMetadataForDocument( + anyString(), + anyString(), + eq(documentId) + ) + ).thenReturn(document); + + when(responseEntity.getBody()).thenReturn(new ByteArrayResource("test".getBytes())); + + when(documentDownloadClient.downloadBinary( + anyString(), + anyString(), + eq(USER_ROLES_JOINED), + anyString(), + eq(documentBinary) + ) + ).thenReturn(responseEntity); + + byte[] pdf = documentManagementService.downloadDocument(BEARER_TOKEN, documentPath); + + assertNotNull(pdf); + assertArrayEquals("test".getBytes(), pdf); + + verify(caseDocumentClientApi).getMetadataForDocument(anyString(), anyString(), eq(documentId)); + + verify(documentDownloadClient) + .downloadBinary(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), eq(documentBinary)); + } + + @Test + void shouldDownloadDocumentFromDocumentManagement_FromCaseDocumentClientApi() throws JsonProcessingException { + + Document document = mapper.readValue( + ResourceReader.readString("document-management/download.success.json"), + Document.class + ); + String documentPath = URI.create(document.links.self.href).getPath(); + UUID documentId = getDocumentIdFromSelfHref(documentPath); + + when(responseEntity.getBody()).thenReturn(new ByteArrayResource("test".getBytes())); + + when(caseDocumentClientApi.getDocumentBinary( + anyString(), + anyString(), + eq(documentId) + ) + ).thenReturn(responseEntity); + + byte[] pdf = documentManagementService.downloadDocument(BEARER_TOKEN, documentPath); + + assertNotNull(pdf); + assertArrayEquals("test".getBytes(), pdf); + + verify(caseDocumentClientApi) + .getDocumentBinary(anyString(), anyString(), eq(documentId)); + } + + @Test + void shouldThrow_whenDocumentDownloadFails() throws JsonProcessingException { + Document document = mapper.readValue( + ResourceReader.readString("document-management/download.success.json"), + Document.class + ); + String documentPath = "/documents/85d97996-22a5-40d7-882e-3a382c8ae1b7"; + String documentBinary = "documents/85d97996-22a5-40d7-882e-3a382c8ae1b7/binary"; + UUID documentId = getDocumentIdFromSelfHref(documentPath); + + when(caseDocumentClientApi.getMetadataForDocument( + anyString(), + anyString(), + eq(documentId) + ) + ).thenReturn(document); + + when(documentDownloadClient + .downloadBinary(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), eq(documentBinary)) + ).thenReturn(null); + + DocumentDownloadException documentManagementException = assertThrows( + DocumentDownloadException.class, + () -> documentManagementService.downloadDocument(BEARER_TOKEN, documentPath) + ); + + assertEquals(format(MESSAGE_TEMPLATE, documentPath), documentManagementException.getMessage()); + + verify(caseDocumentClientApi).getMetadataForDocument(anyString(), anyString(), eq(documentId)); + } + } + + @Nested + class DocumentMetaData { + @Test + void getDocumentMetaData() throws JsonProcessingException { + String documentPath = "/documents/85d97996-22a5-40d7-882e-3a382c8ae1b3"; + UUID documentId = getDocumentIdFromSelfHref(documentPath); + + when(caseDocumentClientApi.getMetadataForDocument( + anyString(), + anyString(), + eq(documentId) + ) + ).thenReturn(mapper.readValue( + ResourceReader.readString("document-management/metadata.success.json"), Document.class) + ); + + when(responseEntity.getBody()).thenReturn(new ByteArrayResource("test".getBytes())); + + uk.gov.hmcts.reform.ccd.document.am.model.Document documentMetaData + = documentManagementService.getDocumentMetaData(BEARER_TOKEN, documentPath); + + Assertions.assertEquals(72552L, documentMetaData.size); + Assertions.assertEquals("TEST_DOCUMENT_1.pdf", documentMetaData.originalDocumentName); + + verify(caseDocumentClientApi).getMetadataForDocument(anyString(), anyString(), eq(documentId)); + } + + @Test + void shouldThrow_whenMetadataDownloadFails() { + String documentPath = "/documents/85d97996-22a5-40d7-882e-3a382c8ae1b5"; + UUID documentId = getDocumentIdFromSelfHref(documentPath); + + when(caseDocumentClientApi + .getMetadataForDocument(anyString(), anyString(), eq(documentId)) + ).thenThrow(new RuntimeException("Failed to access document metadata")); + + DocumentDownloadException documentManagementException = assertThrows( + DocumentDownloadException.class, + () -> documentManagementService.getDocumentMetaData(BEARER_TOKEN, documentPath) + ); + + assertEquals( + String.format(MESSAGE_TEMPLATE, documentPath), + documentManagementException.getMessage() + ); + + verify(caseDocumentClientApi) + .getMetadataForDocument(anyString(), anyString(), eq(documentId)); + } + } + + private UUID getDocumentIdFromSelfHref(String selfHref) { + return UUID.fromString(selfHref.substring(selfHref.length() - DOC_UUID_LENGTH)); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/documentmanagement/UnsecuredDocumentManagementServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/documentmanagement/UnsecuredDocumentManagementServiceTest.java new file mode 100644 index 00000000..45fac9bd --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/documentmanagement/UnsecuredDocumentManagementServiceTest.java @@ -0,0 +1,296 @@ +package uk.gov.hmcts.reform.civil.documentmanagement; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.documentmanagement.model.CaseDocument; +import uk.gov.hmcts.reform.civil.documentmanagement.model.PDF; +import uk.gov.hmcts.reform.civil.service.UserService; +import uk.gov.hmcts.reform.civil.utils.ResourceReader; +import uk.gov.hmcts.reform.document.DocumentDownloadClientApi; +import uk.gov.hmcts.reform.document.DocumentMetadataDownloadClientApi; +import uk.gov.hmcts.reform.document.DocumentUploadClientApi; +import uk.gov.hmcts.reform.document.domain.Classification; +import uk.gov.hmcts.reform.document.domain.Document; +import uk.gov.hmcts.reform.document.domain.UploadResponse; +import uk.gov.hmcts.reform.document.utils.InMemoryMultipartFile; +import uk.gov.hmcts.reform.idam.client.models.UserInfo; + +import java.net.URI; +import java.util.List; + +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.http.MediaType.APPLICATION_PDF_VALUE; +import static uk.gov.hmcts.reform.civil.documentmanagement.DocumentDownloadException.MESSAGE_TEMPLATE; +import static uk.gov.hmcts.reform.civil.documentmanagement.UnsecuredDocumentManagementService.FILES_NAME; +import static uk.gov.hmcts.reform.civil.documentmanagement.model.DocumentType.SEALED_CLAIM; + +@SpringBootTest(classes = { + UnsecuredDocumentManagementService.class, + JacksonAutoConfiguration.class, + DocumentManagementConfiguration.class}, + properties = {"document_management.secured=false"}) +class UnsecuredDocumentManagementServiceTest { + + private static final List USER_ROLES = List.of("caseworker-civil", "caseworker-civil-solicitor"); + private static final String USER_ROLES_JOINED = "caseworker-civil,caseworker-civil-solicitor"; + public static final String BEARER_TOKEN = "Bearer Token"; + + @MockBean + private DocumentMetadataDownloadClientApi documentMetadataDownloadClient; + @MockBean + private DocumentDownloadClientApi documentDownloadClient; + @MockBean + private DocumentUploadClientApi documentUploadClient; + @MockBean + private AuthTokenGenerator authTokenGenerator; + @MockBean + private UserService userService; + + @Autowired + private ObjectMapper mapper; + + @Autowired + private UnsecuredDocumentManagementService documentManagementService; + + @Mock + private ResponseEntity responseEntity; + private final UserInfo userInfo = UserInfo.builder() + .roles(List.of("role")) + .uid("id") + .givenName("userFirstName") + .familyName("userLastName") + .sub("mail@mail.com") + .build(); + + @BeforeEach + public void setUp() { + when(authTokenGenerator.generate()).thenReturn(BEARER_TOKEN); + when(userService.getUserInfo(anyString())).thenReturn(userInfo); + } + + @Nested + class UploadDocument { + + @Test + void shouldUploadToDocumentManagement() throws JsonProcessingException { + PDF document = new PDF("0000-claim.pdf", "test".getBytes(), SEALED_CLAIM); + + List files = List.of(new InMemoryMultipartFile( + FILES_NAME, + document.getFileBaseName(), + APPLICATION_PDF_VALUE, + document.getBytes() + )); + + UploadResponse uploadResponse = mapper.readValue( + ResourceReader.readString("document-management/response.success.json"), UploadResponse.class); + + when(documentUploadClient.upload( + anyString(), + anyString(), + anyString(), + eq(USER_ROLES), + any(Classification.class), + eq(files) + ) + ).thenReturn(uploadResponse); + + CaseDocument caseDocument = documentManagementService.uploadDocument(BEARER_TOKEN, document); + assertNotNull(caseDocument.getDocumentLink()); + Assertions.assertEquals( + uploadResponse.getEmbedded().getDocuments().get(0).links.self.href, + caseDocument.getDocumentLink().getDocumentUrl() + ); + + verify(documentUploadClient) + .upload(anyString(), anyString(), anyString(), eq(USER_ROLES), any(Classification.class), eq(files)); + } + + @Test + void shouldThrow_whenUploadDocumentFails() throws JsonProcessingException { + PDF document = new PDF("0000-failed-claim.pdf", "failed-test".getBytes(), SEALED_CLAIM); + + List files = List.of(new InMemoryMultipartFile( + FILES_NAME, + document.getFileBaseName(), + APPLICATION_PDF_VALUE, + document.getBytes() + )); + + when(documentUploadClient.upload( + anyString(), + anyString(), + anyString(), + eq(USER_ROLES), + any(Classification.class), + eq(files) + ) + ).thenReturn(mapper.readValue( + ResourceReader.readString("document-management/response.failure.json"), UploadResponse.class)); + + DocumentUploadException documentManagementException = assertThrows( + DocumentUploadException.class, + () -> documentManagementService.uploadDocument(BEARER_TOKEN, document) + ); + + assertEquals( + "Unable to upload document 0000-failed-claim.pdf to document management.", + documentManagementException.getMessage() + ); + + verify(documentUploadClient) + .upload(anyString(), anyString(), anyString(), eq(USER_ROLES), any(Classification.class), eq(files)); + } + } + + @Nested + class DownloadDocument { + + @Mock + Document documentMetaData; + + @Test + void shouldDownloadDocumentFromDocumentManagement() throws JsonProcessingException { + + Document document = mapper.readValue( + ResourceReader.readString("document-management/download.success.json"), + Document.class + ); + String documentPath = URI.create(document.links.self.href).getPath(); + String documentBinary = URI.create(document.links.binary.href).getPath(); + + when(documentMetadataDownloadClient.getDocumentMetadata( + anyString(), + anyString(), + eq(USER_ROLES_JOINED), + anyString(), + eq(documentPath) + ) + ).thenReturn(document); + + when(responseEntity.getBody()).thenReturn(new ByteArrayResource("test".getBytes())); + + when(documentDownloadClient.downloadBinary( + anyString(), + anyString(), + eq(USER_ROLES_JOINED), + anyString(), + eq(documentBinary) + ) + ).thenReturn(responseEntity); + + byte[] pdf = documentManagementService.downloadDocument(BEARER_TOKEN, documentPath); + + assertNotNull(pdf); + assertArrayEquals("test".getBytes(), pdf); + + verify(documentMetadataDownloadClient) + .getDocumentMetadata(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), eq(documentPath)); + + verify(documentDownloadClient) + .downloadBinary(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), eq(documentBinary)); + } + + @Test + void shouldThrow_whenDocumentDownloadFails() { + String documentPath = "/documents/85d97996-22a5-40d7-882e-3a382c8ae1b7"; + String documentBinary = "/documents/85d97996-22a5-40d7-882e-3a382c8ae1b7/binary"; + when(documentMetadataDownloadClient.getDocumentMetadata( + anyString(), + anyString(), + eq(USER_ROLES_JOINED), + anyString(), + eq(documentPath) + ) + ).thenReturn(documentMetaData); + + when(documentDownloadClient + .downloadBinary(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), eq(documentBinary)) + ).thenReturn(null); + + DocumentDownloadException documentManagementException = assertThrows( + DocumentDownloadException.class, + () -> documentManagementService.downloadDocument(BEARER_TOKEN, documentPath) + ); + + assertEquals(format(MESSAGE_TEMPLATE, documentPath), documentManagementException.getMessage()); + + verify(documentMetadataDownloadClient) + .getDocumentMetadata(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), eq(documentPath)); + } + } + + @Nested + class DocumentMetaData { + @Test + void getDocumentMetaData() throws JsonProcessingException { + String documentPath = "/documents/85d97996-22a5-40d7-882e-3a382c8ae1b3"; + + when(documentMetadataDownloadClient.getDocumentMetadata( + anyString(), + anyString(), + eq(USER_ROLES_JOINED), + anyString(), + eq(documentPath) + ) + ).thenReturn(mapper.readValue( + ResourceReader.readString("document-management/metadata.success.json"), Document.class) + ); + + when(responseEntity.getBody()).thenReturn(new ByteArrayResource("test".getBytes())); + + Document documentMetaData = documentManagementService.getDocumentMetaData(BEARER_TOKEN, documentPath); + + Assertions.assertEquals(72552L, documentMetaData.size); + Assertions.assertEquals("TEST_DOCUMENT_1.pdf", documentMetaData.originalDocumentName); + + verify(documentMetadataDownloadClient) + .getDocumentMetadata(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), eq(documentPath)); + } + + @Test + void shouldThrow_whenMetadataDownloadFails() { + when(documentMetadataDownloadClient + .getDocumentMetadata(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), anyString()) + ).thenThrow(new RuntimeException("Failed to access document metadata")); + + String documentPath = "/documents/85d97996-22a5-40d7-882e-3a382c8ae1b5"; + + DocumentDownloadException documentManagementException = assertThrows( + DocumentDownloadException.class, + () -> documentManagementService.getDocumentMetaData(BEARER_TOKEN, documentPath) + ); + + assertEquals( + String.format(MESSAGE_TEMPLATE, documentPath), + documentManagementException.getMessage() + ); + + verify(documentMetadataDownloadClient) + .getDocumentMetadata(anyString(), anyString(), eq(USER_ROLES_JOINED), anyString(), eq(documentPath)); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/helpers/DateFormatHelperTest.java b/src/test/java/uk/gov/hmcts/reform/civil/helpers/DateFormatHelperTest.java new file mode 100644 index 00000000..c80c72b8 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/helpers/DateFormatHelperTest.java @@ -0,0 +1,40 @@ +package uk.gov.hmcts.reform.civil.helpers; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static uk.gov.hmcts.reform.civil.helpers.DateFormatHelper.DATE; +import static uk.gov.hmcts.reform.civil.helpers.DateFormatHelper.DATE_TIME_AT; +import static uk.gov.hmcts.reform.civil.helpers.DateFormatHelper.formatLocalDate; +import static uk.gov.hmcts.reform.civil.helpers.DateFormatHelper.formatLocalDateTime; + +class DateFormatHelperTest { + + @Nested + class LocalDateTimeFormat { + + @Test + void shouldReturnExpectedDateTimeFormat_whenValidFormatIsPassed() { + LocalDateTime now = LocalDateTime.of(2999, 1, 1, 9, 0, 0); + + assertThat(formatLocalDateTime(now, DATE_TIME_AT)) + .isEqualTo("9:00am on 1 January 2999"); + } + } + + @Nested + class LocalDateFormat { + + @Test + void shouldReturnExpectedDateFormat_whenValidFormatIsPassed() { + LocalDate now = LocalDate.of(2999, 1, 1); + + assertThat(formatLocalDate(now, DATE)) + .isEqualTo("1 January 2999"); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/helpers/LocalDateTimeHelperTest.java b/src/test/java/uk/gov/hmcts/reform/civil/helpers/LocalDateTimeHelperTest.java new file mode 100644 index 00000000..e9d2510b --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/helpers/LocalDateTimeHelperTest.java @@ -0,0 +1,26 @@ +package uk.gov.hmcts.reform.civil.helpers; + +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class LocalDateTimeHelperTest { + + @Test + void fromUTC_shouldReturnTimeInLocalZone() { + // Given + LocalDateTime farAwayLocalDateTime = LocalDateTime.now(ZoneId.of("America/New_York")); + + // when + LocalDateTime expectedDateTime = LocalDateTimeHelper.fromUTC(farAwayLocalDateTime); + + // then + assertThat(farAwayLocalDateTime.atZone(LocalDateTimeHelper.UTC_ZONE).toLocalDateTime()) + .isNotEqualTo(LocalDateTime.now().atZone(LocalDateTimeHelper.UTC_ZONE).toLocalDateTime()); + assertThat(farAwayLocalDateTime.atZone(LocalDateTimeHelper.UTC_ZONE).toLocalDateTime()) + .isEqualTo(expectedDateTime.atZone(LocalDateTimeHelper.UTC_ZONE).toLocalDateTime()); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/helpers/ResourceReaderTest.java b/src/test/java/uk/gov/hmcts/reform/civil/helpers/ResourceReaderTest.java new file mode 100644 index 00000000..461b681c --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/helpers/ResourceReaderTest.java @@ -0,0 +1,33 @@ +package uk.gov.hmcts.reform.civil.helpers; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ResourceReaderTest { + + @Test + void shouldThrowIllegalStateException_whenResourcePathIsNull() { + Exception exception = assertThrows( + IllegalStateException.class, + () -> ResourceReader.readString(null) + ); + String expectedMessage = "Unable to read resource: null"; + String actualMessage = exception.getMessage(); + + assertEquals(expectedMessage, actualMessage); + } + + @Test + void shouldThrowIllegalStateException_whenResourcePathIsInvalid() { + Exception exception = assertThrows( + IllegalStateException.class, + () -> ResourceReader.readString("abc") + ); + String expectedMessage = "Unable to read resource: abc"; + String actualMessage = exception.getMessage(); + + assertEquals(expectedMessage, actualMessage); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleApiTest.java b/src/test/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleApiTest.java new file mode 100644 index 00000000..6981a8ae --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleApiTest.java @@ -0,0 +1,86 @@ +package uk.gov.hmcts.reform.civil.launchdarkly; + +import com.google.common.collect.ImmutableList; +import com.launchdarkly.sdk.LDUser; +import com.launchdarkly.sdk.server.interfaces.LDClientInterface; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class FeatureToggleApiTest { + + private static final String FAKE_FEATURE = "fake-feature"; + private static final String FAKE_ENVIRONMENT = "fake-env"; + + @Mock + private LDClientInterface ldClient; + + @Captor + private ArgumentCaptor ldUserArgumentCaptor; + + private FeatureToggleApi featureToggleApi; + + @BeforeEach + void setUp() { + featureToggleApi = new FeatureToggleApi(ldClient, FAKE_ENVIRONMENT); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldReturnCorrectState_whenUserIsProvided(Boolean toggleState) { + LDUser ldUSer = new LDUser.Builder("civil-service") + .custom("timestamp", String.valueOf(System.currentTimeMillis())) + .custom("environment", FAKE_ENVIRONMENT).build(); + givenToggle(FAKE_FEATURE, toggleState); + + assertThat(featureToggleApi.isFeatureEnabled(FAKE_FEATURE, ldUSer)).isEqualTo(toggleState); + + verify(ldClient).boolVariation( + FAKE_FEATURE, + ldUSer, + false + ); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldReturnCorrectState_whenDefaultServiceUser(Boolean toggleState) { + givenToggle(FAKE_FEATURE, toggleState); + + assertThat(featureToggleApi.isFeatureEnabled(FAKE_FEATURE)).isEqualTo(toggleState); + verifyBoolVariationCalled(FAKE_FEATURE, List.of("timestamp", "environment")); + } + + private void givenToggle(String feature, boolean state) { + when(ldClient.boolVariation(eq(feature), any(LDUser.class), anyBoolean())) + .thenReturn(state); + } + + private void verifyBoolVariationCalled(String feature, List customAttributesKeys) { + verify(ldClient).boolVariation( + eq(feature), + ldUserArgumentCaptor.capture(), + eq(false) + ); + + var capturedLdUser = ldUserArgumentCaptor.getValue(); + assertThat(capturedLdUser.getKey()).isEqualTo("civil-service"); + assertThat(ImmutableList.copyOf(capturedLdUser.getCustomAttributes())).extracting("name") + .containsOnlyOnceElementsOf(customAttributesKeys); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleAspectTest.java b/src/test/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleAspectTest.java new file mode 100644 index 00000000..8541bcdd --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/launchdarkly/FeatureToggleAspectTest.java @@ -0,0 +1,79 @@ +package uk.gov.hmcts.reform.civil.launchdarkly; + +import com.launchdarkly.sdk.LDUser; +import com.launchdarkly.sdk.server.LDClient; +import lombok.SneakyThrows; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = { + FeatureToggleAspect.class, + FeatureToggleApi.class +}) +class FeatureToggleAspectTest { + + private static final String NEW_FEATURE = "NEW_FEATURE"; + @Autowired + FeatureToggleAspect featureToggleAspect; + + @MockBean + LDClient ldClient; + @MockBean + ProceedingJoinPoint proceedingJoinPoint; + @MockBean + FeatureToggle featureToggle; + @MockBean + MethodSignature methodSignature; + + @BeforeEach + void setUp() { + when(featureToggle.feature()).thenReturn(NEW_FEATURE); + when(proceedingJoinPoint.getSignature()).thenReturn(methodSignature); + when(methodSignature.getName()).thenReturn("myFeatureToggledMethod"); + } + + @SneakyThrows + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldProceedToMethodInvocation_whenFeatureToggleIsEnabled(Boolean state) { + when(featureToggle.value()).thenReturn(state); + givenToggle(NEW_FEATURE, state); + + featureToggleAspect.checkFeatureEnabled(proceedingJoinPoint, featureToggle); + + verify(proceedingJoinPoint).proceed(); + } + + @SneakyThrows + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldNotProceedToMethodInvocation_whenFeatureToggleIsDisabled(Boolean state) { + when(featureToggle.value()).thenReturn(state); + givenToggle(NEW_FEATURE, !state); + + featureToggleAspect.checkFeatureEnabled(proceedingJoinPoint, featureToggle); + + verify(proceedingJoinPoint, never()).proceed(); + } + + private void givenToggle(String feature, boolean state) { + when(ldClient.boolVariation(eq(feature), any(LDUser.class), anyBoolean())) + .thenReturn(state); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/notify/NotificationServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/notify/NotificationServiceTest.java new file mode 100644 index 00000000..22501ff4 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/notify/NotificationServiceTest.java @@ -0,0 +1,72 @@ +package uk.gov.hmcts.reform.civil.notify; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.gov.service.notify.NotificationClient; +import uk.gov.service.notify.NotificationClientException; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class NotificationServiceTest { + + @InjectMocks + private NotificationService service; + + @Mock + private NotificationClient notificationClient; + + @Test + void shouldCallNotificationClient_whenRequestsSendEmail() throws NotificationClientException { + // Given + given(notificationClient.sendEmail(any(), any(), any(), any())).willCallRealMethod(); + // When + service.sendMail("email@email.com", "template", Map.of("param1", "param1"), "reference"); + // Then + verify(notificationClient) + .sendEmail("template", "email@email.com", Map.of("param1", "param1"), "reference"); + } + + @Test + void shouldReturnException_whenNotificationClientSendEmailReturnsException() throws NotificationClientException { + // Given + given(notificationClient.sendEmail(any(), any(), any(), any())) + .willThrow(new NotificationClientException("error")); + // When // Then + NotificationException exception = assertThrows( + NotificationException.class, () -> + service.sendMail("email@email.com", "template", Map.of("param1", "param1"), "reference")); + assertNotNull(exception.getMessage()); + } + + @Test + void shouldCallNotificationClient_whenRequestsSendLetter() throws NotificationClientException { + // Given + given(notificationClient.sendLetter(any(), any(), any())).willAnswer(invocation -> null); + // When + service.sendLetter("template", Map.of("param1", "param1"), "reference"); + // Then + verify(notificationClient) + .sendLetter("template", Map.of("param1", "param1"), "reference"); + } + + @Test + void shouldReturnException_whenNotificationClientSendLetterReturnsException() throws NotificationClientException { + // Given + given(notificationClient.sendLetter(any(), any(), any())).willThrow(new NotificationClientException("error")); + // When // Then + NotificationException exception = assertThrows( + NotificationException.class, () -> + service.sendLetter("template", Map.of("param1", "param1"), "reference")); + assertNotNull(exception.getMessage()); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupServiceTest.java new file mode 100644 index 00000000..a5421985 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/postcode/PostcodeLookupServiceTest.java @@ -0,0 +1,53 @@ +package uk.gov.hmcts.reform.civil.postcode; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import uk.gov.hmcts.reform.civil.postcode.PostcodeLookupConfiguration; +import uk.gov.hmcts.reform.civil.postcode.PostcodeLookupService; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@SpringBootTest(classes = PostcodeLookupService.class) +class PostcodeLookupServiceTest { + + @MockBean + private RestTemplate restTemplate; + @MockBean + private PostcodeLookupConfiguration postcodeLookupConfiguration; + + @Autowired + private PostcodeLookupService postcodeLookupService; + + @Test + public void shouldReturnFalseWhenCountryIsNullGivenPostCodeIsValid() { + + ResponseEntity responseEntity = new ResponseEntity("Ok", HttpStatus.ACCEPTED); + when(postcodeLookupConfiguration.getUrl()).thenReturn("https://api.ordnancesurvey.co.uk/opennames/v1/find"); + when(postcodeLookupConfiguration.getAccessKey()).thenReturn("dummy"); + when(restTemplate.exchange( + ArgumentMatchers.anyString(), + ArgumentMatchers.any(HttpMethod.class), + ArgumentMatchers.>any(), + ArgumentMatchers.>any() + )).thenReturn(responseEntity); + + assertThat(postcodeLookupService.validatePostCodeForDefendant("IG11 7YL")).isFalse(); + } + + @Test + public void shouldReturnExpcetionWhenUrlIsEmpty() { + assertThrows( + RuntimeException.class, () -> postcodeLookupService.validatePostCodeForDefendant("IG11 7YL") + ); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/referencedata/JudicialRefDataServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/referencedata/JudicialRefDataServiceTest.java new file mode 100644 index 00000000..88bd5ede --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/referencedata/JudicialRefDataServiceTest.java @@ -0,0 +1,103 @@ +package uk.gov.hmcts.reform.civil.referencedata; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.referencedata.model.JudgeRefData; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpStatus.OK; + +@SpringBootTest(classes = {RestTemplate.class}) +class JudicialRefDataServiceTest { + + @Captor + private ArgumentCaptor uriCaptor; + + @Captor + private ArgumentCaptor httpMethodCaptor; + + @Captor + private ArgumentCaptor> httpEntityCaptor; + + @Mock + private RestTemplate restTemplate; + + @Mock + private JRDConfiguration jrdConfiguration; + + @Mock + private AuthTokenGenerator authTokenGenerator; + + @InjectMocks + private JudicialRefDataService refDataService; + + @BeforeEach + void setUp() { + when(jrdConfiguration.getUrl()).thenReturn("dummy_url"); + when(jrdConfiguration.getEndpoint()).thenReturn("/fees-register/fees/lookup"); + } + + @Test + void shouldReturnLocations_whenLRDReturnsAllLocations() { + List judgeRefData = Arrays.asList( + JudgeRefData.builder().title("Mr").surname("Murphy").emailId("mr.murphy@email.com").build(), + JudgeRefData.builder().title("Mr").surname("McGee").emailId("mr.mcgee@email.com").build(), + JudgeRefData.builder().title("Mr").surname("Brad").emailId("mr.brad@email.com").build(), + JudgeRefData.builder().title("Mrs").surname("Lee").emailId("mrs.lee@email.com").build() + ); + + when(authTokenGenerator.generate()).thenReturn("service_token"); + when(restTemplate.exchange( + uriCaptor.capture(), + httpMethodCaptor.capture(), + httpEntityCaptor.capture(), + ArgumentMatchers.>>any() + )) + .thenReturn(getJudgeRefDataResponse()); + + List judgeRefDataReturn = refDataService.getJudgeReferenceData("ABC", "user_token"); + + assertThat(judgeRefDataReturn.size()).isEqualTo(4); + assertThat(judgeRefDataReturn).isEqualTo(judgeRefData); + verify(jrdConfiguration, times(1)).getUrl(); + verify(jrdConfiguration, times(1)).getEndpoint(); + assertThat(httpMethodCaptor.getValue()).isEqualTo(HttpMethod.POST); + assertThat(httpEntityCaptor.getValue().getHeaders().getFirst("Authorization")).isEqualTo("user_token"); + assertThat(httpEntityCaptor.getValue().getHeaders().getFirst("ServiceAuthorization")) + .isEqualTo("service_token"); + } + + private ResponseEntity> getJudgeRefDataResponse() { + List responseData = new ArrayList(); + responseData.add(getJudicialRefData("Mr", "Murphy", "mr.murphy@email.com")); + responseData.add(getJudicialRefData("Mr", "McGee", "mr.mcgee@email.com")); + responseData.add(getJudicialRefData("Mr", "Brad", "mr.brad@email.com")); + responseData.add(getJudicialRefData("Mrs", "Lee", "mrs.lee@email.com")); + + return new ResponseEntity>(responseData, OK); + } + + private JudgeRefData getJudicialRefData(String title, String surname, String emailId) { + return JudgeRefData.builder().title(title).surname(surname).emailId(emailId).build(); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/EmailAttachmentTest.java b/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/EmailAttachmentTest.java new file mode 100644 index 00000000..35b09ae6 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/EmailAttachmentTest.java @@ -0,0 +1,64 @@ +package uk.gov.hmcts.reform.civil.sendgrid; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.ByteArrayResource; +import uk.gov.hmcts.reform.civil.sendgrid.EmailAttachment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class EmailAttachmentTest { + + private static final ByteArrayResource CONTENT = new ByteArrayResource(new byte[]{1, 2, 3, 4}); + private static final String PDF_CONTENT_TYPE = "application/pdf"; + private static final byte[] PDF_CONTENT = {1, 2, 3, 4}; + private static final String PDF_FILE_NAME = "document.pdf"; + private static final String JSON_CONTENT_TYPE = "application/json"; + private static final String JSON_FILE_NAME = "document.json"; + private static final String JSON_CONTENT = "{'field':'value'}"; + + @Nested + class MissingRequiredProperties { + + @Test + void shouldThrowNPE_whenContentIsNull() { + assertThrows( + NullPointerException.class, + () -> new EmailAttachment(null, PDF_CONTENT_TYPE, PDF_FILE_NAME) + ); + } + + @Test + void shouldThrowNPE_whenContentTypeIsNull() { + assertThrows( + NullPointerException.class, + () -> new EmailAttachment(CONTENT, null, PDF_FILE_NAME) + ); + } + + @Test + void shouldThrowNPE_whenFileNameIsNull() { + assertThrows( + NullPointerException.class, + () -> new EmailAttachment(CONTENT, PDF_CONTENT_TYPE, null) + ); + } + } + + @Nested + class ValidEmailAttachment { + + @Test + void shouldCreateEmailAttachment_whenPdfAttachmentIsProvided() { + assertThat(EmailAttachment.pdf(PDF_CONTENT, PDF_FILE_NAME).getContentType()) + .isEqualTo(PDF_CONTENT_TYPE); + } + + @Test + void shouldCreateEmailAttachment_whenJsonAttachmentIsProvided() { + assertThat(EmailAttachment.json(JSON_CONTENT.getBytes(), JSON_FILE_NAME).getContentType()) + .isEqualTo(JSON_CONTENT_TYPE); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/EmailDataTest.java b/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/EmailDataTest.java new file mode 100644 index 00000000..38febddf --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/EmailDataTest.java @@ -0,0 +1,40 @@ +package uk.gov.hmcts.reform.civil.sendgrid; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import uk.gov.hmcts.reform.civil.sendgrid.EmailAttachment; +import uk.gov.hmcts.reform.civil.sendgrid.EmailData; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class EmailDataTest { + + @Test + void shouldReturnTrue_whenHasAnAttachment() { + EmailData emailData = EmailData.builder() + .to("to@server.net") + .subject("my email") + .message("My email message") + .attachments(List.of(EmailAttachment.pdf(new byte[]{1, 2, 3}, "test.pdf"))) + .build(); + + assertTrue(emailData.hasAttachments()); + } + + @ParameterizedTest + @NullAndEmptySource + void shouldReturnFalse_whenHasNoAttachment(List attachments) { + EmailData emailData = EmailData.builder() + .to("to@server.net") + .subject("my email") + .message("My email message") + .attachments(attachments) + .build(); + + assertFalse(emailData.hasAttachments()); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/SendGridClientTest.java b/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/SendGridClientTest.java new file mode 100644 index 00000000..078ed4d9 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/SendGridClientTest.java @@ -0,0 +1,205 @@ +package uk.gov.hmcts.reform.civil.sendgrid; + +import com.sendgrid.Method; +import com.sendgrid.Request; +import com.sendgrid.Response; +import com.sendgrid.SendGrid; +import lombok.SneakyThrows; +import org.apache.http.HttpException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.civil.sendgrid.EmailAttachment; +import uk.gov.hmcts.reform.civil.sendgrid.EmailData; +import uk.gov.hmcts.reform.civil.sendgrid.EmailSendFailedException; +import uk.gov.hmcts.reform.civil.sendgrid.SendGridClient; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {SendGridClient.class}) +class SendGridClientTest { + + private static final Response SUCCESSFUL_RESPONSE = new Response( + 202, + "response body", + Map.of() + ); + private static final EmailData EMAIL_DATA = EmailData.builder() + .to("to@server.net") + .subject("my email") + .message("My email message") + .attachments(List.of()) + .build(); + private static final String EMAIL_FROM = "from@server.net"; + + @MockBean + private SendGrid sendGrid; + + @Captor + private ArgumentCaptor requestCaptor; + + @Autowired + private SendGridClient sendGridClient; + + @Nested + class InvalidDataProvided { + + @ParameterizedTest + @NullAndEmptySource + void shouldThrowIllegalArgumentException_whenFromIsNullOrBlank(String from) { + assertThrows( + IllegalArgumentException.class, + () -> sendGridClient.sendEmail(from, EMAIL_DATA) + ); + } + + @ParameterizedTest + @NullAndEmptySource + void shouldThrowIllegalArgumentException_whenToIsNullOrBlank(String to) { + EmailData emailData = EmailData.builder() + .to(to) + .subject("my email") + .message("My email message") + .attachments(List.of()) + .build(); + assertThrows( + IllegalArgumentException.class, + () -> sendGridClient.sendEmail(EMAIL_FROM, emailData) + ); + } + + @ParameterizedTest + @NullAndEmptySource + void shouldThrowIllegalArgumentException_whenSubjectIsNullOrBlank(String subject) { + EmailData emailData = EmailData.builder() + .to("to@server.net") + .subject(subject) + .message("My email message") + .attachments(List.of()) + .build(); + assertThrows( + IllegalArgumentException.class, + () -> sendGridClient.sendEmail(EMAIL_FROM, emailData) + ); + } + + @Test + void shouldThrowIllegalArgumentException_whenEmailIsNull() { + assertThrows( + IllegalArgumentException.class, + () -> sendGridClient.sendEmail(EMAIL_FROM, null) + ); + } + } + + @Nested + class SuccessfulSendEmail { + + @BeforeEach + void setup() { + clearInvocations(sendGrid); + } + + @Test + @SneakyThrows + void shouldSendEmail_whenValidArgumentsProvided() { + when(sendGrid.api(any(Request.class))).thenReturn(SUCCESSFUL_RESPONSE); + + sendGridClient.sendEmail(EMAIL_FROM, EMAIL_DATA); + + verify(sendGrid).api(requestCaptor.capture()); + Request capturedRequest = requestCaptor.getValue(); + assertEquals(Method.POST, capturedRequest.getMethod()); + assertEquals("mail/send", capturedRequest.getEndpoint()); + assertTrue(capturedRequest.getBody().contains("\"email\":\"" + EMAIL_FROM + "\"")); + } + + @Test + @SneakyThrows + void shouldSendEmail_whenEmptyMessageIsProvided() { + when(sendGrid.api(any(Request.class))).thenReturn(SUCCESSFUL_RESPONSE); + + sendGridClient.sendEmail(EMAIL_FROM, EmailData.builder() + .to("to@server.net") + .subject("subject") + .message("") + .attachments(List.of()) + .build()); + + verify(sendGrid).api(requestCaptor.capture()); + Request capturedRequest = requestCaptor.getValue(); + assertTrue(capturedRequest.getBody().contains("\"content\":[{\"type\":\"text/plain\",\"value\":\" \"}]")); + } + + @Test + @SneakyThrows + void shouldSendEmail_whenAttachmentIsProvided() { + when(sendGrid.api(any(Request.class))).thenReturn(SUCCESSFUL_RESPONSE); + + sendGridClient.sendEmail(EMAIL_FROM, EmailData.builder() + .to("to@server.net") + .subject("subject") + .message("message") + .attachments(List.of(EmailAttachment.pdf(new byte[]{1, 2, 3}, "test.pdf"))) + .build()); + + verify(sendGrid).api(requestCaptor.capture()); + Request capturedRequest = requestCaptor.getValue(); + assertTrue(capturedRequest.getBody().contains("\"filename\":\"test.pdf\"")); + } + } + + @Nested + class FailureResponseFromSendGrid { + + @Test + @SneakyThrows + void shouldThrowEmailSendFailedException_whenSendGridThrowsIOException() { + when(sendGrid.api(any(Request.class))).thenThrow(new IOException("expected exception")); + assertThrows( + EmailSendFailedException.class, + () -> sendGridClient.sendEmail(EMAIL_FROM, EMAIL_DATA) + ); + } + + @Test + @SneakyThrows + void shouldThrowEmailSendFailedException_whenSendGridThrows400Response() { + when(sendGrid.api(any(Request.class))) + .thenReturn(new Response(400, "bad request", Map.of())); + + EmailSendFailedException emailSendFailedException = assertThrows( + EmailSendFailedException.class, + () -> sendGridClient.sendEmail(EMAIL_FROM, EMAIL_DATA) + ); + + Throwable causeThrowable = emailSendFailedException.getCause(); + assertTrue(causeThrowable instanceof HttpException); + HttpException cause = (HttpException) causeThrowable; + assertEquals( + "SendGrid returned a non-success response (400); body: bad request", + cause.getMessage() + ); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridAutoConfigurationTests.java b/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridAutoConfigurationTests.java new file mode 100644 index 00000000..d3ebf3c4 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/sendgrid/config/SendGridAutoConfigurationTests.java @@ -0,0 +1,96 @@ +package uk.gov.hmcts.reform.civil.sendgrid.config; + +import com.sendgrid.SendGrid; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.context.properties.source.ConfigurationPropertySources; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import uk.gov.hmcts.reform.civil.sendgrid.config.SendGridAutoConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +class SendGridAutoConfigurationTests { + + private static final String API_KEY = "SEND.GRID.SECRET-API-KEY"; + private static final String MY_CUSTOM_API_KEY = "SEND.GRID.MY.CUSTOM_API_KEY"; + + private AnnotationConfigApplicationContext context; + + @AfterEach + void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + void shouldAutoCreateSendGridBean_whenApiKeyIsConfigured() { + loadContext("sendgrid.api-key:" + API_KEY); + SendGrid sendGrid = this.context.getBean(SendGrid.class); + assertThat(sendGrid.getRequestHeaders()).containsEntry("Authorization", "Bearer " + API_KEY); + } + + @Test + void shouldNotFiredAutoConfigure_whenApiKeyIsNotConfigured() { + loadContext(); + assertThatExceptionOfType(NoSuchBeanDefinitionException.class) + .isThrownBy(() -> this.context.getBean(SendGrid.class)); + } + + @Test + void shouldNotFiredAutoConfigure_whenBeanAlreadyCreatedManually() { + loadContext(ManualSendGridConfiguration.class, "sendgrid.api-key:" + API_KEY); + SendGrid sendGrid = this.context.getBean(SendGrid.class); + assertThat(sendGrid.getRequestHeaders()).containsEntry("Authorization", "Bearer " + MY_CUSTOM_API_KEY); + } + + @Test + void shouldAutoCreateSendGridBeanWithTestEnabled_whenApiKeyAndTestPropertyIsConfigured() { + loadContext("sendgrid.api-key:" + API_KEY, "sendgrid.test:true", "sendgrid.host:localhost"); + SendGrid sendGrid = this.context.getBean(SendGrid.class); + assertThat(sendGrid).extracting("client").extracting("test").isEqualTo(true); + } + + @Test + void shouldAutoCreateSendGridBeanWithCustomHost_whenApiKeyAndHostPropertyIsConfigured() { + loadContext("sendgrid.api-key:" + API_KEY, "sendgrid.host:localhost"); + SendGrid sendGrid = this.context.getBean(SendGrid.class); + assertThat(sendGrid).extracting("host").isEqualTo("localhost"); + } + + @Test + void shouldAutoCreateSendGridBeanWithCustomVersion_whenApiKeyAndVersionPropertyIsConfigured() { + loadContext("sendgrid.api-key:" + API_KEY, "sendgrid.version:v2"); + SendGrid sendGrid = this.context.getBean(SendGrid.class); + assertThat(sendGrid).extracting("version").isEqualTo("v2"); + } + + private void loadContext(String... environment) { + loadContext(null, environment); + } + + private void loadContext(Class additionalConfiguration, String... environment) { + this.context = new AnnotationConfigApplicationContext(); + TestPropertyValues.of(environment).applyTo(this.context); + ConfigurationPropertySources.attach(this.context.getEnvironment()); + this.context.register(SendGridAutoConfiguration.class); + if (additionalConfiguration != null) { + this.context.register(additionalConfiguration); + } + this.context.refresh(); + } + + @Configuration(proxyBeanMethods = false) + static class ManualSendGridConfiguration { + + @Bean + SendGrid sendGrid() { + return new SendGrid(MY_CUSTOM_API_KEY, true); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/service/AssignCaseServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/service/AssignCaseServiceTest.java new file mode 100644 index 00000000..28e0fad9 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/service/AssignCaseServiceTest.java @@ -0,0 +1,67 @@ +package uk.gov.hmcts.reform.civil.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.idam.client.models.UserInfo; +import uk.gov.hmcts.reform.civil.prd.model.Organisation; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static uk.gov.hmcts.reform.civil.enums.CaseRole.RESPONDENTSOLICITORONE; + +@ExtendWith(SpringExtension.class) +public class AssignCaseServiceTest { + + private static final String AUTHORIZATION = "authorisation"; + private static final String CASE_ID = "1"; + private static final String UID = "abra-abra-cadabra"; + private static final String ORG_ID = "org_id"; + private static final UserInfo USER_INFO = UserInfo.builder().uid(UID).build(); + private static final Organisation ORGANISATION = Organisation + .builder() + .organisationIdentifier(ORG_ID) + .build(); + + @Mock + private CoreCaseUserService coreCaseUserService; + + @Mock + private UserService userService; + + @Mock + private OrganisationService organisationService; + + @InjectMocks + private AssignCaseService assignCaseService; + + @BeforeEach + void setUp() { + given(userService.getUserInfo(anyString())).willReturn(USER_INFO); + } + + @Test + void shouldCallAssignCaseWithOrganisationIdSuccessfully() { + given(organisationService.findOrganisation(anyString())).willReturn(Optional.of(ORGANISATION)); + + assignCaseService.assignCase(AUTHORIZATION, CASE_ID, Optional.of(RESPONDENTSOLICITORONE)); + + verify(coreCaseUserService, times(1)) + .assignCase(CASE_ID, UID, ORG_ID, RESPONDENTSOLICITORONE); + } + + @Test + void shouldCallAssignCaseWithoutOrganisationIdSuccessfully() { + assignCaseService.assignCase(AUTHORIZATION, CASE_ID, Optional.of(RESPONDENTSOLICITORONE)); + verify(coreCaseUserService, times(1)) + .assignCase(CASE_ID, UID, null, RESPONDENTSOLICITORONE); + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/service/BulkPrintServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/service/BulkPrintServiceTest.java new file mode 100644 index 00000000..c55d2422 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/service/BulkPrintServiceTest.java @@ -0,0 +1,56 @@ +package uk.gov.hmcts.reform.civil.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.sendletter.api.LetterWithPdfsRequest; +import uk.gov.hmcts.reform.sendletter.api.SendLetterApi; + +import java.util.Base64; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static uk.gov.hmcts.reform.civil.service.BulkPrintService.ADDITIONAL_DATA_CASE_IDENTIFIER_KEY; +import static uk.gov.hmcts.reform.civil.service.BulkPrintService.ADDITIONAL_DATA_CASE_REFERENCE_NUMBER_KEY; +import static uk.gov.hmcts.reform.civil.service.BulkPrintService.ADDITIONAL_DATA_LETTER_TYPE_KEY; +import static uk.gov.hmcts.reform.civil.service.BulkPrintService.XEROX_TYPE_PARAMETER; + +@ExtendWith(SpringExtension.class) +class BulkPrintServiceTest { + + @Mock + private SendLetterApi sendLetterApi; + @Mock + private AuthTokenGenerator authTokenGenerator; + @InjectMocks + private BulkPrintService bulkPrintService; + + private final String authentication = "Authentication"; + private final String letterType = "Letter type"; + private final String claimId = "1"; + + private final Map additionalInformation = + Map.of(ADDITIONAL_DATA_LETTER_TYPE_KEY, letterType, + ADDITIONAL_DATA_CASE_IDENTIFIER_KEY, claimId, + ADDITIONAL_DATA_CASE_REFERENCE_NUMBER_KEY, claimId + ); + private final byte[] letterTemplate = new byte[]{1, 2, 3}; + private final LetterWithPdfsRequest letter = + new LetterWithPdfsRequest(List.of(Base64.getEncoder().encodeToString(letterTemplate)), + XEROX_TYPE_PARAMETER, additionalInformation + ); + + @Test + void shouldSendLetterToBulkPrintSuccessfully() { + given(authTokenGenerator.generate()).willReturn(authentication); + bulkPrintService.printLetter(letterTemplate, claimId, claimId, letterType); + verify(sendLetterApi).sendLetter(refEq(authentication), refEq(letter)); + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/service/CategoryServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/service/CategoryServiceTest.java new file mode 100644 index 00000000..71fc5bd1 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/service/CategoryServiceTest.java @@ -0,0 +1,99 @@ +package uk.gov.hmcts.reform.civil.service; + +import feign.FeignException; +import feign.Request; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.crd.client.ListOfValuesApi; +import uk.gov.hmcts.reform.civil.crd.model.CategorySearchResult; + +import java.util.Map; +import java.util.Optional; + +import static feign.Request.HttpMethod.GET; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(SpringExtension.class) +class CategoryServiceTest { + + private static final String AUTH_TOKEN = "Bearer token"; + private static final String AUTH_TOKEN_UNAUTHORISED = "Bearer token unauthorised"; + private static final String SERVICE_AUTH_TOKEN = "Bearer service-token"; + private static final String CATEGORY_ID = "HearingChannel"; + private static final String SERVICE_ID = "AAA6"; + private static final String WRONG_SERVICE_ID = "ABCDE"; + + private final FeignException notFoundFeignException = new FeignException.NotFound( + "not found message", + Request.create(GET, "", Map.of(), new byte[]{}, UTF_8, null), + "not found response body".getBytes(UTF_8), + Map.of() + ); + + private final FeignException forbiddenException = new FeignException.Forbidden( + "forbidden message", + Request.create(GET, "", Map.of(), new byte[]{}, UTF_8, null), + "forbidden response body".getBytes(UTF_8), + Map.of() + ); + + private final CategorySearchResult categorySearchResult = CategorySearchResult.builder().build(); + + @Mock + private ListOfValuesApi listOfValuesApi; + + @Mock + private AuthTokenGenerator authTokenGenerator; + + @InjectMocks + private CategoryService categoryService; + + @BeforeEach + void setUp() { + given(listOfValuesApi.findCategoryByCategoryIdAndServiceId( + any(), + any(), + any(), + any())).willReturn(categorySearchResult); + given(authTokenGenerator.generate()).willReturn(SERVICE_AUTH_TOKEN); + } + + @Test + void shouldReturnCategory_whenInvoked() { + var category = categoryService.findCategoryByCategoryIdAndServiceId(AUTH_TOKEN, CATEGORY_ID, SERVICE_ID); + + verify(listOfValuesApi).findCategoryByCategoryIdAndServiceId(CATEGORY_ID, SERVICE_ID, AUTH_TOKEN, + authTokenGenerator.generate()); + assertThat(category).isEqualTo(Optional.of(categorySearchResult)); + } + + @Test + void shouldReturnEmptyOptional_whenCategoryNotFound() { + given(listOfValuesApi.findCategoryByCategoryIdAndServiceId(any(), eq(WRONG_SERVICE_ID), any(), any())) + .willThrow(notFoundFeignException); + var category = categoryService.findCategoryByCategoryIdAndServiceId(AUTH_TOKEN, CATEGORY_ID, WRONG_SERVICE_ID); + verify(listOfValuesApi).findCategoryByCategoryIdAndServiceId(CATEGORY_ID, WRONG_SERVICE_ID, AUTH_TOKEN, + authTokenGenerator.generate()); + assertThat(category).isEmpty(); + } + + @Test + void shouldReturnEmptyOptional_whenAccessForbidden() { + given(listOfValuesApi.findCategoryByCategoryIdAndServiceId(any(), any(), eq(AUTH_TOKEN_UNAUTHORISED), any())) + .willThrow(forbiddenException); + var category = categoryService.findCategoryByCategoryIdAndServiceId(AUTH_TOKEN_UNAUTHORISED, CATEGORY_ID, SERVICE_ID); + verify(listOfValuesApi).findCategoryByCategoryIdAndServiceId(CATEGORY_ID, SERVICE_ID, AUTH_TOKEN_UNAUTHORISED, + authTokenGenerator.generate()); + assertThat(category).isEmpty(); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/service/CoreCaseUserServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/service/CoreCaseUserServiceTest.java new file mode 100644 index 00000000..02b530c0 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/service/CoreCaseUserServiceTest.java @@ -0,0 +1,256 @@ +package uk.gov.hmcts.reform.civil.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +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.mock.mockito.MockBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.ccd.client.CaseAccessDataStoreApi; +import uk.gov.hmcts.reform.ccd.model.AddCaseAssignedUserRolesRequest; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRole; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRoleWithOrganisation; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRolesRequest; +import uk.gov.hmcts.reform.ccd.model.CaseAssignedUserRolesResource; +import uk.gov.hmcts.reform.civil.config.CrossAccessUserConfiguration; +import uk.gov.hmcts.reform.civil.enums.CaseRole; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = { + CoreCaseUserService.class +}) +class CoreCaseUserServiceTest { + + private static final String CAA_USER_AUTH_TOKEN = "Bearer caa-user-xyz"; + private static final String SERVICE_AUTH_TOKEN = "Bearer service-xyz"; + private static final String CASE_ID = "1"; + private static final String USER_ID = "User1"; + private static final String USER_ID2 = "User2"; + public static final String ORG_ID = "62LYJRF"; + + @MockBean + private CrossAccessUserConfiguration userConfig; + + @MockBean + private CaseAccessDataStoreApi caseAccessDataStoreApi; + + @MockBean + private UserService userService; + + @MockBean + private AuthTokenGenerator authTokenGenerator; + + @Autowired + private CoreCaseUserService service; + + @BeforeEach + void init() { + clearInvocations(authTokenGenerator); + clearInvocations(userService); + when(authTokenGenerator.generate()).thenReturn(SERVICE_AUTH_TOKEN); + when(userService.getAccessToken(userConfig.getUserName(), userConfig.getPassword())).thenReturn( + CAA_USER_AUTH_TOKEN); + } + + @Nested + class AssignCase { + + @Test + void shouldAssignCaseToUser_WhenSameUserWithRequestedCaseRoleDoesNotExist() { + when(caseAccessDataStoreApi.getUserRoles(CAA_USER_AUTH_TOKEN, SERVICE_AUTH_TOKEN, List.of(CASE_ID))) + .thenReturn(CaseAssignedUserRolesResource.builder().caseAssignedUserRoles(List.of()).build()); + + service.assignCase(CASE_ID, USER_ID, ORG_ID, CaseRole.APPLICANTSOLICITORONE); + + verify(caseAccessDataStoreApi).addCaseUserRoles( + CAA_USER_AUTH_TOKEN, + SERVICE_AUTH_TOKEN, + getAddCaseAssignedUserRolesRequest(CaseRole.APPLICANTSOLICITORONE) + ); + } + + @Test + void shouldNotAssignCaseToUser_WhenSameUserWithRequestedCaseRoleAlreadyExist() { + CaseAssignedUserRole caseAssignedUserRole = CaseAssignedUserRole.builder() + .userId(USER_ID) + .caseRole(CaseRole.RESPONDENTSOLICITORONE.getFormattedName()) + .build(); + + when(caseAccessDataStoreApi.getUserRoles(CAA_USER_AUTH_TOKEN, SERVICE_AUTH_TOKEN, List.of(CASE_ID))) + .thenReturn(CaseAssignedUserRolesResource.builder().caseAssignedUserRoles(List.of(caseAssignedUserRole)) + .build()); + + service.assignCase(CASE_ID, USER_ID, ORG_ID, CaseRole.APPLICANTSOLICITORONE); + + verify(caseAccessDataStoreApi, never()).addCaseUserRoles( + CAA_USER_AUTH_TOKEN, + SERVICE_AUTH_TOKEN, + getAddCaseAssignedUserRolesRequest(CaseRole.RESPONDENTSOLICITORONE) + ); + } + + private AddCaseAssignedUserRolesRequest getAddCaseAssignedUserRolesRequest(CaseRole caseRole) { + CaseAssignedUserRoleWithOrganisation caseAssignedUserRoleWithOrganisation + = CaseAssignedUserRoleWithOrganisation.builder() + .caseDataId(CASE_ID) + .userId(USER_ID) + .caseRole(caseRole.getFormattedName()) + .organisationId(ORG_ID) + .build(); + + return AddCaseAssignedUserRolesRequest.builder() + .caseAssignedUserRoles(List.of(caseAssignedUserRoleWithOrganisation)) + .build(); + } + + } + + @Nested + class RemoveCaseAssignment { + + @Test + void shouldRemoveCaseAssignmentToUser_WhenUserWithCaseRoleAlreadyExist() { + CaseAssignedUserRole caseAssignedUserRole = CaseAssignedUserRole.builder() + .userId(USER_ID) + .caseRole(CaseRole.CREATOR.getFormattedName()) + .build(); + + when(caseAccessDataStoreApi.getUserRoles(CAA_USER_AUTH_TOKEN, SERVICE_AUTH_TOKEN, List.of(CASE_ID))) + .thenReturn(CaseAssignedUserRolesResource.builder().caseAssignedUserRoles(List.of(caseAssignedUserRole)) + .build()); + + service.removeCreatorRoleCaseAssignment(CASE_ID, USER_ID, ORG_ID); + + verify(caseAccessDataStoreApi).removeCaseUserRoles( + CAA_USER_AUTH_TOKEN, + SERVICE_AUTH_TOKEN, + getCaseAssignedUserRolesRequest(CaseRole.CREATOR) + ); + } + + @Test + void shouldNotRemoveCaseAssignmentToUser_WhenUserWithCaseRoleDoesNotExist() { + CaseAssignedUserRole caseAssignedUserRole + = CaseAssignedUserRole.builder().userId(USER_ID) + .caseRole(CaseRole.APPLICANTSOLICITORONE.getFormattedName()) + .build(); + + when(caseAccessDataStoreApi.getUserRoles(CAA_USER_AUTH_TOKEN, SERVICE_AUTH_TOKEN, List.of(CASE_ID))) + .thenReturn(CaseAssignedUserRolesResource.builder().caseAssignedUserRoles(List.of(caseAssignedUserRole)) + .build()); + + service.removeCreatorRoleCaseAssignment(CASE_ID, USER_ID, ORG_ID); + + verify(caseAccessDataStoreApi, never()).removeCaseUserRoles( + CAA_USER_AUTH_TOKEN, + SERVICE_AUTH_TOKEN, + getCaseAssignedUserRolesRequest(CaseRole.CREATOR) + ); + } + + private CaseAssignedUserRolesRequest getCaseAssignedUserRolesRequest(CaseRole caseRole) { + CaseAssignedUserRoleWithOrganisation caseAssignedUserRoleWithOrganisation + = CaseAssignedUserRoleWithOrganisation.builder() + .caseDataId(CASE_ID) + .userId(USER_ID) + .caseRole(caseRole.getFormattedName()) + .organisationId(ORG_ID) + .build(); + + return CaseAssignedUserRolesRequest.builder() + .caseAssignedUserRoles(List.of(caseAssignedUserRoleWithOrganisation)) + .build(); + } + } + + @Nested + class UserHasCaseRole { + + @BeforeEach + void setup() { + when(userService.getAccessToken(userConfig.getUserName(), userConfig.getPassword())).thenReturn( + CAA_USER_AUTH_TOKEN); + CaseAssignedUserRolesResource caseAssignedUserRolesResource = CaseAssignedUserRolesResource.builder() + .caseAssignedUserRoles(List.of( + CaseAssignedUserRole.builder() + .userId(USER_ID) + .caseRole(CaseRole.RESPONDENTSOLICITORONE.getFormattedName()) + .build(), + CaseAssignedUserRole.builder() + .userId(USER_ID2) + .caseRole(CaseRole.RESPONDENTSOLICITORTWO.getFormattedName()) + .build())) + .build(); + when(caseAccessDataStoreApi.getUserRoles(CAA_USER_AUTH_TOKEN, SERVICE_AUTH_TOKEN, List.of(CASE_ID))) + .thenReturn(caseAssignedUserRolesResource); + } + + @Test + void shouldReturnTrue_whenCaseRoleAssignedToUser() { + assertThat(service.userHasCaseRole(CASE_ID, USER_ID, CaseRole.RESPONDENTSOLICITORONE)).isTrue(); + assertThat(service.userHasCaseRole(CASE_ID, USER_ID2, CaseRole.RESPONDENTSOLICITORTWO)).isTrue(); + + verify(caseAccessDataStoreApi, times(2)).getUserRoles( + CAA_USER_AUTH_TOKEN, + SERVICE_AUTH_TOKEN, + List.of(CASE_ID) + ); + } + + @Test + void shouldReturnFalse_whenCaseRoleNotAssignedToUser() { + assertThat(service.userHasCaseRole(CASE_ID, USER_ID, CaseRole.RESPONDENTSOLICITORTWO)).isFalse(); + assertThat(service.userHasCaseRole(CASE_ID, USER_ID2, CaseRole.RESPONDENTSOLICITORONE)).isFalse(); + + verify(caseAccessDataStoreApi, times(2)).getUserRoles( + CAA_USER_AUTH_TOKEN, + SERVICE_AUTH_TOKEN, + List.of(CASE_ID) + ); + } + } + + @Nested + class GetUserCaseRoles { + + @BeforeEach + void setup() { + when(userService.getAccessToken(userConfig.getUserName(), userConfig.getPassword())).thenReturn( + CAA_USER_AUTH_TOKEN); + } + + @Test + void shouldReturnCaseRoles_whenCaseRoleAssignedToUser() { + CaseAssignedUserRolesResource caseAssignedUserRolesResource = CaseAssignedUserRolesResource.builder() + .caseAssignedUserRoles(List.of( + CaseAssignedUserRole.builder() + .userId(USER_ID) + .caseRole(CaseRole.RESPONDENTSOLICITORONE.getFormattedName()) + .build(), + CaseAssignedUserRole.builder() + .userId(USER_ID2) + .caseRole(CaseRole.RESPONDENTSOLICITORTWO.getFormattedName()) + .build())) + .build(); + when(caseAccessDataStoreApi.getUserRoles(CAA_USER_AUTH_TOKEN, SERVICE_AUTH_TOKEN, List.of(CASE_ID))) + .thenReturn(caseAssignedUserRolesResource); + + List caseRoles = service.getUserCaseRoles(CASE_ID, USER_ID); + + assertThat(caseRoles.contains("[RESPONDENTSOLICITORONE]")); + assertThat(!caseRoles.contains("[RESPONDENTSOLICITORTWO]")); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/service/OrganisationServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/service/OrganisationServiceTest.java new file mode 100644 index 00000000..35c2cf2d --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/service/OrganisationServiceTest.java @@ -0,0 +1,113 @@ +package uk.gov.hmcts.reform.civil.service; + +import feign.FeignException; +import feign.Request; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.config.PrdAdminUserConfiguration; +import uk.gov.hmcts.reform.civil.prd.client.OrganisationApi; +import uk.gov.hmcts.reform.civil.prd.model.Organisation; + +import java.util.Map; +import java.util.Optional; + +import static feign.Request.HttpMethod.GET; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +class OrganisationServiceTest { + + private static final String AUTH_TOKEN = "Bearer token"; + private static final String PRD_ADMIN_AUTH_TOKEN = "Bearer token"; + private static final String SERVICE_AUTH_TOKEN = "Bearer service-token"; + private static final String ORG_ID = "ORG ID"; + + private final FeignException notFoundFeignException = new FeignException.NotFound( + "not found message", + Request.create(GET, "", Map.of(), new byte[]{}, UTF_8, null), + "not found response body".getBytes(UTF_8), + Map.of() + ); + private final Organisation expectedOrganisation = Organisation.builder() + .organisationIdentifier(ORG_ID) + .build(); + + @Mock + private OrganisationApi organisationApi; + + @Mock + private AuthTokenGenerator authTokenGenerator; + + @Mock + private UserService userService; + + @Mock + private PrdAdminUserConfiguration userConfig; + + @InjectMocks + private OrganisationService organisationService; + + @BeforeEach + void setUp() { + given(organisationApi.findUserOrganisation(any(), any())).willReturn(expectedOrganisation); + given(organisationApi.findOrganisationById(any(), any(), any())).willReturn(expectedOrganisation); + given(authTokenGenerator.generate()).willReturn(SERVICE_AUTH_TOKEN); + when(userService.getAccessToken(userConfig.getUsername(), userConfig.getPassword())).thenReturn( + PRD_ADMIN_AUTH_TOKEN); + } + + @Nested + class FindOrganisation { + + @Test + void shouldReturnOrganisation_whenInvoked() { + var organisation = organisationService.findOrganisation(AUTH_TOKEN); + + verify(organisationApi).findUserOrganisation(AUTH_TOKEN, SERVICE_AUTH_TOKEN); + assertThat(organisation).isEqualTo(Optional.of(expectedOrganisation)); + } + + @Test + void shouldReturnEmptyOptional_whenOrganisationNotFound() { + given(organisationApi.findUserOrganisation(any(), any())).willThrow(notFoundFeignException); + var organisation = organisationService.findOrganisation(AUTH_TOKEN); + + verify(organisationApi).findUserOrganisation(AUTH_TOKEN, SERVICE_AUTH_TOKEN); + assertThat(organisation).isEmpty(); + } + } + + @Nested + class FindOrganisationById { + + @Test + void shouldReturnOrganisation_whenInvoked() { + var organisation = organisationService.findOrganisationById(ORG_ID); + + verify(userService).getAccessToken(userConfig.getUsername(), userConfig.getPassword()); + verify(organisationApi).findOrganisationById(PRD_ADMIN_AUTH_TOKEN, SERVICE_AUTH_TOKEN, ORG_ID); + assertThat(organisation).isEqualTo(Optional.of(expectedOrganisation)); + } + + @Test + void shouldReturnEmptyOptional_whenOrganisationNotFound() { + given(organisationApi.findOrganisationById(any(), any(), any())).willThrow(notFoundFeignException); + var organisation = organisationService.findOrganisationById(ORG_ID); + + verify(userService).getAccessToken(userConfig.getUsername(), userConfig.getPassword()); + verify(organisationApi).findOrganisationById(PRD_ADMIN_AUTH_TOKEN, SERVICE_AUTH_TOKEN, ORG_ID); + assertThat(organisation).isEmpty(); + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/service/RoleAssignmentsServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/service/RoleAssignmentsServiceTest.java new file mode 100644 index 00000000..188750eb --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/service/RoleAssignmentsServiceTest.java @@ -0,0 +1,63 @@ +package uk.gov.hmcts.reform.civil.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.mock.mockito.MockBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import uk.gov.hmcts.reform.authorisation.generators.AuthTokenGenerator; +import uk.gov.hmcts.reform.civil.ras.client.RoleAssignmentsApi; +import uk.gov.hmcts.reform.civil.ras.model.RoleAssignmentResponse; +import uk.gov.hmcts.reform.civil.ras.model.RoleAssignmentServiceResponse; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = { + RoleAssignmentsService.class +}) +class RoleAssignmentsServiceTest { + + private static final String USER_AUTH_TOKEN = "Bearer caa-user-xyz"; + private static final String ACTORID = "1111111"; + private static RoleAssignmentServiceResponse RAS_RESPONSE = RoleAssignmentServiceResponse + .builder() + .roleAssignmentResponse( + List.of(RoleAssignmentResponse + .builder() + .actorId(ACTORID) + .build() + ) + ) + .build(); + + @MockBean + private AuthTokenGenerator authTokenGenerator; + + @MockBean + private RoleAssignmentsApi roleAssignmentApi; + + @Autowired + private RoleAssignmentsService roleAssignmentsService; + + @BeforeEach + void init() { + clearInvocations(authTokenGenerator); + when(authTokenGenerator.generate()).thenReturn(USER_AUTH_TOKEN); + when(roleAssignmentApi.getRoleAssignments(anyString(), anyString(), anyString())).thenReturn(RAS_RESPONSE); + } + + @Test + void shouldReturn() { + var roleAssignmentsExpected = roleAssignmentsService.getRoleAssignments(ACTORID, authTokenGenerator.generate()); + assertEquals(roleAssignmentsExpected, RAS_RESPONSE); + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/service/UserServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/service/UserServiceTest.java new file mode 100644 index 00000000..df6672bd --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/service/UserServiceTest.java @@ -0,0 +1,69 @@ +package uk.gov.hmcts.reform.civil.service; + +import com.google.common.collect.Lists; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.gov.hmcts.reform.idam.client.IdamClient; +import uk.gov.hmcts.reform.idam.client.models.UserInfo; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + + private static final String SUB = "user-idam@reform.local"; + private static final String UID = "user-idam-01"; + private static final String NAME = "User IDAM"; + private static final String GIVEN_NAME = "User"; + private static final String FAMILY_NAME = "IDAM"; + private static final List ROLES = Lists.newArrayList("citizen"); + + private static final String AUTHORISATION = "Bearer I am a valid token"; + private static final String PASSWORD = "User password"; + + private static final UserInfo userInfo = UserInfo.builder() + .sub(SUB) + .uid(UID) + .name(NAME) + .givenName(GIVEN_NAME) + .familyName(FAMILY_NAME) + .roles(ROLES) + .build(); + + @Mock + private IdamClient idamClient; + + private UserService userService; + + @BeforeEach + public void setup() { + userService = new UserService(idamClient); + } + + @Test + void shouldReturnUserInfo_whenValidAuthToken() { + when(idamClient.getUserInfo(AUTHORISATION)).thenReturn(userInfo); + UserInfo found = userService.getUserInfo(AUTHORISATION); + + assertThat(found.getSub()).isEqualTo(SUB); + assertThat(found.getUid()).isEqualTo(UID); + assertThat(found.getName()).isEqualTo(NAME); + assertThat(found.getGivenName()).isEqualTo(GIVEN_NAME); + assertThat(found.getFamilyName()).isEqualTo(FAMILY_NAME); + assertThat(found.getRoles()).isEqualTo(ROLES); + } + + @Test + void shouldReturnAccessToken_whenValidUserDetailsAreGiven() { + when(idamClient.getAccessToken(SUB, PASSWORD)).thenReturn(AUTHORISATION); + String accessToken = userService.getAccessToken(SUB, PASSWORD); + + assertThat(accessToken).isEqualTo(AUTHORISATION); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/utils/MonetaryConversionsTest.java b/src/test/java/uk/gov/hmcts/reform/civil/utils/MonetaryConversionsTest.java new file mode 100644 index 00000000..cc937429 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/utils/MonetaryConversionsTest.java @@ -0,0 +1,78 @@ +package uk.gov.hmcts.reform.civil.utils; + +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MonetaryConversionsTest { + + @Test + void shouldThrowNullPointer_whenGivenNullAmount() { + assertThrows(NullPointerException.class, () -> + MonetaryConversions.penniesToPounds(null)); + } + + @Test + void shouldConvertToZeroPounds_whenZeroPennies() { + BigDecimal converted = MonetaryConversions.penniesToPounds(BigDecimal.ZERO); + assertThat(converted).isEqualByComparingTo("0"); + } + + @Test + void shouldConvertToZeroPennies_whenZeroPounds() { + BigInteger converted = MonetaryConversions.poundsToPennies(BigDecimal.ZERO); + assertThat(converted).isEqualTo(new BigInteger("0")); + } + + @Test + void shouldConvertToOneHundredthOfPound_whenOnePenny() { + BigDecimal converted = MonetaryConversions.penniesToPounds(new BigDecimal("1")); + assertThat(converted).isEqualByComparingTo("0.01"); + } + + @Test + void shouldConvertToOnePenny_whenOneHundredthOfPound() { + BigInteger converted = MonetaryConversions.poundsToPennies(new BigDecimal("0.01")); + assertThat(converted).isEqualTo(new BigInteger("1")); + } + + @Test + void shouldConvertToOneTenthOfPound_whenTenPennies() { + BigDecimal converted = MonetaryConversions.penniesToPounds(new BigDecimal("10")); + assertThat(converted).isEqualByComparingTo("0.10"); + } + + @Test + void shouldConvertToTenPennies_whenOneTenthOfPound() { + BigInteger converted = MonetaryConversions.poundsToPennies(new BigDecimal("0.10")); + assertThat(converted).isEqualTo(new BigInteger("10")); + } + + @Test + void shouldConvertToOnePound_whenHundredPennies() { + BigDecimal converted = MonetaryConversions.penniesToPounds(new BigDecimal("100")); + assertThat(converted).isEqualByComparingTo("1.00"); + } + + @Test + void shouldConvertToHundredPennies_whenOnePound() { + BigInteger converted = MonetaryConversions.poundsToPennies(new BigDecimal("1.00")); + assertThat(converted).isEqualTo(new BigInteger("100")); + } + + @Test + void shouldConvertToTwentyFivePounds_whenTwoAndHalfThousandPennies() { + BigDecimal converted = MonetaryConversions.penniesToPounds(new BigDecimal("2500")); + assertThat(converted).isEqualByComparingTo("25.00"); + } + + @Test + void shouldConvertToTwoAndHalfThousandPennies_whenTwentyFivePounds() { + BigInteger converted = MonetaryConversions.poundsToPennies(new BigDecimal("25.00")); + assertThat(converted).isEqualTo(new BigInteger("2500")); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/utils/ObjectUtilsTest.java b/src/test/java/uk/gov/hmcts/reform/civil/utils/ObjectUtilsTest.java new file mode 100644 index 00000000..df4c88b5 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/utils/ObjectUtilsTest.java @@ -0,0 +1,99 @@ +package uk.gov.hmcts.reform.civil.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static uk.gov.hmcts.reform.civil.utils.ObjectUtils.fifthNonNull; +import static uk.gov.hmcts.reform.civil.utils.ObjectUtils.firstNonNull; +import static uk.gov.hmcts.reform.civil.utils.ObjectUtils.fourthNonNull; +import static uk.gov.hmcts.reform.civil.utils.ObjectUtils.secondNonNull; +import static uk.gov.hmcts.reform.civil.utils.ObjectUtils.thirdNonNull; + +class ObjectUtilsTest { + + @Test + void shouldReturnNull_whenNonNullDoesNotExist() { + assertNull(firstNonNull(null, null, null)); + } + + @Test + void shouldReturnFirstNonNull_whenProvided() { + assertEquals( + "value1", + firstNonNull( + null, + null, + "value1", + "value2", + "value3", + "value4", + "value5" + ) + ); + } + + @Test + void shouldReturnSecondNonNull_whenProvided() { + assertEquals( + "value2", + secondNonNull( + null, + "value1", + "value2", + "value3", + "value4", + null, + "value5" + ) + ); + } + + @Test + void shouldReturnThirdNonNull_whenProvided() { + assertEquals( + "value3", + thirdNonNull( + null, + "value1", + "value2", + null, + "value3", + "value4", + "value5" + ) + ); + } + + @Test + void shouldReturnFourthNonNull_whenProvided() { + assertEquals( + "value4", + fourthNonNull( + null, + "value1", + "value2", + null, + "value3", + "value4", + "value5" + ) + ); + } + + @Test + void shouldReturnFifthNonNull_whenProvided() { + assertEquals( + "value5", + fifthNonNull( + null, + "value1", + "value2", + null, + "value3", + "value4", + "value5" + ) + ); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/utils/ResourceReaderTest.java b/src/test/java/uk/gov/hmcts/reform/civil/utils/ResourceReaderTest.java new file mode 100644 index 00000000..3fe43752 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/utils/ResourceReaderTest.java @@ -0,0 +1,33 @@ +package uk.gov.hmcts.reform.civil.utils; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ResourceReaderTest { + + @Test + void shouldReturnStringIfResourceExists() { + String content = ResourceReader.readString("sample-resource.txt"); + assertThat(content).contains("Sample content"); + } + + @Test + void shouldThrowExceptionWhileReadingStringIfResourceDoesNotExist() { + assertThatThrownBy(() -> ResourceReader.readString("non-existing-resource.txt")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldReturnBytesIfResourceExists() { + byte[] content = ResourceReader.readBytes("sample-resource.txt"); + assertThat(content).contains("Sample content".getBytes()); + } + + @Test + void shouldThrowExceptionWhileReadingBytesIfResourceDoesNotExist() { + assertThatThrownBy(() -> ResourceReader.readBytes("non-existing-resource.txt")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/utils/StringUtilsTest.java b/src/test/java/uk/gov/hmcts/reform/civil/utils/StringUtilsTest.java new file mode 100644 index 00000000..890da5f3 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/utils/StringUtilsTest.java @@ -0,0 +1,34 @@ +package uk.gov.hmcts.reform.civil.utils; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; +import static uk.gov.hmcts.reform.civil.utils.StringUtils.joinNonNull; + +class StringUtilsTest { + + @Test + void shouldReturnNull_whenAllValuesPassedAreNull() { + String result = joinNonNull(", ", null, null); + assertNull(result); + } + + @Test + void shouldReturnCombined_whenValuesPassed() { + String result = joinNonNull(", ", "Line1", "Line2"); + assertThat(result).isEqualTo("Line1, Line2"); + } + + @Test + void shouldReturnValid_whenFirstValueIsPassed() { + String result = joinNonNull(", ", "Line1", null); + assertThat(result).isEqualTo("Line1"); + } + + @Test + void shouldReturnValid_whenSecondValueIsPassed() { + String result = joinNonNull(", ", null, "Line2"); + assertThat(result).isEqualTo("Line2"); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/validation/PostCodeValidatorTest.java b/src/test/java/uk/gov/hmcts/reform/civil/validation/PostCodeValidatorTest.java new file mode 100644 index 00000000..7cd2924b --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/validation/PostCodeValidatorTest.java @@ -0,0 +1,61 @@ +package uk.gov.hmcts.reform.civil.validation; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import uk.gov.hmcts.reform.civil.postcode.PostcodeLookupService; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@SpringBootTest(classes = { + PostcodeValidator.class +}) +public class PostCodeValidatorTest { + + @MockBean + private PostcodeLookupService postcodeLookupService; + + @Autowired + private PostcodeValidator postcodeValidator; + + @Nested + class ValidatePostCode { + + @Test + void returnError_whenStartswithBT() { + List errors = postcodeValidator.validate("BT11SS"); + + assertThat(errors).containsOnly("Postcode must be in England or Wales"); + } + + @Test + void returnError_whenInputisNull() { + List errors = postcodeValidator.validate(null); + + assertThat(errors).containsOnly("Please enter Postcode"); + } + + @Test + void returnError_whenInputisNotFound() { + when(postcodeLookupService.validatePostCodeForDefendant(any())).thenReturn(false); + List errors = postcodeValidator.validate("TEST"); + + assertThat(errors).containsOnly("Postcode must be in England or Wales"); + } + + @Test + void returnTrue_whenInputisFound() { + when(postcodeLookupService.validatePostCodeForDefendant(any())).thenReturn(true); + List errors = postcodeValidator.validate("BA12SS"); + + assertThat(errors).isEmpty(); + } + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/civil/validation/ValidateEmailServiceTest.java b/src/test/java/uk/gov/hmcts/reform/civil/validation/ValidateEmailServiceTest.java new file mode 100644 index 00000000..3f9fe188 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/civil/validation/ValidateEmailServiceTest.java @@ -0,0 +1,93 @@ +package uk.gov.hmcts.reform.civil.validation; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + +class ValidateEmailServiceTest { + + private final ValidateEmailService validateEmailService = new ValidateEmailService(); + + private static final String ERROR_MESSAGE = "Enter an email address in the correct format, " + + "for example john.smith@example.com"; + + @ParameterizedTest + @MethodSource("validEmailAddresses") + void shouldNotReturnAnErrorMessageIfEmailIsValid(String email) { + assertThat(validateEmailService.validate(email)).isEmpty(); + } + + @ParameterizedTest + @MethodSource("invalidEmailAddresses") + void shouldReturnAnErrorMessageIfEmailIsInvalid(String email) { + assertThat(validateEmailService.validate(email)).contains(ERROR_MESSAGE); + } + + //See https://github.com/alphagov/notifications-utils/blob/master/tests/test_recipient_validation.py#L104-L121 + private static Stream validEmailAddresses() { + return Stream.of( + "email@domain.com", + "email@domain.COM", + "firstname.lastname@domain.com", + "email@subdomain.domain.com", + "firstname+lastname@domain.com", + "1234567890@domain.com", + "email@domain-one.com", + "_______@domain.com", + "email@domain.name", + "email@domain.superlongtld", + "email@domain.co.jp", + "firstname-lastname@domain.com", + "info@german-financial-services.vermögensberatung", + "info@german-financial-services.reallylongarbitrarytldthatiswaytoohugejustincase", + "japanese-info@例え.テスト", + format("%s@example.com", "a".repeat(64)), + format("%s@example.com", "a".repeat(63))); + } + + //See https://github.com/alphagov/notifications-utils/blob/master/tests/test_recipient_validation.py#L122-L152 + private static Stream invalidEmailAddresses() { + return Stream.of( + "invalid@tld.co.k", + " johndoe@email.com", + "very.unusual.”@”.unusual.com@example.com", + "very.”(),:;<>[]”.VERY.”very@\\\\ \"very”.unusual@strange.example.com", + "email@123.123.123.123", + "email@[123.123.123.123]", + "plainaddress", + "@no-local-part.com", + "Outlook Contact ", + "no-at.domain.com", + "no-tld@domain", + ";beginning-semicolon@domain.co.uk", + "middle-semicolon@domain.co;uk", + "trailing-semicolon@domain.com;", + "\"email+leading-quotes@domain.com", + "email+middle\"-quotes@domain.com", + "\"quoted-local-part\"@domain.com", + "\"quoted@domain.com\"", + "lots-of-dots@domain..gov..uk", + "two-dots..in-local@domain.com", + "multiple@domains@domain.com", + "spaces in local@domain.com", + "spaces-in-domain@dom ain.com", + "underscores-in-domain@dom_ain.com", + "pipe-in-domain@example.com|gov.uk", + "comma,in-local@gov.uk", + "comma-in-domain@domain,gov.uk", + "pound-sign-in-local£@domain.com", + "local-with-’-apostrophe@domain.com", + "local-with-”-quotes@domain.com", + "domain-starts-with-a-dot@.domain.com", + "brackets(in)local@domain.com", + ".Douglas.@hmcts.net", + "Douglas.@hmcts.net", + "firstname.o'lastname@domain.com", + format("%s@example.com", "a".repeat(65)), + format("email-too-long-%s@example.com", "a".repeat(320))); + } +} diff --git a/src/test/resources/bank-holidays.json b/src/test/resources/bank-holidays.json new file mode 100644 index 00000000..73b3e7bc --- /dev/null +++ b/src/test/resources/bank-holidays.json @@ -0,0 +1,343 @@ +{ + "england-and-wales": { + "division": "england-and-wales", + "events": [ + { + "title": "New Year’s Day", + "date": "2015-01-01", + "notes": "", + "bunting": true + }, + { + "title": "Good Friday", + "date": "2015-04-03", + "notes": "", + "bunting": false + }, + { + "title": "Easter Monday", + "date": "2015-04-06", + "notes": "", + "bunting": true + }, + { + "title": "Early May bank holiday", + "date": "2015-05-04", + "notes": "", + "bunting": true + }, + { + "title": "Spring bank holiday", + "date": "2015-05-25", + "notes": "", + "bunting": true + }, + { + "title": "Summer bank holiday", + "date": "2015-08-31", + "notes": "", + "bunting": true + }, + { + "title": "Christmas Day", + "date": "2015-12-25", + "notes": "", + "bunting": true + }, + { + "title": "Boxing Day", + "date": "2015-12-28", + "notes": "Substitute day", + "bunting": true + }, + { + "title": "New Year’s Day", + "date": "2016-01-01", + "notes": "", + "bunting": true + }, + { + "title": "Good Friday", + "date": "2016-03-25", + "notes": "", + "bunting": false + }, + { + "title": "Easter Monday", + "date": "2016-03-28", + "notes": "", + "bunting": true + }, + { + "title": "Early May bank holiday", + "date": "2016-05-02", + "notes": "", + "bunting": true + }, + { + "title": "Spring bank holiday", + "date": "2016-05-30", + "notes": "", + "bunting": true + }, + { + "title": "Summer bank holiday", + "date": "2016-08-29", + "notes": "", + "bunting": true + }, + { + "title": "Boxing Day", + "date": "2016-12-26", + "notes": "", + "bunting": true + }, + { + "title": "Christmas Day", + "date": "2016-12-27", + "notes": "Substitute day", + "bunting": true + }, + { + "title": "New Year’s Day", + "date": "2017-01-02", + "notes": "Substitute day", + "bunting": true + }, + { + "title": "Good Friday", + "date": "2017-04-14", + "notes": "", + "bunting": false + }, + { + "title": "Easter Monday", + "date": "2017-04-17", + "notes": "", + "bunting": true + }, + { + "title": "Early May bank holiday", + "date": "2017-05-01", + "notes": "", + "bunting": true + }, + { + "title": "Spring bank holiday", + "date": "2017-05-29", + "notes": "", + "bunting": true + }, + { + "title": "Summer bank holiday", + "date": "2017-08-28", + "notes": "", + "bunting": true + }, + { + "title": "Christmas Day", + "date": "2017-12-25", + "notes": "", + "bunting": true + }, + { + "title": "Boxing Day", + "date": "2017-12-26", + "notes": "", + "bunting": true + }, + { + "title": "New Year’s Day", + "date": "2018-01-01", + "notes": "", + "bunting": true + }, + { + "title": "Good Friday", + "date": "2018-03-30", + "notes": "", + "bunting": false + }, + { + "title": "Easter Monday", + "date": "2018-04-02", + "notes": "", + "bunting": true + }, + { + "title": "Early May bank holiday", + "date": "2018-05-07", + "notes": "", + "bunting": true + }, + { + "title": "Spring bank holiday", + "date": "2018-05-28", + "notes": "", + "bunting": true + }, + { + "title": "Summer bank holiday", + "date": "2018-08-27", + "notes": "", + "bunting": true + }, + { + "title": "Christmas Day", + "date": "2018-12-25", + "notes": "", + "bunting": true + }, + { + "title": "Boxing Day", + "date": "2018-12-26", + "notes": "", + "bunting": true + }, + { + "title": "New Year’s Day", + "date": "2019-01-01", + "notes": "", + "bunting": true + }, + { + "title": "Good Friday", + "date": "2019-04-19", + "notes": "", + "bunting": false + }, + { + "title": "Easter Monday", + "date": "2019-04-22", + "notes": "", + "bunting": true + }, + { + "title": "Early May bank holiday", + "date": "2019-05-06", + "notes": "", + "bunting": true + }, + { + "title": "Spring bank holiday", + "date": "2019-05-27", + "notes": "", + "bunting": true + }, + { + "title": "Summer bank holiday", + "date": "2019-08-26", + "notes": "", + "bunting": true + }, + { + "title": "Christmas Day", + "date": "2019-12-25", + "notes": "", + "bunting": true + }, + { + "title": "Boxing Day", + "date": "2019-12-26", + "notes": "", + "bunting": true + }, + { + "title": "New Year’s Day", + "date": "2020-01-01", + "notes": "", + "bunting": true + }, + { + "title": "Good Friday", + "date": "2020-04-10", + "notes": "", + "bunting": false + }, + { + "title": "Easter Monday", + "date": "2020-04-13", + "notes": "", + "bunting": false + }, + { + "title": "Early May bank holiday (VE day)", + "date": "2020-05-08", + "notes": "", + "bunting": true + }, + { + "title": "Spring bank holiday", + "date": "2020-05-25", + "notes": "", + "bunting": true + }, + { + "title": "Summer bank holiday", + "date": "2020-08-31", + "notes": "", + "bunting": true + }, + { + "title": "Christmas Day", + "date": "2020-12-25", + "notes": "", + "bunting": true + }, + { + "title": "Boxing Day", + "date": "2020-12-28", + "notes": "Substitute day", + "bunting": true + }, + { + "title": "New Year’s Day", + "date": "2021-01-01", + "notes": "", + "bunting": true + }, + { + "title": "Good Friday", + "date": "2021-04-02", + "notes": "", + "bunting": false + }, + { + "title": "Easter Monday", + "date": "2021-04-05", + "notes": "", + "bunting": true + }, + { + "title": "Early May bank holiday", + "date": "2021-05-03", + "notes": "", + "bunting": true + }, + { + "title": "Spring bank holiday", + "date": "2021-05-31", + "notes": "", + "bunting": true + }, + { + "title": "Summer bank holiday", + "date": "2021-08-30", + "notes": "", + "bunting": true + }, + { + "title": "Christmas Day", + "date": "2021-12-27", + "notes": "Substitute day", + "bunting": true + }, + { + "title": "Boxing Day", + "date": "2021-12-28", + "notes": "Substitute day", + "bunting": true + } + ] + } +} diff --git a/src/test/resources/document-management/download.document.json b/src/test/resources/document-management/download.document.json new file mode 100644 index 00000000..fe8519c3 --- /dev/null +++ b/src/test/resources/document-management/download.document.json @@ -0,0 +1,13 @@ +{ + "documentLink": { + "document_url": "http://dm-store:8080/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4", + "document_binary_url": "http://dm-store:8080/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4/binary", + "document_filename": "sealed_claim_form_299MC207.pdf", + "document_hash": "4d77c2e30e264e6c153cc27ddfab1731e4689bb79652e63045dcc6a3b70edbb4" + }, + "documentName": "sealed_claim_form_299MC207.pdf", + "documentType": "SEALED_CLAIM", + "documentSize": 79673, + "createdDatetime": "2022-06-07T23:15:29", + "createdBy": "Civil" +} diff --git a/src/test/resources/document-management/download.success.json b/src/test/resources/document-management/download.success.json new file mode 100644 index 00000000..dad29fdf --- /dev/null +++ b/src/test/resources/document-management/download.success.json @@ -0,0 +1,19 @@ +{ + "size": 72552, + "mimeType": "application/pdf", + "originalDocumentName": "TEST_DOCUMENT_1.pdf", + "createdBy": "15", + "lastModifiedBy": "15", + "modifiedOn": "2017-11-01T10:23:55.120+0000", + "createdOn": "2017-11-01T10:23:55.271+0000", + "classification": "PRIVATE", + "roles": null, + "_links": { + "self": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4" + }, + "binary": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4/binary" + } + } +} diff --git a/src/test/resources/document-management/metadata.success.json b/src/test/resources/document-management/metadata.success.json new file mode 100644 index 00000000..0e854a27 --- /dev/null +++ b/src/test/resources/document-management/metadata.success.json @@ -0,0 +1,19 @@ +{ + "size": 72552, + "mimeType": "application/pdf", + "originalDocumentName": "TEST_DOCUMENT_1.pdf", + "createdBy": "15", + "lastModifiedBy": "15", + "modifiedOn": "2017-11-01T10:23:55.120+0000", + "createdOn": "2017-11-01T10:23:55.271+0000", + "classification": "PRIVATE", + "roles": null, + "_links": { + "self": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b3" + }, + "binary": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b3/binary" + } + } +} diff --git a/src/test/resources/document-management/response.failure.json b/src/test/resources/document-management/response.failure.json new file mode 100644 index 00000000..fde1ed1b --- /dev/null +++ b/src/test/resources/document-management/response.failure.json @@ -0,0 +1,5 @@ +{ + "_embedded": { + "documents": [] + } +} diff --git a/src/test/resources/document-management/response.success.json b/src/test/resources/document-management/response.success.json new file mode 100644 index 00000000..d94c9287 --- /dev/null +++ b/src/test/resources/document-management/response.success.json @@ -0,0 +1,96 @@ +{ + "_embedded": { + "documents": [ + { + "size": 72552, + "mimeType": "application/pdf", + "originalDocumentName": "TEST_DOCUMENT_1.pdf", + "createdBy": "15", + "lastModifiedBy": "15", + "modifiedOn": "2017-11-01T10:23:55.120+0000", + "createdOn": "2017-11-01T10:23:55.271+0000", + "classification": "PRIVATE", + "roles": null, + "_links": { + "self": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b2" + }, + "binary": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b2/binary" + } + }, + "_embedded": { + "allDocumentVersions": { + "_embedded": { + "documentVersions": [ + { + "size": 72552, + "mimeType": "application/pdf", + "originalDocumentName": "000DC002.pdf", + "createdBy": "15", + "createdOn": "2017-11-01T10:23:55.271+0000", + "_links": { + "document": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4" + }, + "self": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4/versions/bd3b0ecc-714a-46c0-9db7-275c42986af5" + }, + "binary": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4/versions/bd3b0ecc-714a-46c0-9db7-275c42986af5/binary" + } + } + } + ] + } + } + } + }, + { + "size": 75130, + "mimeType": "application/pdf", + "originalDocumentName": "000DC050.pdf", + "createdBy": "15", + "lastModifiedBy": "15", + "modifiedOn": "2017-11-01T10:23:55.363+0000", + "createdOn": "2017-11-01T10:23:55.364+0000", + "classification": "PRIVATE", + "roles": null, + "_links": { + "self": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57" + }, + "binary": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57/binary" + } + }, + "_embedded": { + "allDocumentVersions": { + "_embedded": { + "documentVersions": [ + { + "size": 75130, + "mimeType": "application/pdf", + "originalDocumentName": "000DC050.pdf", + "createdBy": "15", + "createdOn": "2017-11-01T10:23:55.364+0000", + "_links": { + "document": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57" + }, + "self": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57/versions/50f70cb0-03c1-47bd-b93c-bcf6287cba13" + }, + "binary": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57/versions/50f70cb0-03c1-47bd-b93c-bcf6287cba13/binary" + } + } + } + ] + } + } + } + } + ] + } +} diff --git a/src/test/resources/document-management/secured.response.failure.json b/src/test/resources/document-management/secured.response.failure.json new file mode 100644 index 00000000..7590be7d --- /dev/null +++ b/src/test/resources/document-management/secured.response.failure.json @@ -0,0 +1,3 @@ +{ + "documents": [] +} diff --git a/src/test/resources/document-management/secured.response.success.json b/src/test/resources/document-management/secured.response.success.json new file mode 100644 index 00000000..51e46aa2 --- /dev/null +++ b/src/test/resources/document-management/secured.response.success.json @@ -0,0 +1,92 @@ +{ + "documents": [ + { + "size": 72552, + "mimeType": "application/pdf", + "originalDocumentName": "TEST_DOCUMENT_1.pdf", + "createdBy": "15", + "lastModifiedBy": "15", + "modifiedOn": "2017-11-01T10:23:55.120+0000", + "createdOn": "2017-11-01T10:23:55.271+0000", + "classification": "PRIVATE", + "roles": null, + "_links": { + "self": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b2" + }, + "binary": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b2/binary" + } + }, + "_embedded": { + "allDocumentVersions": { + "_embedded": { + "documentVersions": [ + { + "size": 72552, + "mimeType": "application/pdf", + "originalDocumentName": "000DC002.pdf", + "createdBy": "15", + "createdOn": "2017-11-01T10:23:55.271+0000", + "_links": { + "document": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4" + }, + "self": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4/versions/bd3b0ecc-714a-46c0-9db7-275c42986af5" + }, + "binary": { + "href": "http://dm-store:4506/documents/85d97996-22a5-40d7-882e-3a382c8ae1b4/versions/bd3b0ecc-714a-46c0-9db7-275c42986af5/binary" + } + } + } + ] + } + } + } + }, + { + "size": 75130, + "mimeType": "application/pdf", + "originalDocumentName": "000DC050.pdf", + "createdBy": "15", + "lastModifiedBy": "15", + "modifiedOn": "2017-11-01T10:23:55.363+0000", + "createdOn": "2017-11-01T10:23:55.364+0000", + "classification": "PRIVATE", + "roles": null, + "_links": { + "self": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57" + }, + "binary": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57/binary" + } + }, + "allDocumentVersions": { + "_embedded": { + "documentVersions": [ + { + "size": 75130, + "mimeType": "application/pdf", + "originalDocumentName": "000DC050.pdf", + "createdBy": "15", + "createdOn": "2017-11-01T10:23:55.364+0000", + "_links": { + "document": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57" + }, + "self": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57/versions/50f70cb0-03c1-47bd-b93c-bcf6287cba13" + }, + "binary": { + "href": "http://dm-store:4506/documents/5cc80e35-cdb8-4933-967e-54cef53d4a57/versions/50f70cb0-03c1-47bd-b93c-bcf6287cba13/binary" + } + } + } + ] + } + } + } + ] +} diff --git a/src/test/resources/non-working-days/nwd-empty-file.dat b/src/test/resources/non-working-days/nwd-empty-file.dat new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/non-working-days/nwd-invalid.dat b/src/test/resources/non-working-days/nwd-invalid.dat new file mode 100644 index 00000000..f68ec205 --- /dev/null +++ b/src/test/resources/non-working-days/nwd-invalid.dat @@ -0,0 +1,3 @@ +2019 +I am a teapot +¯\_(ツ)_/¯ diff --git a/src/test/resources/non-working-days/nwd-valid.dat b/src/test/resources/non-working-days/nwd-valid.dat new file mode 100644 index 00000000..b8365cb6 --- /dev/null +++ b/src/test/resources/non-working-days/nwd-valid.dat @@ -0,0 +1,4 @@ +2020-12-01 +2020-12-02 +2020-12-04 +2020-12-05 diff --git a/src/test/resources/sample-resource.txt b/src/test/resources/sample-resource.txt new file mode 100644 index 00000000..53f3099a --- /dev/null +++ b/src/test/resources/sample-resource.txt @@ -0,0 +1 @@ +Sample content