mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-24 05:58:31 +00:00
* feat: added logic to delete logical test suite + added check to prevent adding existing testCases to executable test suite * feat: added elasticsearch index logic for testCases * feat: added deletion logic from index logic when deleting test suites * feat: added test case index search to endpoint * feat: add executable/logical filter in list testSuite + filterOut tables without tests in Table resource * feat: added summary field to testSuite * feat: added executionSummary endpoint for test cases * feat: removed tick marks around timestamp * feat: addressed test failures * feat: ran python linting * feat: add limit to fetch all tables in TableResource testSuite test * feat: fix conflict * feat: ran java checkstyle * feat: fixed mongo linting + disabled mongo failing tests * feat: removed mongo test skip * feat: removed unsued pytest import
This commit is contained in:
parent
db59207ffe
commit
3f01ee938f
@ -15,8 +15,9 @@ To be used by OpenMetadata class
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Type, Union
|
||||
from urllib.parse import quote
|
||||
from uuid import UUID
|
||||
|
||||
from metadata.generated.schema.api.tests.createLogicalTestCases import (
|
||||
CreateLogicalTestCases,
|
||||
@ -37,6 +38,7 @@ from metadata.generated.schema.tests.testDefinition import (
|
||||
from metadata.generated.schema.tests.testSuite import TestSuite
|
||||
from metadata.ingestion.models.encoders import show_secrets_encoder
|
||||
from metadata.ingestion.ometa.client import REST
|
||||
from metadata.ingestion.ometa.utils import model_str
|
||||
from metadata.utils.logger import ometa_logger
|
||||
|
||||
logger = ometa_logger()
|
||||
@ -241,6 +243,25 @@ class OMetaTestsMixin:
|
||||
|
||||
return entity_class.parse_obj(resp)
|
||||
|
||||
def delete_executable_test_suite(
|
||||
self,
|
||||
entity: Type[TestSuite],
|
||||
entity_id: Union[str, UUID],
|
||||
recursive: bool = False,
|
||||
hard_delete: bool = False,
|
||||
) -> None:
|
||||
"""Delete executable test suite
|
||||
|
||||
Args:
|
||||
entity_id (str): test suite ID
|
||||
recursive (bool, optional): delete children if true
|
||||
hard_delete (bool, optional): hard delete if true
|
||||
"""
|
||||
url = f"{self.get_suffix(entity)}/executable/{model_str(entity_id)}"
|
||||
url += f"?recursive={str(recursive).lower()}"
|
||||
url += f"&hardDelete={str(hard_delete).lower()}"
|
||||
self.client.delete(url)
|
||||
|
||||
def add_logical_test_cases(self, data: CreateLogicalTestCases) -> None:
|
||||
"""Add logical test cases to a test suite
|
||||
|
||||
|
||||
@ -168,7 +168,7 @@ class OMetaTestSuiteTest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
cls.metadata.delete(
|
||||
cls.metadata.delete_executable_test_suite(
|
||||
entity=TestSuite,
|
||||
entity_id=cls.test_suite.id,
|
||||
recursive=True,
|
||||
|
||||
@ -23,7 +23,6 @@ from metadata.generated.schema.entity.services.connections.metadata.openMetadata
|
||||
OpenMetadataConnection,
|
||||
)
|
||||
from metadata.generated.schema.tests.testCase import TestCase
|
||||
from metadata.generated.schema.tests.testDefinition import TestDefinition
|
||||
from metadata.generated.schema.tests.testSuite import TestSuite
|
||||
from metadata.ingestion.ometa.ometa_api import OpenMetadata
|
||||
|
||||
@ -76,7 +75,7 @@ class TestSuiteWorkflowTests(unittest.TestCase):
|
||||
hard_delete=True,
|
||||
)
|
||||
for test_suite_id in self.test_suite_ids:
|
||||
self.metadata.delete(
|
||||
self.metadata.delete_executable_test_suite(
|
||||
entity=TestSuite,
|
||||
entity_id=test_suite_id,
|
||||
recursive=True,
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
|
||||
package org.openmetadata.service.elasticsearch;
|
||||
|
||||
import static org.openmetadata.schema.type.EventType.ENTITY_DELETED;
|
||||
import static org.openmetadata.schema.type.EventType.ENTITY_UPDATED;
|
||||
import static org.openmetadata.service.Entity.ADMIN_USER_NAME;
|
||||
import static org.openmetadata.service.Entity.FIELD_FOLLOWERS;
|
||||
import static org.openmetadata.service.Entity.FIELD_USAGE_SUMMARY;
|
||||
@ -51,15 +53,18 @@ import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.engine.DocumentMissingException;
|
||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||
import org.elasticsearch.index.query.MatchQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.query.TermQueryBuilder;
|
||||
import org.elasticsearch.index.query.WildcardQueryBuilder;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.openmetadata.schema.EntityInterface;
|
||||
import org.openmetadata.schema.api.CreateEventPublisherJob;
|
||||
import org.openmetadata.schema.entity.classification.Classification;
|
||||
import org.openmetadata.schema.entity.classification.Tag;
|
||||
@ -80,11 +85,14 @@ import org.openmetadata.schema.system.EventPublisherJob;
|
||||
import org.openmetadata.schema.system.EventPublisherJob.Status;
|
||||
import org.openmetadata.schema.system.Failure;
|
||||
import org.openmetadata.schema.system.FailureDetails;
|
||||
import org.openmetadata.schema.tests.TestCase;
|
||||
import org.openmetadata.schema.tests.TestSuite;
|
||||
import org.openmetadata.schema.type.ChangeDescription;
|
||||
import org.openmetadata.schema.type.ChangeEvent;
|
||||
import org.openmetadata.schema.type.EntityReference;
|
||||
import org.openmetadata.schema.type.EventType;
|
||||
import org.openmetadata.schema.type.FieldChange;
|
||||
import org.openmetadata.schema.type.Include;
|
||||
import org.openmetadata.schema.type.TagLabel;
|
||||
import org.openmetadata.schema.type.UsageDetails;
|
||||
import org.openmetadata.service.Entity;
|
||||
@ -178,6 +186,12 @@ public class ElasticSearchEventPublisher extends AbstractEventPublisher {
|
||||
case Entity.CLASSIFICATION:
|
||||
updateClassification(event);
|
||||
break;
|
||||
case Entity.TEST_CASE:
|
||||
updateTestCase(event);
|
||||
break;
|
||||
case Entity.TEST_SUITE:
|
||||
updateTestSuite(event);
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Ignoring Entity Type {}", entityType);
|
||||
}
|
||||
@ -427,6 +441,106 @@ public class ElasticSearchEventPublisher extends AbstractEventPublisher {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTestSuite(ChangeEvent event) throws IOException {
|
||||
ElasticSearchIndexType indexType = ElasticSearchIndexDefinition.getIndexMappingByEntityType(Entity.TEST_CASE);
|
||||
TestSuite testSuite = (TestSuite) event.getEntity();
|
||||
UUID testSuiteId = testSuite.getId();
|
||||
|
||||
if (event.getEventType() == ENTITY_DELETED) {
|
||||
if (testSuite.getExecutable()) {
|
||||
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(indexType.indexName);
|
||||
deleteByQueryRequest.setQuery(new MatchQueryBuilder("testSuite.id", testSuiteId.toString()));
|
||||
deleteEntityFromElasticSearchByQuery(deleteByQueryRequest);
|
||||
} else {
|
||||
UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(indexType.indexName);
|
||||
updateByQueryRequest.setQuery(new MatchQueryBuilder("testSuite.id", testSuiteId.toString()));
|
||||
String scriptTxt =
|
||||
"for (int i = 0; i < ctx._source.testSuite.length; i++) { if (ctx._source.testSuite[i].id == '%s') { ctx._source.testSuite.remove(i) }}";
|
||||
Script script =
|
||||
new Script(
|
||||
ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, String.format(scriptTxt, testSuiteId), new HashMap<>());
|
||||
updateByQueryRequest.setScript(script);
|
||||
updateElasticSearchByQuery(updateByQueryRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTestCase(ChangeEvent event) throws IOException {
|
||||
ElasticSearchIndexType indexType = ElasticSearchIndexDefinition.getIndexMappingByEntityType(Entity.TEST_CASE);
|
||||
// creating a new test case will return a TestCase entity while bulk adding test cases will return
|
||||
// the logical test suite entity with the newly added test cases
|
||||
EntityInterface entityInterface = (EntityInterface) event.getEntity();
|
||||
if (entityInterface instanceof TestCase) {
|
||||
processTestCase((TestCase) entityInterface, event, indexType);
|
||||
} else {
|
||||
addTestCaseFromLogicalTestSuite((TestSuite) entityInterface, event, indexType);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTestCaseFromLogicalTestSuite(TestSuite testSuite, ChangeEvent event, ElasticSearchIndexType indexType)
|
||||
throws IOException {
|
||||
// Process creation of test cases (linked to a logical test suite) by adding reference to existing test cases
|
||||
List<EntityReference> testCaseReferences = testSuite.getTests();
|
||||
TestSuite testSuiteReference =
|
||||
new TestSuite()
|
||||
.withId(testSuite.getId())
|
||||
.withName(testSuite.getName())
|
||||
.withDisplayName(testSuite.getDisplayName())
|
||||
.withDescription(testSuite.getDescription())
|
||||
.withFullyQualifiedName(testSuite.getFullyQualifiedName())
|
||||
.withDeleted(testSuite.getDeleted())
|
||||
.withHref(testSuite.getHref())
|
||||
.withExecutable(testSuite.getExecutable());
|
||||
Map<String, Object> testSuiteDoc = JsonUtils.getMap(testSuiteReference);
|
||||
if (event.getEventType() == ENTITY_UPDATED) {
|
||||
for (EntityReference testcaseReference : testCaseReferences) {
|
||||
UpdateRequest updateRequest = new UpdateRequest(indexType.indexName, testcaseReference.getId().toString());
|
||||
String scripText = "ctx._source.testSuite.add(params)";
|
||||
Script script = new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, scripText, testSuiteDoc);
|
||||
updateRequest.script(script);
|
||||
updateElasticSearch(updateRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processTestCase(TestCase testCase, ChangeEvent event, ElasticSearchIndexType indexType)
|
||||
throws IOException {
|
||||
// Process creation of test cases (linked to an executable test suite
|
||||
UpdateRequest updateRequest = new UpdateRequest(indexType.indexName, testCase.getId().toString());
|
||||
TestCaseIndex testCaseIndex;
|
||||
|
||||
switch (event.getEventType()) {
|
||||
case ENTITY_CREATED:
|
||||
testCaseIndex = new TestCaseIndex((TestCase) event.getEntity());
|
||||
updateRequest.doc(JsonUtils.pojoToJson(testCaseIndex.buildESDocForCreate()), XContentType.JSON);
|
||||
updateRequest.docAsUpsert(true);
|
||||
updateElasticSearch(updateRequest);
|
||||
break;
|
||||
case ENTITY_UPDATED:
|
||||
testCaseIndex = new TestCaseIndex((TestCase) event.getEntity());
|
||||
scriptedUpsert(testCaseIndex.buildESDoc(), updateRequest);
|
||||
updateElasticSearch(updateRequest);
|
||||
break;
|
||||
case ENTITY_SOFT_DELETED:
|
||||
softDeleteEntity(updateRequest);
|
||||
updateElasticSearch(updateRequest);
|
||||
break;
|
||||
case ENTITY_DELETED:
|
||||
EntityReference testSuiteReference = ((TestCase) event.getEntity()).getTestSuite();
|
||||
TestSuite testSuite = Entity.getEntity(Entity.TEST_SUITE, testSuiteReference.getId(), "", Include.ALL);
|
||||
if (testSuite.getExecutable()) {
|
||||
// Delete the test case from the index if deleted from an executable test suite
|
||||
DeleteRequest deleteRequest = new DeleteRequest(indexType.indexName, event.getEntityId().toString());
|
||||
deleteEntityFromElasticSearch(deleteRequest);
|
||||
} else {
|
||||
// for non-executable test suites, simply remove the testSuite from the testCase and update the index
|
||||
scriptedDeleteTestCase(updateRequest, testSuite.getId());
|
||||
updateElasticSearch(updateRequest);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTag(ChangeEvent event) throws IOException {
|
||||
UpdateRequest updateRequest =
|
||||
new UpdateRequest(ElasticSearchIndexType.TAG_SEARCH_INDEX.indexName, event.getEntityId().toString());
|
||||
@ -633,6 +747,15 @@ public class ElasticSearchEventPublisher extends AbstractEventPublisher {
|
||||
updateRequest.scriptedUpsert(true);
|
||||
}
|
||||
|
||||
private void scriptedDeleteTestCase(UpdateRequest updateRequest, UUID testSuiteId) {
|
||||
// Remove logical test suite from test case `testSuite` field
|
||||
String scriptTxt =
|
||||
"for (int i = 0; i < ctx._source.testSuite.length; i++) { if (ctx._source.testSuite[i].id == '%s') { ctx._source.testSuite.remove(i) }}";
|
||||
scriptTxt = String.format(scriptTxt, testSuiteId);
|
||||
Script script = new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, scriptTxt, new HashMap<>());
|
||||
updateRequest.script(script);
|
||||
}
|
||||
|
||||
private void softDeleteEntity(UpdateRequest updateRequest) {
|
||||
String scriptTxt = "ctx._source.deleted=true";
|
||||
Script script = new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, scriptTxt, new HashMap<>());
|
||||
@ -646,6 +769,13 @@ public class ElasticSearchEventPublisher extends AbstractEventPublisher {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateElasticSearchByQuery(UpdateByQueryRequest updateByQueryRequest) throws IOException {
|
||||
if (updateByQueryRequest != null) {
|
||||
LOG.debug(SENDING_REQUEST_TO_ELASTIC_SEARCH, updateByQueryRequest);
|
||||
client.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteEntityFromElasticSearch(DeleteRequest deleteRequest) throws IOException {
|
||||
if (deleteRequest != null) {
|
||||
LOG.debug(SENDING_REQUEST_TO_ELASTIC_SEARCH, deleteRequest);
|
||||
|
||||
@ -99,6 +99,8 @@ public class ElasticSearchIndexDefinition {
|
||||
TAG_SEARCH_INDEX(Entity.TAG, "tag_search_index", "/elasticsearch/%s/tag_index_mapping.json"),
|
||||
ENTITY_REPORT_DATA_INDEX(
|
||||
ENTITY_REPORT_DATA, "entity_report_data_index", "/elasticsearch/entity_report_data_index.json"),
|
||||
TEST_CASE_SEARCH_INDEX(
|
||||
Entity.TEST_CASE, "test_case_search_index", "/elasticsearch/%s/test_case_index_mapping.json"),
|
||||
WEB_ANALYTIC_ENTITY_VIEW_REPORT_DATA_INDEX(
|
||||
Entity.WEB_ANALYTIC_EVENT,
|
||||
"web_analytic_entity_view_report_data_index",
|
||||
@ -270,6 +272,8 @@ public class ElasticSearchIndexDefinition {
|
||||
return ElasticSearchIndexType.CONTAINER_SEARCH_INDEX;
|
||||
} else if (type.equalsIgnoreCase(Entity.QUERY)) {
|
||||
return ElasticSearchIndexType.QUERY_SEARCH_INDEX;
|
||||
} else if (type.equalsIgnoreCase(Entity.TEST_SUITE) || type.equalsIgnoreCase(Entity.TEST_CASE)) {
|
||||
return ElasticSearchIndexType.TEST_CASE_SEARCH_INDEX;
|
||||
}
|
||||
throw new EventPublisherException("Failed to find index doc for type " + type);
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
package org.openmetadata.service.elasticsearch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.openmetadata.schema.tests.TestCase;
|
||||
import org.openmetadata.schema.tests.TestSuite;
|
||||
import org.openmetadata.schema.type.EntityReference;
|
||||
import org.openmetadata.schema.type.Include;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.util.JsonUtils;
|
||||
|
||||
public class TestCaseIndex implements ElasticSearchIndex {
|
||||
TestCase testCase;
|
||||
|
||||
public TestCaseIndex(TestCase testCase) {
|
||||
this.testCase = testCase;
|
||||
}
|
||||
|
||||
public Map<String, Object> buildESDoc() {
|
||||
return JsonUtils.getMap(testCase);
|
||||
}
|
||||
|
||||
public Map<String, Object> buildESDocForCreate() throws IOException {
|
||||
EntityReference testSuiteEntityReference = testCase.getTestSuite();
|
||||
TestSuite testSuite = getTestSuite(testSuiteEntityReference.getId());
|
||||
List<TestSuite> testSuiteArray = new ArrayList<>();
|
||||
testSuiteArray.add(testSuite);
|
||||
Map<String, Object> doc = JsonUtils.getMap(testCase);
|
||||
doc.put("testSuite", testSuiteArray);
|
||||
return doc;
|
||||
}
|
||||
|
||||
private TestSuite getTestSuite(UUID testSuiteId) throws IOException {
|
||||
TestSuite testSuite = Entity.getEntity(Entity.TEST_SUITE, testSuiteId, "", Include.ALL);
|
||||
return new TestSuite()
|
||||
.withId(testSuite.getId())
|
||||
.withName(testSuite.getName())
|
||||
.withDisplayName(testSuite.getDisplayName())
|
||||
.withDescription(testSuite.getDescription())
|
||||
.withFullyQualifiedName(testSuite.getFullyQualifiedName())
|
||||
.withDeleted(testSuite.getDeleted())
|
||||
.withHref(testSuite.getHref())
|
||||
.withExecutable(testSuite.getExecutable());
|
||||
}
|
||||
}
|
||||
@ -42,6 +42,7 @@ import org.openmetadata.schema.type.ChangeEvent;
|
||||
import org.openmetadata.schema.type.EntityReference;
|
||||
import org.openmetadata.schema.type.EventType;
|
||||
import org.openmetadata.schema.type.FieldChange;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.formatter.decorators.MessageDecorator;
|
||||
import org.openmetadata.service.formatter.factory.ParserFactory;
|
||||
import org.openmetadata.service.formatter.field.DefaultFieldFormatter;
|
||||
@ -268,6 +269,17 @@ public class FormatterUtil {
|
||||
.withEntityFullyQualifiedName(entityFQN);
|
||||
}
|
||||
|
||||
// Handles Bulk Add test cases to a logical test suite
|
||||
if (changeType.equals(RestUtil.LOGICAL_TEST_CASES_ADDED)) {
|
||||
EntityInterface entityInterface = (EntityInterface) responseContext.getEntity();
|
||||
EntityReference entityReference = entityInterface.getEntityReference();
|
||||
String entityType = Entity.TEST_CASE;
|
||||
String entityFQN = entityReference.getFullyQualifiedName();
|
||||
return getChangeEvent(updateBy, EventType.ENTITY_UPDATED, entityType, entityInterface)
|
||||
.withEntity(entityInterface)
|
||||
.withEntityFullyQualifiedName(entityFQN);
|
||||
}
|
||||
|
||||
// PUT or PATCH operation didn't result in any change
|
||||
if (changeType == null || RestUtil.ENTITY_NO_CHANGE.equals(changeType)) {
|
||||
return null;
|
||||
|
||||
@ -666,7 +666,14 @@ public interface CollectionDAO {
|
||||
@Bind("relation") int relation,
|
||||
@Bind("json") String json);
|
||||
|
||||
@SqlUpdate("INSERT INTO entity_relationship(fromId, toId, fromEntity, toEntity, relation) VALUES <values>")
|
||||
@ConnectionAwareSqlUpdate(
|
||||
value = "INSERT IGNORE INTO entity_relationship(fromId, toId, fromEntity, toEntity, relation) VALUES <values>",
|
||||
connectionType = MYSQL)
|
||||
@ConnectionAwareSqlUpdate(
|
||||
value =
|
||||
"INSERT INTO entity_relationship(fromId, toId, fromEntity, toEntity, relation) VALUES <values>"
|
||||
+ "ON CONFLICT DO NOTHING",
|
||||
connectionType = POSTGRES)
|
||||
void bulkInsertTo(
|
||||
@BindBeanList(
|
||||
value = "values",
|
||||
@ -2878,6 +2885,12 @@ public interface CollectionDAO {
|
||||
+ "ORDER BY timestamp DESC LIMIT 1")
|
||||
String getLatestExtension(@Bind("entityFQN") String entityFQN, @Bind("extension") String extension);
|
||||
|
||||
@SqlQuery(
|
||||
"SELECT ranked.json FROM (SELECT json, ROW_NUMBER() OVER(PARTITION BY entityFQN ORDER BY timestamp DESC) AS row_num "
|
||||
+ "FROM entity_extension_time_series WHERE entityFQN IN (<entityFQNs>)) ranked WHERE ranked.row_num = 1")
|
||||
List<String> getLatestExtensionByFQNs(
|
||||
@BindList("entityFQNs") List<String> entityFQNs, @Bind("extension") String extension);
|
||||
|
||||
@SqlQuery(
|
||||
"SELECT json FROM entity_extension_time_series WHERE extension = :extension "
|
||||
+ "ORDER BY timestamp DESC LIMIT 1")
|
||||
|
||||
@ -1449,6 +1449,8 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
||||
private void updateDescription() throws JsonProcessingException {
|
||||
if (operation.isPut() && !nullOrEmpty(original.getDescription()) && updatedByBot()) {
|
||||
// Revert change to non-empty description if it is being updated by a bot
|
||||
// This is to prevent bots from overwriting the description. Description need to be
|
||||
// updated with a PATCH request
|
||||
updated.setDescription(original.getDescription());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ public class ListFilter {
|
||||
condition = addCondition(condition, getWebhookCondition(tableName));
|
||||
condition = addCondition(condition, getWebhookTypeCondition(tableName));
|
||||
condition = addCondition(condition, getTestCaseCondition());
|
||||
condition = addCondition(condition, getTestSuiteCondition());
|
||||
return condition.isEmpty() ? "WHERE TRUE" : "WHERE " + condition;
|
||||
}
|
||||
|
||||
@ -130,6 +131,29 @@ public class ListFilter {
|
||||
return addCondition(condition1, condition2);
|
||||
}
|
||||
|
||||
private String getTestSuiteCondition() {
|
||||
String testSuiteType = getQueryParam("testSuiteType");
|
||||
|
||||
if (testSuiteType == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (testSuiteType) {
|
||||
case ("executable"):
|
||||
if (DatasourceConfig.getInstance().isMySQL()) {
|
||||
return "JSON_UNQUOTE(JSON_EXTRACT(json, '$.executable')) = 'true'";
|
||||
}
|
||||
return "json->>'executable' = 'true'";
|
||||
case ("logical"):
|
||||
if (DatasourceConfig.getInstance().isMySQL()) {
|
||||
return "JSON_UNQUOTE(JSON_EXTRACT(json, '$.executable')) = 'false'";
|
||||
}
|
||||
return "json->>'executable' = 'false'";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getFqnPrefixCondition(String tableName, String fqnPrefix) {
|
||||
fqnPrefix = escape(fqnPrefix);
|
||||
return tableName == null
|
||||
|
||||
@ -5,13 +5,16 @@ import static org.openmetadata.service.Entity.TEST_DEFINITION;
|
||||
import static org.openmetadata.service.Entity.TEST_SUITE;
|
||||
import static org.openmetadata.service.util.RestUtil.ENTITY_NO_CHANGE;
|
||||
import static org.openmetadata.service.util.RestUtil.ENTITY_UPDATED;
|
||||
import static org.openmetadata.service.util.RestUtil.LOGICAL_TEST_CASES_ADDED;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.json.JsonPatch;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
@ -23,6 +26,8 @@ import org.openmetadata.schema.tests.TestCaseParameterValue;
|
||||
import org.openmetadata.schema.tests.TestDefinition;
|
||||
import org.openmetadata.schema.tests.TestSuite;
|
||||
import org.openmetadata.schema.tests.type.TestCaseResult;
|
||||
import org.openmetadata.schema.tests.type.TestCaseStatus;
|
||||
import org.openmetadata.schema.tests.type.TestSummary;
|
||||
import org.openmetadata.schema.type.ChangeDescription;
|
||||
import org.openmetadata.schema.type.ChangeEvent;
|
||||
import org.openmetadata.schema.type.EntityReference;
|
||||
@ -284,20 +289,56 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
||||
}
|
||||
}
|
||||
|
||||
public RestUtil.PutResponse<?> addTestCasesToLogicalTestSuite(TestSuite testSuite, List<UUID> testCaseIds) {
|
||||
public RestUtil.PutResponse<TestSuite> addTestCasesToLogicalTestSuite(TestSuite testSuite, List<UUID> testCaseIds)
|
||||
throws IOException {
|
||||
bulkAddToRelationship(testSuite.getId(), testCaseIds, TEST_SUITE, TEST_CASE, Relationship.CONTAINS);
|
||||
return new RestUtil.PutResponse<>(
|
||||
Response.Status.OK,
|
||||
testSuite,
|
||||
String.format(RestUtil.TEST_CASES_ADDED, testCaseIds.size(), testSuite.getName()));
|
||||
List<EntityReference> testCasesEntityReferences = new ArrayList<>();
|
||||
for (UUID testCaseId : testCaseIds) {
|
||||
TestCase testCase = Entity.getEntity(Entity.TEST_CASE, testCaseId, "", Include.ALL);
|
||||
testCasesEntityReferences.add(
|
||||
new EntityReference()
|
||||
.withId(testCase.getId())
|
||||
.withName(testCase.getName())
|
||||
.withFullyQualifiedName(testCase.getFullyQualifiedName())
|
||||
.withDescription(testCase.getDescription())
|
||||
.withDisplayName(testCase.getDisplayName())
|
||||
.withHref(testCase.getHref())
|
||||
.withDeleted(testCase.getDeleted()));
|
||||
}
|
||||
testSuite.setTests(testCasesEntityReferences);
|
||||
return new RestUtil.PutResponse<>(Response.Status.OK, testSuite, LOGICAL_TEST_CASES_ADDED);
|
||||
}
|
||||
|
||||
public RestUtil.DeleteResponse<TestCase> deleteTestCaseFromLogicalTestSuite(UUID testSuiteId, UUID testCaseId)
|
||||
throws IOException {
|
||||
TestCase testCase = Entity.getEntity(Entity.TEST_CASE, testCaseId, null, null);
|
||||
deleteRelationship(testSuiteId, TEST_SUITE, testCaseId, TEST_CASE, Relationship.CONTAINS);
|
||||
return new RestUtil.DeleteResponse<>(
|
||||
testCase, String.format(RestUtil.TEST_CASE_REMOVED_FROM_LOGICAL_TEST_SUITE, testSuiteId));
|
||||
EntityReference entityReference = Entity.getEntityReferenceById(TEST_SUITE, testSuiteId, Include.ALL);
|
||||
testCase.setTestSuite(entityReference);
|
||||
return new RestUtil.DeleteResponse<>(testCase, RestUtil.ENTITY_DELETED);
|
||||
}
|
||||
|
||||
public TestSummary getTestSummary() throws IOException {
|
||||
List<TestCase> testCases = listAll(Fields.EMPTY_FIELDS, new ListFilter());
|
||||
List<String> testCaseFQNs = testCases.stream().map(TestCase::getFullyQualifiedName).collect(Collectors.toList());
|
||||
|
||||
if (testCaseFQNs.isEmpty()) return new TestSummary();
|
||||
|
||||
List<String> jsonList =
|
||||
daoCollection.entityExtensionTimeSeriesDao().getLatestExtensionByFQNs(testCaseFQNs, TESTCASE_RESULT_EXTENSION);
|
||||
|
||||
HashMap<String, Integer> testCaseSummary = new HashMap<>();
|
||||
for (String json : jsonList) {
|
||||
TestCaseResult testCaseResult = JsonUtils.readValue(json, TestCaseResult.class);
|
||||
String status = testCaseResult.getTestCaseStatus().toString();
|
||||
testCaseSummary.put(status, testCaseSummary.getOrDefault(status, 0) + 1);
|
||||
}
|
||||
|
||||
return new TestSummary()
|
||||
.withAborted(testCaseSummary.getOrDefault(TestCaseStatus.Aborted.toString(), 0))
|
||||
.withFailed(testCaseSummary.getOrDefault(TestCaseStatus.Failed.toString(), 0))
|
||||
.withSuccess(testCaseSummary.getOrDefault(TestCaseStatus.Success.toString(), 0))
|
||||
.withTotal(testCaseFQNs.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -3,18 +3,28 @@ package org.openmetadata.service.jdbi3;
|
||||
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
|
||||
import static org.openmetadata.service.Entity.TEST_CASE;
|
||||
import static org.openmetadata.service.Entity.TEST_SUITE;
|
||||
import static org.openmetadata.service.jdbi3.TestCaseRepository.TESTCASE_RESULT_EXTENSION;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.openmetadata.schema.entity.data.Table;
|
||||
import org.openmetadata.schema.tests.TestSuite;
|
||||
import org.openmetadata.schema.tests.type.TestCaseResult;
|
||||
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.Relationship;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.jdbi3.EntityRepository.EntityUpdater;
|
||||
import org.openmetadata.service.resources.dqtests.TestSuiteResource;
|
||||
import org.openmetadata.service.util.EntityUtil;
|
||||
import org.openmetadata.service.util.JsonUtils;
|
||||
import org.openmetadata.service.util.RestUtil;
|
||||
|
||||
@Slf4j
|
||||
public class TestSuiteRepository extends EntityRepository<TestSuite> {
|
||||
private static final String UPDATE_FIELDS = "owner,tests";
|
||||
private static final String PATCH_FIELDS = "owner,tests";
|
||||
@ -33,9 +43,33 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
|
||||
@Override
|
||||
public TestSuite setFields(TestSuite entity, EntityUtil.Fields fields) throws IOException {
|
||||
entity.setPipelines(fields.contains("pipelines") ? getIngestionPipelines(entity) : null);
|
||||
entity.setSummary(fields.contains("summary") ? getTestSummary(entity) : null);
|
||||
return entity.withTests(fields.contains("tests") ? getTestCases(entity) : null);
|
||||
}
|
||||
|
||||
private TestSummary getTestSummary(TestSuite entity) throws IOException {
|
||||
List<EntityReference> testCases = getTestCases(entity);
|
||||
HashMap<String, Integer> testCaseSummary = new HashMap<>();
|
||||
List<String> testCaseFQNs =
|
||||
testCases.stream().map(EntityReference::getFullyQualifiedName).collect(Collectors.toList());
|
||||
|
||||
if (testCaseFQNs.isEmpty()) return new TestSummary();
|
||||
|
||||
List<String> jsonList =
|
||||
daoCollection.entityExtensionTimeSeriesDao().getLatestExtensionByFQNs(testCaseFQNs, TESTCASE_RESULT_EXTENSION);
|
||||
|
||||
for (String json : jsonList) {
|
||||
TestCaseResult testCaseResult = JsonUtils.readValue(json, TestCaseResult.class);
|
||||
String status = testCaseResult.getTestCaseStatus().toString();
|
||||
testCaseSummary.put(status, testCaseSummary.getOrDefault(status, 0) + 1);
|
||||
}
|
||||
return new TestSummary()
|
||||
.withAborted(testCaseSummary.getOrDefault(TestCaseStatus.Aborted.toString(), 0))
|
||||
.withFailed(testCaseSummary.getOrDefault(TestCaseStatus.Failed.toString(), 0))
|
||||
.withSuccess(testCaseSummary.getOrDefault(TestCaseStatus.Success.toString(), 0))
|
||||
.withTotal(testCaseFQNs.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(TestSuite entity) {
|
||||
/* Nothing to do */
|
||||
@ -73,6 +107,34 @@ public class TestSuiteRepository extends EntityRepository<TestSuite> {
|
||||
addRelationship(table.getId(), testSuite.getId(), Entity.TABLE, TEST_SUITE, Relationship.CONTAINS);
|
||||
}
|
||||
|
||||
public RestUtil.DeleteResponse<TestSuite> deleteLogicalTestSuite(
|
||||
SecurityContext securityContext, TestSuite original, boolean hardDelete) throws IOException {
|
||||
// deleting a logical will delete the test suite and only remove
|
||||
// the relationship to test cases if hardDelete is true. Test Cases
|
||||
// will not be deleted.
|
||||
String updatedBy = securityContext.getUserPrincipal().getName();
|
||||
preDelete(original);
|
||||
setFieldsInternal(original, putFields);
|
||||
|
||||
String changeType;
|
||||
TestSuite updated = JsonUtils.readValue(JsonUtils.pojoToJson(original), TestSuite.class);
|
||||
setFieldsInternal(updated, putFields);
|
||||
|
||||
if (supportsSoftDelete && !hardDelete) {
|
||||
updated.setUpdatedBy(updatedBy);
|
||||
updated.setUpdatedAt(System.currentTimeMillis());
|
||||
updated.setDeleted(true);
|
||||
EntityUpdater updater = getUpdater(original, updated, Operation.SOFT_DELETE);
|
||||
updater.update();
|
||||
changeType = RestUtil.ENTITY_SOFT_DELETED;
|
||||
} else {
|
||||
cleanup(updated);
|
||||
changeType = RestUtil.ENTITY_DELETED;
|
||||
}
|
||||
LOG.info("{} deleted {}", hardDelete ? "Hard" : "Soft", updated.getFullyQualifiedName());
|
||||
return new RestUtil.DeleteResponse<>(updated, changeType);
|
||||
}
|
||||
|
||||
private EntityReference getIngestionPipeline(TestSuite testSuite) throws IOException {
|
||||
return getToEntityRef(testSuite.getId(), Relationship.CONTAINS, Entity.INGESTION_PIPELINE, false);
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.json.JsonPatch;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.Max;
|
||||
@ -173,6 +174,13 @@ public class TableResource extends EntityResource<Table, TableRepository> {
|
||||
schema = @Schema(type = "string", example = "snowflakeWestCoast.financeDB.schema"))
|
||||
@QueryParam("databaseSchema")
|
||||
String databaseSchemaParam,
|
||||
@Parameter(
|
||||
description =
|
||||
"Include tables with an empty test suite (i.e. no test cases have been created for this table). Default to true",
|
||||
schema = @Schema(type = "boolean", example = "true"))
|
||||
@QueryParam("includeEmptyTestSuite")
|
||||
@DefaultValue("true")
|
||||
boolean includeEmptyTestSuite,
|
||||
@Parameter(description = "Limit the number tables returned. (1 to 1000000, default = " + "10) ")
|
||||
@DefaultValue("10")
|
||||
@Min(0)
|
||||
@ -196,7 +204,15 @@ public class TableResource extends EntityResource<Table, TableRepository> {
|
||||
new ListFilter(include)
|
||||
.addQueryParam("database", databaseParam)
|
||||
.addQueryParam("databaseSchema", databaseSchemaParam);
|
||||
return super.listInternal(uriInfo, securityContext, fieldsParam, filter, limitParam, before, after);
|
||||
ResultList<Table> tableList =
|
||||
super.listInternal(uriInfo, securityContext, fieldsParam, filter, limitParam, before, after);
|
||||
if (!includeEmptyTestSuite) {
|
||||
tableList.setData(
|
||||
tableList.getData().stream()
|
||||
.filter(table -> table.getTestSuite() != null && !table.getTestSuite().getTests().isEmpty())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return tableList;
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
@ -43,6 +43,7 @@ import org.openmetadata.schema.entity.data.Table;
|
||||
import org.openmetadata.schema.tests.TestCase;
|
||||
import org.openmetadata.schema.tests.TestSuite;
|
||||
import org.openmetadata.schema.tests.type.TestCaseResult;
|
||||
import org.openmetadata.schema.tests.type.TestSummary;
|
||||
import org.openmetadata.schema.type.Column;
|
||||
import org.openmetadata.schema.type.EntityHistory;
|
||||
import org.openmetadata.schema.type.Include;
|
||||
@ -682,7 +683,9 @@ public class TestCaseResource extends EntityResource<TestCase, TestCaseRepositor
|
||||
OperationContext operationContext = new OperationContext(Entity.TEST_SUITE, MetadataOperation.EDIT_TESTS);
|
||||
ResourceContextInterface resourceContext = TestCaseResourceContext.builder().entity(testSuite).build();
|
||||
authorizer.authorize(securityContext, operationContext, resourceContext);
|
||||
|
||||
if (testSuite.getExecutable()) {
|
||||
throw new IllegalArgumentException("You are trying to add test cases to an executable test suite.");
|
||||
}
|
||||
List<UUID> testCaseIds = createLogicalTestCases.getTestCaseIds();
|
||||
|
||||
int existingTestCaseCount = repository.getTestCaseCount(testCaseIds);
|
||||
@ -692,6 +695,26 @@ public class TestCaseResource extends EntityResource<TestCase, TestCaseRepositor
|
||||
return repository.addTestCasesToLogicalTestSuite(testSuite, testCaseIds).toResponse();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/executionSummary")
|
||||
@Operation(
|
||||
operationId = "getExecutionSummaryOfTestCases",
|
||||
summary = "Get the execution summary of test cases",
|
||||
description = "Get the execution summary of test cases.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "Tests Execution Summary",
|
||||
content = @Content(mediaType = "application/json", schema = @Schema(implementation = TestSummary.class)))
|
||||
})
|
||||
public TestSummary getTestsExecutionSummary(@Context UriInfo uriInfo, @Context SecurityContext securityContext)
|
||||
throws IOException {
|
||||
ResourceContextInterface resourceContext = TestCaseResourceContext.builder().build();
|
||||
OperationContext operationContext = new OperationContext(Entity.TABLE, MetadataOperation.VIEW_TESTS);
|
||||
authorizer.authorize(securityContext, operationContext, resourceContext);
|
||||
return repository.getTestSummary();
|
||||
}
|
||||
|
||||
private TestCase getTestCase(CreateTestCase create, String user, EntityLink entityLink) throws IOException {
|
||||
return copy(new TestCase(), create, user)
|
||||
.withDescription(create.getDescription())
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package org.openmetadata.service.resources.dqtests;
|
||||
|
||||
import static org.openmetadata.schema.type.Include.ALL;
|
||||
|
||||
import io.swagger.v3.oas.annotations.ExternalDocumentation;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@ -48,6 +50,7 @@ import org.openmetadata.service.jdbi3.TestSuiteRepository;
|
||||
import org.openmetadata.service.resources.Collection;
|
||||
import org.openmetadata.service.resources.EntityResource;
|
||||
import org.openmetadata.service.security.Authorizer;
|
||||
import org.openmetadata.service.security.policyevaluator.OperationContext;
|
||||
import org.openmetadata.service.util.RestUtil;
|
||||
import org.openmetadata.service.util.ResultList;
|
||||
|
||||
@ -59,7 +62,12 @@ import org.openmetadata.service.util.ResultList;
|
||||
@Collection(name = "TestSuites")
|
||||
public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteRepository> {
|
||||
public static final String COLLECTION_PATH = "/v1/dataQuality/testSuites";
|
||||
static final String FIELDS = "owner,tests";
|
||||
public static final String EXECUTABLE_TEST_SUITE_DELETION_ERROR =
|
||||
"Cannot delete logical test suite. To delete logical test suite, use DELETE /v1/dataQuality/testSuites/<...>";
|
||||
public static final String NON_EXECUTABLE_TEST_SUITE_DELETION_ERROR =
|
||||
"Cannot delete executable test suite. To delete executable test suite, use DELETE /v1/dataQuality/testSuites/executable/<...>";
|
||||
|
||||
static final String FIELDS = "owner,tests,summary";
|
||||
|
||||
@Override
|
||||
public TestSuite addHref(UriInfo uriInfo, TestSuite testSuite) {
|
||||
@ -113,6 +121,11 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
|
||||
@Min(0)
|
||||
@Max(1000000)
|
||||
int limitParam,
|
||||
@Parameter(
|
||||
description = "Returns executable or logical test suites. If omitted, returns all test suites.",
|
||||
schema = @Schema(type = "string", example = "executable"))
|
||||
@QueryParam("testSuiteType")
|
||||
String testSuiteType,
|
||||
@Parameter(description = "Returns list of test definitions before this cursor", schema = @Schema(type = "string"))
|
||||
@QueryParam("before")
|
||||
String before,
|
||||
@ -127,6 +140,7 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
|
||||
Include include)
|
||||
throws IOException {
|
||||
ListFilter filter = new ListFilter(include);
|
||||
filter.addQueryParam("testSuiteType", testSuiteType);
|
||||
return super.listInternal(uriInfo, securityContext, fieldsParam, filter, limitParam, before, after);
|
||||
}
|
||||
|
||||
@ -351,8 +365,72 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
|
||||
return createOrUpdate(uriInfo, securityContext, testSuite);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Operation(
|
||||
operationId = "deleteLogicalTestSuite",
|
||||
summary = "Delete a logical test suite",
|
||||
description = "Delete a logical test suite by `id`.",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK"),
|
||||
@ApiResponse(responseCode = "404", description = "Logical test suite for instance {id} is not found")
|
||||
})
|
||||
public Response delete(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Parameter(description = "Hard delete the logical entity. (Default = `false`)")
|
||||
@QueryParam("hardDelete")
|
||||
@DefaultValue("false")
|
||||
boolean hardDelete,
|
||||
@Parameter(description = "Id of the logical test suite", schema = @Schema(type = "UUID")) @PathParam("id")
|
||||
UUID id)
|
||||
throws IOException {
|
||||
OperationContext operationContext = new OperationContext(entityType, MetadataOperation.DELETE);
|
||||
authorizer.authorize(securityContext, operationContext, getResourceContextById(id));
|
||||
TestSuite testSuite = Entity.getEntity(Entity.TEST_SUITE, id, "*", ALL);
|
||||
if (testSuite.getExecutable()) {
|
||||
throw new IllegalArgumentException(NON_EXECUTABLE_TEST_SUITE_DELETION_ERROR);
|
||||
}
|
||||
RestUtil.DeleteResponse<TestSuite> response =
|
||||
repository.deleteLogicalTestSuite(securityContext, testSuite, hardDelete);
|
||||
addHref(uriInfo, response.getEntity());
|
||||
return response.toResponse();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/name/{name}")
|
||||
@Operation(
|
||||
operationId = "deleteLogicalTestSuite",
|
||||
summary = "Delete a logical test suite",
|
||||
description = "Delete a logical test suite by `name`.",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK"),
|
||||
@ApiResponse(responseCode = "404", description = "Logical Test suite for instance {name} is not found")
|
||||
})
|
||||
public Response delete(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Parameter(description = "Hard delete the logical entity. (Default = `false`)")
|
||||
@QueryParam("hardDelete")
|
||||
@DefaultValue("false")
|
||||
boolean hardDelete,
|
||||
@Parameter(description = "FQN of the logical test suite", schema = @Schema(type = "String")) @PathParam("name")
|
||||
String name)
|
||||
throws IOException {
|
||||
OperationContext operationContext = new OperationContext(entityType, MetadataOperation.DELETE);
|
||||
authorizer.authorize(securityContext, operationContext, getResourceContextByName(name));
|
||||
TestSuite testSuite = Entity.getEntityByName(Entity.TEST_SUITE, name, "*", ALL);
|
||||
if (testSuite.getExecutable()) {
|
||||
throw new IllegalArgumentException(NON_EXECUTABLE_TEST_SUITE_DELETION_ERROR);
|
||||
}
|
||||
RestUtil.DeleteResponse<TestSuite> response =
|
||||
repository.deleteLogicalTestSuite(securityContext, testSuite, hardDelete);
|
||||
addHref(uriInfo, response.getEntity());
|
||||
return response.toResponse();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/executable/name/{name}")
|
||||
@Operation(
|
||||
operationId = "deleteTestSuiteByName",
|
||||
summary = "Delete a test suite",
|
||||
@ -361,9 +439,13 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
|
||||
@ApiResponse(responseCode = "200", description = "OK"),
|
||||
@ApiResponse(responseCode = "404", description = "Test suite for instance {name} is not found")
|
||||
})
|
||||
public Response delete(
|
||||
public Response deleteExecutable(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Parameter(description = "Recursively delete this entity and it's children. (Default `false`)")
|
||||
@DefaultValue("false")
|
||||
@QueryParam("recursive")
|
||||
boolean recursive,
|
||||
@Parameter(description = "Hard delete the entity. (Default = `false`)")
|
||||
@QueryParam("hardDelete")
|
||||
@DefaultValue("false")
|
||||
@ -371,11 +453,20 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
|
||||
@Parameter(description = "Name of the test suite", schema = @Schema(type = "string")) @PathParam("name")
|
||||
String name)
|
||||
throws IOException {
|
||||
return deleteByName(uriInfo, securityContext, name, false, hardDelete);
|
||||
OperationContext operationContext = new OperationContext(entityType, MetadataOperation.DELETE);
|
||||
authorizer.authorize(securityContext, operationContext, getResourceContextByName(name));
|
||||
TestSuite testSuite = Entity.getEntityByName(Entity.TEST_SUITE, name, "*", ALL);
|
||||
if (!testSuite.getExecutable()) {
|
||||
throw new IllegalArgumentException(EXECUTABLE_TEST_SUITE_DELETION_ERROR);
|
||||
}
|
||||
RestUtil.DeleteResponse<TestSuite> response =
|
||||
repository.deleteByName(securityContext.getUserPrincipal().getName(), name, recursive, hardDelete);
|
||||
addHref(uriInfo, response.getEntity());
|
||||
return response.toResponse();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@Path("/executable/{id}")
|
||||
@Operation(
|
||||
operationId = "deleteTestSuite",
|
||||
summary = "Delete a test suite",
|
||||
@ -384,7 +475,7 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
|
||||
@ApiResponse(responseCode = "200", description = "OK"),
|
||||
@ApiResponse(responseCode = "404", description = "Test suite for instance {id} is not found")
|
||||
})
|
||||
public Response delete(
|
||||
public Response deleteExecutable(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Parameter(description = "Recursively delete this entity and it's children. (Default `false`)")
|
||||
@ -397,7 +488,16 @@ public class TestSuiteResource extends EntityResource<TestSuite, TestSuiteReposi
|
||||
boolean hardDelete,
|
||||
@Parameter(description = "Id of the test suite", schema = @Schema(type = "UUID")) @PathParam("id") UUID id)
|
||||
throws IOException {
|
||||
return delete(uriInfo, securityContext, id, recursive, hardDelete);
|
||||
OperationContext operationContext = new OperationContext(entityType, MetadataOperation.DELETE);
|
||||
authorizer.authorize(securityContext, operationContext, getResourceContextById(id));
|
||||
TestSuite testSuite = Entity.getEntity(Entity.TEST_SUITE, id, "*", ALL);
|
||||
if (!testSuite.getExecutable()) {
|
||||
throw new IllegalArgumentException(EXECUTABLE_TEST_SUITE_DELETION_ERROR);
|
||||
}
|
||||
RestUtil.DeleteResponse<TestSuite> response =
|
||||
repository.delete(securityContext.getUserPrincipal().getName(), id, recursive, hardDelete);
|
||||
addHref(uriInfo, response.getEntity());
|
||||
return response.toResponse();
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
||||
@ -258,6 +258,9 @@ public class SearchResource {
|
||||
case "query_search_index":
|
||||
searchSourceBuilder = buildQuerySearchBuilder(query, from, size);
|
||||
break;
|
||||
case "test_case_search_index":
|
||||
searchSourceBuilder = buildTestCaseSearch(query, from, size);
|
||||
break;
|
||||
default:
|
||||
searchSourceBuilder = buildAggregateSearchBuilder(query, from, size);
|
||||
break;
|
||||
@ -906,4 +909,37 @@ public class SearchResource {
|
||||
|
||||
return searchBuilder(queryBuilder, hb, from, size);
|
||||
}
|
||||
|
||||
private SearchSourceBuilder buildTestCaseSearch(String query, int from, int size) {
|
||||
QueryStringQueryBuilder queryBuilder =
|
||||
QueryBuilders.queryStringQuery(query)
|
||||
.field(FIELD_NAME, 10.0f)
|
||||
.field(DESCRIPTION, 3.0f)
|
||||
.field("testSuite.fullyQualifiedName", 10.0f)
|
||||
.field("testSuite.name", 10.0f)
|
||||
.field("testSuite.description", 3.0f)
|
||||
.field("entityLink", 3.0f)
|
||||
.field("entityFQN", 10.0f)
|
||||
.defaultOperator(Operator.AND)
|
||||
.fuzziness(Fuzziness.AUTO);
|
||||
|
||||
HighlightBuilder.Field highlightTestCaseDescription = new HighlightBuilder.Field(FIELD_DESCRIPTION);
|
||||
highlightTestCaseDescription.highlighterType(UNIFIED);
|
||||
HighlightBuilder.Field highlightTestCaseName = new HighlightBuilder.Field(FIELD_NAME);
|
||||
highlightTestCaseName.highlighterType(UNIFIED);
|
||||
HighlightBuilder.Field highlightTestSuiteName = new HighlightBuilder.Field("testSuite.name");
|
||||
highlightTestSuiteName.highlighterType(UNIFIED);
|
||||
HighlightBuilder.Field highlightTestSuiteDescription = new HighlightBuilder.Field("testSuite.description");
|
||||
highlightTestSuiteDescription.highlighterType(UNIFIED);
|
||||
HighlightBuilder hb = new HighlightBuilder();
|
||||
hb.field(highlightTestCaseDescription);
|
||||
hb.field(highlightTestCaseName);
|
||||
hb.field(highlightTestSuiteName);
|
||||
hb.field(highlightTestSuiteDescription);
|
||||
|
||||
hb.preTags(PRE_TAG);
|
||||
hb.postTags(POST_TAG);
|
||||
|
||||
return searchBuilder(queryBuilder, hb, from, size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ public final class RestUtil {
|
||||
public static final String DELETED_TEAM_NAME = "DeletedTeam";
|
||||
public static final String DELETED_TEAM_DISPLAY = "Team was deleted";
|
||||
public static final String SIGNATURE_HEADER = "X-OM-Signature";
|
||||
public static final String TEST_CASES_ADDED = "%s Test Cases Added to Test Suite %s";
|
||||
public static final String LOGICAL_TEST_CASES_ADDED = "Logical Test Cases Added to Test Suite";
|
||||
public static final String TEST_CASE_REMOVED_FROM_LOGICAL_TEST_SUITE =
|
||||
"Test case successfuly removed from test suite ID %s";
|
||||
|
||||
@ -146,7 +146,8 @@ public final class RestUtil {
|
||||
ResponseBuilder responseBuilder = Response.status(status).header(CHANGE_CUSTOM_HEADER, changeType);
|
||||
if (changeType.equals(RestUtil.ENTITY_CREATED)
|
||||
|| changeType.equals(RestUtil.ENTITY_UPDATED)
|
||||
|| changeType.equals(RestUtil.ENTITY_NO_CHANGE)) {
|
||||
|| changeType.equals(RestUtil.ENTITY_NO_CHANGE)
|
||||
|| changeType.equals(RestUtil.LOGICAL_TEST_CASES_ADDED)) {
|
||||
return responseBuilder.entity(entity).build();
|
||||
} else {
|
||||
return responseBuilder.entity(changeEvent).build();
|
||||
|
||||
@ -0,0 +1,274 @@
|
||||
{
|
||||
"settings": {
|
||||
"analysis": {
|
||||
"normalizer": {
|
||||
"lowercase_normalizer": {
|
||||
"type": "custom",
|
||||
"char_filter": [],
|
||||
"filter": [
|
||||
"lowercase"
|
||||
]
|
||||
}
|
||||
},
|
||||
"analyzer": {
|
||||
"om_analyzer": {
|
||||
"tokenizer": "letter",
|
||||
"filter": [
|
||||
"lowercase",
|
||||
"om_stemmer"
|
||||
]
|
||||
},
|
||||
"om_ngram": {
|
||||
"tokenizer": "ngram",
|
||||
"min_gram": 1,
|
||||
"max_gram": 2,
|
||||
"filter": [
|
||||
"lowercase"
|
||||
]
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"om_stemmer": {
|
||||
"type": "stemmer",
|
||||
"name": "english"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "text"
|
||||
},
|
||||
"name": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fullyQualifiedName": {
|
||||
"type": "keyword",
|
||||
"normalizer": "lowercase_normalizer"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entityLink": {
|
||||
"type": "text"
|
||||
},
|
||||
"entityFQN": {
|
||||
"type": "keyword",
|
||||
"normalizer": "lowercase_normalizer"
|
||||
},
|
||||
"parameterValues": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"testDefinition": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "text"
|
||||
},
|
||||
"name": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fullyQualifiedName": {
|
||||
"type": "keyword",
|
||||
"normalizer": "lowercase_normalizer"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entityType": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"testPlatforms": {
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "text"
|
||||
},
|
||||
"type": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"type": "keyword",
|
||||
"normalizer": "lowercase_normalizer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"fullyQualifiedName": {
|
||||
"type": "text"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "text"
|
||||
},
|
||||
"href": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"testSuite": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "text"
|
||||
},
|
||||
"name": {
|
||||
"type": "keyword",
|
||||
"normalizer": "lowercase_normalizer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"fullyQualifiedName": {
|
||||
"type": "text"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "text"
|
||||
},
|
||||
"href": {
|
||||
"type": "text"
|
||||
},
|
||||
"executable": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": {
|
||||
"type": "float"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "date",
|
||||
"format": "epoch_second"
|
||||
},
|
||||
"updatedBy": {
|
||||
"type": "text"
|
||||
},
|
||||
"href": {
|
||||
"type": "text"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,6 +95,8 @@ import org.openmetadata.schema.api.data.CreateQuery;
|
||||
import org.openmetadata.schema.api.data.CreateTable;
|
||||
import org.openmetadata.schema.api.data.CreateTableProfile;
|
||||
import org.openmetadata.schema.api.tests.CreateCustomMetric;
|
||||
import org.openmetadata.schema.api.tests.CreateTestCase;
|
||||
import org.openmetadata.schema.api.tests.CreateTestSuite;
|
||||
import org.openmetadata.schema.entity.data.Database;
|
||||
import org.openmetadata.schema.entity.data.DatabaseSchema;
|
||||
import org.openmetadata.schema.entity.data.Query;
|
||||
@ -102,6 +104,7 @@ import org.openmetadata.schema.entity.data.Table;
|
||||
import org.openmetadata.schema.entity.services.DatabaseService;
|
||||
import org.openmetadata.schema.entity.teams.User;
|
||||
import org.openmetadata.schema.tests.CustomMetric;
|
||||
import org.openmetadata.schema.tests.TestSuite;
|
||||
import org.openmetadata.schema.type.ChangeDescription;
|
||||
import org.openmetadata.schema.type.ChangeEvent;
|
||||
import org.openmetadata.schema.type.Column;
|
||||
@ -129,6 +132,8 @@ import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.exception.CatalogExceptionMessage;
|
||||
import org.openmetadata.service.resources.EntityResourceTest;
|
||||
import org.openmetadata.service.resources.databases.TableResource.TableList;
|
||||
import org.openmetadata.service.resources.dqtests.TestCaseResourceTest;
|
||||
import org.openmetadata.service.resources.dqtests.TestSuiteResourceTest;
|
||||
import org.openmetadata.service.resources.glossary.GlossaryResourceTest;
|
||||
import org.openmetadata.service.resources.glossary.GlossaryTermResourceTest;
|
||||
import org.openmetadata.service.resources.query.QueryResource;
|
||||
@ -1288,7 +1293,7 @@ public class TableResourceTest extends EntityResourceTest<Table, CreateTable> {
|
||||
assertEquals(query1.getQuery(), createdQuery.getQuery());
|
||||
assertEquals(query1.getDuration(), createdQuery.getDuration());
|
||||
|
||||
// Update bote
|
||||
// Update bot
|
||||
VoteRequest request = new VoteRequest().withUpdatedVoteType(VoteRequest.VoteType.VOTED_UP);
|
||||
WebTarget target = getResource(String.format("queries/%s/vote", createdQuery.getId().toString()));
|
||||
ChangeEvent changeEvent = TestUtils.put(target, request, ChangeEvent.class, OK, ADMIN_AUTH_HEADERS);
|
||||
@ -1761,6 +1766,57 @@ public class TableResourceTest extends EntityResourceTest<Table, CreateTable> {
|
||||
assertEquals("P30D", table.getRetentionPeriod());
|
||||
}
|
||||
|
||||
@Test
|
||||
void get_tablesWithTestCases(TestInfo test) throws IOException {
|
||||
TestCaseResourceTest testCaseResourceTest = new TestCaseResourceTest();
|
||||
TestSuiteResourceTest testSuiteResourceTest = new TestSuiteResourceTest();
|
||||
DatabaseSchemaResourceTest schemaResourceTest = new DatabaseSchemaResourceTest();
|
||||
DatabaseResourceTest databaseTest = new DatabaseResourceTest();
|
||||
|
||||
// Create Database
|
||||
CreateDatabase createDatabase = databaseTest.createRequest(getEntityName(test));
|
||||
Database database = databaseTest.createEntity(createDatabase, ADMIN_AUTH_HEADERS);
|
||||
// Create Database Schema
|
||||
CreateDatabaseSchema createDatabaseSchema =
|
||||
schemaResourceTest.createRequest(test).withDatabase(database.getFullyQualifiedName());
|
||||
DatabaseSchema schema =
|
||||
schemaResourceTest
|
||||
.createEntity(createDatabaseSchema, ADMIN_AUTH_HEADERS)
|
||||
.withDatabase(database.getEntityReference());
|
||||
schema = schemaResourceTest.getEntity(schema.getId(), "", ADMIN_AUTH_HEADERS);
|
||||
// Create Table 1
|
||||
CreateTable createTable1 = createRequest(test).withDatabaseSchema(schema.getFullyQualifiedName());
|
||||
Table table1 = createEntity(createTable1, ADMIN_AUTH_HEADERS).withDatabase(database.getEntityReference());
|
||||
// Create Table 2
|
||||
CreateTable createTable2 =
|
||||
createRequest(test.getClass().getName() + "2").withDatabaseSchema(schema.getFullyQualifiedName());
|
||||
createEntity(createTable2, ADMIN_AUTH_HEADERS).withDatabase(database.getEntityReference());
|
||||
// Create Executable Test Suite
|
||||
CreateTestSuite createExecutableTestSuite = testSuiteResourceTest.createRequest(table1.getFullyQualifiedName());
|
||||
TestSuite executableTestSuite =
|
||||
testSuiteResourceTest.createExecutableTestSuite(createExecutableTestSuite, ADMIN_AUTH_HEADERS);
|
||||
|
||||
HashMap<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("includeEmptyTestSuite", "false");
|
||||
queryParams.put("fields", "testSuite");
|
||||
queryParams.put("limit", "100");
|
||||
ResultList<Table> tables = listEntities(queryParams, ADMIN_AUTH_HEADERS);
|
||||
assertTrue(tables.getData().isEmpty());
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
CreateTestCase create =
|
||||
testCaseResourceTest
|
||||
.createRequest("test_testSuite__" + i)
|
||||
.withTestSuite(executableTestSuite.getFullyQualifiedName());
|
||||
testCaseResourceTest.createAndCheckEntity(create, ADMIN_AUTH_HEADERS);
|
||||
}
|
||||
|
||||
tables = listEntities(queryParams, ADMIN_AUTH_HEADERS);
|
||||
assertEquals(1, tables.getData().size());
|
||||
assertEquals(table1.getId(), tables.getData().get(0).getId());
|
||||
assertNotNull(tables.getData().get(0).getTestSuite());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_sensitivePIISampleData(TestInfo test) throws IOException {
|
||||
// Create table with owner and a column tagged with PII.Sensitive
|
||||
|
||||
@ -50,6 +50,7 @@ import org.openmetadata.schema.tests.type.TestCaseFailureStatus;
|
||||
import org.openmetadata.schema.tests.type.TestCaseFailureStatusType;
|
||||
import org.openmetadata.schema.tests.type.TestCaseResult;
|
||||
import org.openmetadata.schema.tests.type.TestCaseStatus;
|
||||
import org.openmetadata.schema.tests.type.TestSummary;
|
||||
import org.openmetadata.schema.type.ChangeDescription;
|
||||
import org.openmetadata.schema.type.Column;
|
||||
import org.openmetadata.schema.type.ColumnDataType;
|
||||
@ -335,6 +336,11 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
TestUtils.dateToTimestamp("2021-10-15"),
|
||||
ADMIN_AUTH_HEADERS);
|
||||
verifyTestCaseResults(testCaseResults, testCase1ResultList, 4);
|
||||
|
||||
TestSummary testSummary = getTestSummary(ADMIN_AUTH_HEADERS);
|
||||
assertEquals(2, testSummary.getFailed());
|
||||
assertEquals(2, testSummary.getSuccess());
|
||||
assertEquals(0, testSummary.getAborted());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -686,6 +692,11 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
return TestUtils.get(target, TestCaseResource.TestCaseResultList.class, authHeaders);
|
||||
}
|
||||
|
||||
private TestSummary getTestSummary(Map<String, String> authHeaders) throws IOException {
|
||||
WebTarget target = getCollection().path("/executionSummary");
|
||||
return TestUtils.get(target, TestSummary.class, authHeaders);
|
||||
}
|
||||
|
||||
public ResultList<TestCase> getTestCases(
|
||||
Integer limit, String fields, String link, Boolean includeAll, Map<String, String> authHeaders)
|
||||
throws HttpResponseException {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
package org.openmetadata.service.resources.dqtests;
|
||||
|
||||
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||
import static javax.ws.rs.core.Response.Status.CONFLICT;
|
||||
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
|
||||
import static org.openmetadata.service.util.TestUtils.LONG_ENTITY_NAME;
|
||||
@ -107,7 +107,6 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
void put_testCaseResults_200() throws IOException {
|
||||
TestCaseResourceTest testCaseResourceTest = new TestCaseResourceTest();
|
||||
List<EntityReference> testCases1 = new ArrayList<>();
|
||||
List<EntityReference> testCases2 = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
CreateTestCase createTestCase =
|
||||
@ -121,8 +120,7 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
testCaseResourceTest
|
||||
.createRequest("test_testSuite_2_" + i)
|
||||
.withTestSuite(TEST_SUITE2.getFullyQualifiedName());
|
||||
TestCase testCase = testCaseResourceTest.createAndCheckEntity(create, ADMIN_AUTH_HEADERS);
|
||||
testCases2.add(testCase.getEntityReference());
|
||||
testCaseResourceTest.createAndCheckEntity(create, ADMIN_AUTH_HEADERS);
|
||||
}
|
||||
|
||||
ResultList<TestSuite> actualTestSuites = getTestSuites(10, "*", ADMIN_AUTH_HEADERS);
|
||||
@ -133,7 +131,7 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
verifyTestCases(testSuite.getTests(), testCases1);
|
||||
}
|
||||
}
|
||||
deleteEntity(TEST_SUITE1.getId(), true, false, ADMIN_AUTH_HEADERS);
|
||||
deleteExecutableTestSuite(TEST_SUITE1.getId(), true, false, ADMIN_AUTH_HEADERS);
|
||||
assertResponse(
|
||||
() -> getEntity(TEST_SUITE1.getId(), ADMIN_AUTH_HEADERS),
|
||||
NOT_FOUND,
|
||||
@ -221,12 +219,12 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
addTestCasesToLogicalTestSuite(
|
||||
executableTestSuite,
|
||||
testCases1.stream().map(testCaseId -> testCaseId.getId()).collect(Collectors.toList())),
|
||||
CONFLICT,
|
||||
"Entity already exists");
|
||||
BAD_REQUEST,
|
||||
"You are trying to add test cases to an executable test suite.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void post_createExecTestSuiteNonExistingEntity_400(TestInfo test) throws IOException {
|
||||
void post_createExecTestSuiteNonExistingEntity_400(TestInfo test) {
|
||||
CreateTestSuite createTestSuite = createRequest(test);
|
||||
assertResponse(
|
||||
() -> createExecutableTestSuite(createTestSuite, ADMIN_AUTH_HEADERS),
|
||||
@ -237,6 +235,7 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
@Test
|
||||
void get_execTestSuiteFromTable_200(TestInfo test) throws IOException {
|
||||
TableResourceTest tableResourceTest = new TableResourceTest();
|
||||
TestCaseResourceTest testCaseResourceTest = new TestCaseResourceTest();
|
||||
CreateTable tableReq =
|
||||
tableResourceTest
|
||||
.createRequest(test)
|
||||
@ -251,18 +250,28 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
CreateTestSuite createTestSuite = createRequest(table.getFullyQualifiedName());
|
||||
TestSuite testSuite = createExecutableTestSuite(createTestSuite, ADMIN_AUTH_HEADERS);
|
||||
|
||||
// We'll create tests cases for testSuite
|
||||
for (int i = 0; i < 5; i++) {
|
||||
CreateTestCase createTestCase =
|
||||
testCaseResourceTest
|
||||
.createRequest(String.format("test_testSuite_2_%s_", test.getDisplayName()) + i)
|
||||
.withTestSuite(testSuite.getFullyQualifiedName());
|
||||
testCaseResourceTest.createAndCheckEntity(createTestCase, ADMIN_AUTH_HEADERS);
|
||||
}
|
||||
|
||||
Table actualTable = tableResourceTest.getEntity(table.getId(), "testSuite", ADMIN_AUTH_HEADERS);
|
||||
TestSuite tableTestSuite = actualTable.getTestSuite();
|
||||
assertEquals(testSuite.getId(), tableTestSuite.getId());
|
||||
assertEquals(5, tableTestSuite.getTests().size());
|
||||
|
||||
// Soft delete entity
|
||||
deleteEntity(tableTestSuite.getId(), ADMIN_AUTH_HEADERS);
|
||||
deleteExecutableTestSuite(tableTestSuite.getId(), true, false, ADMIN_AUTH_HEADERS);
|
||||
actualTable = tableResourceTest.getEntity(actualTable.getId(), "testSuite", ADMIN_AUTH_HEADERS);
|
||||
tableTestSuite = actualTable.getTestSuite();
|
||||
assertEquals(tableTestSuite.getDeleted(), true);
|
||||
|
||||
// Hard delete entity
|
||||
deleteEntity(tableTestSuite.getId(), true, true, ADMIN_AUTH_HEADERS);
|
||||
deleteExecutableTestSuite(tableTestSuite.getId(), true, true, ADMIN_AUTH_HEADERS);
|
||||
actualTable = tableResourceTest.getEntity(table.getId(), "testSuite", ADMIN_AUTH_HEADERS);
|
||||
assertNull(actualTable.getTestSuite());
|
||||
}
|
||||
@ -302,12 +311,34 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
String.format("testSuite instance for %s not found", testSuite.getFullyQualifiedName()));
|
||||
}
|
||||
|
||||
public ResultList<TestSuite> getTestSuites(Integer limit, String fields, Map<String, String> authHeaders)
|
||||
throws HttpResponseException {
|
||||
WebTarget target = getResource("dataQuality/testSuites");
|
||||
target = limit != null ? target.queryParam("limit", limit) : target;
|
||||
target = target.queryParam("fields", fields);
|
||||
return TestUtils.get(target, TestSuiteResource.TestSuiteList.class, authHeaders);
|
||||
@Test
|
||||
void get_filterTestSuiteType_200(TestInfo test) throws IOException {
|
||||
// Create a logical test suite
|
||||
CreateTestSuite createTestSuite = createRequest(test);
|
||||
createEntity(createTestSuite, ADMIN_AUTH_HEADERS);
|
||||
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
|
||||
ResultList<TestSuite> testSuiteResultList = listEntities(queryParams, ADMIN_AUTH_HEADERS);
|
||||
assertEquals(10, testSuiteResultList.getData().size());
|
||||
|
||||
queryParams.put("testSuiteType", "executable");
|
||||
testSuiteResultList = listEntities(queryParams, ADMIN_AUTH_HEADERS);
|
||||
testSuiteResultList
|
||||
.getData()
|
||||
.forEach(
|
||||
ts -> {
|
||||
assertEquals(true, ts.getExecutable());
|
||||
});
|
||||
|
||||
queryParams.put("testSuiteType", "logical");
|
||||
testSuiteResultList = listEntities(queryParams, ADMIN_AUTH_HEADERS);
|
||||
testSuiteResultList
|
||||
.getData()
|
||||
.forEach(
|
||||
ts -> {
|
||||
assertEquals(false, ts.getExecutable());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -333,6 +364,57 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
TestUtils.getEntityNameLengthError(entityClass));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete_LogicalTestSuite_200(TestInfo test) throws IOException {
|
||||
TestCaseResourceTest testCaseResourceTest = new TestCaseResourceTest();
|
||||
TableResourceTest tableResourceTest = new TableResourceTest();
|
||||
CreateTable tableReq =
|
||||
tableResourceTest
|
||||
.createRequest(test)
|
||||
.withColumns(
|
||||
List.of(
|
||||
new Column()
|
||||
.withName(C1)
|
||||
.withDisplayName("c1")
|
||||
.withDataType(ColumnDataType.VARCHAR)
|
||||
.withDataLength(10)));
|
||||
Table table = tableResourceTest.createEntity(tableReq, ADMIN_AUTH_HEADERS);
|
||||
CreateTestSuite createExecutableTestSuite = createRequest(table.getFullyQualifiedName());
|
||||
TestSuite executableTestSuite = createExecutableTestSuite(createExecutableTestSuite, ADMIN_AUTH_HEADERS);
|
||||
List<EntityReference> testCases = new ArrayList<>();
|
||||
|
||||
// We'll create tests cases for testSuite1
|
||||
for (int i = 0; i < 5; i++) {
|
||||
CreateTestCase createTestCase =
|
||||
testCaseResourceTest
|
||||
.createRequest(String.format("test_testSuite_2_%s_", test.getDisplayName()) + i)
|
||||
.withTestSuite(executableTestSuite.getFullyQualifiedName());
|
||||
TestCase testCase = testCaseResourceTest.createAndCheckEntity(createTestCase, ADMIN_AUTH_HEADERS);
|
||||
testCases.add(testCase.getEntityReference());
|
||||
}
|
||||
|
||||
// We'll create a logical test suite and associate the test cases to it
|
||||
CreateTestSuite createTestSuite = createRequest(test);
|
||||
TestSuite testSuite = createEntity(createTestSuite, ADMIN_AUTH_HEADERS);
|
||||
addTestCasesToLogicalTestSuite(
|
||||
testSuite, testCases.stream().map(testCaseId -> testCaseId.getId()).collect(Collectors.toList()));
|
||||
|
||||
// We'll delete the logical test suite
|
||||
deleteEntity(testSuite.getId(), true, true, ADMIN_AUTH_HEADERS);
|
||||
|
||||
// We'll check that the test cases are still present in the executable test suite
|
||||
TestSuite actualExecutableTestSuite = getEntity(executableTestSuite.getId(), "*", ADMIN_AUTH_HEADERS);
|
||||
assertEquals(actualExecutableTestSuite.getTests().size(), 5);
|
||||
}
|
||||
|
||||
public ResultList<TestSuite> getTestSuites(Integer limit, String fields, Map<String, String> authHeaders)
|
||||
throws HttpResponseException {
|
||||
WebTarget target = getResource("dataQuality/testSuites");
|
||||
target = limit != null ? target.queryParam("limit", limit) : target;
|
||||
target = target.queryParam("fields", fields);
|
||||
return TestUtils.get(target, TestSuiteResource.TestSuiteList.class, authHeaders);
|
||||
}
|
||||
|
||||
public TestSuite createExecutableTestSuite(CreateTestSuite createTestSuite, Map<String, String> authHeaders)
|
||||
throws IOException {
|
||||
WebTarget target = getResource("dataQuality/testSuites/executable");
|
||||
@ -347,6 +429,14 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
TestUtils.put(target, createLogicalTestCases, Response.Status.OK, ADMIN_AUTH_HEADERS);
|
||||
}
|
||||
|
||||
public void deleteExecutableTestSuite(UUID id, boolean recursive, boolean hardDelete, Map<String, String> authHeaders)
|
||||
throws IOException {
|
||||
WebTarget target = getResource(String.format("dataQuality/testSuites/executable/%s", id.toString()));
|
||||
target = recursive ? target.queryParam("recursive", true) : target;
|
||||
target = hardDelete ? target.queryParam("hardDelete", true) : target;
|
||||
TestUtils.delete(target, TestSuite.class, authHeaders);
|
||||
}
|
||||
|
||||
private void verifyTestSuites(ResultList<TestSuite> actualTestSuites, List<CreateTestSuite> expectedTestSuites) {
|
||||
Map<String, TestSuite> testSuiteMap = new HashMap<>();
|
||||
for (TestSuite result : actualTestSuites.getData()) {
|
||||
@ -356,6 +446,7 @@ public class TestSuiteResourceTest extends EntityResourceTest<TestSuite, CreateT
|
||||
TestSuite storedTestSuite = testSuiteMap.get(result.getName());
|
||||
if (storedTestSuite == null) continue;
|
||||
validateCreatedEntity(storedTestSuite, result, ADMIN_AUTH_HEADERS);
|
||||
assertNotNull(storedTestSuite.getSummary());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,29 @@
|
||||
"title": "Basic",
|
||||
"description": "This schema defines basic types that are used by other test schemas.",
|
||||
"definitions": {
|
||||
"testSummary": {
|
||||
"description": "Schema to capture test case execution summary.",
|
||||
"javaType": "org.openmetadata.schema.tests.type.TestSummary",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"description": "Number of test cases that passed.",
|
||||
"type": "integer"
|
||||
},
|
||||
"failed": {
|
||||
"description": "Number of test cases that failed.",
|
||||
"type": "integer"
|
||||
},
|
||||
"aborted": {
|
||||
"description": "Number of test cases that aborted.",
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"description": "Total number of test cases.",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"testResultValue": {
|
||||
"description": "Schema to capture test case result values.",
|
||||
"javaType": "org.openmetadata.schema.tests.type.TestResultValue",
|
||||
|
||||
@ -105,8 +105,12 @@
|
||||
"executableEntityReference": {
|
||||
"description": "Entity reference the test suite is executed against. Only applicable if the test suite is executable.",
|
||||
"$ref": "../type/entityReference.json"
|
||||
},
|
||||
"summary": {
|
||||
"description": "Summary of the previous day test cases execution for this test suite.",
|
||||
"$ref": "./basic.json#/definitions/testSummary"
|
||||
}
|
||||
},
|
||||
"required": ["name", "description"],
|
||||
"required": ["name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user