mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-06 08:37:22 +00:00
MINOR - Create Test Case Resolution ts entry & delete resolution when… (#14541)
* MINOR - Create Test Case Resolution ts entry & delete resolution when Test Case is deleted
This commit is contained in:
parent
a07ef89cbb
commit
0255171218
@ -19,10 +19,6 @@
|
|||||||
],
|
],
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"sequenceOne": [
|
"sequenceOne": [
|
||||||
{
|
|
||||||
"testCaseResolutionStatusType": "New",
|
|
||||||
"severity": "Severity1"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testCaseResolutionStatusType": "Ack",
|
"testCaseResolutionStatusType": "Ack",
|
||||||
"severity": "Severity1"
|
"severity": "Severity1"
|
||||||
@ -108,10 +104,6 @@
|
|||||||
],
|
],
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"sequenceOne": [
|
"sequenceOne": [
|
||||||
{
|
|
||||||
"testCaseResolutionStatusType": "New",
|
|
||||||
"severity": "Severity1"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testCaseResolutionStatusType": "Ack",
|
"testCaseResolutionStatusType": "Ack",
|
||||||
"severity": "Severity1"
|
"severity": "Severity1"
|
||||||
@ -186,10 +178,6 @@
|
|||||||
],
|
],
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"sequenceOne": [
|
"sequenceOne": [
|
||||||
{
|
|
||||||
"testCaseResolutionStatusType": "New",
|
|
||||||
"severity": "Severity1"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testCaseResolutionStatusType": "Ack",
|
"testCaseResolutionStatusType": "Ack",
|
||||||
"severity": "Severity1"
|
"severity": "Severity1"
|
||||||
@ -264,10 +252,6 @@
|
|||||||
],
|
],
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"sequenceOne": [
|
"sequenceOne": [
|
||||||
{
|
|
||||||
"testCaseResolutionStatusType": "New",
|
|
||||||
"severity": "Severity1"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"testCaseResolutionStatusType": "Ack",
|
"testCaseResolutionStatusType": "Ack",
|
||||||
"severity": "Severity1"
|
"severity": "Severity1"
|
||||||
|
@ -17,6 +17,9 @@ from typing import List
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from metadata.generated.schema.api.tests.createTestCase import CreateTestCaseRequest
|
from metadata.generated.schema.api.tests.createTestCase import CreateTestCaseRequest
|
||||||
|
from metadata.generated.schema.api.tests.createTestCaseResolutionStatus import (
|
||||||
|
CreateTestCaseResolutionStatus,
|
||||||
|
)
|
||||||
from metadata.generated.schema.api.tests.createTestSuite import CreateTestSuiteRequest
|
from metadata.generated.schema.api.tests.createTestSuite import CreateTestSuiteRequest
|
||||||
from metadata.generated.schema.tests.basic import TestCaseResult
|
from metadata.generated.schema.tests.basic import TestCaseResult
|
||||||
from metadata.generated.schema.tests.testCase import TestCase
|
from metadata.generated.schema.tests.testCase import TestCase
|
||||||
@ -38,3 +41,9 @@ class OMetaTestCaseSample(BaseModel):
|
|||||||
class OMetaTestCaseResultsSample(BaseModel):
|
class OMetaTestCaseResultsSample(BaseModel):
|
||||||
test_case_results: TestCaseResult
|
test_case_results: TestCaseResult
|
||||||
test_case_name: str
|
test_case_name: str
|
||||||
|
|
||||||
|
|
||||||
|
class OMetaTestCaseResolutionStatus(BaseModel):
|
||||||
|
"""For sample data"""
|
||||||
|
|
||||||
|
test_case_resolution: CreateTestCaseResolutionStatus
|
||||||
|
@ -47,6 +47,9 @@ from metadata.generated.schema.entity.teams.team import Team
|
|||||||
from metadata.generated.schema.entity.teams.user import User
|
from metadata.generated.schema.entity.teams.user import User
|
||||||
from metadata.generated.schema.tests.basic import TestCaseResult
|
from metadata.generated.schema.tests.basic import TestCaseResult
|
||||||
from metadata.generated.schema.tests.testCase import TestCase
|
from metadata.generated.schema.tests.testCase import TestCase
|
||||||
|
from metadata.generated.schema.tests.testCaseResolutionStatus import (
|
||||||
|
TestCaseResolutionStatus,
|
||||||
|
)
|
||||||
from metadata.generated.schema.tests.testSuite import TestSuite
|
from metadata.generated.schema.tests.testSuite import TestSuite
|
||||||
from metadata.generated.schema.type.schema import Topic
|
from metadata.generated.schema.type.schema import Topic
|
||||||
from metadata.ingestion.api.models import Either, Entity, StackTraceError
|
from metadata.ingestion.api.models import Either, Entity, StackTraceError
|
||||||
@ -67,6 +70,7 @@ from metadata.ingestion.models.profile_data import OMetaTableProfileSampleData
|
|||||||
from metadata.ingestion.models.search_index_data import OMetaIndexSampleData
|
from metadata.ingestion.models.search_index_data import OMetaIndexSampleData
|
||||||
from metadata.ingestion.models.tests_data import (
|
from metadata.ingestion.models.tests_data import (
|
||||||
OMetaLogicalTestSuiteSample,
|
OMetaLogicalTestSuiteSample,
|
||||||
|
OMetaTestCaseResolutionStatus,
|
||||||
OMetaTestCaseResultsSample,
|
OMetaTestCaseResultsSample,
|
||||||
OMetaTestCaseSample,
|
OMetaTestCaseSample,
|
||||||
OMetaTestSuiteSample,
|
OMetaTestSuiteSample,
|
||||||
@ -411,6 +415,15 @@ class MetadataRestSink(Sink): # pylint: disable=too-many-public-methods
|
|||||||
)
|
)
|
||||||
return Either(right=res)
|
return Either(right=res)
|
||||||
|
|
||||||
|
@_run_dispatch.register
|
||||||
|
def write_test_case_resolution_status(
|
||||||
|
self, record: OMetaTestCaseResolutionStatus
|
||||||
|
) -> TestCaseResolutionStatus:
|
||||||
|
"""For sample data"""
|
||||||
|
res = self.metadata.create_test_case_resolution(record.test_case_resolution)
|
||||||
|
|
||||||
|
return Either(right=res)
|
||||||
|
|
||||||
@_run_dispatch.register
|
@_run_dispatch.register
|
||||||
def write_data_insight_sample(
|
def write_data_insight_sample(
|
||||||
self, record: OMetaDataInsightSample
|
self, record: OMetaDataInsightSample
|
||||||
|
@ -118,6 +118,7 @@ from metadata.ingestion.models.pipeline_status import OMetaPipelineStatus
|
|||||||
from metadata.ingestion.models.profile_data import OMetaTableProfileSampleData
|
from metadata.ingestion.models.profile_data import OMetaTableProfileSampleData
|
||||||
from metadata.ingestion.models.tests_data import (
|
from metadata.ingestion.models.tests_data import (
|
||||||
OMetaLogicalTestSuiteSample,
|
OMetaLogicalTestSuiteSample,
|
||||||
|
OMetaTestCaseResolutionStatus,
|
||||||
OMetaTestCaseResultsSample,
|
OMetaTestCaseResultsSample,
|
||||||
OMetaTestCaseSample,
|
OMetaTestCaseSample,
|
||||||
OMetaTestSuiteSample,
|
OMetaTestSuiteSample,
|
||||||
@ -566,6 +567,7 @@ class SampleDataSource(
|
|||||||
yield from self.ingest_test_suite()
|
yield from self.ingest_test_suite()
|
||||||
yield from self.ingest_test_case()
|
yield from self.ingest_test_case()
|
||||||
yield from self.ingest_test_case_results()
|
yield from self.ingest_test_case_results()
|
||||||
|
yield from self.ingest_incidents()
|
||||||
yield from self.ingest_logical_test_suite()
|
yield from self.ingest_logical_test_suite()
|
||||||
yield from self.ingest_data_insights()
|
yield from self.ingest_data_insights()
|
||||||
yield from self.ingest_life_cycle()
|
yield from self.ingest_life_cycle()
|
||||||
@ -1408,6 +1410,15 @@ class SampleDataSource(
|
|||||||
)
|
)
|
||||||
yield Either(right=test_case_req)
|
yield Either(right=test_case_req)
|
||||||
|
|
||||||
|
def ingest_incidents(self) -> Iterable[Either[OMetaTestCaseResolutionStatus]]:
|
||||||
|
"""
|
||||||
|
Ingest incidents after the first test failures have been added.
|
||||||
|
|
||||||
|
The test failure already creates the incident with NEW, so we
|
||||||
|
start always from ACK in the sample flows.
|
||||||
|
"""
|
||||||
|
for test_suite in self.tests_suites["tests"]:
|
||||||
|
for test_case in test_suite["testCases"]:
|
||||||
test_case_fqn = f"{entity_link.get_table_or_column_fqn(test_case['entityLink'])}.{test_case['name']}"
|
test_case_fqn = f"{entity_link.get_table_or_column_fqn(test_case['entityLink'])}.{test_case['name']}"
|
||||||
|
|
||||||
for _, resolutions in test_case["resolutions"].items():
|
for _, resolutions in test_case["resolutions"].items():
|
||||||
@ -1449,8 +1460,10 @@ class SampleDataSource(
|
|||||||
testCaseFailureComment="Resolution comment",
|
testCaseFailureComment="Resolution comment",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.metadata.create_test_case_resolution(
|
yield Either(
|
||||||
create_test_case_resolution
|
right=OMetaTestCaseResolutionStatus(
|
||||||
|
test_case_resolution=create_test_case_resolution
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def ingest_test_case_results(self) -> Iterable[Either[OMetaTestCaseResultsSample]]:
|
def ingest_test_case_results(self) -> Iterable[Either[OMetaTestCaseResultsSample]]:
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package org.openmetadata.service.exception;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.openmetadata.schema.tests.type.TestCaseResolutionStatusTypes;
|
||||||
|
import org.openmetadata.sdk.exception.WebServiceException;
|
||||||
|
|
||||||
|
public class IncidentManagerException extends WebServiceException {
|
||||||
|
|
||||||
|
protected IncidentManagerException(Response.Status status, String message) {
|
||||||
|
super(status.getStatusCode(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncidentManagerException(String message) {
|
||||||
|
super(Response.Status.INTERNAL_SERVER_ERROR, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IncidentManagerException invalidStatus(
|
||||||
|
TestCaseResolutionStatusTypes lastStatus, TestCaseResolutionStatusTypes newStatus) {
|
||||||
|
return new IncidentManagerException(
|
||||||
|
Response.Status.BAD_REQUEST,
|
||||||
|
String.format("Incident with status [%s] cannot be moved to [%s]", lastStatus, newStatus));
|
||||||
|
}
|
||||||
|
}
|
@ -3592,6 +3592,32 @@ public interface CollectionDAO {
|
|||||||
default String getTimeSeriesTableName() {
|
default String getTimeSeriesTableName() {
|
||||||
return "data_quality_data_time_series";
|
return "data_quality_data_time_series";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConnectionAwareSqlQuery(
|
||||||
|
value =
|
||||||
|
"SELECT json FROM data_quality_data_time_series where entityFQNHash = :testCaseFQNHash "
|
||||||
|
+ "AND JSON_EXTRACT(json, '$.incidentId') IS NOT NULL",
|
||||||
|
connectionType = MYSQL)
|
||||||
|
@ConnectionAwareSqlQuery(
|
||||||
|
value =
|
||||||
|
"SELECT json FROM data_quality_data_time_series where entityFQNHash = :testCaseFQNHash "
|
||||||
|
+ "AND json ->> 'incidentId' IS NOT NULL",
|
||||||
|
connectionType = POSTGRES)
|
||||||
|
List<String> getResultsWithIncidents(@Bind("testCaseFQNHash") String testCaseFQNHash);
|
||||||
|
|
||||||
|
@ConnectionAwareSqlUpdate(
|
||||||
|
value =
|
||||||
|
"SELECT json FROM data_quality_data_time_series where entityFQNHash = :entityFQNHash "
|
||||||
|
+ "AND JSON_EXTRACT(json, '$.incidentId') IS NOT NULL",
|
||||||
|
connectionType = MYSQL)
|
||||||
|
@ConnectionAwareSqlUpdate(
|
||||||
|
value =
|
||||||
|
"SELECT json FROM data_quality_data_time_series where entityFQNHash = :entityFQNHash "
|
||||||
|
+ "AND json ->> 'incidentId' IS NOT NULL",
|
||||||
|
connectionType = POSTGRES)
|
||||||
|
// TODO: need to find the right way to get this cleaned
|
||||||
|
void cleanTestCaseIncidents(
|
||||||
|
@Bind("entityFQNHash") String entityFQNHash, @Bind("stateId") String stateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TestCaseResolutionStatusTimeSeriesDAO extends EntityTimeSeriesDAO {
|
interface TestCaseResolutionStatusTimeSeriesDAO extends EntityTimeSeriesDAO {
|
||||||
@ -3605,6 +3631,10 @@ public interface CollectionDAO {
|
|||||||
"SELECT json FROM test_case_resolution_status_time_series "
|
"SELECT json FROM test_case_resolution_status_time_series "
|
||||||
+ "WHERE stateId = :stateId ORDER BY timestamp DESC")
|
+ "WHERE stateId = :stateId ORDER BY timestamp DESC")
|
||||||
List<String> listTestCaseResolutionStatusesForStateId(@Bind("stateId") String stateId);
|
List<String> listTestCaseResolutionStatusesForStateId(@Bind("stateId") String stateId);
|
||||||
|
|
||||||
|
@SqlUpdate(
|
||||||
|
"DELETE FROM test_case_resolution_status_time_series WHERE entityFQNHash = :entityFQNHash")
|
||||||
|
void delete(@BindFQN("entityFQNHash") String entityFQNHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntitiesCountRowMapper implements RowMapper<EntitiesCount> {
|
class EntitiesCountRowMapper implements RowMapper<EntitiesCount> {
|
||||||
|
@ -19,6 +19,7 @@ public abstract class EntityTimeSeriesRepository<T extends EntityTimeSeriesInter
|
|||||||
@Getter protected final SearchRepository searchRepository;
|
@Getter protected final SearchRepository searchRepository;
|
||||||
@Getter protected final String entityType;
|
@Getter protected final String entityType;
|
||||||
@Getter protected final Class<T> entityClass;
|
@Getter protected final Class<T> entityClass;
|
||||||
|
@Getter protected final CollectionDAO daoCollection;
|
||||||
|
|
||||||
protected EntityTimeSeriesRepository(
|
protected EntityTimeSeriesRepository(
|
||||||
String collectionPath,
|
String collectionPath,
|
||||||
@ -30,6 +31,7 @@ public abstract class EntityTimeSeriesRepository<T extends EntityTimeSeriesInter
|
|||||||
this.entityClass = entityClass;
|
this.entityClass = entityClass;
|
||||||
this.entityType = entityType;
|
this.entityType = entityType;
|
||||||
this.searchRepository = Entity.getSearchRepository();
|
this.searchRepository = Entity.getSearchRepository();
|
||||||
|
this.daoCollection = Entity.getCollectionDAO();
|
||||||
Entity.registerEntity(entityClass, entityType, this);
|
Entity.registerEntity(entityClass, entityType, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -725,7 +725,7 @@ public class FeedRepository {
|
|||||||
|
|
||||||
restorePatchAttributes(original, updated);
|
restorePatchAttributes(original, updated);
|
||||||
|
|
||||||
if (!updated.getReactions().isEmpty()) {
|
if (!nullOrEmpty(updated.getReactions())) {
|
||||||
populateUserReactions(updated.getReactions());
|
populateUserReactions(updated.getReactions());
|
||||||
updated
|
updated
|
||||||
.getReactions()
|
.getReactions()
|
||||||
@ -885,8 +885,10 @@ public class FeedRepository {
|
|||||||
&& !Collections.isEmpty(updated.getReactions()))
|
&& !Collections.isEmpty(updated.getReactions()))
|
||||||
|| (!Collections.isEmpty(original.getReactions())
|
|| (!Collections.isEmpty(original.getReactions())
|
||||||
&& Collections.isEmpty(updated.getReactions()))
|
&& Collections.isEmpty(updated.getReactions()))
|
||||||
|| original.getReactions().size() != updated.getReactions().size()
|
|| (original.getReactions() != null
|
||||||
|| !original.getReactions().containsAll(updated.getReactions())
|
&& updated.getReactions() != null
|
||||||
|
&& (original.getReactions().size() != updated.getReactions().size()
|
||||||
|
|| !original.getReactions().containsAll(updated.getReactions())))
|
||||||
|| (original.getAnnouncement() != null
|
|| (original.getAnnouncement() != null
|
||||||
&& (!original
|
&& (!original
|
||||||
.getAnnouncement()
|
.getAnnouncement()
|
||||||
|
@ -15,6 +15,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.json.JsonPatch;
|
import javax.json.JsonPatch;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
@ -29,8 +30,8 @@ import org.openmetadata.schema.tests.TestCaseParameter;
|
|||||||
import org.openmetadata.schema.tests.TestCaseParameterValue;
|
import org.openmetadata.schema.tests.TestCaseParameterValue;
|
||||||
import org.openmetadata.schema.tests.TestDefinition;
|
import org.openmetadata.schema.tests.TestDefinition;
|
||||||
import org.openmetadata.schema.tests.TestSuite;
|
import org.openmetadata.schema.tests.TestSuite;
|
||||||
import org.openmetadata.schema.tests.type.Assigned;
|
|
||||||
import org.openmetadata.schema.tests.type.Resolved;
|
import org.openmetadata.schema.tests.type.Resolved;
|
||||||
|
import org.openmetadata.schema.tests.type.TestCaseFailureReasonType;
|
||||||
import org.openmetadata.schema.tests.type.TestCaseResolutionStatus;
|
import org.openmetadata.schema.tests.type.TestCaseResolutionStatus;
|
||||||
import org.openmetadata.schema.tests.type.TestCaseResolutionStatusTypes;
|
import org.openmetadata.schema.tests.type.TestCaseResolutionStatusTypes;
|
||||||
import org.openmetadata.schema.tests.type.TestCaseResult;
|
import org.openmetadata.schema.tests.type.TestCaseResult;
|
||||||
@ -57,6 +58,7 @@ import org.openmetadata.service.util.ResultList;
|
|||||||
public class TestCaseRepository extends EntityRepository<TestCase> {
|
public class TestCaseRepository extends EntityRepository<TestCase> {
|
||||||
private static final String TEST_SUITE_FIELD = "testSuite";
|
private static final String TEST_SUITE_FIELD = "testSuite";
|
||||||
private static final String TEST_CASE_RESULT_FIELD = "testCaseResult";
|
private static final String TEST_CASE_RESULT_FIELD = "testCaseResult";
|
||||||
|
private static final String INCIDENTS_FIELD = "incidents";
|
||||||
public static final String COLLECTION_PATH = "/v1/dataQuality/testCases";
|
public static final String COLLECTION_PATH = "/v1/dataQuality/testCases";
|
||||||
private static final String UPDATE_FIELDS = "owner,entityLink,testSuite,testDefinition";
|
private static final String UPDATE_FIELDS = "owner,entityLink,testSuite,testDefinition";
|
||||||
private static final String PATCH_FIELDS = "owner,entityLink,testSuite,testDefinition";
|
private static final String PATCH_FIELDS = "owner,entityLink,testSuite,testDefinition";
|
||||||
@ -79,10 +81,12 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
test.setTestSuite(fields.contains(TEST_SUITE_FIELD) ? getTestSuite(test) : test.getTestSuite());
|
test.setTestSuite(fields.contains(TEST_SUITE_FIELD) ? getTestSuite(test) : test.getTestSuite());
|
||||||
test.setTestDefinition(
|
test.setTestDefinition(
|
||||||
fields.contains(TEST_DEFINITION) ? getTestDefinition(test) : test.getTestDefinition());
|
fields.contains(TEST_DEFINITION) ? getTestDefinition(test) : test.getTestDefinition());
|
||||||
test.withTestCaseResult(
|
test.setTestCaseResult(
|
||||||
fields.contains(TEST_CASE_RESULT_FIELD)
|
fields.contains(TEST_CASE_RESULT_FIELD)
|
||||||
? getTestCaseResult(test)
|
? getTestCaseResult(test)
|
||||||
: test.getTestCaseResult());
|
: test.getTestCaseResult());
|
||||||
|
test.setIncidents(
|
||||||
|
fields.contains(INCIDENTS_FIELD) ? getIncidentIds(test) : test.getIncidents());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,7 +94,7 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
test.setTestSuites(fields.contains("testSuites") ? test.getTestSuites() : null);
|
test.setTestSuites(fields.contains("testSuites") ? test.getTestSuites() : null);
|
||||||
test.setTestSuite(fields.contains(TEST_SUITE) ? test.getTestSuite() : null);
|
test.setTestSuite(fields.contains(TEST_SUITE) ? test.getTestSuite() : null);
|
||||||
test.setTestDefinition(fields.contains(TEST_DEFINITION) ? test.getTestDefinition() : null);
|
test.setTestDefinition(fields.contains(TEST_DEFINITION) ? test.getTestDefinition() : null);
|
||||||
test.withTestCaseResult(
|
test.setTestCaseResult(
|
||||||
fields.contains(TEST_CASE_RESULT_FIELD) ? test.getTestCaseResult() : null);
|
fields.contains(TEST_CASE_RESULT_FIELD) ? test.getTestCaseResult() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,16 +232,22 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
Relationship.APPLIED_TO);
|
Relationship.APPLIED_TO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void postDelete(TestCase test) {
|
||||||
|
// If we delete the test case, we need to clean up the resolution ts
|
||||||
|
daoCollection.testCaseResolutionStatusTimeSeriesDao().delete(test.getFullyQualifiedName());
|
||||||
|
}
|
||||||
|
|
||||||
public RestUtil.PutResponse<TestCaseResult> addTestCaseResult(
|
public RestUtil.PutResponse<TestCaseResult> addTestCaseResult(
|
||||||
String updatedBy, UriInfo uriInfo, String fqn, TestCaseResult testCaseResult) {
|
String updatedBy, UriInfo uriInfo, String fqn, TestCaseResult testCaseResult) {
|
||||||
// Validate the request content
|
// Validate the request content
|
||||||
TestCase testCase = findByName(fqn, Include.NON_DELETED);
|
TestCase testCase = findByName(fqn, Include.NON_DELETED);
|
||||||
|
|
||||||
// set the test case resolution status reference if test failed
|
// set the test case resolution status reference if test failed
|
||||||
testCaseResult.setTestCaseResolutionStatusReference(
|
testCaseResult.setIncidentId(
|
||||||
testCaseResult.getTestCaseStatus() != TestCaseStatus.Failed
|
testCaseResult.getTestCaseStatus() == TestCaseStatus.Failed
|
||||||
? null
|
? createIncidentOnFailure(testCase, updatedBy)
|
||||||
: setTestCaseResolutionStatus(testCase, updatedBy));
|
: null);
|
||||||
|
|
||||||
daoCollection
|
daoCollection
|
||||||
.dataQualityDataTimeSeriesDao()
|
.dataQualityDataTimeSeriesDao()
|
||||||
@ -260,8 +270,12 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
Response.Status.CREATED, changeEvent, RestUtil.ENTITY_FIELDS_CHANGED);
|
Response.Status.CREATED, changeEvent, RestUtil.ENTITY_FIELDS_CHANGED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestCaseResolutionStatus setTestCaseResolutionStatus(
|
private UUID createIncidentOnFailure(TestCase testCase, String updatedBy) {
|
||||||
TestCase testCase, String updatedBy) {
|
|
||||||
|
TestCaseResolutionStatusRepository testCaseResolutionStatusRepository =
|
||||||
|
(TestCaseResolutionStatusRepository)
|
||||||
|
Entity.getEntityTimeSeriesRepository(Entity.TEST_CASE_RESOLUTION_STATUS);
|
||||||
|
|
||||||
String json =
|
String json =
|
||||||
daoCollection
|
daoCollection
|
||||||
.testCaseResolutionStatusTimeSeriesDao()
|
.testCaseResolutionStatusTimeSeriesDao()
|
||||||
@ -270,21 +284,27 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
TestCaseResolutionStatus storedTestCaseResolutionStatus =
|
TestCaseResolutionStatus storedTestCaseResolutionStatus =
|
||||||
json != null ? JsonUtils.readValue(json, TestCaseResolutionStatus.class) : null;
|
json != null ? JsonUtils.readValue(json, TestCaseResolutionStatus.class) : null;
|
||||||
|
|
||||||
if ((storedTestCaseResolutionStatus != null)
|
|
||||||
&& (storedTestCaseResolutionStatus.getTestCaseResolutionStatusType()
|
|
||||||
!= TestCaseResolutionStatusTypes.Resolved)) {
|
|
||||||
// if we already have a non resolve status then we'll simply return it
|
// if we already have a non resolve status then we'll simply return it
|
||||||
return storedTestCaseResolutionStatus;
|
if (Boolean.TRUE.equals(
|
||||||
|
testCaseResolutionStatusRepository.unresolvedIncident(storedTestCaseResolutionStatus))) {
|
||||||
|
return storedTestCaseResolutionStatus.getStateId();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the test case resolution is null or resolved then we'll create a new one
|
// if the test case resolution is null or resolved then we'll create a new one
|
||||||
return new TestCaseResolutionStatus()
|
TestCaseResolutionStatus status =
|
||||||
|
new TestCaseResolutionStatus()
|
||||||
.withStateId(UUID.randomUUID())
|
.withStateId(UUID.randomUUID())
|
||||||
.withTimestamp(System.currentTimeMillis())
|
.withTimestamp(System.currentTimeMillis())
|
||||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.New)
|
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.New)
|
||||||
.withUpdatedBy(getEntityReferenceByName(Entity.USER, updatedBy, Include.ALL))
|
.withUpdatedBy(getEntityReferenceByName(Entity.USER, updatedBy, Include.ALL))
|
||||||
.withUpdatedAt(System.currentTimeMillis())
|
.withUpdatedAt(System.currentTimeMillis())
|
||||||
.withTestCaseReference(testCase.getEntityReference());
|
.withTestCaseReference(testCase.getEntityReference());
|
||||||
|
|
||||||
|
TestCaseResolutionStatus incident =
|
||||||
|
testCaseResolutionStatusRepository.createNewRecord(
|
||||||
|
status, null, testCase.getFullyQualifiedName());
|
||||||
|
|
||||||
|
return incident.getStateId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RestUtil.PutResponse<TestCaseResult> deleteTestCaseResult(
|
public RestUtil.PutResponse<TestCaseResult> deleteTestCaseResult(
|
||||||
@ -487,6 +507,26 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
testCaseResults, String.valueOf(startTs), String.valueOf(endTs), testCaseResults.size());
|
testCaseResults, String.valueOf(startTs), String.valueOf(endTs), testCaseResults.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check all the test case results that have an ongoing incident and get the stateId of the incident
|
||||||
|
*/
|
||||||
|
private List<UUID> getIncidentIds(TestCase test) {
|
||||||
|
List<TestCaseResult> testCaseResults;
|
||||||
|
testCaseResults =
|
||||||
|
JsonUtils.readObjects(
|
||||||
|
daoCollection
|
||||||
|
.dataQualityDataTimeSeriesDao()
|
||||||
|
.getResultsWithIncidents(
|
||||||
|
FullyQualifiedName.buildHash(test.getFullyQualifiedName())),
|
||||||
|
TestCaseResult.class);
|
||||||
|
|
||||||
|
return testCaseResults.stream()
|
||||||
|
.map(TestCaseResult::getIncidentId)
|
||||||
|
.collect(Collectors.toSet())
|
||||||
|
.stream()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
public int getTestCaseCount(List<UUID> testCaseIds) {
|
public int getTestCaseCount(List<UUID> testCaseIds) {
|
||||||
return daoCollection.testCaseDAO().countOfTestCases(testCaseIds);
|
return daoCollection.testCaseDAO().countOfTestCases(testCaseIds);
|
||||||
}
|
}
|
||||||
@ -683,6 +723,9 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
Entity.getEntityTimeSeriesRepository(Entity.TEST_CASE_RESOLUTION_STATUS);
|
Entity.getEntityTimeSeriesRepository(Entity.TEST_CASE_RESOLUTION_STATUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the task is resolved, we'll resolve the Incident with the given reason
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transaction
|
@Transaction
|
||||||
public TestCase performTask(String userName, ResolveTask resolveTask) {
|
public TestCase performTask(String userName, ResolveTask resolveTask) {
|
||||||
@ -719,14 +762,20 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
Entity.TEST_CASE_RESOLUTION_STATUS,
|
Entity.TEST_CASE_RESOLUTION_STATUS,
|
||||||
JsonUtils.pojoToJson(testCaseResolutionStatus));
|
JsonUtils.pojoToJson(testCaseResolutionStatus));
|
||||||
testCaseResolutionStatusRepository.postCreate(testCaseResolutionStatus);
|
testCaseResolutionStatusRepository.postCreate(testCaseResolutionStatus);
|
||||||
|
|
||||||
|
// TODO: remove incident ID from test case result
|
||||||
|
|
||||||
return Entity.getEntity(testCaseResolutionStatus.getTestCaseReference(), "", Include.ALL);
|
return Entity.getEntity(testCaseResolutionStatus.getTestCaseReference(), "", Include.ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we close the task, we'll flag the incident as Resolved as a False Positive, if
|
||||||
|
* it is not resolved yet.
|
||||||
|
* Closing the task means that the incident is not applicable.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transaction
|
@Transaction
|
||||||
public void closeTask(String userName, CloseTask closeTask) {
|
public void closeTask(String userName, CloseTask closeTask) {
|
||||||
// closing task in the context of test case resolution status means that the resolution task
|
|
||||||
// has been reassigned to someone else
|
|
||||||
TestCaseResolutionStatus latestTestCaseResolutionStatus =
|
TestCaseResolutionStatus latestTestCaseResolutionStatus =
|
||||||
testCaseResolutionStatusRepository.getLatestRecord(closeTask.getTestCaseFQN());
|
testCaseResolutionStatusRepository.getLatestRecord(closeTask.getTestCaseFQN());
|
||||||
if (latestTestCaseResolutionStatus == null) {
|
if (latestTestCaseResolutionStatus == null) {
|
||||||
@ -740,19 +789,23 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = Entity.getEntityByName(Entity.USER, userName, "", Include.ALL);
|
User user = getEntityByName(Entity.USER, userName, "", Include.ALL);
|
||||||
User assignee = Entity.getEntityByName(Entity.USER, closeTask.getComment(), "", Include.ALL);
|
|
||||||
TestCaseResolutionStatus testCaseResolutionStatus =
|
TestCaseResolutionStatus testCaseResolutionStatus =
|
||||||
new TestCaseResolutionStatus()
|
new TestCaseResolutionStatus()
|
||||||
.withId(UUID.randomUUID())
|
.withId(UUID.randomUUID())
|
||||||
.withStateId(latestTestCaseResolutionStatus.getStateId())
|
.withStateId(latestTestCaseResolutionStatus.getStateId())
|
||||||
.withTimestamp(System.currentTimeMillis())
|
.withTimestamp(System.currentTimeMillis())
|
||||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Resolved)
|
||||||
.withTestCaseResolutionStatusDetails(
|
.withTestCaseResolutionStatusDetails(
|
||||||
new Assigned().withAssignee(assignee.getEntityReference()))
|
new Resolved()
|
||||||
|
.withTestCaseFailureComment(closeTask.getComment())
|
||||||
|
// If we close the task directly we won't know the reason
|
||||||
|
.withTestCaseFailureReason(TestCaseFailureReasonType.FalsePositive)
|
||||||
|
.withResolvedBy(user.getEntityReference()))
|
||||||
.withUpdatedAt(System.currentTimeMillis())
|
.withUpdatedAt(System.currentTimeMillis())
|
||||||
.withTestCaseReference(latestTestCaseResolutionStatus.getTestCaseReference())
|
.withTestCaseReference(latestTestCaseResolutionStatus.getTestCaseReference())
|
||||||
.withUpdatedBy(user.getEntityReference());
|
.withUpdatedBy(user.getEntityReference());
|
||||||
|
|
||||||
Entity.getCollectionDAO()
|
Entity.getCollectionDAO()
|
||||||
.testCaseResolutionStatusTimeSeriesDao()
|
.testCaseResolutionStatusTimeSeriesDao()
|
||||||
.insert(
|
.insert(
|
||||||
|
@ -11,7 +11,6 @@ import java.util.UUID;
|
|||||||
import javax.json.JsonPatch;
|
import javax.json.JsonPatch;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import org.jdbi.v3.sqlobject.transaction.Transaction;
|
import org.jdbi.v3.sqlobject.transaction.Transaction;
|
||||||
import org.openmetadata.schema.api.feed.CloseTask;
|
|
||||||
import org.openmetadata.schema.api.feed.ResolveTask;
|
import org.openmetadata.schema.api.feed.ResolveTask;
|
||||||
import org.openmetadata.schema.entity.feed.Thread;
|
import org.openmetadata.schema.entity.feed.Thread;
|
||||||
import org.openmetadata.schema.entity.teams.User;
|
import org.openmetadata.schema.entity.teams.User;
|
||||||
@ -22,12 +21,14 @@ import org.openmetadata.schema.tests.type.TestCaseResolutionStatus;
|
|||||||
import org.openmetadata.schema.tests.type.TestCaseResolutionStatusTypes;
|
import org.openmetadata.schema.tests.type.TestCaseResolutionStatusTypes;
|
||||||
import org.openmetadata.schema.type.EntityReference;
|
import org.openmetadata.schema.type.EntityReference;
|
||||||
import org.openmetadata.schema.type.Include;
|
import org.openmetadata.schema.type.Include;
|
||||||
|
import org.openmetadata.schema.type.Relationship;
|
||||||
import org.openmetadata.schema.type.TaskDetails;
|
import org.openmetadata.schema.type.TaskDetails;
|
||||||
import org.openmetadata.schema.type.TaskStatus;
|
import org.openmetadata.schema.type.TaskStatus;
|
||||||
import org.openmetadata.schema.type.TaskType;
|
import org.openmetadata.schema.type.TaskType;
|
||||||
import org.openmetadata.schema.type.ThreadType;
|
import org.openmetadata.schema.type.ThreadType;
|
||||||
import org.openmetadata.service.Entity;
|
import org.openmetadata.service.Entity;
|
||||||
import org.openmetadata.service.exception.EntityNotFoundException;
|
import org.openmetadata.service.exception.EntityNotFoundException;
|
||||||
|
import org.openmetadata.service.exception.IncidentManagerException;
|
||||||
import org.openmetadata.service.resources.feeds.MessageParser;
|
import org.openmetadata.service.resources.feeds.MessageParser;
|
||||||
import org.openmetadata.service.util.EntityUtil;
|
import org.openmetadata.service.util.EntityUtil;
|
||||||
import org.openmetadata.service.util.JsonUtils;
|
import org.openmetadata.service.util.JsonUtils;
|
||||||
@ -96,11 +97,47 @@ public class TestCaseResolutionStatusRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Boolean unresolvedIncident(TestCaseResolutionStatus incident) {
|
||||||
protected void postCreate(TestCaseResolutionStatus entity) {
|
return incident != null
|
||||||
super.postCreate(entity);
|
&& !incident
|
||||||
if (entity.getTestCaseResolutionStatusType() == TestCaseResolutionStatusTypes.Assigned) {
|
.getTestCaseResolutionStatusType()
|
||||||
createAssignedTask(entity);
|
.equals(TestCaseResolutionStatusTypes.Resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Thread getIncidentTask(TestCaseResolutionStatus incident) {
|
||||||
|
// Fetch the latest task (which comes from the NEW state) and close it
|
||||||
|
String jsonThread =
|
||||||
|
Entity.getCollectionDAO()
|
||||||
|
.feedDAO()
|
||||||
|
.fetchThreadByTestCaseResolutionStatusId(incident.getStateId());
|
||||||
|
return JsonUtils.readValue(jsonThread, Thread.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure we are following the correct status flow
|
||||||
|
*/
|
||||||
|
private void validateStatus(
|
||||||
|
TestCaseResolutionStatusTypes lastStatus, TestCaseResolutionStatusTypes newStatus) {
|
||||||
|
switch (lastStatus) {
|
||||||
|
case New -> {
|
||||||
|
/* New can go to any status */
|
||||||
|
}
|
||||||
|
case Ack -> {
|
||||||
|
if (newStatus.equals(TestCaseResolutionStatusTypes.New)) {
|
||||||
|
throw IncidentManagerException.invalidStatus(lastStatus, newStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Assigned -> {
|
||||||
|
if (List.of(TestCaseResolutionStatusTypes.New, TestCaseResolutionStatusTypes.Ack)
|
||||||
|
.contains(newStatus)) {
|
||||||
|
throw IncidentManagerException.invalidStatus(lastStatus, newStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Resolved -> {
|
||||||
|
if (!newStatus.equals(TestCaseResolutionStatusTypes.Resolved)) {
|
||||||
|
throw IncidentManagerException.invalidStatus(lastStatus, newStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,62 +145,107 @@ public class TestCaseResolutionStatusRepository
|
|||||||
@Transaction
|
@Transaction
|
||||||
public TestCaseResolutionStatus createNewRecord(
|
public TestCaseResolutionStatus createNewRecord(
|
||||||
TestCaseResolutionStatus recordEntity, String extension, String recordFQN) {
|
TestCaseResolutionStatus recordEntity, String extension, String recordFQN) {
|
||||||
TestCaseResolutionStatus latestTestCaseFailure =
|
|
||||||
|
TestCaseResolutionStatus lastIncident =
|
||||||
getLatestRecord(recordEntity.getTestCaseReference().getFullyQualifiedName());
|
getLatestRecord(recordEntity.getTestCaseReference().getFullyQualifiedName());
|
||||||
|
|
||||||
recordEntity.setStateId(
|
if (recordEntity.getStateId() == null) {
|
||||||
((latestTestCaseFailure != null)
|
recordEntity.setStateId(UUID.randomUUID());
|
||||||
&& (latestTestCaseFailure.getTestCaseResolutionStatusType()
|
}
|
||||||
!= TestCaseResolutionStatusTypes.Resolved))
|
|
||||||
? latestTestCaseFailure.getStateId()
|
|
||||||
: UUID.randomUUID());
|
|
||||||
|
|
||||||
if (latestTestCaseFailure != null
|
// if we have an ongoing incident, set the stateId if the new record to be created
|
||||||
&& latestTestCaseFailure
|
// and validate the flow
|
||||||
.getTestCaseResolutionStatusType()
|
if (Boolean.TRUE.equals(unresolvedIncident(lastIncident))) {
|
||||||
.equals(TestCaseResolutionStatusTypes.Assigned)) {
|
recordEntity.setStateId(lastIncident.getStateId());
|
||||||
String jsonThread =
|
validateStatus(
|
||||||
Entity.getCollectionDAO()
|
lastIncident.getTestCaseResolutionStatusType(),
|
||||||
.feedDAO()
|
recordEntity.getTestCaseResolutionStatusType());
|
||||||
.fetchThreadByTestCaseResolutionStatusId(latestTestCaseFailure.getId());
|
}
|
||||||
Thread thread = JsonUtils.readValue(jsonThread, Thread.class);
|
|
||||||
if (recordEntity
|
switch (recordEntity.getTestCaseResolutionStatusType()) {
|
||||||
.getTestCaseResolutionStatusType()
|
// When we create a NEW incident, we need to open a task with the test case owner as the
|
||||||
.equals(TestCaseResolutionStatusTypes.Assigned)) {
|
// assignee. We don't need to check any past history
|
||||||
// We have an open task and we are passing an assigned status type
|
case New -> {
|
||||||
// (i.e. we are re-assigning). This scenario is when the test case resolution status is
|
// If there is already an unresolved incident, return it without doing any
|
||||||
// being sent through the API (and not resolving an open task)
|
// further logic.
|
||||||
// we'll get the associated thread with the latest test case failure
|
if (Boolean.TRUE.equals(unresolvedIncident(lastIncident))) {
|
||||||
|
return getLatestRecord(lastIncident.getTestCaseReference().getFullyQualifiedName());
|
||||||
|
}
|
||||||
|
openNewTask(recordEntity);
|
||||||
|
}
|
||||||
|
case Ack -> {
|
||||||
|
/* nothing to do for ACK. The Owner already has the task open. It will close it when reassigning it */
|
||||||
|
}
|
||||||
|
case Assigned -> assignTask(recordEntity, lastIncident);
|
||||||
|
// When the incident is Resolved, we will close the Assigned task.
|
||||||
|
case Resolved -> {
|
||||||
|
resolveTask(recordEntity, lastIncident);
|
||||||
|
// We don't create a new record. The new status will be added via the
|
||||||
|
// TestCaseFailureResolutionTaskWorkflow
|
||||||
|
// implemented in the TestCaseRepository.
|
||||||
|
return getLatestRecord(recordEntity.getTestCaseReference().getFullyQualifiedName());
|
||||||
|
}
|
||||||
|
default -> throw new IllegalArgumentException(
|
||||||
|
String.format("Invalid status %s", recordEntity.getTestCaseResolutionStatusType()));
|
||||||
|
}
|
||||||
|
return super.createNewRecord(recordEntity, extension, recordFQN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openNewTask(TestCaseResolutionStatus incidentStatus) {
|
||||||
|
|
||||||
|
List<EntityReference> owners =
|
||||||
|
EntityUtil.getEntityReferences(
|
||||||
|
daoCollection
|
||||||
|
.relationshipDAO()
|
||||||
|
.findFrom(
|
||||||
|
incidentStatus.getTestCaseReference().getId(),
|
||||||
|
Entity.TEST_CASE,
|
||||||
|
Relationship.OWNS.ordinal(),
|
||||||
|
Entity.USER));
|
||||||
|
createTask(incidentStatus, owners, "New Incident");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assignTask(
|
||||||
|
TestCaseResolutionStatus newIncidentStatus, TestCaseResolutionStatus lastIncidentStatus) {
|
||||||
|
|
||||||
|
if (lastIncidentStatus == null) {
|
||||||
|
throw new IncidentManagerException(
|
||||||
|
String.format(
|
||||||
|
"Cannot find the last incident status for stateId %s",
|
||||||
|
newIncidentStatus.getStateId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread thread = getIncidentTask(lastIncidentStatus);
|
||||||
|
|
||||||
// we'll close the task (the flow will also create a new assigned test case resolution
|
|
||||||
// status and open a new task)
|
|
||||||
Assigned assigned =
|
Assigned assigned =
|
||||||
JsonUtils.convertValue(
|
JsonUtils.convertValue(
|
||||||
recordEntity.getTestCaseResolutionStatusDetails(), Assigned.class);
|
newIncidentStatus.getTestCaseResolutionStatusDetails(), Assigned.class);
|
||||||
User assignee =
|
|
||||||
Entity.getEntity(Entity.USER, assigned.getAssignee().getId(), "", Include.ALL);
|
|
||||||
User updatedBy =
|
User updatedBy =
|
||||||
Entity.getEntity(Entity.USER, recordEntity.getUpdatedBy().getId(), "", Include.ALL);
|
Entity.getEntity(Entity.USER, newIncidentStatus.getUpdatedBy().getId(), "", Include.ALL);
|
||||||
CloseTask closeTask =
|
|
||||||
new CloseTask()
|
patchTaskAssignee(thread, assigned.getAssignee(), updatedBy.getName());
|
||||||
.withComment(assignee.getFullyQualifiedName())
|
}
|
||||||
.withTestCaseFQN(recordEntity.getTestCaseReference().getFullyQualifiedName());
|
|
||||||
Entity.getFeedRepository().closeTask(thread, updatedBy.getFullyQualifiedName(), closeTask);
|
private void resolveTask(
|
||||||
return getLatestRecord(recordEntity.getTestCaseReference().getFullyQualifiedName());
|
TestCaseResolutionStatus newIncidentStatus, TestCaseResolutionStatus lastIncidentStatus) {
|
||||||
} else if (recordEntity
|
|
||||||
.getTestCaseResolutionStatusType()
|
if (lastIncidentStatus == null) {
|
||||||
.equals(TestCaseResolutionStatusTypes.Resolved)) {
|
throw new IncidentManagerException(
|
||||||
// We have an open task and we are passing a resolved status type (i.e. we are marking it as
|
String.format(
|
||||||
// resolved). This scenario is when the test case resolution status is being sent through
|
"Cannot find the last incident status for stateId %s",
|
||||||
// the API (and not resolving an open task)
|
newIncidentStatus.getStateId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread thread = getIncidentTask(lastIncidentStatus);
|
||||||
|
|
||||||
Resolved resolved =
|
Resolved resolved =
|
||||||
JsonUtils.convertValue(
|
JsonUtils.convertValue(
|
||||||
recordEntity.getTestCaseResolutionStatusDetails(), Resolved.class);
|
newIncidentStatus.getTestCaseResolutionStatusDetails(), Resolved.class);
|
||||||
TestCase testCase =
|
TestCase testCase =
|
||||||
Entity.getEntity(
|
Entity.getEntity(
|
||||||
Entity.TEST_CASE, recordEntity.getTestCaseReference().getId(), "", Include.ALL);
|
Entity.TEST_CASE, newIncidentStatus.getTestCaseReference().getId(), "", Include.ALL);
|
||||||
User updatedBy =
|
User updatedBy =
|
||||||
Entity.getEntity(Entity.USER, recordEntity.getUpdatedBy().getId(), "", Include.ALL);
|
Entity.getEntity(Entity.USER, newIncidentStatus.getUpdatedBy().getId(), "", Include.ALL);
|
||||||
ResolveTask resolveTask =
|
ResolveTask resolveTask =
|
||||||
new ResolveTask()
|
new ResolveTask()
|
||||||
.withTestCaseFQN(testCase.getFullyQualifiedName())
|
.withTestCaseFQN(testCase.getFullyQualifiedName())
|
||||||
@ -174,43 +256,45 @@ public class TestCaseResolutionStatusRepository
|
|||||||
new FeedRepository.ThreadContext(thread),
|
new FeedRepository.ThreadContext(thread),
|
||||||
updatedBy.getFullyQualifiedName(),
|
updatedBy.getFullyQualifiedName(),
|
||||||
resolveTask);
|
resolveTask);
|
||||||
return getLatestRecord(testCase.getFullyQualifiedName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException(
|
private void createTask(
|
||||||
String.format(
|
TestCaseResolutionStatus incidentStatus, List<EntityReference> assignees, String message) {
|
||||||
"Test Case Resolution status %s with type `Assigned` cannot be moved to `New` or `Ack`. You can `Assign` or `Resolve` the test case failure. ",
|
|
||||||
latestTestCaseFailure.getId().toString()));
|
|
||||||
}
|
|
||||||
return super.createNewRecord(recordEntity, extension, recordFQN);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createAssignedTask(TestCaseResolutionStatus entity) {
|
|
||||||
Assigned assigned =
|
|
||||||
JsonUtils.convertValue(entity.getTestCaseResolutionStatusDetails(), Assigned.class);
|
|
||||||
List<EntityReference> assignees = Collections.singletonList(assigned.getAssignee());
|
|
||||||
TaskDetails taskDetails =
|
TaskDetails taskDetails =
|
||||||
new TaskDetails()
|
new TaskDetails()
|
||||||
.withAssignees(assignees)
|
.withAssignees(assignees)
|
||||||
.withType(TaskType.RequestTestCaseFailureResolution)
|
.withType(TaskType.RequestTestCaseFailureResolution)
|
||||||
.withStatus(TaskStatus.Open)
|
.withStatus(TaskStatus.Open)
|
||||||
.withTestCaseResolutionStatusId(entity.getId());
|
// Each incident flow - flagged by its State ID - will have a single unique Task
|
||||||
|
.withTestCaseResolutionStatusId(incidentStatus.getStateId());
|
||||||
|
|
||||||
MessageParser.EntityLink entityLink =
|
MessageParser.EntityLink entityLink =
|
||||||
new MessageParser.EntityLink(
|
new MessageParser.EntityLink(
|
||||||
Entity.TEST_CASE, entity.getTestCaseReference().getFullyQualifiedName());
|
Entity.TEST_CASE, incidentStatus.getTestCaseReference().getFullyQualifiedName());
|
||||||
Thread thread =
|
Thread thread =
|
||||||
new Thread()
|
new Thread()
|
||||||
.withId(UUID.randomUUID())
|
.withId(UUID.randomUUID())
|
||||||
.withThreadTs(System.currentTimeMillis())
|
.withThreadTs(System.currentTimeMillis())
|
||||||
.withMessage("Test Case Failure Resolution requested for ")
|
.withMessage(message)
|
||||||
.withCreatedBy(entity.getUpdatedBy().getName())
|
.withCreatedBy(incidentStatus.getUpdatedBy().getName())
|
||||||
.withAbout(entityLink.getLinkString())
|
.withAbout(entityLink.getLinkString())
|
||||||
.withType(ThreadType.Task)
|
.withType(ThreadType.Task)
|
||||||
.withTask(taskDetails)
|
.withTask(taskDetails)
|
||||||
.withUpdatedBy(entity.getUpdatedBy().getName())
|
.withUpdatedBy(incidentStatus.getUpdatedBy().getName())
|
||||||
.withUpdatedAt(System.currentTimeMillis());
|
.withUpdatedAt(System.currentTimeMillis());
|
||||||
FeedRepository feedRepository = Entity.getFeedRepository();
|
FeedRepository feedRepository = Entity.getFeedRepository();
|
||||||
feedRepository.create(thread);
|
feedRepository.create(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void patchTaskAssignee(Thread originalTask, EntityReference newAssignee, String user) {
|
||||||
|
Thread updatedTask = JsonUtils.deepCopy(originalTask, Thread.class);
|
||||||
|
updatedTask.setTask(
|
||||||
|
updatedTask.getTask().withAssignees(Collections.singletonList(newAssignee)));
|
||||||
|
|
||||||
|
JsonPatch patch = JsonUtils.getJsonPatch(originalTask, updatedTask);
|
||||||
|
|
||||||
|
FeedRepository feedRepository = Entity.getFeedRepository();
|
||||||
|
feedRepository.patchThread(null, originalTask.getId(), user, patch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ import org.openmetadata.service.util.ResultList;
|
|||||||
public class TestCaseResource extends EntityResource<TestCase, TestCaseRepository> {
|
public class TestCaseResource extends EntityResource<TestCase, TestCaseRepository> {
|
||||||
public static final String COLLECTION_PATH = "/v1/dataQuality/testCases";
|
public static final String COLLECTION_PATH = "/v1/dataQuality/testCases";
|
||||||
|
|
||||||
static final String FIELDS = "owner,testSuite,testDefinition,testSuites";
|
static final String FIELDS = "owner,testSuite,testDefinition,testSuites,incidents";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TestCase addHref(UriInfo uriInfo, TestCase test) {
|
public TestCase addHref(UriInfo uriInfo, TestCase test) {
|
||||||
|
@ -1089,54 +1089,41 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
paginate(maxEntities, allEntities, logicalTestSuite);
|
paginate(maxEntities, allEntities, logicalTestSuite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test Case Failure Status Tests
|
|
||||||
@Test
|
@Test
|
||||||
void post_createTestCaseResultFailure(TestInfo test) throws HttpResponseException {
|
void post_createTestCaseResultFailure(TestInfo test)
|
||||||
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
throws HttpResponseException, ParseException {
|
||||||
|
// We're going to check how each test only has a single open stateID
|
||||||
// Create a test case failure status for each status type
|
// and 2 tests have their own flow
|
||||||
List<CreateTestCaseResolutionStatus> testCaseFailureStatuses = new ArrayList<>();
|
|
||||||
List<CreateTestCaseResolutionStatus> resolvedTestCaseFailureStatus = new ArrayList<>();
|
|
||||||
for (TestCaseResolutionStatusTypes statusType : TestCaseResolutionStatusTypes.values()) {
|
|
||||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus =
|
|
||||||
new CreateTestCaseResolutionStatus()
|
|
||||||
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
|
||||||
.withTestCaseResolutionStatusType(statusType)
|
|
||||||
.withTestCaseResolutionStatusDetails(null);
|
|
||||||
if (statusType.equals(TestCaseResolutionStatusTypes.Assigned)) {
|
|
||||||
createTestCaseFailureStatus.setTestCaseResolutionStatusDetails(
|
|
||||||
new Assigned().withAssignee(USER1_REF));
|
|
||||||
}
|
|
||||||
if (statusType.equals(TestCaseResolutionStatusTypes.Resolved)) {
|
|
||||||
createTestCaseFailureStatus.setTestCaseResolutionStatusDetails(
|
|
||||||
new Resolved()
|
|
||||||
.withTestCaseFailureComment("resolved")
|
|
||||||
.withTestCaseFailureReason(TestCaseFailureReasonType.MissingData)
|
|
||||||
.withResolvedBy(USER1_REF));
|
|
||||||
resolvedTestCaseFailureStatus.add(createTestCaseFailureStatus);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
testCaseFailureStatuses.add(createTestCaseFailureStatus);
|
|
||||||
}
|
|
||||||
// Create 2 the test case failure statuses with all stages
|
|
||||||
// this should generate 2 sequence IDs
|
|
||||||
Long startTs = System.currentTimeMillis();
|
Long startTs = System.currentTimeMillis();
|
||||||
createTestCaseResolutionStatus(testCaseFailureStatuses);
|
TestCase testCaseEntity1 = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
||||||
// create resolved test case failure status last
|
TestCase testCaseEntity2 =
|
||||||
createTestCaseResolutionStatus(resolvedTestCaseFailureStatus);
|
createEntity(createRequest(getEntityName(test) + "2"), ADMIN_AUTH_HEADERS);
|
||||||
|
|
||||||
// Start a new sequence ID
|
// Add a failed result, which will create a NEW incident and add a new status
|
||||||
createTestCaseResolutionStatus(testCaseFailureStatuses);
|
for (TestCase testCase : List.of(testCaseEntity1, testCaseEntity2)) {
|
||||||
// create resolved test case failure status last
|
putTestCaseResult(
|
||||||
createTestCaseResolutionStatus(resolvedTestCaseFailureStatus);
|
testCase.getFullyQualifiedName(),
|
||||||
|
new TestCaseResult()
|
||||||
|
.withResult("result")
|
||||||
|
.withTestCaseStatus(TestCaseStatus.Failed)
|
||||||
|
.withTimestamp(TestUtils.dateToTimestamp("2024-01-01")),
|
||||||
|
ADMIN_AUTH_HEADERS);
|
||||||
|
|
||||||
|
CreateTestCaseResolutionStatus createAckIncident =
|
||||||
|
new CreateTestCaseResolutionStatus()
|
||||||
|
.withTestCaseReference(testCase.getFullyQualifiedName())
|
||||||
|
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Ack)
|
||||||
|
.withTestCaseResolutionStatusDetails(null);
|
||||||
|
createTestCaseFailureStatus(createAckIncident);
|
||||||
|
}
|
||||||
Long endTs = System.currentTimeMillis();
|
Long endTs = System.currentTimeMillis();
|
||||||
|
|
||||||
// Get the test case failure statuses
|
// Get the test case failure statuses
|
||||||
ResultList<TestCaseResolutionStatus> testCaseFailureStatusResultList =
|
ResultList<TestCaseResolutionStatus> testCaseFailureStatusResultList =
|
||||||
getTestCaseFailureStatus(startTs, endTs, null, null);
|
getTestCaseFailureStatus(startTs, endTs, null, null);
|
||||||
assertEquals(8, testCaseFailureStatusResultList.getData().size());
|
assertEquals(4, testCaseFailureStatusResultList.getData().size());
|
||||||
|
|
||||||
// check we have only 2 distinct sequence IDs
|
// check we have only 2 distinct sequence IDs, one for each test case
|
||||||
List<UUID> stateIds =
|
List<UUID> stateIds =
|
||||||
testCaseFailureStatusResultList.getData().stream()
|
testCaseFailureStatusResultList.getData().stream()
|
||||||
.map(TestCaseResolutionStatus::getStateId)
|
.map(TestCaseResolutionStatus::getStateId)
|
||||||
@ -1156,58 +1143,38 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
// Get the test case failure statuses by sequence ID
|
// Get the test case failure statuses by sequence ID
|
||||||
ResultList<TestCaseResolutionStatus> storedTestCaseResolutions =
|
ResultList<TestCaseResolutionStatus> storedTestCaseResolutions =
|
||||||
getTestCaseFailureStatusByStateId(stateId);
|
getTestCaseFailureStatusByStateId(stateId);
|
||||||
assertEquals(4, storedTestCaseResolutions.getData().size());
|
assertEquals(2, storedTestCaseResolutions.getData().size());
|
||||||
assertEquals(stateId, storedTestCaseResolutions.getData().get(0).getStateId());
|
assertEquals(stateId, storedTestCaseResolutions.getData().get(0).getStateId());
|
||||||
|
|
||||||
// Get the test case resolution statuses by status type
|
// Get the test case resolution statuses by status type
|
||||||
storedTestCaseResolutions =
|
storedTestCaseResolutions =
|
||||||
getTestCaseFailureStatus(startTs, endTs, null, TestCaseResolutionStatusTypes.Assigned);
|
getTestCaseFailureStatus(startTs, endTs, null, TestCaseResolutionStatusTypes.Ack);
|
||||||
assertEquals(2, storedTestCaseResolutions.getData().size());
|
assertEquals(2, storedTestCaseResolutions.getData().size());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
TestCaseResolutionStatusTypes.Assigned,
|
TestCaseResolutionStatusTypes.Ack,
|
||||||
storedTestCaseResolutions.getData().get(0).getTestCaseResolutionStatusType());
|
storedTestCaseResolutions.getData().get(0).getTestCaseResolutionStatusType());
|
||||||
|
|
||||||
// Get test case resolution statuses by assignee name
|
|
||||||
storedTestCaseResolutions = getTestCaseFailureStatus(startTs, endTs, USER1.getName(), null);
|
|
||||||
assertEquals(2, storedTestCaseResolutions.getData().size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test_listTestCaseFailureStatusPagination(TestInfo test) throws IOException {
|
void test_listTestCaseFailureStatusPagination(TestInfo test) throws IOException, ParseException {
|
||||||
// Create a number of entities between 5 and 20 inclusive
|
// Create a number of entities between 5 and 20 inclusive
|
||||||
Random rand = new Random();
|
Random rand = new Random();
|
||||||
int maxEntities = rand.nextInt(16) + 5;
|
int maxEntities = rand.nextInt(16) + 5;
|
||||||
|
|
||||||
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
|
||||||
TestCaseResolutionStatusTypes[] testCaseFailureStatusTypes =
|
|
||||||
TestCaseResolutionStatusTypes.values();
|
|
||||||
List<CreateTestCaseResolutionStatus> testCaseFailureStatuses = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < maxEntities; i++) {
|
|
||||||
// randomly pick a status type
|
|
||||||
TestCaseResolutionStatusTypes testCaseFailureStatusType =
|
|
||||||
testCaseFailureStatusTypes[i % testCaseFailureStatusTypes.length];
|
|
||||||
|
|
||||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus =
|
|
||||||
new CreateTestCaseResolutionStatus()
|
|
||||||
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
|
||||||
.withTestCaseResolutionStatusType(testCaseFailureStatusType)
|
|
||||||
.withTestCaseResolutionStatusDetails(null);
|
|
||||||
if (testCaseFailureStatusType.equals(TestCaseResolutionStatusTypes.Assigned)) {
|
|
||||||
createTestCaseFailureStatus.setTestCaseResolutionStatusDetails(
|
|
||||||
new Assigned().withAssignee(USER1_REF));
|
|
||||||
}
|
|
||||||
if (testCaseFailureStatusType.equals(TestCaseResolutionStatusTypes.Resolved)) {
|
|
||||||
createTestCaseFailureStatus.setTestCaseResolutionStatusDetails(
|
|
||||||
new Resolved()
|
|
||||||
.withTestCaseFailureComment("resolved")
|
|
||||||
.withTestCaseFailureReason(TestCaseFailureReasonType.MissingData)
|
|
||||||
.withResolvedBy(USER1_REF));
|
|
||||||
}
|
|
||||||
testCaseFailureStatuses.add(createTestCaseFailureStatus);
|
|
||||||
}
|
|
||||||
Long startTs = System.currentTimeMillis() - 1000;
|
Long startTs = System.currentTimeMillis() - 1000;
|
||||||
createTestCaseResolutionStatus(testCaseFailureStatuses);
|
for (int i = 0; i < maxEntities; i++) {
|
||||||
|
// We'll create random test cases
|
||||||
|
TestCase testCaseEntity =
|
||||||
|
createEntity(createRequest(getEntityName(test) + i), ADMIN_AUTH_HEADERS);
|
||||||
|
// Adding failed test case, which will create a NEW incident
|
||||||
|
putTestCaseResult(
|
||||||
|
testCaseEntity.getFullyQualifiedName(),
|
||||||
|
new TestCaseResult()
|
||||||
|
.withResult("result")
|
||||||
|
.withTestCaseStatus(TestCaseStatus.Failed)
|
||||||
|
.withTimestamp(TestUtils.dateToTimestamp("2024-01-01")),
|
||||||
|
ADMIN_AUTH_HEADERS);
|
||||||
|
}
|
||||||
Long endTs = System.currentTimeMillis() + 1000;
|
Long endTs = System.currentTimeMillis() + 1000;
|
||||||
|
|
||||||
// List all entities and use it for checking pagination
|
// List all entities and use it for checking pagination
|
||||||
@ -1217,57 +1184,6 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
paginateTestCaseFailureStatus(maxEntities, allEntities, null, startTs, endTs);
|
paginateTestCaseFailureStatus(maxEntities, allEntities, null, startTs, endTs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void test_listTestCaseFailureStatusLatestPagination(TestInfo test) throws IOException {
|
|
||||||
// Create a number of entities between 5 and 20 inclusive
|
|
||||||
Random rand = new Random();
|
|
||||||
TestCase testCaseEntity;
|
|
||||||
int maxEntities = rand.nextInt(16) + 5;
|
|
||||||
|
|
||||||
TestCaseResolutionStatusTypes[] testCaseFailureStatusTypes =
|
|
||||||
TestCaseResolutionStatusTypes.values();
|
|
||||||
List<CreateTestCaseResolutionStatus> testCaseFailureStatuses = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < maxEntities; i++) {
|
|
||||||
// create `maxEntities` number of test cases
|
|
||||||
testCaseEntity = createEntity(createRequest(getEntityName(test) + i), ADMIN_AUTH_HEADERS);
|
|
||||||
|
|
||||||
for (int j = 0; j < 5; j++) {
|
|
||||||
// create 5 test case failure statuses for each test case
|
|
||||||
// randomly pick a status type
|
|
||||||
TestCaseResolutionStatusTypes testCaseFailureStatusType =
|
|
||||||
testCaseFailureStatusTypes[j % TestCaseResolutionStatusTypes.values().length];
|
|
||||||
|
|
||||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus =
|
|
||||||
new CreateTestCaseResolutionStatus()
|
|
||||||
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
|
||||||
.withTestCaseResolutionStatusType(testCaseFailureStatusType)
|
|
||||||
.withTestCaseResolutionStatusDetails(null);
|
|
||||||
if (testCaseFailureStatusType.equals(TestCaseResolutionStatusTypes.Assigned)) {
|
|
||||||
createTestCaseFailureStatus.setTestCaseResolutionStatusDetails(
|
|
||||||
new Assigned().withAssignee(USER1_REF));
|
|
||||||
}
|
|
||||||
if (testCaseFailureStatusType.equals(TestCaseResolutionStatusTypes.Resolved)) {
|
|
||||||
createTestCaseFailureStatus.setTestCaseResolutionStatusDetails(
|
|
||||||
new Resolved()
|
|
||||||
.withTestCaseFailureComment("resolved")
|
|
||||||
.withTestCaseFailureReason(TestCaseFailureReasonType.MissingData)
|
|
||||||
.withResolvedBy(USER1_REF));
|
|
||||||
}
|
|
||||||
testCaseFailureStatuses.add(createTestCaseFailureStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Long startTs = System.currentTimeMillis() - 1000;
|
|
||||||
createTestCaseResolutionStatus(testCaseFailureStatuses);
|
|
||||||
Long endTs = System.currentTimeMillis() + 1000;
|
|
||||||
|
|
||||||
// List all entities and use it for checking pagination
|
|
||||||
ResultList<TestCaseResolutionStatus> allEntities =
|
|
||||||
getTestCaseFailureStatus(1000000, null, true, startTs, endTs, null);
|
|
||||||
|
|
||||||
paginateTestCaseFailureStatus(maxEntities, allEntities, true, startTs, endTs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void patch_TestCaseResultFailure(TestInfo test) throws HttpResponseException {
|
void patch_TestCaseResultFailure(TestInfo test) throws HttpResponseException {
|
||||||
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
||||||
@ -1326,24 +1242,34 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_testCaseResolutionTaskResolveWorkflowThruFeed(TestInfo test)
|
public void test_testCaseResolutionTaskResolveWorkflowThruFeed(TestInfo test)
|
||||||
throws HttpResponseException {
|
throws HttpResponseException, ParseException {
|
||||||
Long startTs = System.currentTimeMillis();
|
Long startTs = System.currentTimeMillis();
|
||||||
FeedResourceTest feedResourceTest = new FeedResourceTest();
|
FeedResourceTest feedResourceTest = new FeedResourceTest();
|
||||||
|
|
||||||
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
||||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus =
|
|
||||||
|
// Add failed test case, which will create a NEW incident
|
||||||
|
putTestCaseResult(
|
||||||
|
testCaseEntity.getFullyQualifiedName(),
|
||||||
|
new TestCaseResult()
|
||||||
|
.withResult("result")
|
||||||
|
.withTestCaseStatus(TestCaseStatus.Failed)
|
||||||
|
.withTimestamp(TestUtils.dateToTimestamp("2024-01-01")),
|
||||||
|
ADMIN_AUTH_HEADERS);
|
||||||
|
|
||||||
|
// Now, we should be good to create an ASSIGNED status
|
||||||
|
CreateTestCaseResolutionStatus createAssignedIncident =
|
||||||
new CreateTestCaseResolutionStatus()
|
new CreateTestCaseResolutionStatus()
|
||||||
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
||||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
||||||
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
||||||
TestCaseResolutionStatus testCaseFailureStatus =
|
TestCaseResolutionStatus assignedIncident = createTestCaseFailureStatus(createAssignedIncident);
|
||||||
createTestCaseFailureStatus(createTestCaseFailureStatus);
|
|
||||||
String jsonThread =
|
String jsonThread =
|
||||||
Entity.getCollectionDAO()
|
Entity.getCollectionDAO()
|
||||||
.feedDAO()
|
.feedDAO()
|
||||||
.fetchThreadByTestCaseResolutionStatusId(testCaseFailureStatus.getId());
|
.fetchThreadByTestCaseResolutionStatusId(assignedIncident.getStateId());
|
||||||
Thread thread = JsonUtils.readValue(jsonThread, Thread.class);
|
Thread thread = JsonUtils.readValue(jsonThread, Thread.class);
|
||||||
assertEquals(testCaseFailureStatus.getId(), thread.getTask().getTestCaseResolutionStatusId());
|
assertEquals(assignedIncident.getStateId(), thread.getTask().getTestCaseResolutionStatusId());
|
||||||
assertEquals(TaskStatus.Open, thread.getTask().getStatus());
|
assertEquals(TaskStatus.Open, thread.getTask().getStatus());
|
||||||
|
|
||||||
// resolve the task. The old task should be closed and the latest test case resolution status
|
// resolve the task. The old task should be closed and the latest test case resolution status
|
||||||
@ -1358,7 +1284,7 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
jsonThread =
|
jsonThread =
|
||||||
Entity.getCollectionDAO()
|
Entity.getCollectionDAO()
|
||||||
.feedDAO()
|
.feedDAO()
|
||||||
.fetchThreadByTestCaseResolutionStatusId(testCaseFailureStatus.getId());
|
.fetchThreadByTestCaseResolutionStatusId(assignedIncident.getStateId());
|
||||||
thread = JsonUtils.readValue(jsonThread, Thread.class);
|
thread = JsonUtils.readValue(jsonThread, Thread.class);
|
||||||
// Confirm that the task is closed
|
// Confirm that the task is closed
|
||||||
assertEquals(TaskStatus.Closed, thread.getTask().getStatus());
|
assertEquals(TaskStatus.Closed, thread.getTask().getStatus());
|
||||||
@ -1380,7 +1306,7 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
TestCaseResolutionStatusTypes.Resolved,
|
TestCaseResolutionStatusTypes.Resolved,
|
||||||
mostRecentTestCaseResolutionStatusData.getTestCaseResolutionStatusType());
|
mostRecentTestCaseResolutionStatusData.getTestCaseResolutionStatusType());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
testCaseFailureStatus.getStateId(), mostRecentTestCaseResolutionStatusData.getStateId());
|
assignedIncident.getStateId(), mostRecentTestCaseResolutionStatusData.getStateId());
|
||||||
Resolved resolved =
|
Resolved resolved =
|
||||||
JsonUtils.convertValue(
|
JsonUtils.convertValue(
|
||||||
mostRecentTestCaseResolutionStatusData.getTestCaseResolutionStatusDetails(),
|
mostRecentTestCaseResolutionStatusData.getTestCaseResolutionStatusDetails(),
|
||||||
@ -1391,31 +1317,40 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_testCaseResolutionTaskCloseWorkflowThruFeed(TestInfo test)
|
public void test_testCaseResolutionTaskCloseWorkflowThruFeed(TestInfo test)
|
||||||
throws HttpResponseException {
|
throws HttpResponseException, ParseException {
|
||||||
Long startTs = System.currentTimeMillis();
|
Long startTs = System.currentTimeMillis();
|
||||||
FeedResourceTest feedResourceTest = new FeedResourceTest();
|
FeedResourceTest feedResourceTest = new FeedResourceTest();
|
||||||
|
|
||||||
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
||||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus =
|
|
||||||
|
// Add failed test case, which will create a NEW incident
|
||||||
|
putTestCaseResult(
|
||||||
|
testCaseEntity.getFullyQualifiedName(),
|
||||||
|
new TestCaseResult()
|
||||||
|
.withResult("result")
|
||||||
|
.withTestCaseStatus(TestCaseStatus.Failed)
|
||||||
|
.withTimestamp(TestUtils.dateToTimestamp("2024-01-01")),
|
||||||
|
ADMIN_AUTH_HEADERS);
|
||||||
|
|
||||||
|
// Now, we should be good to create an ASSIGNED status
|
||||||
|
CreateTestCaseResolutionStatus createAssignedIncident =
|
||||||
new CreateTestCaseResolutionStatus()
|
new CreateTestCaseResolutionStatus()
|
||||||
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
||||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
||||||
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
||||||
TestCaseResolutionStatus testCaseFailureStatus =
|
TestCaseResolutionStatus assignedIncident = createTestCaseFailureStatus(createAssignedIncident);
|
||||||
createTestCaseFailureStatus(createTestCaseFailureStatus);
|
|
||||||
|
|
||||||
// Assert that the task is open
|
// Assert that the task is open
|
||||||
String jsonThread =
|
String jsonThread =
|
||||||
Entity.getCollectionDAO()
|
Entity.getCollectionDAO()
|
||||||
.feedDAO()
|
.feedDAO()
|
||||||
.fetchThreadByTestCaseResolutionStatusId(testCaseFailureStatus.getId());
|
.fetchThreadByTestCaseResolutionStatusId(assignedIncident.getStateId());
|
||||||
Thread thread = JsonUtils.readValue(jsonThread, Thread.class);
|
Thread thread = JsonUtils.readValue(jsonThread, Thread.class);
|
||||||
assertEquals(testCaseFailureStatus.getId(), thread.getTask().getTestCaseResolutionStatusId());
|
assertEquals(assignedIncident.getStateId(), thread.getTask().getTestCaseResolutionStatusId());
|
||||||
assertEquals(TaskStatus.Open, thread.getTask().getStatus());
|
assertEquals(TaskStatus.Open, thread.getTask().getStatus());
|
||||||
|
|
||||||
// close the task. The old task should be closed and the latest test case resolution status
|
// close the task. The old task should be closed and the latest test case resolution status
|
||||||
// should be updated (assigned) with the same state ID and a new task should be opened
|
// should be updated (resolved) with the same state ID.
|
||||||
|
|
||||||
CloseTask closeTask =
|
CloseTask closeTask =
|
||||||
new CloseTask()
|
new CloseTask()
|
||||||
.withComment(USER1.getFullyQualifiedName())
|
.withComment(USER1.getFullyQualifiedName())
|
||||||
@ -1424,7 +1359,7 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
jsonThread =
|
jsonThread =
|
||||||
Entity.getCollectionDAO()
|
Entity.getCollectionDAO()
|
||||||
.feedDAO()
|
.feedDAO()
|
||||||
.fetchThreadByTestCaseResolutionStatusId(testCaseFailureStatus.getId());
|
.fetchThreadByTestCaseResolutionStatusId(assignedIncident.getStateId());
|
||||||
thread = JsonUtils.readValue(jsonThread, Thread.class);
|
thread = JsonUtils.readValue(jsonThread, Thread.class);
|
||||||
assertEquals(TaskStatus.Closed, thread.getTask().getStatus());
|
assertEquals(TaskStatus.Closed, thread.getTask().getStatus());
|
||||||
|
|
||||||
@ -1442,49 +1377,48 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
TestCaseResolutionStatus mostRecentTestCaseResolutionStatusData =
|
TestCaseResolutionStatus mostRecentTestCaseResolutionStatusData =
|
||||||
mostRecentTestCaseResolutionStatus.getData().get(0);
|
mostRecentTestCaseResolutionStatus.getData().get(0);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
TestCaseResolutionStatusTypes.Assigned,
|
TestCaseResolutionStatusTypes.Resolved,
|
||||||
mostRecentTestCaseResolutionStatusData.getTestCaseResolutionStatusType());
|
mostRecentTestCaseResolutionStatusData.getTestCaseResolutionStatusType());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
testCaseFailureStatus.getStateId(), mostRecentTestCaseResolutionStatusData.getStateId());
|
assignedIncident.getStateId(), mostRecentTestCaseResolutionStatusData.getStateId());
|
||||||
Assigned assigned =
|
|
||||||
JsonUtils.convertValue(
|
|
||||||
mostRecentTestCaseResolutionStatusData.getTestCaseResolutionStatusDetails(),
|
|
||||||
Assigned.class);
|
|
||||||
assertEquals(USER1.getFullyQualifiedName(), assigned.getAssignee().getFullyQualifiedName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_testCaseResolutionTaskWorkflowThruAPI(TestInfo test)
|
public void test_testCaseResolutionTaskWorkflowThruAPI(TestInfo test)
|
||||||
throws HttpResponseException {
|
throws HttpResponseException, ParseException {
|
||||||
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
||||||
|
|
||||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus =
|
// Add failed test case, which will create a NEW incident
|
||||||
|
putTestCaseResult(
|
||||||
|
testCaseEntity.getFullyQualifiedName(),
|
||||||
|
new TestCaseResult()
|
||||||
|
.withResult("result")
|
||||||
|
.withTestCaseStatus(TestCaseStatus.Failed)
|
||||||
|
.withTimestamp(TestUtils.dateToTimestamp("2024-01-01")),
|
||||||
|
ADMIN_AUTH_HEADERS);
|
||||||
|
|
||||||
|
// Now, we should be good to create an ASSIGNED status
|
||||||
|
CreateTestCaseResolutionStatus createAssignedIncident =
|
||||||
new CreateTestCaseResolutionStatus()
|
new CreateTestCaseResolutionStatus()
|
||||||
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
||||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.New)
|
|
||||||
.withTestCaseResolutionStatusDetails(null);
|
|
||||||
|
|
||||||
createTestCaseFailureStatus(createTestCaseFailureStatus);
|
|
||||||
TestCaseResolutionStatus testCaseFailureStatusAssigned =
|
|
||||||
createTestCaseFailureStatus(
|
|
||||||
createTestCaseFailureStatus
|
|
||||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
||||||
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF)));
|
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
||||||
|
|
||||||
|
TestCaseResolutionStatus assignedIncident = createTestCaseFailureStatus(createAssignedIncident);
|
||||||
|
|
||||||
// Confirm that the task is open
|
// Confirm that the task is open
|
||||||
String jsonThread =
|
String jsonThread =
|
||||||
Entity.getCollectionDAO()
|
Entity.getCollectionDAO()
|
||||||
.feedDAO()
|
.feedDAO()
|
||||||
.fetchThreadByTestCaseResolutionStatusId(testCaseFailureStatusAssigned.getId());
|
.fetchThreadByTestCaseResolutionStatusId(assignedIncident.getStateId());
|
||||||
Thread thread = JsonUtils.readValue(jsonThread, Thread.class);
|
Thread thread = JsonUtils.readValue(jsonThread, Thread.class);
|
||||||
assertEquals(TaskStatus.Open, thread.getTask().getStatus());
|
assertEquals(TaskStatus.Open, thread.getTask().getStatus());
|
||||||
assertEquals(
|
assertEquals(assignedIncident.getStateId(), thread.getTask().getTestCaseResolutionStatusId());
|
||||||
testCaseFailureStatusAssigned.getId(), thread.getTask().getTestCaseResolutionStatusId());
|
|
||||||
|
|
||||||
// Create a new test case resolution status with type Resolved
|
// Create a new test case resolution status with type Resolved
|
||||||
// and confirm the task is closed
|
// and confirm the task is closed
|
||||||
CreateTestCaseResolutionStatus createTestCaseFailureStatusResolved =
|
CreateTestCaseResolutionStatus createTestCaseFailureStatusResolved =
|
||||||
createTestCaseFailureStatus
|
createAssignedIncident
|
||||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Resolved)
|
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Resolved)
|
||||||
.withTestCaseResolutionStatusDetails(
|
.withTestCaseResolutionStatusDetails(
|
||||||
new Resolved()
|
new Resolved()
|
||||||
@ -1499,22 +1433,33 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void unauthorizedTestCaseResolutionFlow(TestInfo test) throws HttpResponseException {
|
public void unauthorizedTestCaseResolutionFlow(TestInfo test)
|
||||||
|
throws HttpResponseException, ParseException {
|
||||||
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
||||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus =
|
// Add failed test case, which will create a NEW incident
|
||||||
|
putTestCaseResult(
|
||||||
|
testCaseEntity.getFullyQualifiedName(),
|
||||||
|
new TestCaseResult()
|
||||||
|
.withResult("result")
|
||||||
|
.withTestCaseStatus(TestCaseStatus.Failed)
|
||||||
|
.withTimestamp(TestUtils.dateToTimestamp("2024-01-01")),
|
||||||
|
ADMIN_AUTH_HEADERS);
|
||||||
|
|
||||||
|
// Now, we should be good to create an ASSIGNED status
|
||||||
|
CreateTestCaseResolutionStatus createAssignedIncident =
|
||||||
new CreateTestCaseResolutionStatus()
|
new CreateTestCaseResolutionStatus()
|
||||||
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
||||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
||||||
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
||||||
createTestCaseFailureStatus(createTestCaseFailureStatus);
|
createTestCaseFailureStatus(createAssignedIncident);
|
||||||
|
|
||||||
assertResponseContains(
|
assertResponseContains(
|
||||||
() ->
|
() ->
|
||||||
createTestCaseFailureStatus(
|
createTestCaseFailureStatus(
|
||||||
createTestCaseFailureStatus.withTestCaseResolutionStatusType(
|
createAssignedIncident.withTestCaseResolutionStatusType(
|
||||||
TestCaseResolutionStatusTypes.Ack)),
|
TestCaseResolutionStatusTypes.Ack)),
|
||||||
BAD_REQUEST,
|
BAD_REQUEST,
|
||||||
"with type `Assigned` cannot be moved to `New` or `Ack`. You can `Assign` or `Resolve` the test case failure.");
|
"Incident with status [Assigned] cannot be moved to [Ack]");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteTestCaseResult(String fqn, Long timestamp, Map<String, String> authHeaders)
|
public void deleteTestCaseResult(String fqn, Long timestamp, Map<String, String> authHeaders)
|
||||||
@ -1626,7 +1571,7 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
|||||||
assertEquals(expectedTestCaseResults.size(), actualTestCaseResults.getData().size());
|
assertEquals(expectedTestCaseResults.size(), actualTestCaseResults.getData().size());
|
||||||
Map<Long, TestCaseResult> testCaseResultMap = new HashMap<>();
|
Map<Long, TestCaseResult> testCaseResultMap = new HashMap<>();
|
||||||
for (TestCaseResult result : actualTestCaseResults.getData()) {
|
for (TestCaseResult result : actualTestCaseResults.getData()) {
|
||||||
result.setTestCaseResolutionStatusReference(null);
|
result.setIncidentId(null);
|
||||||
testCaseResultMap.put(result.getTimestamp(), result);
|
testCaseResultMap.put(result.getTimestamp(), result);
|
||||||
}
|
}
|
||||||
for (TestCaseResult result : expectedTestCaseResults) {
|
for (TestCaseResult result : expectedTestCaseResults) {
|
||||||
|
@ -86,9 +86,9 @@
|
|||||||
"$ref": "#/definitions/testResultValue"
|
"$ref": "#/definitions/testResultValue"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"testCaseResolutionStatusReference": {
|
"incidentId": {
|
||||||
"description": "Reference to the failure status object for the test case result.",
|
"description": "Reference to an ongoing Incident ID (stateId) for this result.",
|
||||||
"$ref": "./testCaseResolutionStatus.json"
|
"$ref": "../type/basic.json#/definitions/uuid"
|
||||||
},
|
},
|
||||||
"passedRows": {
|
"passedRows": {
|
||||||
"description": "Number of rows that passed.",
|
"description": "Number of rows that passed.",
|
||||||
|
@ -69,6 +69,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"testCaseResult": {
|
"testCaseResult": {
|
||||||
|
"description": "Latest test case result obtained for this test case.",
|
||||||
"$ref": "./basic.json#/definitions/testCaseResult"
|
"$ref": "./basic.json#/definitions/testCaseResult"
|
||||||
},
|
},
|
||||||
"version": {
|
"version": {
|
||||||
@ -105,6 +106,13 @@
|
|||||||
"description": "Compute the passed and failed row count for the test case.",
|
"description": "Compute the passed and failed row count for the test case.",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"incidents": {
|
||||||
|
"description": "List of incident IDs (stateId) for any testCaseResult of a given test case.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "../type/basic.json#/definitions/uuid"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "testDefinition", "entityLink", "testSuite"],
|
"required": ["name", "testDefinition", "entityLink", "testSuite"],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user