mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-09 05:56:17 +00:00
* Fix #23445: User with Owner role unable to update Incident Status via API * fix: failing build * fix: permission on test case incidents * playwright: added e2e test for issue #23445 (#23459) * fix: entitylink parsing for entity resource * fix: restore severity state in PATCH test --------- Co-authored-by: Teddy Crepineau <teddy.crepineau@gmail.com> Co-authored-by: Shailesh Parmar <shailesh.parmar.webdev@gmail.com>
This commit is contained in:
parent
6e9c0316e5
commit
aa7715be0d
@ -30,12 +30,13 @@ import jakarta.ws.rs.core.UriInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.openmetadata.schema.EntityInterface;
|
||||
import org.openmetadata.schema.api.tests.CreateTestCaseResolutionStatus;
|
||||
import org.openmetadata.schema.tests.TestCase;
|
||||
import org.openmetadata.schema.tests.type.TestCaseResolutionStatus;
|
||||
import org.openmetadata.schema.tests.type.TestCaseResolutionStatusTypes;
|
||||
import org.openmetadata.schema.type.Include;
|
||||
@ -53,9 +54,9 @@ import org.openmetadata.service.security.AuthRequest;
|
||||
import org.openmetadata.service.security.AuthorizationLogic;
|
||||
import org.openmetadata.service.security.Authorizer;
|
||||
import org.openmetadata.service.security.policyevaluator.OperationContext;
|
||||
import org.openmetadata.service.security.policyevaluator.ResourceContext;
|
||||
import org.openmetadata.service.security.policyevaluator.ResourceContextInterface;
|
||||
import org.openmetadata.service.security.policyevaluator.TestCaseResourceContext;
|
||||
import org.openmetadata.service.util.EntityUtil;
|
||||
import org.openmetadata.service.util.EntityUtil.Fields;
|
||||
import org.openmetadata.service.util.FullyQualifiedName;
|
||||
import org.openmetadata.service.util.RestUtil;
|
||||
@ -159,18 +160,12 @@ public class TestCaseResolutionStatusResource
|
||||
@Parameter(description = "Filter incidents by domain", schema = @Schema(type = "String"))
|
||||
@QueryParam("domain")
|
||||
String domain) {
|
||||
List<AuthRequest> requests = new ArrayList<>();
|
||||
OperationContext testCaseOperationContext =
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.VIEW_ALL);
|
||||
ResourceContextInterface testCaseResourceContext = getTestCaseResourceContext(testCaseFQN);
|
||||
requests.add(new AuthRequest(testCaseOperationContext, testCaseResourceContext));
|
||||
if (originEntityFQN != null) {
|
||||
OperationContext entityOperationContext =
|
||||
new OperationContext(Entity.TABLE, MetadataOperation.VIEW_TESTS);
|
||||
ResourceContextInterface entityResourceContext =
|
||||
new ResourceContext<>(Entity.TABLE, null, originEntityFQN);
|
||||
requests.add(new AuthRequest(entityOperationContext, entityResourceContext));
|
||||
}
|
||||
ResourceContextInterface entityResourceContext =
|
||||
buildEntityResourceContext(testCaseFQN, testCaseId, originEntityFQN);
|
||||
List<AuthRequest> requests =
|
||||
buildViewAuthRequests(testCaseResourceContext, entityResourceContext);
|
||||
|
||||
authorizer.authorizeRequests(securityContext, requests, AuthorizationLogic.ANY);
|
||||
|
||||
ListFilter filter = new ListFilter(include);
|
||||
@ -202,10 +197,11 @@ public class TestCaseResolutionStatusResource
|
||||
@Context SecurityContext securityContext,
|
||||
@Parameter(description = "Sequence ID", schema = @Schema(type = "UUID")) @PathParam("stateId")
|
||||
UUID stateId) {
|
||||
OperationContext testCaseOperationContext =
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.VIEW_ALL);
|
||||
ResourceContextInterface testCaseResourceContext = TestCaseResourceContext.builder().build();
|
||||
authorizer.authorize(securityContext, testCaseOperationContext, testCaseResourceContext);
|
||||
ResourceContextInterface entityResourceContext = TestCaseResourceContext.builder().build();
|
||||
List<AuthRequest> requests =
|
||||
buildViewAuthRequests(testCaseResourceContext, entityResourceContext);
|
||||
authorizer.authorizeRequests(securityContext, requests, AuthorizationLogic.ANY);
|
||||
|
||||
return repository.listTestCaseResolutionStatusesForStateId(stateId);
|
||||
}
|
||||
@ -230,12 +226,26 @@ public class TestCaseResolutionStatusResource
|
||||
@Parameter(description = "Test Case Failure Status ID", schema = @Schema(type = "UUID"))
|
||||
@PathParam("id")
|
||||
UUID testCaseResolutionStatusId) {
|
||||
OperationContext testCaseOperationContext =
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.VIEW_ALL);
|
||||
ResourceContextInterface testCaseResourceContext = TestCaseResourceContext.builder().build();
|
||||
authorizer.authorize(securityContext, testCaseOperationContext, testCaseResourceContext);
|
||||
TestCaseResolutionStatus testCaseResolutionStatus =
|
||||
repository.getById(testCaseResolutionStatusId);
|
||||
TestCase testCase =
|
||||
Entity.getEntityByName(
|
||||
Entity.TEST_CASE,
|
||||
testCaseResolutionStatus.getTestCaseReference().getFullyQualifiedName(),
|
||||
"",
|
||||
Include.ALL);
|
||||
|
||||
return repository.getById(testCaseResolutionStatusId);
|
||||
MessageParser.EntityLink entityLink = MessageParser.EntityLink.parse(testCase.getEntityLink());
|
||||
|
||||
ResourceContextInterface testCaseResourceContext =
|
||||
TestCaseResourceContext.builder().name(testCase.getFullyQualifiedName()).build();
|
||||
ResourceContextInterface entityResourceContext =
|
||||
TestCaseResourceContext.builder().entityLink(entityLink).build();
|
||||
List<AuthRequest> requests =
|
||||
buildViewAuthRequests(testCaseResourceContext, entityResourceContext);
|
||||
authorizer.authorizeRequests(securityContext, requests, AuthorizationLogic.ANY);
|
||||
|
||||
return testCaseResolutionStatus;
|
||||
}
|
||||
|
||||
@POST
|
||||
@ -256,17 +266,22 @@ public class TestCaseResolutionStatusResource
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Valid CreateTestCaseResolutionStatus createTestCaseResolutionStatus) {
|
||||
OperationContext testCaseOperationContext =
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.EDIT_TESTS);
|
||||
ResourceContextInterface testCaseResourceContext = TestCaseResourceContext.builder().build();
|
||||
OperationContext entityOperationContext =
|
||||
new OperationContext(Entity.TABLE, MetadataOperation.EDIT_TESTS);
|
||||
ResourceContextInterface entityResourceContext = TestCaseResourceContext.builder().build();
|
||||
|
||||
TestCase testCase =
|
||||
Entity.getEntityByName(
|
||||
Entity.TEST_CASE,
|
||||
createTestCaseResolutionStatus.getTestCaseReference(),
|
||||
"",
|
||||
Include.ALL);
|
||||
|
||||
MessageParser.EntityLink entityLink = MessageParser.EntityLink.parse(testCase.getEntityLink());
|
||||
|
||||
ResourceContextInterface testCaseResourceContext =
|
||||
TestCaseResourceContext.builder().name(testCase.getFullyQualifiedName()).build();
|
||||
ResourceContextInterface entityResourceContext =
|
||||
TestCaseResourceContext.builder().entityLink(entityLink).build();
|
||||
List<AuthRequest> requests =
|
||||
List.of(
|
||||
new AuthRequest(entityOperationContext, entityResourceContext),
|
||||
new AuthRequest(testCaseOperationContext, testCaseResourceContext));
|
||||
buildEditAuthRequests(testCaseResourceContext, entityResourceContext);
|
||||
|
||||
authorizer.authorizeRequests(securityContext, requests, AuthorizationLogic.ANY);
|
||||
TestCaseResolutionStatus testCaseResolutionStatus =
|
||||
@ -305,10 +320,25 @@ public class TestCaseResolutionStatusResource
|
||||
}))
|
||||
JsonPatch patch)
|
||||
throws IntrospectionException, InvocationTargetException, IllegalAccessException {
|
||||
OperationContext testCaseOperationContext =
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.EDIT_TESTS);
|
||||
ResourceContextInterface testCaseResourceContext = TestCaseResourceContext.builder().build();
|
||||
authorizer.authorize(securityContext, testCaseOperationContext, testCaseResourceContext);
|
||||
|
||||
TestCaseResolutionStatus testCaseResolutionStatus = repository.getById(id);
|
||||
TestCase testCase =
|
||||
Entity.getEntityByName(
|
||||
Entity.TEST_CASE,
|
||||
testCaseResolutionStatus.getTestCaseReference().getFullyQualifiedName(),
|
||||
"",
|
||||
Include.ALL);
|
||||
|
||||
MessageParser.EntityLink entityLink = MessageParser.EntityLink.parse(testCase.getEntityLink());
|
||||
|
||||
ResourceContextInterface testCaseResourceContext =
|
||||
TestCaseResourceContext.builder().name(testCase.getFullyQualifiedName()).build();
|
||||
ResourceContextInterface entityResourceContext =
|
||||
TestCaseResourceContext.builder().entityLink(entityLink).build();
|
||||
List<AuthRequest> requests =
|
||||
buildEditAuthRequests(testCaseResourceContext, entityResourceContext);
|
||||
|
||||
authorizer.authorizeRequests(securityContext, requests, AuthorizationLogic.ANY);
|
||||
RestUtil.PatchResponse<TestCaseResolutionStatus> response =
|
||||
repository.patch(id, patch, securityContext.getUserPrincipal().getName());
|
||||
return response.toResponse();
|
||||
@ -420,11 +450,19 @@ public class TestCaseResolutionStatusResource
|
||||
searchListFilter.addQueryParam("originEntityFQN", originEntityFQN);
|
||||
searchListFilter.addQueryParam("domains", domain);
|
||||
|
||||
OperationContext testCaseOperationContext =
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.VIEW_ALL);
|
||||
ResourceContextInterface testCaseResourceContext = getTestCaseResourceContext(testCaseFQN);
|
||||
ResourceContextInterface testCaseResourceContext = TestCaseResourceContext.builder().build();
|
||||
ResourceContextInterface entityResourceContext =
|
||||
buildEntityResourceContext(testCaseFQN, null, originEntityFQN);
|
||||
List<AuthRequest> requests =
|
||||
List.of(
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.VIEW_ALL),
|
||||
testCaseResourceContext),
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TABLE, MetadataOperation.VIEW_ALL),
|
||||
entityResourceContext));
|
||||
|
||||
authorizer.authorize(securityContext, testCaseOperationContext, testCaseResourceContext);
|
||||
authorizer.authorizeRequests(securityContext, requests, AuthorizationLogic.ANY);
|
||||
|
||||
if (latest) {
|
||||
// For latest results, use aggregation grouped by test case to get the latest status per test
|
||||
@ -436,17 +474,8 @@ public class TestCaseResolutionStatusResource
|
||||
// case
|
||||
null);
|
||||
} else {
|
||||
return super.listInternalFromSearch(
|
||||
securityContext,
|
||||
new Fields(null),
|
||||
searchListFilter,
|
||||
limit,
|
||||
offset,
|
||||
searchSortFilter,
|
||||
null,
|
||||
null,
|
||||
testCaseOperationContext,
|
||||
testCaseResourceContext);
|
||||
return repository.listFromSearchWithOffset(
|
||||
new Fields(null), searchListFilter, limit, offset, searchSortFilter, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,4 +500,58 @@ public class TestCaseResolutionStatusResource
|
||||
}
|
||||
return resourceContext;
|
||||
}
|
||||
|
||||
protected static List<AuthRequest> buildViewAuthRequests(
|
||||
ResourceContextInterface testCaseResourceContext,
|
||||
ResourceContextInterface entityResourceContext) {
|
||||
return List.of(
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.VIEW_ALL),
|
||||
testCaseResourceContext),
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TABLE, MetadataOperation.VIEW_ALL), entityResourceContext),
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TABLE, MetadataOperation.VIEW_TESTS),
|
||||
entityResourceContext));
|
||||
}
|
||||
|
||||
protected static List<AuthRequest> buildEditAuthRequests(
|
||||
ResourceContextInterface testCaseResourceContext,
|
||||
ResourceContextInterface entityResourceContext) {
|
||||
return List.of(
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TABLE, MetadataOperation.EDIT_TESTS),
|
||||
entityResourceContext),
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TABLE, MetadataOperation.EDIT_ALL), entityResourceContext),
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.EDIT_TESTS),
|
||||
testCaseResourceContext),
|
||||
new AuthRequest(
|
||||
new OperationContext(Entity.TEST_CASE, MetadataOperation.EDIT_ALL),
|
||||
testCaseResourceContext));
|
||||
}
|
||||
|
||||
protected static ResourceContextInterface buildEntityResourceContext(
|
||||
String testCaseFQN, UUID testCaseId, String originEntityFQN) {
|
||||
if (testCaseFQN != null) {
|
||||
TestCase testCase = Entity.getEntityByName(Entity.TEST_CASE, testCaseFQN, "", Include.ALL);
|
||||
MessageParser.EntityLink entityLink =
|
||||
MessageParser.EntityLink.parse(testCase.getEntityLink());
|
||||
return TestCaseResourceContext.builder().entityLink(entityLink).build();
|
||||
} else if (testCaseId != null) {
|
||||
TestCase testCase = Entity.getEntity(Entity.TEST_CASE, testCaseId, "", Include.ALL);
|
||||
MessageParser.EntityLink entityLink =
|
||||
MessageParser.EntityLink.parse(testCase.getEntityLink());
|
||||
return TestCaseResourceContext.builder().entityLink(entityLink).build();
|
||||
} else if (originEntityFQN != null) {
|
||||
EntityInterface entityInterface =
|
||||
Entity.getEntityByName(Entity.TABLE, originEntityFQN, "", Include.ALL);
|
||||
String entityLinkStr =
|
||||
EntityUtil.buildEntityLink(Entity.TABLE, entityInterface.getFullyQualifiedName());
|
||||
MessageParser.EntityLink entityLink = MessageParser.EntityLink.parse(entityLinkStr);
|
||||
return TestCaseResourceContext.builder().entityLink(entityLink).build();
|
||||
}
|
||||
return TestCaseResourceContext.builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,6 +242,10 @@ public final class EntityUtil {
|
||||
return Entity.getEntityReferenceByName(entityType, fqn, ALL);
|
||||
}
|
||||
|
||||
public static String buildEntityLink(String entityType, String fullyQualifiedName) {
|
||||
return String.format("<#E::%s::%s>", entityType, fullyQualifiedName);
|
||||
}
|
||||
|
||||
public static UsageDetails getLatestUsage(UsageDAO usageDAO, UUID entityId) {
|
||||
LOG.debug("Getting latest usage for {}", entityId);
|
||||
UsageDetails details = usageDAO.getLatestUsage(entityId.toString());
|
||||
|
||||
@ -1631,7 +1631,7 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
|
||||
// Get the test case resolution by FQN
|
||||
Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("testCaseFQN", TEST_TABLE1.getFullyQualifiedName());
|
||||
queryParams.put("testCaseFQN", testCaseEntity2.getFullyQualifiedName());
|
||||
storedTestCaseResolutions = getTestCaseFailureStatus(startTs, endTs, null, null, queryParams);
|
||||
assertTrue(
|
||||
storedTestCaseResolutions.getData().stream()
|
||||
@ -1639,7 +1639,7 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
t ->
|
||||
t.getTestCaseReference()
|
||||
.getFullyQualifiedName()
|
||||
.equals(testCaseEntity1.getFullyQualifiedName())));
|
||||
.equals(testCaseEntity2.getFullyQualifiedName())));
|
||||
|
||||
// Get the test case resolution by origin entity FQN
|
||||
queryParams.clear();
|
||||
@ -1654,8 +1654,10 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
}
|
||||
|
||||
queryParams.put("originEntityFQN", "IDONOTEXIST123");
|
||||
storedTestCaseResolutions = getTestCaseFailureStatus(startTs, endTs, null, null, queryParams);
|
||||
assertEquals(0, storedTestCaseResolutions.getData().size());
|
||||
assertResponse(
|
||||
() -> getTestCaseFailureStatus(startTs, endTs, null, null, queryParams),
|
||||
NOT_FOUND,
|
||||
"table instance for IDONOTEXIST123 not found");
|
||||
|
||||
// Delete test case recursively and check that the test case resolution status is also deleted
|
||||
// 1. soft delete - should not delete the test case resolution status
|
||||
@ -1744,32 +1746,91 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
}
|
||||
|
||||
@Test
|
||||
void patch_TestCaseResultFailure(TestInfo test) throws HttpResponseException {
|
||||
TestCase testCaseEntity = createEntity(createRequest(getEntityName(test)), ADMIN_AUTH_HEADERS);
|
||||
void patch_TestCaseResultFailure(TestInfo test) throws IOException {
|
||||
// Create a table owned by USER_TABLE_OWNER to test owner permissions
|
||||
TableResourceTest tableResourceTest = new TableResourceTest();
|
||||
CreateTable tableReq =
|
||||
tableResourceTest
|
||||
.createRequest(test)
|
||||
.withName("ownerAuthTestTable")
|
||||
.withDatabaseSchema(DATABASE_SCHEMA.getFullyQualifiedName())
|
||||
.withColumns(
|
||||
List.of(new Column().withName(C1).withDisplayName("c1").withDataType(BIGINT)))
|
||||
.withOwners(List.of(USER_TABLE_OWNER.getEntityReference()));
|
||||
Table table = tableResourceTest.createAndCheckEntity(tableReq, ADMIN_AUTH_HEADERS);
|
||||
|
||||
CreateTestCase createTestCase = createRequest(test);
|
||||
createTestCase
|
||||
.withEntityLink(String.format("<#E::table::%s>", table.getFullyQualifiedName()))
|
||||
.withTestDefinition(TEST_DEFINITION4.getFullyQualifiedName())
|
||||
.withParameterValues(
|
||||
List.of(new TestCaseParameterValue().withValue("100").withName("maxValue")))
|
||||
.withOwners(List.of(USER_TABLE_OWNER.getEntityReference()));
|
||||
TestCase testCaseEntity = createAndCheckEntity(createTestCase, ADMIN_AUTH_HEADERS);
|
||||
|
||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus =
|
||||
new CreateTestCaseResolutionStatus()
|
||||
.withTestCaseReference(testCaseEntity.getFullyQualifiedName())
|
||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Ack)
|
||||
.withSeverity(Severity.Severity2)
|
||||
.withTestCaseResolutionStatusDetails(null);
|
||||
|
||||
// Test 1: Admin can create incident status
|
||||
TestCaseResolutionStatus testCaseFailureStatus =
|
||||
createTestCaseFailureStatus(createTestCaseFailureStatus);
|
||||
|
||||
// Test 2: USER_TABLE_OWNER (owner with EDIT_TESTS via isOwner() policy) can patch
|
||||
String original = JsonUtils.pojoToJson(testCaseFailureStatus);
|
||||
String updated =
|
||||
JsonUtils.pojoToJson(
|
||||
testCaseFailureStatus
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withUpdatedBy(USER1_REF)
|
||||
.withUpdatedBy(USER_TABLE_OWNER.getEntityReference())
|
||||
.withSeverity(Severity.Severity1));
|
||||
JsonNode patch = TestUtils.getJsonPatch(original, updated);
|
||||
TestCaseResolutionStatus patched =
|
||||
patchTestCaseResultFailureStatus(testCaseFailureStatus.getId(), patch);
|
||||
patchTestCaseResultFailureStatus(
|
||||
testCaseFailureStatus.getId(), patch, authHeaders(USER_TABLE_OWNER.getName()));
|
||||
TestCaseResolutionStatus stored = getTestCaseFailureStatus(testCaseFailureStatus.getId());
|
||||
|
||||
// check our patch fields have been updated
|
||||
assertEquals(patched.getUpdatedAt(), stored.getUpdatedAt());
|
||||
assertEquals(patched.getUpdatedBy(), stored.getUpdatedBy());
|
||||
assertEquals(patched.getSeverity(), stored.getSeverity());
|
||||
assertEquals(Severity.Severity1, stored.getSeverity());
|
||||
|
||||
// Test 3: USER_NO_PERMISSIONS cannot patch (not owner, no EDIT_ALL)
|
||||
String updatedForNoPerms =
|
||||
JsonUtils.pojoToJson(
|
||||
stored
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withUpdatedBy(USER_NO_PERMISSIONS.getEntityReference())
|
||||
.withSeverity(Severity.Severity3));
|
||||
JsonNode patchForNoPerms =
|
||||
TestUtils.getJsonPatch(JsonUtils.pojoToJson(stored), updatedForNoPerms);
|
||||
assertResponse(
|
||||
() ->
|
||||
patchTestCaseResultFailureStatus(
|
||||
stored.getId(), patchForNoPerms, authHeaders(USER_NO_PERMISSIONS.getName())),
|
||||
FORBIDDEN,
|
||||
"User does not have ANY of the required permissions.");
|
||||
|
||||
// Test 4: CREATE_ALL_OPS_USER (with EDIT_ALL on TEST_CASE) can patch even if not owner
|
||||
String updatedForAllOps =
|
||||
JsonUtils.pojoToJson(
|
||||
stored
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withUpdatedBy(CREATE_ALL_OPS_USER.getEntityReference())
|
||||
.withSeverity(Severity.Severity4));
|
||||
JsonNode patchForAllOps =
|
||||
TestUtils.getJsonPatch(
|
||||
JsonUtils.pojoToJson(
|
||||
stored.withSeverity(
|
||||
Severity.Severity3)), // restore to previous state as modified ln 1823
|
||||
updatedForAllOps);
|
||||
TestCaseResolutionStatus patchedByAllOps =
|
||||
patchTestCaseResultFailureStatus(
|
||||
stored.getId(), patchForAllOps, authHeaders(CREATE_ALL_OPS_USER.getName()));
|
||||
assertEquals(Severity.Severity4, patchedByAllOps.getSeverity());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -2140,6 +2201,216 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_incidentStatusPermissions(TestInfo test) throws IOException, ParseException {
|
||||
// Create a table without specific ownership for testing permissions
|
||||
TableResourceTest tableResourceTest = new TableResourceTest();
|
||||
CreateTable tableReq =
|
||||
tableResourceTest
|
||||
.createRequest(test)
|
||||
.withName("permissionTestTable")
|
||||
.withDatabaseSchema(DATABASE_SCHEMA.getFullyQualifiedName())
|
||||
.withColumns(
|
||||
List.of(new Column().withName(C1).withDisplayName("c1").withDataType(BIGINT)));
|
||||
Table table = tableResourceTest.createAndCheckEntity(tableReq, ADMIN_AUTH_HEADERS);
|
||||
|
||||
// Create a test case for the table
|
||||
CreateTestCase create = createRequest(test);
|
||||
create
|
||||
.withEntityLink(String.format("<#E::table::%s>", table.getFullyQualifiedName()))
|
||||
.withTestDefinition(TEST_DEFINITION4.getFullyQualifiedName())
|
||||
.withParameterValues(
|
||||
List.of(new TestCaseParameterValue().withValue("100").withName("maxValue")));
|
||||
TestCase testCase = createAndCheckEntity(create, ADMIN_AUTH_HEADERS);
|
||||
|
||||
// Add a failed test result to create an incident
|
||||
postTestCaseResult(
|
||||
testCase.getFullyQualifiedName(),
|
||||
new CreateTestCaseResult()
|
||||
.withResult("failed")
|
||||
.withTestCaseStatus(TestCaseStatus.Failed)
|
||||
.withTimestamp(TestUtils.dateToTimestamp("2024-01-01")),
|
||||
ADMIN_AUTH_HEADERS);
|
||||
|
||||
CreateTestCaseResolutionStatus createIncident =
|
||||
new CreateTestCaseResolutionStatus()
|
||||
.withTestCaseReference(testCase.getFullyQualifiedName())
|
||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
||||
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
||||
|
||||
// Test 1: User with EDIT_TESTS permission on Table should be able to create incident
|
||||
TestCaseResolutionStatus incident1 =
|
||||
createTestCaseFailureStatus(createIncident, authHeaders(USER_TABLE_EDIT_TESTS.getName()));
|
||||
assertNotNull(incident1);
|
||||
assertEquals(
|
||||
TestCaseResolutionStatusTypes.Assigned, incident1.getTestCaseResolutionStatusType());
|
||||
|
||||
// Test 2: User with EDIT_TESTS permission on Table should be able to update incident
|
||||
String original1 = JsonUtils.pojoToJson(incident1);
|
||||
String updated1 =
|
||||
JsonUtils.pojoToJson(
|
||||
incident1
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withUpdatedBy(USER_TABLE_EDIT_TESTS.getEntityReference())
|
||||
.withSeverity(Severity.Severity1));
|
||||
JsonNode patch1 = TestUtils.getJsonPatch(original1, updated1);
|
||||
TestCaseResolutionStatus patched1 =
|
||||
patchTestCaseResultFailureStatus(
|
||||
incident1.getId(), patch1, authHeaders(USER_TABLE_EDIT_TESTS.getName()));
|
||||
assertEquals(Severity.Severity1, patched1.getSeverity());
|
||||
|
||||
// Test 3: User with EDIT_ALL permission on TestCase should be able to create incident
|
||||
CreateTestCaseResolutionStatus createIncident2 =
|
||||
new CreateTestCaseResolutionStatus()
|
||||
.withTestCaseReference(testCase.getFullyQualifiedName())
|
||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Resolved)
|
||||
.withTestCaseResolutionStatusDetails(new Resolved());
|
||||
|
||||
TestCaseResolutionStatus incident2 =
|
||||
createTestCaseFailureStatus(createIncident2, authHeaders(USER_TEST_CASE_UPDATE.getName()));
|
||||
assertNotNull(incident2);
|
||||
assertEquals(
|
||||
TestCaseResolutionStatusTypes.Resolved, incident2.getTestCaseResolutionStatusType());
|
||||
|
||||
// Test 4: User with EDIT_ALL permission on TestCase should be able to update incident
|
||||
String original2 = JsonUtils.pojoToJson(incident2);
|
||||
String updated2 =
|
||||
JsonUtils.pojoToJson(
|
||||
incident2
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withUpdatedBy(USER_TEST_CASE_UPDATE.getEntityReference())
|
||||
.withSeverity(Severity.Severity2));
|
||||
JsonNode patch2 = TestUtils.getJsonPatch(original2, updated2);
|
||||
TestCaseResolutionStatus patched2 =
|
||||
patchTestCaseResultFailureStatus(
|
||||
incident2.getId(), patch2, authHeaders(USER_TEST_CASE_UPDATE.getName()));
|
||||
assertEquals(Severity.Severity2, patched2.getSeverity());
|
||||
|
||||
// Test 5: User with ALL permissions should be able to create incident
|
||||
TestCaseResolutionStatus incident3 =
|
||||
createTestCaseFailureStatus(createIncident, authHeaders(CREATE_ALL_OPS_USER.getName()));
|
||||
assertNotNull(incident3);
|
||||
|
||||
// Test 6: User with ALL permissions should be able to update incident
|
||||
String original3 = JsonUtils.pojoToJson(incident3);
|
||||
String updated3 =
|
||||
JsonUtils.pojoToJson(
|
||||
incident3
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withUpdatedBy(CREATE_ALL_OPS_USER.getEntityReference())
|
||||
.withSeverity(Severity.Severity3));
|
||||
JsonNode patch3 = TestUtils.getJsonPatch(original3, updated3);
|
||||
TestCaseResolutionStatus patched3 =
|
||||
patchTestCaseResultFailureStatus(
|
||||
incident3.getId(), patch3, authHeaders(CREATE_ALL_OPS_USER.getName()));
|
||||
assertEquals(Severity.Severity3, patched3.getSeverity());
|
||||
|
||||
// Test 7: User without required permissions should NOT be able to create incident
|
||||
assertThrows(
|
||||
HttpResponseException.class,
|
||||
() ->
|
||||
createTestCaseFailureStatus(createIncident, authHeaders(USER_NO_PERMISSIONS.getName())),
|
||||
"User without permissions should not be able to create incident status");
|
||||
|
||||
// Test 8: User without required permissions should NOT be able to update incident
|
||||
String originalNoPerms = JsonUtils.pojoToJson(incident1);
|
||||
String updatedNoPerms =
|
||||
JsonUtils.pojoToJson(
|
||||
incident1
|
||||
.withUpdatedAt(System.currentTimeMillis())
|
||||
.withUpdatedBy(USER_NO_PERMISSIONS.getEntityReference())
|
||||
.withSeverity(Severity.Severity4));
|
||||
JsonNode patchNoPerms = TestUtils.getJsonPatch(originalNoPerms, updatedNoPerms);
|
||||
|
||||
assertThrows(
|
||||
HttpResponseException.class,
|
||||
() ->
|
||||
patchTestCaseResultFailureStatus(
|
||||
incident1.getId(), patchNoPerms, authHeaders(USER_NO_PERMISSIONS.getName())),
|
||||
"User without permissions should not be able to update incident status");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_getTestCaseResolutionStatusPermissions(TestInfo test)
|
||||
throws IOException, ParseException {
|
||||
// Create a test case to test TestCaseResolutionStatus GET permissions
|
||||
CreateTestCase create = createRequest(test);
|
||||
create
|
||||
.withEntityLink(TABLE_LINK)
|
||||
.withTestDefinition(TEST_DEFINITION4.getFullyQualifiedName())
|
||||
.withParameterValues(
|
||||
List.of(new TestCaseParameterValue().withValue("100").withName("maxValue")));
|
||||
TestCase testCase = createAndCheckEntity(create, ADMIN_AUTH_HEADERS);
|
||||
|
||||
// Add a failed test result to create an incident
|
||||
postTestCaseResult(
|
||||
testCase.getFullyQualifiedName(),
|
||||
new CreateTestCaseResult()
|
||||
.withResult("failed")
|
||||
.withTestCaseStatus(TestCaseStatus.Failed)
|
||||
.withTimestamp(TestUtils.dateToTimestamp("2024-01-01")),
|
||||
ADMIN_AUTH_HEADERS);
|
||||
|
||||
// Create a test case resolution status (incident)
|
||||
CreateTestCaseResolutionStatus createIncident =
|
||||
new CreateTestCaseResolutionStatus()
|
||||
.withTestCaseReference(testCase.getFullyQualifiedName())
|
||||
.withTestCaseResolutionStatusType(TestCaseResolutionStatusTypes.Assigned)
|
||||
.withTestCaseResolutionStatusDetails(new Assigned().withAssignee(USER1_REF));
|
||||
TestCaseResolutionStatus incident =
|
||||
createTestCaseFailureStatus(createIncident, ADMIN_AUTH_HEADERS);
|
||||
|
||||
// Test GET by ID endpoint permissions for TestCaseResolutionStatus
|
||||
// Admin should be able to retrieve test case resolution status
|
||||
TestCaseResolutionStatus retrievedIncident1 =
|
||||
getTestCaseFailureStatus(incident.getId(), ADMIN_AUTH_HEADERS);
|
||||
assertNotNull(retrievedIncident1);
|
||||
|
||||
// Data consumer should be able to view test case resolution status (has VIEW permissions)
|
||||
TestCaseResolutionStatus retrievedIncident2 =
|
||||
getTestCaseFailureStatus(incident.getId(), authHeaders(DATA_CONSUMER.getName()));
|
||||
assertNotNull(retrievedIncident2);
|
||||
|
||||
// Data steward should be able to view test case resolution status (has VIEW permissions)
|
||||
TestCaseResolutionStatus retrievedIncident3 =
|
||||
getTestCaseFailureStatus(incident.getId(), authHeaders(DATA_STEWARD.getName()));
|
||||
assertNotNull(retrievedIncident3);
|
||||
|
||||
// Test GET list endpoint permissions for TestCaseResolutionStatus with testCaseFQN parameter
|
||||
// Admin should be able to list test case resolution statuses
|
||||
ResultList<TestCaseResolutionStatus> listResult1 =
|
||||
listTestCaseFailureStatusWithFQN(testCase.getFullyQualifiedName(), ADMIN_AUTH_HEADERS);
|
||||
assertNotNull(listResult1);
|
||||
|
||||
// Data consumer should be able to list test case resolution statuses
|
||||
ResultList<TestCaseResolutionStatus> listResult2 =
|
||||
listTestCaseFailureStatusWithFQN(
|
||||
testCase.getFullyQualifiedName(), authHeaders(DATA_CONSUMER.getName()));
|
||||
assertNotNull(listResult2);
|
||||
|
||||
// Data steward should be able to list test case resolution statuses
|
||||
ResultList<TestCaseResolutionStatus> listResult3 =
|
||||
listTestCaseFailureStatusWithFQN(
|
||||
testCase.getFullyQualifiedName(), authHeaders(DATA_STEWARD.getName()));
|
||||
assertNotNull(listResult3);
|
||||
|
||||
// Test GET list endpoint permissions for TestCaseResolutionStatus with testCaseId parameter
|
||||
// Admin should be able to list test case resolution statuses by test case ID
|
||||
ResultList<TestCaseResolutionStatus> listResult4 =
|
||||
listTestCaseFailureStatusWithId(testCase.getId(), ADMIN_AUTH_HEADERS);
|
||||
assertNotNull(listResult4);
|
||||
|
||||
// Data consumer should be able to list test case resolution statuses by test case ID
|
||||
ResultList<TestCaseResolutionStatus> listResult5 =
|
||||
listTestCaseFailureStatusWithId(testCase.getId(), authHeaders(DATA_CONSUMER.getName()));
|
||||
assertNotNull(listResult5);
|
||||
|
||||
// Data steward should be able to list test case resolution statuses by test case ID
|
||||
ResultList<TestCaseResolutionStatus> listResult6 =
|
||||
listTestCaseFailureStatusWithId(testCase.getId(), authHeaders(DATA_STEWARD.getName()));
|
||||
assertNotNull(listResult6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void wrongMinMaxTestParameter(TestInfo test) throws HttpResponseException {
|
||||
CreateTestCase validTestCase = createRequest(test);
|
||||
@ -3670,19 +3941,27 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
|
||||
private TestCaseResolutionStatus createTestCaseFailureStatus(
|
||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus) throws HttpResponseException {
|
||||
return createTestCaseFailureStatus(createTestCaseFailureStatus, ADMIN_AUTH_HEADERS);
|
||||
}
|
||||
|
||||
private TestCaseResolutionStatus createTestCaseFailureStatus(
|
||||
CreateTestCaseResolutionStatus createTestCaseFailureStatus, Map<String, String> authHeaders)
|
||||
throws HttpResponseException {
|
||||
WebTarget target = getCollection().path("/testCaseIncidentStatus");
|
||||
return TestUtils.post(
|
||||
target,
|
||||
createTestCaseFailureStatus,
|
||||
TestCaseResolutionStatus.class,
|
||||
200,
|
||||
ADMIN_AUTH_HEADERS);
|
||||
target, createTestCaseFailureStatus, TestCaseResolutionStatus.class, 200, authHeaders);
|
||||
}
|
||||
|
||||
private TestCaseResolutionStatus patchTestCaseResultFailureStatus(
|
||||
UUID testCaseFailureStatusId, JsonNode patch) throws HttpResponseException {
|
||||
return patchTestCaseResultFailureStatus(testCaseFailureStatusId, patch, ADMIN_AUTH_HEADERS);
|
||||
}
|
||||
|
||||
private TestCaseResolutionStatus patchTestCaseResultFailureStatus(
|
||||
UUID testCaseFailureStatusId, JsonNode patch, Map<String, String> authHeaders)
|
||||
throws HttpResponseException {
|
||||
WebTarget target = getCollection().path("/testCaseIncidentStatus/" + testCaseFailureStatusId);
|
||||
return TestUtils.patch(target, patch, TestCaseResolutionStatus.class, ADMIN_AUTH_HEADERS);
|
||||
return TestUtils.patch(target, patch, TestCaseResolutionStatus.class, authHeaders);
|
||||
}
|
||||
|
||||
private TestCaseResolutionStatus getTestCaseFailureStatus(UUID testCaseFailureStatusId)
|
||||
@ -4645,4 +4924,49 @@ public class TestCaseResourceTest extends EntityResourceTest<TestCase, CreateTes
|
||||
retrievedExplicitTestCase.getReviewers().getFirst().getId(),
|
||||
"TestCase should still have USER1 as reviewer");
|
||||
}
|
||||
|
||||
private ResultList<TestCaseResolutionStatus> listTestCaseFailureStatusWithFQN(
|
||||
String testCaseFQN, Map<String, String> authHeaders) throws HttpResponseException {
|
||||
WebTarget target =
|
||||
getResource("dataQuality/testCases/testCaseIncidentStatus")
|
||||
.queryParam("testCaseFQN", testCaseFQN)
|
||||
.queryParam("startTs", 0)
|
||||
.queryParam("endTs", System.currentTimeMillis());
|
||||
return TestUtils.get(
|
||||
target,
|
||||
TestCaseResolutionStatusResource.TestCaseResolutionStatusResultList.class,
|
||||
authHeaders);
|
||||
}
|
||||
|
||||
private ResultList<TestCaseResolutionStatus> listTestCaseFailureStatusWithId(
|
||||
UUID testCaseId, Map<String, String> authHeaders) throws HttpResponseException {
|
||||
WebTarget target =
|
||||
getResource("dataQuality/testCases/testCaseIncidentStatus")
|
||||
.queryParam("testCaseId", testCaseId)
|
||||
.queryParam("startTs", 0)
|
||||
.queryParam("endTs", System.currentTimeMillis());
|
||||
return TestUtils.get(
|
||||
target,
|
||||
TestCaseResolutionStatusResource.TestCaseResolutionStatusResultList.class,
|
||||
authHeaders);
|
||||
}
|
||||
|
||||
private ResultList<TestCaseResolutionStatus> listTestCaseFailureStatusWithOriginFQN(
|
||||
String originEntityFQN, Map<String, String> authHeaders) throws HttpResponseException {
|
||||
WebTarget target =
|
||||
getResource("dataQuality/testCases/testCaseIncidentStatus")
|
||||
.queryParam("originEntityFQN", originEntityFQN)
|
||||
.queryParam("startTs", 0)
|
||||
.queryParam("endTs", System.currentTimeMillis());
|
||||
return TestUtils.get(
|
||||
target,
|
||||
TestCaseResolutionStatusResource.TestCaseResolutionStatusResultList.class,
|
||||
authHeaders);
|
||||
}
|
||||
|
||||
private TestCaseResolutionStatus getTestCaseFailureStatus(
|
||||
UUID id, Map<String, String> authHeaders) throws HttpResponseException {
|
||||
WebTarget target = getResource("dataQuality/testCases/testCaseIncidentStatus/" + id);
|
||||
return TestUtils.get(target, TestCaseResolutionStatus.class, authHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,20 +10,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import test, { expect } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
import { get } from 'lodash';
|
||||
import { PLAYWRIGHT_INGESTION_TAG_OBJ } from '../../constant/config';
|
||||
import { SidebarItem } from '../../constant/sidebar';
|
||||
import { EntityTypeEndpoint } from '../../support/entity/Entity.interface';
|
||||
import { TableClass } from '../../support/entity/TableClass';
|
||||
import { UserClass } from '../../support/user/UserClass';
|
||||
import { performAdminLogin } from '../../utils/admin';
|
||||
import { resetTokenFromBotPage } from '../../utils/bot';
|
||||
import {
|
||||
clickOutside,
|
||||
createNewPage,
|
||||
descriptionBox,
|
||||
getApiContext,
|
||||
redirectToHomePage,
|
||||
} from '../../utils/common';
|
||||
import { addOwner } from '../../utils/entity';
|
||||
import {
|
||||
acknowledgeTask,
|
||||
assignIncident,
|
||||
@ -32,6 +34,7 @@ import {
|
||||
} from '../../utils/incidentManager';
|
||||
import { makeRetryRequest } from '../../utils/serviceIngestion';
|
||||
import { sidebarClick } from '../../utils/sidebar';
|
||||
import { test } from '../fixtures/pages';
|
||||
|
||||
const user1 = new UserClass();
|
||||
const user2 = new UserClass();
|
||||
@ -39,9 +42,6 @@ const user3 = new UserClass();
|
||||
const users = [user1, user2, user3];
|
||||
const table1 = new TableClass();
|
||||
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => {
|
||||
@ -49,7 +49,7 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => {
|
||||
// since we need to poll for the pipeline status, we need to increase the timeout
|
||||
test.slow();
|
||||
|
||||
const { afterAction, apiContext, page } = await createNewPage(browser);
|
||||
const { afterAction, apiContext, page } = await performAdminLogin(browser);
|
||||
|
||||
if (!process.env.PLAYWRIGHT_IS_OSS) {
|
||||
// Todo: Remove this patch once the issue is fixed #19140
|
||||
@ -88,7 +88,7 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => {
|
||||
});
|
||||
|
||||
test.afterAll(async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
for (const entity of [...users, table1]) {
|
||||
await entity.delete(apiContext);
|
||||
}
|
||||
@ -101,7 +101,10 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => {
|
||||
await redirectToHomePage(page);
|
||||
});
|
||||
|
||||
test('Basic Scenario', async ({ page }) => {
|
||||
test('Complete Incident lifecycle with table owner', async ({
|
||||
page: adminPage,
|
||||
ownerPage: page,
|
||||
}) => {
|
||||
const testCase = table1.testCasesResponseData[0];
|
||||
const testCaseName = testCase?.['name'];
|
||||
const assignee = {
|
||||
@ -109,6 +112,31 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => {
|
||||
displayName: user1.getUserName(),
|
||||
};
|
||||
|
||||
await test.step('Claim ownership of table', async () => {
|
||||
const loggedInUserRequest = page.waitForResponse(
|
||||
`/api/v1/users/loggedInUser*`
|
||||
);
|
||||
await redirectToHomePage(page);
|
||||
const loggedInUserResponse = await loggedInUserRequest;
|
||||
const loggedInUser = await loggedInUserResponse.json();
|
||||
|
||||
await redirectToHomePage(adminPage);
|
||||
|
||||
await table1.visitEntityPage(adminPage);
|
||||
await adminPage.waitForLoadState('networkidle');
|
||||
await adminPage.waitForSelector('[data-testid="loader"]', {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
await addOwner({
|
||||
page: adminPage,
|
||||
owner: loggedInUser.displayName,
|
||||
type: 'Users',
|
||||
endpoint: EntityTypeEndpoint.Table,
|
||||
dataTestId: 'data-assets-header',
|
||||
});
|
||||
});
|
||||
|
||||
await test.step("Acknowledge table test case's failure", async () => {
|
||||
await acknowledgeTask({
|
||||
page,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user