diff --git a/service/src/main/java/uk/gov/hmcts/reform/fpl/model/cafcass/api/CafcassApiFeatureFlag.java b/service/src/main/java/uk/gov/hmcts/reform/fpl/model/cafcass/api/CafcassApiFeatureFlag.java new file mode 100644 index 00000000000..dffc5c512fa --- /dev/null +++ b/service/src/main/java/uk/gov/hmcts/reform/fpl/model/cafcass/api/CafcassApiFeatureFlag.java @@ -0,0 +1,15 @@ +package uk.gov.hmcts.reform.fpl.model.cafcass.api; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +@EqualsAndHashCode +public class CafcassApiFeatureFlag { + private boolean enableApi; + private List whitelist; +} diff --git a/service/src/main/java/uk/gov/hmcts/reform/fpl/service/FeatureToggleService.java b/service/src/main/java/uk/gov/hmcts/reform/fpl/service/FeatureToggleService.java index db8607acd29..34f51624709 100644 --- a/service/src/main/java/uk/gov/hmcts/reform/fpl/service/FeatureToggleService.java +++ b/service/src/main/java/uk/gov/hmcts/reform/fpl/service/FeatureToggleService.java @@ -7,8 +7,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import uk.gov.hmcts.reform.fpl.model.Court; +import uk.gov.hmcts.reform.fpl.model.cafcass.api.CafcassApiFeatureFlag; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import static org.apache.commons.lang3.ObjectUtils.isEmpty; @Service public class FeatureToggleService { @@ -100,6 +105,33 @@ public boolean isCourtNotificationEnabledForWa(Court court) { createLDUser(Map.of(COURT_CODE_KEY, LDValue.of(court.getCode()))), true); } + public boolean isCafcassAPIEnabledForCourt(Court court) { + CafcassApiFeatureFlag flag = getCafcassAPIFlag(); + + if (flag.isEnableApi()) { + if (isEmpty(flag.getWhitelist())) { + return true; + } else { + return flag.getWhitelist().stream() + .anyMatch(whiteListCode -> court.getCode().equalsIgnoreCase(whiteListCode)); + } + } + return false; + } + + public CafcassApiFeatureFlag getCafcassAPIFlag() { + LDValue flag = ldClient.jsonValueVariation("cafcass-api-court", createLDUser(), LDValue.ofNull()); + + LDValue whiteList = flag.get("whitelist"); + return CafcassApiFeatureFlag.builder() + .enableApi(flag.get("enableApi").booleanValue()) + .whitelist((!whiteList.isNull()) + ? StreamSupport.stream(whiteList.valuesAs(LDValue.Convert.String).spliterator(), false) + .collect(Collectors.toList()) + : null) + .build(); + } + private LDUser createLDUser() { return createLDUser(Map.of()); } diff --git a/service/src/main/java/uk/gov/hmcts/reform/fpl/service/cafcass/CafcassNotificationService.java b/service/src/main/java/uk/gov/hmcts/reform/fpl/service/cafcass/CafcassNotificationService.java index 6cf09074534..3406bb17d54 100644 --- a/service/src/main/java/uk/gov/hmcts/reform/fpl/service/cafcass/CafcassNotificationService.java +++ b/service/src/main/java/uk/gov/hmcts/reform/fpl/service/cafcass/CafcassNotificationService.java @@ -89,6 +89,13 @@ public void sendEmail(CaseData caseData, Set documentReferences, CafcassRequestEmailContentProvider provider, CafcassData cafcassData) { + if (featureToggleService.isCafcassAPIEnabledForCourt(caseData.getCourt())) { + log.info("For case id: {} Cafcass API is enabled, skip notifying Cafcass for: {}", + caseData.getId(), + provider.name()); + return; + } + log.info("For case id: {} notifying Cafcass for: {}", caseData.getId(), provider.name()); diff --git a/service/src/main/java/uk/gov/hmcts/reform/fpl/service/cafcass/api/CafcassApiSearchCaseService.java b/service/src/main/java/uk/gov/hmcts/reform/fpl/service/cafcass/api/CafcassApiSearchCaseService.java index d4385282f8a..18c21f40f4e 100644 --- a/service/src/main/java/uk/gov/hmcts/reform/fpl/service/cafcass/api/CafcassApiSearchCaseService.java +++ b/service/src/main/java/uk/gov/hmcts/reform/fpl/service/cafcass/api/CafcassApiSearchCaseService.java @@ -8,17 +8,23 @@ import uk.gov.hmcts.reform.fpl.model.CaseData; import uk.gov.hmcts.reform.fpl.model.cafcass.api.CafcassApiCase; import uk.gov.hmcts.reform.fpl.model.cafcass.api.CafcassApiCaseData; +import uk.gov.hmcts.reform.fpl.model.cafcass.api.CafcassApiFeatureFlag; import uk.gov.hmcts.reform.fpl.service.CaseConverter; +import uk.gov.hmcts.reform.fpl.service.FeatureToggleService; import uk.gov.hmcts.reform.fpl.service.search.SearchService; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.BooleanQuery; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.Filter; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.MatchQuery; +import uk.gov.hmcts.reform.fpl.utils.elasticsearch.Must; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.MustNot; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.RangeQuery; +import uk.gov.hmcts.reform.fpl.utils.elasticsearch.TermsQuery; import java.time.LocalDateTime; import java.util.List; +import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; + @Slf4j @Service @RequiredArgsConstructor(onConstructor = @__(@Autowired)) @@ -33,25 +39,37 @@ public class CafcassApiSearchCaseService { private final CaseConverter caseConverter; private final SearchService searchService; private final List cafcassApiCaseDataConverters; + private final FeatureToggleService featureToggleService; public List searchCaseByDateRange(LocalDateTime startDate, LocalDateTime endDate) { - final RangeQuery searchRange = RangeQuery.builder() - .field("last_modified") - .greaterThanOrEqual(startDate) - .lessThanOrEqual(endDate).build(); + CafcassApiFeatureFlag flag = featureToggleService.getCafcassAPIFlag(); - final BooleanQuery searchCaseQuery = BooleanQuery.builder() - .mustNot(CASE_STATES) - .filter(Filter.builder() - .clauses(List.of(searchRange)) - .build()) - .build(); + if (flag.isEnableApi()) { + final RangeQuery searchRange = RangeQuery.builder() + .field("last_modified") + .greaterThanOrEqual(startDate) + .lessThanOrEqual(endDate).build(); + + final BooleanQuery.BooleanQueryBuilder searchCaseQuery = BooleanQuery.builder() + .mustNot(CASE_STATES) + .filter(Filter.builder() + .clauses(List.of(searchRange)) + .build()); - List caseDetails = searchService.search(searchCaseQuery, 10000, 0); + if (isNotEmpty(flag.getWhitelist())) { + searchCaseQuery.must(Must.builder() + .clauses(List.of(TermsQuery.of("data.court.code", flag.getWhitelist()))) + .build()); + } - return caseDetails.stream() - .map(this::convertToCafcassApiCase) - .toList(); + List caseDetails = searchService.search(searchCaseQuery.build(), 10000, 0); + + return caseDetails.stream() + .map(this::convertToCafcassApiCase) + .toList(); + } else { + return List.of(); + } } private CafcassApiCase convertToCafcassApiCase(CaseDetails caseDetails) { diff --git a/service/src/test/java/uk/gov/hmcts/reform/fpl/service/FeatureToggleServiceTest.java b/service/src/test/java/uk/gov/hmcts/reform/fpl/service/FeatureToggleServiceTest.java index 0cc1706bae8..930a5c1f27d 100644 --- a/service/src/test/java/uk/gov/hmcts/reform/fpl/service/FeatureToggleServiceTest.java +++ b/service/src/test/java/uk/gov/hmcts/reform/fpl/service/FeatureToggleServiceTest.java @@ -1,14 +1,18 @@ package uk.gov.hmcts.reform.fpl.service; import com.launchdarkly.sdk.LDUser; +import com.launchdarkly.sdk.LDValue; import com.launchdarkly.sdk.UserAttribute; import com.launchdarkly.sdk.server.LDClient; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import uk.gov.hmcts.reform.fpl.model.Court; +import uk.gov.hmcts.reform.fpl.model.cafcass.api.CafcassApiFeatureFlag; import java.util.ArrayList; import java.util.Arrays; @@ -172,6 +176,95 @@ void shouldMakeCorrectCallForCourtNotificationEnabledForWa(Boolean toggleState) eq(true)); } + @Nested + class CafcassAPIFeatureFlag { + private static final LDValue CAFCASS_API_ENABLED = + LDValue.parse("{\"enableApi\":true}"); + private static final LDValue CAFCASS_API_DISABLED = + LDValue.parse("{\"enableApi\":false}"); + private static final LDValue CAFCASS_API_WHITELISTED = + LDValue.parse("{\"enableApi\": true, \"whitelist\": [\"151\", \"111\"]}"); + + @Test + void shouldReturnCafcassApiFeatureFlagIfEnabled() { + when(ldClient.jsonValueVariation(any(), any(), any())) + .thenReturn(CAFCASS_API_ENABLED); + + assertThat(service.getCafcassAPIFlag()) + .isEqualTo(CafcassApiFeatureFlag.builder().enableApi(true).build()); + + verify(ldClient).jsonValueVariation( + eq("cafcass-api-court"), + argThat(ldUser(ENVIRONMENT).build()), + eq(LDValue.ofNull())); + } + + @Test + void shouldReturnCafcassApiFeatureFlagIfDisabled() { + when(ldClient.jsonValueVariation(eq("cafcass-api-court"), any(), any())) + .thenReturn(CAFCASS_API_DISABLED); + + assertThat(service.getCafcassAPIFlag()) + .isEqualTo(CafcassApiFeatureFlag.builder().enableApi(false).build()); + + verify(ldClient).jsonValueVariation( + eq("cafcass-api-court"), + argThat(ldUser(ENVIRONMENT).build()), + eq(LDValue.ofNull())); + } + + @Test + void shouldReturnCafcassApiFeatureFlagIfWhiteListed() { + when(ldClient.jsonValueVariation(eq("cafcass-api-court"), any(), any())) + .thenReturn(CAFCASS_API_WHITELISTED); + + assertThat(service.getCafcassAPIFlag()) + .isEqualTo(CafcassApiFeatureFlag.builder().enableApi(true) + .whitelist(List.of("151", "111")).build()); + + verify(ldClient).jsonValueVariation( + eq("cafcass-api-court"), + argThat(ldUser(ENVIRONMENT).build()), + eq(LDValue.ofNull())); + } + + @Test + void shouldReturnTrueIfCourtInTheWhitelist() { + when(ldClient.jsonValueVariation(eq("cafcass-api-court"), any(), any())) + .thenReturn(CAFCASS_API_WHITELISTED); + + assertThat(service.isCafcassAPIEnabledForCourt(Court.builder().code("151").build())) + .isEqualTo(true); + } + + @Test + void shouldReturnTrueIfEnabledToAllCourt() { + when(ldClient.jsonValueVariation(eq("cafcass-api-court"), any(), any())) + .thenReturn(CAFCASS_API_ENABLED); + + assertThat(service.isCafcassAPIEnabledForCourt(Court.builder().code("151").build())) + .isEqualTo(true); + } + + @Test + void shouldReturnFalseIfCourtNotInTheWhitelist() { + when(ldClient.jsonValueVariation(eq("cafcass-api-court"), any(), any())) + .thenReturn(CAFCASS_API_WHITELISTED); + + assertThat(service.isCafcassAPIEnabledForCourt(Court.builder().code("987").build())) + .isEqualTo(false); + } + + @Test + void shouldReturnFalseIfAPIDisabled() { + when(ldClient.jsonValueVariation(eq("cafcass-api-court"), any(), any())) + .thenReturn(CAFCASS_API_DISABLED); + + assertThat(service.isCafcassAPIEnabledForCourt(Court.builder().code("151").build())) + .isEqualTo(false); + } + } + private static List buildAttributes(String... additionalAttributes) { List attributes = new ArrayList<>(); diff --git a/service/src/test/java/uk/gov/hmcts/reform/fpl/service/cafcass/CafcassNotificationServiceTest.java b/service/src/test/java/uk/gov/hmcts/reform/fpl/service/cafcass/CafcassNotificationServiceTest.java index 4743066d6c1..77936a7d4b8 100644 --- a/service/src/test/java/uk/gov/hmcts/reform/fpl/service/cafcass/CafcassNotificationServiceTest.java +++ b/service/src/test/java/uk/gov/hmcts/reform/fpl/service/cafcass/CafcassNotificationServiceTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.mockito.quality.Strictness.LENIENT; import static uk.gov.hmcts.reform.fpl.enums.HearingType.CASE_MANAGEMENT; @@ -156,6 +157,7 @@ void setUp() { entry("newApplication", "APPLICATION") )); + when(featureToggleService.isCafcassAPIEnabledForCourt(any())).thenReturn(false); } @ParameterizedTest @@ -762,6 +764,26 @@ void shouldNotifyWithAttachmentAndLinkWhenThereIsSmallAndLargeDocs(boolean flag, largeDocMessage)); } + @Test + void shouldNotNotifyIfAPIEnabled() { + when(featureToggleService.isCafcassAPIEnabledForCourt(any())).thenReturn(true); + + underTest.sendEmail(caseData, + of(getDocumentReference()), + COURT_BUNDLE, + CourtBundleData.builder() + .hearingDetails(TITLE) + .build() + ); + + underTest.sendEmail(caseData, + CHANGE_OF_ADDRESS, + ChangeOfAddressData.builder().children(true).build() + ); + + verifyNoInteractions(emailService); + } + private DocumentReference getDocumentReference() { return DocumentReference.builder().binaryUrl(DOCUMENT_BINARY_URL) .url(DOCUMENT_URL) diff --git a/service/src/test/java/uk/gov/hmcts/reform/fpl/service/cafcass/api/CafcassApiSearchCaseServiceTest.java b/service/src/test/java/uk/gov/hmcts/reform/fpl/service/cafcass/api/CafcassApiSearchCaseServiceTest.java index 984b36163a0..0282e60c626 100644 --- a/service/src/test/java/uk/gov/hmcts/reform/fpl/service/cafcass/api/CafcassApiSearchCaseServiceTest.java +++ b/service/src/test/java/uk/gov/hmcts/reform/fpl/service/cafcass/api/CafcassApiSearchCaseServiceTest.java @@ -13,14 +13,18 @@ import uk.gov.hmcts.reform.fpl.model.CaseData; import uk.gov.hmcts.reform.fpl.model.cafcass.api.CafcassApiCase; import uk.gov.hmcts.reform.fpl.model.cafcass.api.CafcassApiCaseData; +import uk.gov.hmcts.reform.fpl.model.cafcass.api.CafcassApiFeatureFlag; import uk.gov.hmcts.reform.fpl.service.CaseConverter; +import uk.gov.hmcts.reform.fpl.service.FeatureToggleService; import uk.gov.hmcts.reform.fpl.service.search.SearchService; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.BooleanQuery; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.ESQuery; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.Filter; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.MatchQuery; +import uk.gov.hmcts.reform.fpl.utils.elasticsearch.Must; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.MustNot; import uk.gov.hmcts.reform.fpl.utils.elasticsearch.RangeQuery; +import uk.gov.hmcts.reform.fpl.utils.elasticsearch.TermsQuery; import java.time.LocalDateTime; import java.util.List; @@ -65,6 +69,21 @@ public class CafcassApiSearchCaseServiceTest { private static final CafcassApiCaseData MOCK_CONVERTED_CAFCASSAPICASEDATA = mock(CafcassApiCaseData.class); + private static final CafcassApiCase EXPECTED_CAFCASS_CASE_1 = CafcassApiCase.builder() + .caseId(1L) + .jurisdiction(CaseDefinitionConstants.JURISDICTION) + .state(State.CASE_MANAGEMENT.getValue()) + .caseTypeId(CaseDefinitionConstants.CASE_TYPE) + .createdDate(MOCK_CASE_DETAILS_1.getCreatedDate()) + .lastModified(MOCK_CASE_DETAILS_1.getLastModified()) + .caseData(MOCK_CONVERTED_CAFCASSAPICASEDATA) + .build(); + + private static final CafcassApiCase EXPECTED_CAFCASS_CASE_2 = EXPECTED_CAFCASS_CASE_1.toBuilder() + .caseId(2L) + .caseData(MOCK_CONVERTED_CAFCASSAPICASEDATA) + .build(); + @Mock private CaseConverter caseConverter; @Mock @@ -75,6 +94,8 @@ public class CafcassApiSearchCaseServiceTest { private CafcassApiCaseDataConverter cafcassApiCaseDataConverter2; @Mock private CafcassApiCaseDataConverter cafcassApiCaseDataConverter3; + @Mock + private FeatureToggleService featureToggleService; @Captor private ArgumentCaptor searchQueryCaptor; @@ -82,8 +103,11 @@ public class CafcassApiSearchCaseServiceTest { @BeforeEach void setUpWithMockConverters() { + when(featureToggleService.getCafcassAPIFlag()) + .thenReturn(CafcassApiFeatureFlag.builder().enableApi(true).build()); underTest = new CafcassApiSearchCaseService(caseConverter, searchService, - List.of(cafcassApiCaseDataConverter1, cafcassApiCaseDataConverter2, cafcassApiCaseDataConverter3)); + List.of(cafcassApiCaseDataConverter1, cafcassApiCaseDataConverter2, cafcassApiCaseDataConverter3), + featureToggleService); } @Test @@ -100,23 +124,8 @@ void shouldReturnSearchResultAndBuildCafcassApiCaseByInvokingAllConverter() { when(caseConverter.convert(MOCK_CASE_DETAILS_1)).thenReturn(MOCK_CASE_DATA_1); when(caseConverter.convert(MOCK_CASE_DETAILS_2)).thenReturn(MOCK_CASE_DATA_2); - - CafcassApiCase expectedCafcassApiCase1 = CafcassApiCase.builder() - .caseId(1L) - .jurisdiction(CaseDefinitionConstants.JURISDICTION) - .state(State.CASE_MANAGEMENT.getValue()) - .caseTypeId(CaseDefinitionConstants.CASE_TYPE) - .createdDate(MOCK_CASE_DETAILS_1.getCreatedDate()) - .lastModified(MOCK_CASE_DETAILS_1.getLastModified()) - .caseData(MOCK_CONVERTED_CAFCASSAPICASEDATA) - .build(); - CafcassApiCase expectedCafcassApiCase2 = expectedCafcassApiCase1.toBuilder() - .caseId(2L) - .caseData(MOCK_CONVERTED_CAFCASSAPICASEDATA) - .build(); - List actual = underTest.searchCaseByDateRange(SEARCH_START_DATE, SEARCH_END_DATE); - List expected = List.of(expectedCafcassApiCase1, expectedCafcassApiCase2); + List expected = List.of(EXPECTED_CAFCASS_CASE_1, EXPECTED_CAFCASS_CASE_2); assertEquals(expected, actual); assertEquals(SEARCH_QUERY.toMap(), searchQueryCaptor.getValue().toMap()); @@ -140,4 +149,56 @@ void shouldReturnEmptyListIfNoCaseFound() { assertEquals(List.of(), actual); } + + @Test + void shouldReturnEmptyListIfFeatureToggleDisabled() { + when(featureToggleService.getCafcassAPIFlag()).thenReturn(CafcassApiFeatureFlag.builder() + .enableApi(false).build()); + + List actual = underTest.searchCaseByDateRange(SEARCH_START_DATE, SEARCH_END_DATE); + + assertEquals(List.of(), actual); + + } + + @Test + void shouldFilterCaseByCourtIfFeatureToggleEnabledWithWhiteList() { + final CafcassApiCaseData.CafcassApiCaseDataBuilder mockBuilder = + mock(CafcassApiCaseData.CafcassApiCaseDataBuilder.class); + when(mockBuilder.build()).thenReturn(MOCK_CONVERTED_CAFCASSAPICASEDATA); + when(cafcassApiCaseDataConverter1.convert(any(), any())).thenReturn(mockBuilder); + when(cafcassApiCaseDataConverter2.convert(any(), any())).thenReturn(mockBuilder); + when(cafcassApiCaseDataConverter3.convert(any(), any())).thenReturn(mockBuilder); + + when(featureToggleService.getCafcassAPIFlag()).thenReturn(CafcassApiFeatureFlag.builder() + .enableApi(true).whitelist(List.of("123", "321")).build()); + + final List caseDetails = List.of(MOCK_CASE_DETAILS_1, MOCK_CASE_DETAILS_2); + when(searchService.search(searchQueryCaptor.capture(), anyInt(), anyInt())).thenReturn(caseDetails); + when(caseConverter.convert(MOCK_CASE_DETAILS_1)).thenReturn(MOCK_CASE_DATA_1); + when(caseConverter.convert(MOCK_CASE_DETAILS_2)).thenReturn(MOCK_CASE_DATA_2); + + List actual = underTest.searchCaseByDateRange(SEARCH_START_DATE, SEARCH_END_DATE); + List expected = List.of(EXPECTED_CAFCASS_CASE_1, EXPECTED_CAFCASS_CASE_2); + + BooleanQuery expectedSearchQuery = BooleanQuery.builder() + .mustNot(MustNot.builder() + .clauses(List.of( + MatchQuery.of("state", "Open"), + MatchQuery.of("state", "Deleted"), + MatchQuery.of("state", "RETURNED"))) + .build()) + .filter(Filter.builder() + .clauses(List.of(RangeQuery.builder().field("last_modified") + .greaterThanOrEqual(SEARCH_START_DATE).lessThanOrEqual(SEARCH_END_DATE).build())) + .build()) + .must(Must.builder() + .clauses(List.of( + TermsQuery.of("data.court.code", List.of("123", "321")))) + .build()) + .build(); + + assertEquals(expected, actual); + assertEquals(expectedSearchQuery.toMap(), searchQueryCaptor.getValue().toMap()); + } }