Fix #23445: User with Owner role unable to update Incident Status via API (#23448)

* 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:
Sriharsha Chintalapani 2025-09-26 02:41:36 -07:00 committed by GitHub
parent 6e9c0316e5
commit aa7715be0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 511 additions and 72 deletions

View File

@ -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();
}
}

View File

@ -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());

View File

@ -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);
}
}

View File

@ -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,