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