Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
CCD-5867 use globalsearch when ?global=true
  • Loading branch information
markdathornehmcts committed Oct 14, 2025
commit 1738aa424b239afb13645ea0d97f66c3d6b2de24
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.CaseSearchOperation;
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.CrossCaseTypeSearchRequest;
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.ElasticsearchQueryHelper;
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.SearchIndex;
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.security.AuthorisedCaseSearchOperation;
import uk.gov.hmcts.ccd.endpoint.exceptions.BadRequestException;

Expand All @@ -45,6 +46,7 @@ public class CaseSearchEndpoint {

private final CaseSearchOperation caseSearchOperation;
private final ElasticsearchQueryHelper elasticsearchQueryHelper;
private final ApplicationParams applicationParams;

@Autowired
public CaseSearchEndpoint(@Qualifier(AuthorisedCaseSearchOperation.QUALIFIER)
Expand All @@ -54,6 +56,14 @@ public CaseSearchEndpoint(@Qualifier(AuthorisedCaseSearchOperation.QUALIFIER)
ApplicationParams applicationParams) {
this.caseSearchOperation = caseSearchOperation;
this.elasticsearchQueryHelper = elasticsearchQueryHelper;
this.applicationParams = applicationParams;
}

@Deprecated
public CaseSearchResult searchCases(List<String> caseTypeIds,
String jsonSearchRequest,
boolean dataClassification) {
return searchCases(caseTypeIds, jsonSearchRequest, dataClassification, false);
}

@PostMapping(value = "/searchCases")
Expand All @@ -74,9 +84,10 @@ public CaseSearchResult searchCases(
+ " \"_source\":[\"alias.customer\",\"alias.postcode\"]",
required = true)
@RequestBody String jsonSearchRequest,
@RequestParam(value = "data_classification", defaultValue = "true") boolean dataClassification) {
@RequestParam(value = "data_classification", defaultValue = "true") boolean dataClassification,
@RequestParam(value = "global", required = false, defaultValue = "false") boolean global) {

Instant start = Instant.now();
final Instant start = Instant.now();
validateCtid(caseTypeIds);

ElasticsearchRequest elasticsearchRequest =
Expand All @@ -86,10 +97,7 @@ public CaseSearchResult searchCases(
elasticsearchRequest.setRequestedSupplementaryData(ElasticsearchRequest.WILDCARD);
}

CrossCaseTypeSearchRequest request = new CrossCaseTypeSearchRequest.Builder()
.withCaseTypes(getCaseTypeIds(caseTypeIds))
.withSearchRequest(elasticsearchRequest)
.build();
CrossCaseTypeSearchRequest request = buildCrossCaseTypeSearchRequest(caseTypeIds, elasticsearchRequest, global);

CaseSearchResult result = caseSearchOperation.execute(request, dataClassification);

Expand All @@ -98,6 +106,25 @@ public CaseSearchResult searchCases(
return result;
}

private CrossCaseTypeSearchRequest buildCrossCaseTypeSearchRequest(List<String> caseTypeIds,
ElasticsearchRequest elasticsearchRequest, boolean global) {

CrossCaseTypeSearchRequest.Builder builder = new CrossCaseTypeSearchRequest.Builder()
.withCaseTypes(getCaseTypeIds(caseTypeIds))
.withSearchRequest(elasticsearchRequest);

if (global) {
SearchIndex searchIndex = new SearchIndex(
applicationParams.getGlobalSearchIndexName(),
applicationParams.getGlobalSearchIndexType()
);
builder.withSearchIndex(searchIndex);
log.info("pointing to global search index...");
}

return builder.build();
}

private List<String> getCaseTypeIds(List<String> caseTypeIds) {
if (isAllCaseTypesRequest(caseTypeIds)) {
return elasticsearchQueryHelper.getCaseTypesAvailableToUser();
Expand Down
45 changes: 42 additions & 3 deletions src/test/java/uk/gov/hmcts/ccd/ElasticsearchIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,32 @@ void shouldReturnAllCaseDetailsForDefaultUseCase() throws Exception {
);
}

@Test
void shouldReturnAllCaseDetailsForDefaultUseCaseFromGlobalIndex() throws Exception {
ElasticsearchTestRequest searchRequest = ElasticsearchTestRequest.builder()
.query(boolQuery()
.must(matchQuery(caseData(NUMBER_FIELD), NUMBER_VALUE)) // ES Double
.must(matchQuery(caseData(YES_OR_NO_FIELD), YES_OR_NO_VALUE)) // ES Keyword
.must(matchQuery(caseData(TEXT_FIELD), TEXT_VALUE)) // ES Text
.must(matchQuery(caseData(DATE_FIELD), DATE_VALUE)) // ES Date
.must(matchQuery(caseData(PHONE_FIELD), PHONE_VALUE)) // ES Phone
.must(matchQuery(caseData(COUNTRY_FIELD), ElasticsearchTestHelper.COUNTRY_VALUE)) // Complex
.must(matchQuery(caseData(COLLECTION_FIELD) + VALUE_SUFFIX, COLLECTION_VALUE)) // Collection
.must(matchQuery(STATE, STATE_VALUE))) // Metadata
.build();

CaseSearchResultViewResource caseSearchResultViewResource = executeRequest(searchRequest, CASE_TYPE_A,
null, true);

SearchResultViewItem caseDetails = caseSearchResultViewResource.getCases().getFirst();
assertAll(
() -> assertThat(caseSearchResultViewResource.getTotal(), is(1L)),
() -> assertThat(caseSearchResultViewResource.getCases().size(), is(1)),
() -> assertExampleCaseData(caseDetails.getFields(), false),
() -> assertExampleCaseMetadata(caseDetails.getFields(), false)
);
}

@Test
void shouldReturnAllCaseDetailsForPhoneValueWithSpace() throws Exception {
ElasticsearchTestRequest searchRequest = ElasticsearchTestRequest.builder()
Expand Down Expand Up @@ -420,7 +446,6 @@ void shouldReturnAllHeaderInfoForDefaultUseCaseWhenUserHasSomeAuthorisationOnCas
);
}


@Test
void shouldReturnAllHeaderInfoForDefaultUseCaseWhenUseHaveNoAuthorisationOnCaseField() throws Exception {
ElasticsearchTestRequest searchRequest = caseReferenceRequest(DEFAULT_CASE_REFERENCE);
Expand Down Expand Up @@ -814,8 +839,14 @@ private List<Map<String, Object>> asCollection(Object obj) {

private CaseSearchResultViewResource executeRequest(ElasticsearchTestRequest searchRequest,
String caseTypeParam, String useCase) throws Exception {
return executeRequest(searchRequest, caseTypeParam, useCase, false);
}

private CaseSearchResultViewResource executeRequest(ElasticsearchTestRequest searchRequest,
String caseTypeParam, String useCase, boolean global)
throws Exception {
MockHttpServletRequestBuilder postRequest =
createPostRequest(POST_SEARCH_CASES, searchRequest, caseTypeParam, useCase);
createPostRequest(POST_SEARCH_CASES, searchRequest, caseTypeParam, useCase, global);

return ElasticsearchTestHelper.executeRequest(postRequest, 200, mapper, mockMvc,
CaseSearchResultViewResource.class);
Expand All @@ -825,8 +856,16 @@ private JsonNode executeErrorRequest(ElasticsearchTestRequest searchRequest,
String caseTypeParam,
String useCase,
int expectedErrorCode) throws Exception {
return executeErrorRequest(searchRequest, caseTypeParam, useCase, false, expectedErrorCode);
}

private JsonNode executeErrorRequest(ElasticsearchTestRequest searchRequest,
String caseTypeParam,
String useCase,
boolean global,
int expectedErrorCode) throws Exception {
MockHttpServletRequestBuilder postRequest = createPostRequest(POST_SEARCH_CASES, searchRequest,
caseTypeParam, useCase);
caseTypeParam, useCase, global);

return ElasticsearchTestHelper.executeRequest(postRequest, expectedErrorCode, mapper, mockMvc,
JsonNode.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,41 @@ void testSearchCaseDetails() throws Exception {
equalTo(SecurityClassification.PUBLIC))));
}

@Test
void testSearchCaseDetails_usingGlobal() throws Exception {

final long referenceId = 1535450291607660L;
String caseDetailElastic = create1CaseDetailsElastic(referenceId);
stubElasticSearchSearchRequestWillReturn(caseDetailElastic);

String searchRequest = "{\"query\": {\"match_all\": {}}}";
MvcResult result = mockMvc.perform(post(POST_SEARCH_CASES)
.contentType(JSON_CONTENT_TYPE)
.param("ctid", "TestAddressBookCase")
.param("global", "true")
.content(searchRequest))
.andExpect(status().is(200))
.andReturn();

String responseAsString = result.getResponse().getContentAsString();
CaseSearchResult caseSearchResults = mapper.readValue(responseAsString,
CaseSearchResult.class);

List<CaseDetails> caseDetails = caseSearchResults.getCases();
assertThat(caseDetails, hasSize(1));
assertThat(caseDetails, hasItem(hasProperty("reference", equalTo(referenceId))));
assertThat(caseDetails, hasItem(hasProperty("jurisdiction", equalTo("PROBATE"))));
assertThat(caseDetails, hasItem(hasProperty("caseTypeId",
equalTo("TestAddressBookCase"))));
assertThat(caseDetails, hasItem(hasProperty("lastModified",
equalTo(LocalDateTime.parse("2018-08-28T09:58:11.643")))));
assertThat(caseDetails, hasItem(hasProperty("createdDate",
equalTo(LocalDateTime.parse("2018-08-28T09:58:11.627")))));
assertThat(caseDetails, hasItem(hasProperty("state", equalTo("TODO"))));
assertThat(caseDetails, hasItem(hasProperty("securityClassification",
equalTo(SecurityClassification.PUBLIC))));
}

@Test
void shouldAuditLogSearchCases() throws Exception {

Expand Down Expand Up @@ -143,6 +178,44 @@ void shouldAuditLogSearchCases() throws Exception {
assertThat(captor.getValue().getListOfCaseTypes(), is("TestAddressBookCase,TestAddressBookCase4"));
}

@Test
void shouldAuditLogSearchCases_usingGlobalIndex() throws Exception {

final long reference1 = 1535450291607660L;
final long reference2 = 1535450291607670L;
String caseDetailElastic1 = create2CaseDetailsElastic(reference1, reference2);

stubElasticSearchSearchRequestWillReturn(caseDetailElastic1);

String searchRequest = "{\"query\": {\"match_all\": {}}}";
MvcResult result = mockMvc.perform(post(POST_SEARCH_CASES)
.contentType(JSON_CONTENT_TYPE)
.param("ctid", "TestAddressBookCase", "TestAddressBookCase4")
.param("global", "true")
.content(searchRequest))
.andExpect(status().is(200))
.andReturn();

String responseAsString = result.getResponse().getContentAsString();
CaseSearchResult caseSearchResults = mapper.readValue(responseAsString,
CaseSearchResult.class);

List<CaseDetails> caseDetails = caseSearchResults.getCases();
assertThat(caseDetails, hasSize(2));
assertThat(caseDetails, hasItem(hasProperty("reference", equalTo(1535450291607660L))));
assertThat(caseDetails, hasItem(hasProperty("reference", equalTo(1535450291607670L))));

ArgumentCaptor<AuditEntry> captor = ArgumentCaptor.forClass(AuditEntry.class);
verify(auditRepository).save(captor.capture());

assertThat(captor.getValue().getOperationType(), is(AuditOperationType.SEARCH_CASE.getLabel()));
assertThat(captor.getValue().getCaseId(), is("1535450291607660,1535450291607670"));
assertThat(captor.getValue().getIdamId(), is("123"));
assertThat(captor.getValue().getInvokingService(), is(MockUtils.CCD_GW));
assertThat(captor.getValue().getHttpStatus(), is(200));
assertThat(captor.getValue().getListOfCaseTypes(), is("TestAddressBookCase,TestAddressBookCase4"));
}

private String create1CaseDetailsElastic(Long reference) {
return "{\n" +
" \"took\":177,\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import uk.gov.hmcts.ccd.ApplicationParams;
import uk.gov.hmcts.ccd.domain.model.definition.CaseDetails;
import uk.gov.hmcts.ccd.domain.model.search.CaseSearchResult;
import uk.gov.hmcts.ccd.domain.model.search.elasticsearch.ElasticsearchRequest;
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.CaseSearchOperation;
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.CrossCaseTypeSearchRequest;
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.ElasticsearchQueryHelper;
import uk.gov.hmcts.ccd.domain.service.search.elasticsearch.SearchIndex;
import uk.gov.hmcts.ccd.endpoint.exceptions.BadRequestException;

import java.util.List;
Expand Down Expand Up @@ -46,6 +48,9 @@ class CaseSearchEndpointTest {
@Mock
private ElasticsearchQueryHelper elasticsearchQueryHelper;

@Mock
private ApplicationParams applicationParams;

@InjectMocks
private CaseSearchEndpoint endpoint;

Expand Down Expand Up @@ -126,6 +131,80 @@ void searchCaseDetailsInvokesOperationWithExpandedCaseTypesForWildcard() throws
assertThat(caseSearchResult, is(result));
}

@Test
void searchCases_usesGlobalIndexWhenGlobalTrue() throws Exception {
// ARRANGE
when(applicationParams.getGlobalSearchIndexName()).thenReturn("global_index");
when(applicationParams.getGlobalSearchIndexType()).thenReturn("_doc");

String searchRequest = "{\"query\": {\"match\": {\"reference\": {\"query\": \"123\"}}}}";
JsonNode searchRequestNode = new ObjectMapper().readTree(searchRequest);
ElasticsearchRequest elasticSearchRequest = new ElasticsearchRequest(searchRequestNode);

when(elasticsearchQueryHelper.validateAndConvertRequest(any())).thenReturn(elasticSearchRequest);

CaseSearchResult expected = mock(CaseSearchResult.class);
when(caseSearchOperation.execute(any(CrossCaseTypeSearchRequest.class), anyBoolean())).thenReturn(expected);

List<String> caseTypeIds = singletonList(CASE_TYPE_ID);

// ACT
CaseSearchResult actual = endpoint.searchCases(caseTypeIds, searchRequest, true, true);

// ASSERT
verify(elasticsearchQueryHelper).validateAndConvertRequest(searchRequest);
verify(caseSearchOperation).execute(argThat(req -> {
assertThat(req.getSearchRequestJsonNode(), is(searchRequestNode));
// uses provided case type list
assertThat(req.getCaseTypeIds(), is(List.of(CASE_TYPE_ID)));
// points to global index
SearchIndex idx = req.getSearchIndex().get();
assertThat(idx.getIndexName(), is("global_index"));
assertThat(idx.getIndexType(), is("_doc"));
return true;
}), anyBoolean());
assertThat(actual, is(expected));
}

@Test
void searchCases_globalTrueAndWildcardExpandsCaseTypes() throws Exception {
// ARRANGE
when(applicationParams.getGlobalSearchIndexName()).thenReturn("global_index");
when(applicationParams.getGlobalSearchIndexType()).thenReturn("_doc");

String searchRequest = "{\"query\": {\"match_all\": {}}}";
JsonNode searchRequestNode = new ObjectMapper().readTree(searchRequest);
ElasticsearchRequest elasticSearchRequest = new ElasticsearchRequest(searchRequestNode);
when(elasticsearchQueryHelper.validateAndConvertRequest(any())).thenReturn(elasticSearchRequest);

// When wildcard passed, helper should expand to available case types
when(elasticsearchQueryHelper.getCaseTypesAvailableToUser())
.thenReturn(List.of(CASE_TYPE_ID, CASE_TYPE_ID_2));

CaseSearchResult expected = mock(CaseSearchResult.class);
when(caseSearchOperation.execute(any(CrossCaseTypeSearchRequest.class), anyBoolean())).thenReturn(expected);

List<String> wildcard = singletonList(ElasticsearchRequest.WILDCARD);

// ACT
CaseSearchResult actual = endpoint.searchCases(wildcard, searchRequest, true, true);

// ASSERT
verify(elasticsearchQueryHelper).validateAndConvertRequest(searchRequest);
verify(caseSearchOperation).execute(argThat(req -> {
assertThat(req.getSearchRequestJsonNode(), is(searchRequestNode));
// ✅ wildcard was expanded
assertThat(req.getCaseTypeIds(), is(List.of(CASE_TYPE_ID, CASE_TYPE_ID_2)));
// ✅ global index is selected
SearchIndex idx = req.getSearchIndex().get();
assertThat(idx.getIndexName(), is("global_index"));
assertThat(idx.getIndexType(), is("_doc"));
return true;
}), anyBoolean());
assertThat(actual, is(expected));
}



@Nested
@DisplayName("Build ID lists for LogAudit")
Expand Down
24 changes: 22 additions & 2 deletions src/test/java/uk/gov/hmcts/ccd/test/ElasticsearchTestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public class ElasticsearchTestHelper {
public static final String CASEWORKER_CAA = "caseworker-caa";

private static final String CASE_TYPE_ID_PARAM = "ctid";
private static final String GLOBAL_PARAM = "global";
private static final String USE_CASE_PARAM = "use_case";

private ElasticsearchTestHelper() {
Expand All @@ -134,17 +135,36 @@ public static MockHttpServletRequestBuilder createPostRequest(String url,
searchRequest,
String caseTypeParam,
String useCase) throws Exception {

return createPostRequest(url, searchRequest, caseTypeParam, useCase, false);
}

public static MockHttpServletRequestBuilder createPostRequest(String url,
ElasticsearchBaseTest.ElasticsearchTestRequest
searchRequest,
String caseTypeParam,
String useCase,
boolean global) throws Exception {
MockHttpServletRequestBuilder postRequest = post(url)
.contentType(MediaType.APPLICATION_JSON)
.content(searchRequest.toJsonString())
.param(CASE_TYPE_ID_PARAM, caseTypeParam);
LOG.info("Executing request: {} with param: {}={} and body: {}", url, CASE_TYPE_ID_PARAM, caseTypeParam,
searchRequest.toJsonString());

StringBuilder msgInfo = new StringBuilder("Executing request: ").append(url).append(" with params: ")
.append(CASE_TYPE_ID_PARAM).append("=").append(caseTypeParam);
if (!Strings.isNullOrEmpty(useCase)) {
postRequest.param(USE_CASE_PARAM, useCase);
msgInfo.append(", ").append(USE_CASE_PARAM).append("=").append(useCase);
}

if (global) {
postRequest.param(GLOBAL_PARAM, String.valueOf(global));
msgInfo.append(", ").append(GLOBAL_PARAM).append("=").append(global);
}

msgInfo.append("and body: ").append(searchRequest.toJsonString());
LOG.info(msgInfo.toString());

return postRequest;
}

Expand Down