diff --git a/openmetadata-service/pom.xml b/openmetadata-service/pom.xml index 45fc311e15c..cfbf337038d 100644 --- a/openmetadata-service/pom.xml +++ b/openmetadata-service/pom.xml @@ -335,6 +335,12 @@ 2.40 test + + org.assertj + assertj-core + 3.25.3 + test + javax.json diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java index ae0d2fdcaa4..11cb5f60741 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java @@ -409,13 +409,15 @@ public class TestCaseRepository extends EntityRepository { updateResultSummaries(testCase, isDeleted, resultSummaries, resultSummary); // Update test case result summary attribute for the test suite + TestSuiteRepository testSuiteRepository = + (TestSuiteRepository) Entity.getEntityRepository(Entity.TEST_SUITE); + TestSuite original = + TestSuiteRepository.copyTestSuite( + testSuite); // we'll need the original state to update the test suite testSuite.setTestCaseResultSummary(resultSummaries); - daoCollection - .testSuiteDAO() - .update( - testSuite.getId(), - testSuite.getFullyQualifiedName(), - JsonUtils.pojoToJson(testSuite)); + EntityRepository.EntityUpdater testSuiteUpdater = + testSuiteRepository.getUpdater(original, testSuite, Operation.PUT); + testSuiteUpdater.update(); } } @@ -652,11 +654,16 @@ public class TestCaseRepository extends EntityRepository { testSuite.setSummary(null); // we don't want to store the summary in the database List resultSummaries = testSuite.getTestCaseResultSummary(); resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(testCaseFqn)); + + TestSuiteRepository testSuiteRepository = + (TestSuiteRepository) Entity.getEntityRepository(Entity.TEST_SUITE); + TestSuite original = + TestSuiteRepository.copyTestSuite( + testSuite); // we'll need the original state to update the test suite testSuite.setTestCaseResultSummary(resultSummaries); - daoCollection - .testSuiteDAO() - .update( - testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite)); + EntityRepository.EntityUpdater testSuiteUpdater = + testSuiteRepository.getUpdater(original, testSuite, Operation.PUT); + testSuiteUpdater.update(); } @Override diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java index bbb50770542..c85e618a405 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java @@ -9,10 +9,14 @@ import static org.openmetadata.service.Entity.TEST_CASE; import static org.openmetadata.service.Entity.TEST_SUITE; import static org.openmetadata.service.util.FullyQualifiedName.quoteName; +import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonValue; import javax.ws.rs.core.SecurityContext; import lombok.extern.slf4j.Slf4j; import org.jdbi.v3.sqlobject.transaction.Transaction; @@ -23,7 +27,6 @@ import org.openmetadata.schema.tests.type.TestCaseStatus; import org.openmetadata.schema.tests.type.TestSummary; import org.openmetadata.schema.type.EntityReference; import org.openmetadata.schema.type.EventType; -import org.openmetadata.schema.type.Include; import org.openmetadata.schema.type.Relationship; import org.openmetadata.service.Entity; import org.openmetadata.service.resources.dqtests.TestSuiteResource; @@ -55,7 +58,7 @@ public class TestSuiteRepository extends EntityRepository { fields.contains("pipelines") ? getIngestionPipelines(entity) : entity.getPipelines()); entity.setSummary( fields.contains("summary") ? getTestCasesExecutionSummary(entity) : entity.getSummary()); - entity.withTests(fields.contains("tests") ? getTestCases(entity) : entity.getTests()); + entity.withTests(fields.contains(UPDATE_FIELDS) ? getTestCases(entity) : entity.getTests()); } @Override @@ -71,7 +74,7 @@ public class TestSuiteRepository extends EntityRepository { public void clearFields(TestSuite entity, EntityUtil.Fields fields) { entity.setPipelines(fields.contains("pipelines") ? entity.getPipelines() : null); entity.setSummary(fields.contains("summary") ? entity.getSummary() : null); - entity.withTests(fields.contains("tests") ? entity.getTests() : null); + entity.withTests(fields.contains(UPDATE_FIELDS) ? entity.getTests() : null); } private TestSummary buildTestSummary(Map testCaseSummary) { @@ -117,31 +120,81 @@ public class TestSuiteRepository extends EntityRepository { return buildTestSummary(testCaseSummary); } - private TestSummary getTestCasesExecutionSummary(List entities) { - if (entities.isEmpty()) return new TestSummary(); - Map testsSummary = new HashMap<>(); - for (TestSuite testSuite : entities) { - Map testSummary = getResultSummary(testSuite); - for (Map.Entry entry : testSummary.entrySet()) { - testsSummary.put( - entry.getKey(), testsSummary.getOrDefault(entry.getKey(), 0) + entry.getValue()); + private TestSummary getTestCasesExecutionSummary(JsonObject aggregation) { + // Initialize the test summary with 0 values + TestSummary testSummary = + new TestSummary().withAborted(0).withFailed(0).withSuccess(0).withQueued(0).withTotal(0); + JsonObject summary = aggregation.getJsonObject("nested#testCaseResultSummary"); + testSummary.setTotal(summary.getJsonNumber("doc_count").intValue()); + + JsonObject statusCount = summary.getJsonObject("sterms#status_counts"); + JsonArray buckets = statusCount.getJsonArray("buckets"); + + for (JsonValue bucket : buckets) { + String key = ((JsonObject) bucket).getString("key"); + Integer count = ((JsonObject) bucket).getJsonNumber("doc_count").intValue(); + switch (key) { + case "Success": + testSummary.setSuccess(count); + break; + case "Failed": + testSummary.setFailed(count); + break; + case "Aborted": + testSummary.setAborted(count); + break; + case "Queued": + testSummary.setQueued(count); + break; } - testSuite.getTestCaseResultSummary().size(); } - return buildTestSummary(testsSummary); + return testSummary; } - public TestSummary getTestSummary(UUID testSuiteId) { + public TestSummary getTestSummary(UUID testSuiteId) throws IOException { + String aggregationQuery = + """ + { + "aggregations": { + "test_case_results": { + "nested": { + "path": "testCaseResultSummary" + }, + "aggs": { + "status_counts": { + "terms": { + "field": "testCaseResultSummary.status" + } + } + } + } + } + } + """; + JsonObject aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject(); TestSummary testSummary; if (testSuiteId == null) { - ListFilter filter = new ListFilter(); - filter.addQueryParam("testSuiteType", "executable"); - List testSuites = listAll(EntityUtil.Fields.EMPTY_FIELDS, filter); - testSummary = getTestCasesExecutionSummary(testSuites); + JsonObject testCaseResultSummary = + searchRepository.aggregate(null, TEST_SUITE, aggregationJson); + testSummary = getTestCasesExecutionSummary(testCaseResultSummary); } else { + String query = + """ + { + "query": { + "bool": { + "must": { + "term": {"id": "%s"} + } + } + } + } + """ + .formatted(testSuiteId); // don't want to get it from the cache as test results summary may be stale - TestSuite testSuite = Entity.getEntity(TEST_SUITE, testSuiteId, "", Include.ALL, false); - testSummary = getTestCasesExecutionSummary(testSuite); + JsonObject testCaseResultSummary = + searchRepository.aggregate(query, TEST_SUITE, aggregationJson); + testSummary = getTestCasesExecutionSummary(testCaseResultSummary); } return testSummary; } @@ -211,6 +264,26 @@ public class TestSuiteRepository extends EntityRepository { return new RestUtil.DeleteResponse<>(updated, changeType); } + public static TestSuite copyTestSuite(TestSuite testSuite) { + return new TestSuite() + .withConnection(testSuite.getConnection()) + .withDescription(testSuite.getDescription()) + .withChangeDescription(testSuite.getChangeDescription()) + .withDeleted(testSuite.getDeleted()) + .withDisplayName(testSuite.getDisplayName()) + .withFullyQualifiedName(testSuite.getFullyQualifiedName()) + .withHref(testSuite.getHref()) + .withId(testSuite.getId()) + .withName(testSuite.getName()) + .withExecutable(testSuite.getExecutable()) + .withExecutableEntityReference(testSuite.getExecutableEntityReference()) + .withServiceType(testSuite.getServiceType()) + .withOwner(testSuite.getOwner()) + .withUpdatedBy(testSuite.getUpdatedBy()) + .withUpdatedAt(testSuite.getUpdatedAt()) + .withVersion(testSuite.getVersion()); + } + public class TestSuiteUpdater extends EntityUpdater { public TestSuiteUpdater(TestSuite original, TestSuite updated, Operation operation) { super(original, updated, operation); @@ -221,7 +294,13 @@ public class TestSuiteRepository extends EntityRepository { public void entitySpecificUpdate() { List origTests = listOrEmpty(original.getTests()); List updatedTests = listOrEmpty(updated.getTests()); - recordChange("tests", origTests, updatedTests); + List origTestCaseResultSummary = + listOrEmpty(original.getTestCaseResultSummary()); + List updatedTestCaseResultSummary = + listOrEmpty(updated.getTestCaseResultSummary()); + recordChange(UPDATE_FIELDS, origTests, updatedTests); + recordChange( + "testCaseResultSummary", origTestCaseResultSummary, updatedTestCaseResultSummary); } } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java index 7bf403ae1fe..553577e9b4c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestSuiteResource.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import java.io.IOException; import java.util.List; import java.util.UUID; import javax.json.JsonPatch; @@ -321,7 +322,8 @@ public class TestSuiteResource extends EntityResource resourceContext = getResourceContext(); OperationContext operationContext = new OperationContext(Entity.TABLE, MetadataOperation.VIEW_TESTS); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchClient.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchClient.java index d67843c3f5a..b6ffee7c7ad 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchClient.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchClient.java @@ -8,6 +8,7 @@ import java.text.ParseException; import java.util.List; import java.util.Map; import java.util.TreeMap; +import javax.json.JsonObject; import javax.net.ssl.SSLContext; import javax.ws.rs.core.Response; import org.apache.commons.lang3.tuple.Pair; @@ -87,6 +88,8 @@ public interface SearchClient { Response aggregate(String index, String fieldName, String value, String query) throws IOException; + JsonObject aggregate(String query, String index, JsonObject aggregationJson) throws IOException; + Response suggest(SearchRequest request) throws IOException; void createEntity(String indexName, String docId, String doc); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java index 36d22d87787..353d93f5133 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java @@ -669,6 +669,11 @@ public class SearchRepository { return searchClient.aggregate(index, fieldName, value, query); } + public JsonObject aggregate(String query, String index, JsonObject aggregationJson) + throws IOException { + return searchClient.aggregate(query, index, aggregationJson); + } + public Response suggest(SearchRequest request) throws IOException { return searchClient.suggest(request); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchClient.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchClient.java index 4d7bc8cb68c..e8bd90066bb 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchClient.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchClient.java @@ -105,6 +105,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import javax.json.JsonObject; import javax.net.ssl.SSLContext; import javax.ws.rs.core.Response; import lombok.extern.slf4j.Slf4j; @@ -704,6 +705,82 @@ public class ElasticSearchClient implements SearchClient { return Response.status(OK).entity(response).build(); } + /* + Build dynamic aggregation from elasticsearch JSON like aggregation query. + See TestSuiteResourceTest for example usage (ln. 506) for tested aggregation query. + + @param aggregations - JsonObject containing the aggregation query + */ + public static List buildAggregation(JsonObject aggregations) { + List aggregationBuilders = new ArrayList<>(); + for (String key : aggregations.keySet()) { + JsonObject aggregation = aggregations.getJsonObject(key); + for (String aggregationType : aggregation.keySet()) { + switch (aggregationType) { + case "terms": + JsonObject termAggregation = aggregation.getJsonObject(aggregationType); + TermsAggregationBuilder termsAggregationBuilder = + AggregationBuilders.terms(key).field(termAggregation.getString("field")); + aggregationBuilders.add(termsAggregationBuilder); + break; + case "nested": + JsonObject nestedAggregation = aggregation.getJsonObject("nested"); + AggregationBuilder nestedAggregationBuilder = + AggregationBuilders.nested( + nestedAggregation.getString("path"), nestedAggregation.getString("path")); + JsonObject nestedAggregations = aggregation.getJsonObject("aggs"); + + List nestedAggregationBuilders = + buildAggregation(nestedAggregations); + for (AggregationBuilder nestedAggregationBuilder1 : nestedAggregationBuilders) { + nestedAggregationBuilder.subAggregation(nestedAggregationBuilder1); + } + aggregationBuilders.add(nestedAggregationBuilder); + break; + default: + break; + } + } + } + return aggregationBuilders; + } + + @Override + public JsonObject aggregate(String query, String index, JsonObject aggregationJson) + throws IOException { + JsonObject aggregations = aggregationJson.getJsonObject("aggregations"); + if (aggregations == null) { + return null; + } + + List aggregationBuilder = buildAggregation(aggregations); + es.org.elasticsearch.action.search.SearchRequest searchRequest = + new es.org.elasticsearch.action.search.SearchRequest( + Entity.getSearchRepository().getIndexOrAliasName(index)); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + if (query != null) { + XContentParser queryParser = + XContentType.JSON + .xContent() + .createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, query); + QueryBuilder parsedQuery = SearchSourceBuilder.fromXContent(queryParser).query(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(parsedQuery); + searchSourceBuilder.query(boolQueryBuilder); + } + + searchSourceBuilder.size(0).timeout(new TimeValue(30, TimeUnit.SECONDS)); + + for (AggregationBuilder aggregation : aggregationBuilder) { + searchSourceBuilder.aggregation(aggregation); + } + + searchRequest.source(searchSourceBuilder); + + String response = client.search(searchRequest, RequestOptions.DEFAULT).toString(); + JsonObject jsonResponse = JsonUtils.readJson(response).asJsonObject(); + return jsonResponse.getJsonObject("aggregations"); + } + private static ScriptScoreFunctionBuilder boostScore() { return ScoreFunctionBuilders.scriptFunction( "double score = _score;" diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchClient.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchClient.java index 6cb2fa66a0b..36ed01b4d73 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchClient.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchClient.java @@ -37,6 +37,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import javax.json.JsonObject; import javax.net.ssl.SSLContext; import javax.ws.rs.core.Response; import lombok.extern.slf4j.Slf4j; @@ -724,6 +725,82 @@ public class OpenSearchClient implements SearchClient { return Response.status(OK).entity(response).build(); } + /* + Build dynamic aggregation from elasticsearch JSON like aggregation query. + See TestSuiteResourceTest for example usage (ln. 506) for tested aggregation query. + + @param aggregations - JsonObject containing the aggregation query + */ + public static List buildAggregation(JsonObject aggregations) { + List aggregationBuilders = new ArrayList<>(); + for (String key : aggregations.keySet()) { + JsonObject aggregation = aggregations.getJsonObject(key); + for (String aggregationType : aggregation.keySet()) { + switch (aggregationType) { + case "terms": + JsonObject termAggregation = aggregation.getJsonObject(aggregationType); + TermsAggregationBuilder termsAggregationBuilder = + AggregationBuilders.terms(key).field(termAggregation.getString("field")); + aggregationBuilders.add(termsAggregationBuilder); + break; + case "nested": + JsonObject nestedAggregation = aggregation.getJsonObject("nested"); + AggregationBuilder nestedAggregationBuilder = + AggregationBuilders.nested( + nestedAggregation.getString("path"), nestedAggregation.getString("path")); + JsonObject nestedAggregations = aggregation.getJsonObject("aggs"); + + List nestedAggregationBuilders = + buildAggregation(nestedAggregations); + for (AggregationBuilder nestedAggregationBuilder1 : nestedAggregationBuilders) { + nestedAggregationBuilder.subAggregation(nestedAggregationBuilder1); + } + aggregationBuilders.add(nestedAggregationBuilder); + break; + default: + break; + } + } + } + return aggregationBuilders; + } + + @Override + public JsonObject aggregate(String query, String index, JsonObject aggregationJson) + throws IOException { + JsonObject aggregations = aggregationJson.getJsonObject("aggregations"); + if (aggregations == null) { + return null; + } + + List aggregationBuilder = buildAggregation(aggregations); + os.org.opensearch.action.search.SearchRequest searchRequest = + new os.org.opensearch.action.search.SearchRequest( + Entity.getSearchRepository().getIndexOrAliasName(index)); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + if (query != null) { + XContentParser queryParser = + XContentType.JSON + .xContent() + .createParser(X_CONTENT_REGISTRY, LoggingDeprecationHandler.INSTANCE, query); + QueryBuilder parsedQuery = SearchSourceBuilder.fromXContent(queryParser).query(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(parsedQuery); + searchSourceBuilder.query(boolQueryBuilder); + } + + searchSourceBuilder.size(0).timeout(new TimeValue(30, TimeUnit.SECONDS)); + + for (AggregationBuilder aggregation : aggregationBuilder) { + searchSourceBuilder.aggregation(aggregation); + } + + searchRequest.source(searchSourceBuilder); + + String response = client.search(searchRequest, RequestOptions.DEFAULT).toString(); + JsonObject jsonResponse = JsonUtils.readJson(response).asJsonObject(); + return jsonResponse.getJsonObject("aggregations"); + } + public void updateSearch(UpdateRequest updateRequest) { if (updateRequest != null) { updateRequest.docAsUpsert(true); diff --git a/openmetadata-service/src/main/resources/elasticsearch/en/test_suite_index_mapping.json b/openmetadata-service/src/main/resources/elasticsearch/en/test_suite_index_mapping.json index 353405394c1..6b58935c6a2 100644 --- a/openmetadata-service/src/main/resources/elasticsearch/en/test_suite_index_mapping.json +++ b/openmetadata-service/src/main/resources/elasticsearch/en/test_suite_index_mapping.json @@ -84,6 +84,30 @@ "entityType": { "type": "keyword" }, + "testCaseResultSummary": { + "type": "nested", + "properties": { + "status": { + "type": "keyword" + }, + "testCaseName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + } + } + }, + "timestamp": { + "type": "long" + } + } + }, "suggest": { "type": "completion", "contexts": [ diff --git a/openmetadata-service/src/main/resources/elasticsearch/jp/test_suite_index_mapping.json b/openmetadata-service/src/main/resources/elasticsearch/jp/test_suite_index_mapping.json index 6c7ccb3130d..6062d4074c9 100644 --- a/openmetadata-service/src/main/resources/elasticsearch/jp/test_suite_index_mapping.json +++ b/openmetadata-service/src/main/resources/elasticsearch/jp/test_suite_index_mapping.json @@ -82,6 +82,27 @@ "entityType": { "type": "keyword" }, + "testCaseResultSummary": { + "type": "nested", + "properties": { + "status": { + "type": "keyword" + }, + "testCaseName": { + "type": "text", + "analyzer": "om_analyzer_jp", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "timestamp": { + "type": "long" + } + } + }, "suggest": { "type": "completion", "contexts": [ diff --git a/openmetadata-service/src/main/resources/elasticsearch/zh/test_suite_index_mapping.json b/openmetadata-service/src/main/resources/elasticsearch/zh/test_suite_index_mapping.json index 8860dd5c4ee..c1c45b7b82b 100644 --- a/openmetadata-service/src/main/resources/elasticsearch/zh/test_suite_index_mapping.json +++ b/openmetadata-service/src/main/resources/elasticsearch/zh/test_suite_index_mapping.json @@ -67,6 +67,28 @@ "entityType": { "type": "keyword" }, + "testCaseResultSummary": { + "type": "nested", + "properties": { + "status": { + "type": "keyword" + }, + "testCaseName": { + "type": "text", + "analyzer": "ik_max_word", + "search_analyzer": "ik_smart", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "timestamp": { + "type": "long" + } + } + }, "fqnParts": { "type": "keyword" }, diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java index f1e8d30e259..1fc78d6e3e2 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java @@ -381,17 +381,14 @@ public class TestCaseResourceTest extends EntityResourceTest getTestSummary(ADMIN_AUTH_HEADERS, randomUUID), - NOT_FOUND, - "testSuite instance for " + randomUUID + " not found"); + TestSummary testSummary; + if (supportsSearchIndex && RUN_ELASTIC_SEARCH_TESTCASES) { + testSummary = getTestSummary(ADMIN_AUTH_HEADERS, null); + assertNotEquals(0, testSummary.getFailed()); + assertNotEquals(0, testSummary.getSuccess()); + assertNotEquals(0, testSummary.getTotal()); + assertEquals(0, testSummary.getAborted()); + } // Test that we can get the test summary for a logical test suite and that // adding a logical test suite does not change the total number of tests @@ -403,25 +400,30 @@ public class TestCaseResourceTest extends EntityResourceTest actual; + List expected = new ArrayList<>(); + String aggregationQuery; + + // Test aggregation with nested aggregation + aggregationQuery = + """ + { + "aggregations": { + "test_case_results": { + "nested": { + "path": "testCaseResultSummary" + }, + "aggs": { + "status_counts": { + "terms": { + "field": "testCaseResultSummary.status" + } + } + } + } + } + } + """; + + expected.add( + AggregationBuilders.nested("testCaseResultSummary", "testCaseResultSummary") + .subAggregation( + AggregationBuilders.terms("status_counts").field("testCaseResultSummary.status"))); + + aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject(); + actual = ElasticSearchClient.buildAggregation(aggregationJson.getJsonObject("aggregations")); + assertThat(actual).hasSameElementsAs(expected); + + // Test aggregation with multiple aggregations + aggregationQuery = + """ + { + "aggregations": { + "my-first-agg-name": { + "terms": { + "field": "my-field" + } + }, + "my-second-agg-name": { + "terms": { + "field": "my-other-field" + } + } + } + } + """; + aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject(); + + expected.clear(); + expected.addAll( + List.of( + AggregationBuilders.terms("my-second-agg-name").field("my-other-field"), + AggregationBuilders.terms("my-first-agg-name").field("my-field"))); + + actual = ElasticSearchClient.buildAggregation(aggregationJson.getJsonObject("aggregations")); + assertThat(actual).hasSameElementsAs(expected); + + // Test aggregation with multiple aggregations including a nested one which has itself multiple + // aggregations + aggregationQuery = + """ + { + "aggregations": { + "my-first-agg-name": { + "terms": { + "field": "my-field" + } + }, + "test_case_results": { + "nested": { + "path": "testCaseResultSummary" + }, + "aggs": { + "status_counts": { + "terms": { + "field": "testCaseResultSummary.status" + } + }, + "other_status_counts": { + "terms": { + "field": "testCaseResultSummary.status" + } + } + } + } + } + } + """; + aggregationJson = JsonUtils.readJson(aggregationQuery).asJsonObject(); + + expected.clear(); + expected.addAll( + List.of( + AggregationBuilders.nested("testCaseResultSummary", "testCaseResultSummary") + .subAggregation( + AggregationBuilders.terms("status_counts") + .field("testCaseResultSummary.status")) + .subAggregation( + AggregationBuilders.terms("other_status_counts") + .field("testCaseResultSummary.status")), + AggregationBuilders.terms("my-first-agg-name").field("my-field"))); + + actual = ElasticSearchClient.buildAggregation(aggregationJson.getJsonObject("aggregations")); + assertThat(actual).hasSameElementsAs(expected); + } + @Test void delete_LogicalTestSuite_200(TestInfo test) throws IOException { TestCaseResourceTest testCaseResourceTest = new TestCaseResourceTest();