diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/alerts/AlertsRuleEvaluator.java b/openmetadata-service/src/main/java/org/openmetadata/service/alerts/AlertsRuleEvaluator.java index cd2391794a8..7df739cd904 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/alerts/AlertsRuleEvaluator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/alerts/AlertsRuleEvaluator.java @@ -51,12 +51,12 @@ public class AlertsRuleEvaluator { description = "Returns true if the change event entity being accessed has source as mentioned in condition", examples = {"matchAnySource('bot', 'user')"}, paramInputType = READ_FROM_PARAM_CONTEXT) - public boolean matchAnySource(String... originEntity) { - if (changeEvent == null) { + public boolean matchAnySource(String... originEntities) { + if (changeEvent == null || changeEvent.getEntityType() == null) { return false; } String changeEventEntity = changeEvent.getEntityType(); - for (String entityType : originEntity) { + for (String entityType : originEntities) { if (changeEventEntity.equals(entityType)) { return true; } @@ -74,14 +74,7 @@ public class AlertsRuleEvaluator { if (changeEvent == null || changeEvent.getEntity() == null) { return false; } - Class entityClass = Entity.getEntityClassFromType(changeEvent.getEntityType()); - EntityInterface entity; - if (changeEvent.getEntity() instanceof String) { - entity = JsonUtils.readValue((String) changeEvent.getEntity(), entityClass); - } else { - entity = JsonUtils.convertValue(changeEvent.getEntity(), entityClass); - } - + EntityInterface entity = getEntity(changeEvent); EntityReference ownerReference = entity.getOwner(); if (ownerReference != null) { if (USER.equals(ownerReference.getType())) { @@ -109,11 +102,11 @@ public class AlertsRuleEvaluator { description = "Returns true if the change event entity being accessed has following entityName from the List.", examples = {"matchAnyEntityFqn('Name1', 'Name')"}, paramInputType = ALL_INDEX_ELASTIC_SEARCH) - public boolean matchAnyEntityFqn(String... entityNames) { + public boolean matchAnyEntityFqn(String... entityNames) throws IOException { if (changeEvent == null || changeEvent.getEntity() == null) { return false; } - EntityInterface entity = (EntityInterface) changeEvent.getEntity(); + EntityInterface entity = getEntity(changeEvent); for (String name : entityNames) { if (entity.getFullyQualifiedName().equals(name)) { return true; @@ -128,11 +121,11 @@ public class AlertsRuleEvaluator { description = "Returns true if the change event entity being accessed has following entityId from the List.", examples = {"matchAnyEntityId('uuid1', 'uuid2')"}, paramInputType = ALL_INDEX_ELASTIC_SEARCH) - public boolean matchAnyEntityId(String... entityIds) { + public boolean matchAnyEntityId(String... entityIds) throws IOException { if (changeEvent == null || changeEvent.getEntity() == null) { return false; } - EntityInterface entity = (EntityInterface) changeEvent.getEntity(); + EntityInterface entity = getEntity(changeEvent); for (String id : entityIds) { if (entity.getId().equals(UUID.fromString(id))) { return true; @@ -148,7 +141,7 @@ public class AlertsRuleEvaluator { examples = {"matchAnyEventType('entityCreated', 'entityUpdated', 'entityDeleted', 'entitySoftDeleted')"}, paramInputType = READ_FROM_PARAM_CONTEXT) public boolean matchAnyEventType(String... eventTypesList) { - if (changeEvent == null || changeEvent.getEntity() == null) { + if (changeEvent == null || changeEvent.getEventType() == null) { return false; } String eventType = changeEvent.getEventType().toString(); @@ -167,7 +160,7 @@ public class AlertsRuleEvaluator { examples = {"matchTestResult('Success', 'Failed', 'Aborted')"}, paramInputType = READ_FROM_PARAM_CONTEXT) public boolean matchTestResult(String... testResults) { - if (changeEvent == null || changeEvent.getEntity() == null) { + if (changeEvent == null || changeEvent.getChangeDescription() == null) { return false; } if (!changeEvent.getEntityType().equals(TEST_CASE)) { @@ -195,7 +188,7 @@ public class AlertsRuleEvaluator { examples = {"matchUpdatedBy('user1', 'user2')"}, paramInputType = READ_FROM_PARAM_CONTEXT) public boolean matchUpdatedBy(String... updatedByUserList) { - if (changeEvent == null || changeEvent.getEntity() == null) { + if (changeEvent == null || changeEvent.getUserName() == null) { return false; } String entityUpdatedBy = changeEvent.getUserName(); @@ -214,7 +207,7 @@ public class AlertsRuleEvaluator { examples = {"matchAnyFieldChange('fieldName1', 'fieldName')"}, paramInputType = NOT_REQUIRED) public boolean matchAnyFieldChange(String... fieldChangeUpdate) { - if (changeEvent == null || changeEvent.getEntity() == null) { + if (changeEvent == null || changeEvent.getChangeDescription() == null) { return false; } Set fields = ChangeEventParser.getUpdatedField(changeEvent); @@ -225,4 +218,15 @@ public class AlertsRuleEvaluator { } return false; } + + private EntityInterface getEntity(ChangeEvent event) throws IOException { + Class entityClass = Entity.getEntityClassFromType(event.getEntityType()); + EntityInterface entity; + if (event.getEntity() instanceof String) { + entity = JsonUtils.readValue((String) event.getEntity(), entityClass); + } else { + entity = JsonUtils.convertValue(event.getEntity(), entityClass); + } + return entity; + } } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/alerts/AlertsRuleEvaluatorTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/alerts/AlertsRuleEvaluatorTest.java index 30142bfb95b..37eabca624f 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/alerts/AlertsRuleEvaluatorTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/alerts/AlertsRuleEvaluatorTest.java @@ -3,138 +3,180 @@ package org.openmetadata.service.alerts; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openmetadata.service.security.policyevaluator.CompiledRule.parseExpression; +import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS; -import java.util.ArrayList; +import java.io.IOException; +import java.net.URISyntaxException; import java.util.List; import java.util.UUID; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; +import org.junit.jupiter.api.TestInfo; +import org.openmetadata.schema.api.data.CreateTable; import org.openmetadata.schema.entity.data.Table; -import org.openmetadata.schema.entity.teams.Team; -import org.openmetadata.schema.entity.teams.User; +import org.openmetadata.schema.tests.type.TestCaseResult; +import org.openmetadata.schema.tests.type.TestCaseStatus; +import org.openmetadata.schema.type.ChangeDescription; import org.openmetadata.schema.type.ChangeEvent; -import org.openmetadata.schema.type.EntityReference; -import org.openmetadata.schema.type.TagLabel; +import org.openmetadata.schema.type.Column; +import org.openmetadata.schema.type.ColumnDataType; +import org.openmetadata.schema.type.EventType; +import org.openmetadata.schema.type.FieldChange; import org.openmetadata.service.Entity; -import org.openmetadata.service.jdbi3.CollectionDAO.TeamDAO; -import org.openmetadata.service.jdbi3.CollectionDAO.UserDAO; -import org.openmetadata.service.jdbi3.TableRepository; -import org.openmetadata.service.jdbi3.TeamRepository; -import org.openmetadata.service.jdbi3.UserRepository; -import org.openmetadata.service.security.policyevaluator.ResourceContext; +import org.openmetadata.service.OpenMetadataApplicationTest; +import org.openmetadata.service.resources.EntityResourceTest; +import org.openmetadata.service.resources.databases.TableResourceTest; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.support.StandardEvaluationContext; -class AlertsRuleEvaluatorTest { - private static Table table; - private static User user; - private static EvaluationContext evaluationContext; +class AlertsRuleEvaluatorTest extends OpenMetadataApplicationTest { + private static TableResourceTest tableResourceTest; @BeforeAll - public static void setup() { - Entity.registerEntity(User.class, Entity.USER, Mockito.mock(UserDAO.class), Mockito.mock(UserRepository.class)); - Entity.registerEntity(Team.class, Entity.TEAM, Mockito.mock(TeamDAO.class), Mockito.mock(TeamRepository.class)); - table = new Table().withName("table"); - user = new User().withId(UUID.randomUUID()).withName("user"); - ResourceContext resourceContext = - ResourceContext.builder() - .resource("table") - .entity(table) - .entityRepository(Mockito.mock(TableRepository.class)) - .build(); - // SubjectContext subjectContext = new SubjectContext(user); - // RuleEvaluator ruleEvaluator = new RuleEvaluator(null, subjectContext, resourceContext); - ChangeEvent event = new ChangeEvent(); - // evaluationContext = new StandardEvaluationContext(ruleEvaluator); + public static void setup(TestInfo test) throws URISyntaxException, IOException { + tableResourceTest = new TableResourceTest(); + tableResourceTest.setup(test); } @Test - void test_noOwner() { - // Set no owner to the entity and test noOwner method - table.setOwner(null); - assertTrue(evaluateExpression("noOwner()")); - assertFalse(evaluateExpression("!noOwner()")); - - // Set owner to the entity and test noOwner method - table.setOwner(new EntityReference().withId(UUID.randomUUID()).withType(Entity.USER)); - assertFalse(evaluateExpression("noOwner()")); - assertTrue(evaluateExpression("!noOwner()")); + void test_matchAnySource() { + // Create a change Event with Entity Type and test for source in list and not in list + ChangeEvent changeEvent = new ChangeEvent(); + changeEvent.setEntityType("alert"); + AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); + EvaluationContext evaluationContext = new StandardEvaluationContext(alertsRuleEvaluator); + assertTrue(evaluateExpression("matchAnySource('alert')", evaluationContext)); + assertFalse(evaluateExpression("matchAnySource('bot')", evaluationContext)); } @Test - void test_isOwner() { - // Table owner is a different user (random ID) and hence isOwner returns false - table.setOwner(new EntityReference().withId(UUID.randomUUID()).withType(Entity.USER).withName("otherUser")); - assertFalse(evaluateExpression("isOwner()")); - assertTrue(evaluateExpression("!isOwner()")); + void test_matchAnyOwnerName(TestInfo test) throws IOException { + // Create Table Entity + List columns = List.of(TableResourceTest.getColumn("c1", ColumnDataType.INT, null)); + CreateTable create = + tableResourceTest.createRequest(test).withColumns(columns).withOwner(EntityResourceTest.USER1_REF); + Table createdTable = tableResourceTest.createAndCheckEntity(create, ADMIN_AUTH_HEADERS); - // Table owner is same as the user in subjectContext and hence isOwner returns true - table.setOwner(new EntityReference().withId(user.getId()).withType(Entity.USER).withName(user.getName())); - assertTrue(evaluateExpression("isOwner()")); - assertFalse(evaluateExpression("!isOwner()")); + // Create a change Event with the Entity Table + ChangeEvent changeEvent = new ChangeEvent(); + changeEvent.setEntityType(Entity.TABLE); + changeEvent.setEntity(createdTable); - // noOwner() || isOwner() - with noOwner being true and isOwner false - table.setOwner(null); - assertTrue(evaluateExpression("noOwner() || isOwner()")); - assertFalse(evaluateExpression("!noOwner() && !isOwner()")); - - // noOwner() || isOwner() - with noOwner is false and isOwner true - table.setOwner(new EntityReference().withId(user.getId()).withType(Entity.USER).withName(user.getName())); - assertTrue(evaluateExpression("noOwner() || isOwner()")); - assertFalse(evaluateExpression("!noOwner() && !isOwner()")); + // Test Owner Name Present in list and not present in list + AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); + EvaluationContext evaluationContext = new StandardEvaluationContext(alertsRuleEvaluator); + assertTrue( + evaluateExpression("matchAnyOwnerName('" + EntityResourceTest.USER1.getName() + "')", evaluationContext)); + assertFalse(evaluateExpression("matchAnyOwnerName('tempName')", evaluationContext)); } @Test - void test_matchAllTags() { - table.withTags(getTags("tag1", "tag2", "tag3")); + void test_matchAnyEntityFqn(TestInfo test) throws IOException { + // Create Table Entity + List columns = List.of(TableResourceTest.getColumn("c1", ColumnDataType.INT, null)); + CreateTable create = tableResourceTest.createRequest(test).withColumns(columns); + Table createdTable = tableResourceTest.createAndCheckEntity(create, ADMIN_AUTH_HEADERS); - // All tags present - assertTrue(evaluateExpression("matchAllTags('tag1', 'tag2', 'tag3')")); - assertFalse(evaluateExpression("!matchAllTags('tag1', 'tag2', 'tag3')")); - assertTrue(evaluateExpression("matchAllTags('tag1', 'tag2')")); - assertFalse(evaluateExpression("!matchAllTags('tag1', 'tag2')")); - assertTrue(evaluateExpression("matchAllTags('tag1')")); - assertFalse(evaluateExpression("!matchAllTags('tag1')")); + // Create a change Event with the Entity Table + ChangeEvent changeEvent = new ChangeEvent(); + changeEvent.setEntityType(Entity.TABLE); + changeEvent.setEntity(createdTable); - // Tag 'tag4' is missing - assertFalse(evaluateExpression("matchAllTags('tag1', 'tag2', 'tag3', 'tag4')")); - assertTrue(evaluateExpression("!matchAllTags('tag1', 'tag2', 'tag3', 'tag4')")); - assertFalse(evaluateExpression("matchAllTags('tag1', 'tag2', 'tag4')")); - assertTrue(evaluateExpression("!matchAllTags('tag1', 'tag2', 'tag4')")); - assertFalse(evaluateExpression("matchAllTags('tag2', 'tag4')")); - assertTrue(evaluateExpression("!matchAllTags('tag2', 'tag4')")); - assertFalse(evaluateExpression("matchAllTags('tag4')")); - assertTrue(evaluateExpression("!matchAllTags('tag4')")); + // Test Entity Fqn in List of match and not present in list + AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); + EvaluationContext evaluationContext = new StandardEvaluationContext(alertsRuleEvaluator); + String fqn = createdTable.getFullyQualifiedName(); + assertTrue(evaluateExpression("matchAnyEntityFqn('" + fqn + "')", evaluationContext)); + assertFalse(evaluateExpression("matchAnyEntityFqn('testFQN1')", evaluationContext)); } @Test - void test_matchAnyTag() { - table.withTags(getTags("tag1", "tag2", "tag3")); + void test_matchAnyEntityId(TestInfo test) throws IOException { + // Create Table Entity + List columns = List.of(TableResourceTest.getColumn("c1", ColumnDataType.INT, null)); + CreateTable create = tableResourceTest.createRequest(test).withColumns(columns); + Table createdTable = tableResourceTest.createAndCheckEntity(create, ADMIN_AUTH_HEADERS); - // Tag is present - assertTrue(evaluateExpression("matchAnyTag('tag1', 'tag2', 'tag3', 'tag4')")); - assertFalse(evaluateExpression("!matchAnyTag('tag1', 'tag2', 'tag3', 'tag4')")); - assertTrue(evaluateExpression("matchAnyTag('tag1', 'tag2', 'tag4')")); - assertFalse(evaluateExpression("!matchAnyTag('tag1', 'tag2', 'tag4')")); - assertTrue(evaluateExpression("matchAnyTag('tag1', 'tag2', 'tag4')")); - assertFalse(evaluateExpression("!matchAnyTag('tag1', 'tag2', 'tag4')")); - assertTrue(evaluateExpression("matchAnyTag('tag1', 'tag4')")); - assertFalse(evaluateExpression("!matchAnyTag('tag1', 'tag4')")); + // Create a change Event with Table Entity and Type + ChangeEvent changeEvent = new ChangeEvent(); + changeEvent.setEntityType(Entity.TABLE); + changeEvent.setEntity(createdTable); - // Tag `tag4` is not present - assertFalse(evaluateExpression("matchAnyTag('tag4')")); - assertTrue(evaluateExpression("!matchAnyTag('tag4')")); + // Test Entity Id in List of match and not present in list + AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); + EvaluationContext evaluationContext = new StandardEvaluationContext(alertsRuleEvaluator); + String id = createdTable.getId().toString(); + assertTrue(evaluateExpression("matchAnyEntityId('" + id + "')", evaluationContext)); + assertFalse(evaluateExpression("matchAnyEntityId('" + UUID.randomUUID() + "')", evaluationContext)); } - private Boolean evaluateExpression(String condition) { + @Test + void test_matchAnyEventType() { + // Create a change Event with EventType + ChangeEvent changeEvent = new ChangeEvent(); + changeEvent.setEventType(EventType.ENTITY_CREATED); + + // Check if eventType present in list or absent from the list + AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); + EvaluationContext evaluationContext = new StandardEvaluationContext(alertsRuleEvaluator); + assertTrue(evaluateExpression("matchAnyEventType('entityCreated')", evaluationContext)); + assertFalse(evaluateExpression("matchAnyEventType('entityUpdated')", evaluationContext)); + } + + @Test + void test_matchTestResult() { + // Create a change Description with Test Result Field + ChangeDescription changeDescription = new ChangeDescription(); + changeDescription.setFieldsUpdated( + List.of( + new FieldChange() + .withName("testCaseResult") + .withOldValue("test1") + .withNewValue(new TestCaseResult().withTestCaseStatus(TestCaseStatus.Success)))); + + // Create a change event with Test Case and Test Result Change Description + ChangeEvent changeEvent = new ChangeEvent(); + changeEvent.setEntityType(Entity.TEST_CASE); + changeEvent.setChangeDescription(changeDescription); + + // Test If Test Result status matches in list and if status not matches + AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); + EvaluationContext evaluationContext = new StandardEvaluationContext(alertsRuleEvaluator); + assertTrue(evaluateExpression("matchTestResult('Success')", evaluationContext)); + assertFalse(evaluateExpression("matchTestResult('Failed')", evaluationContext)); + } + + @Test + void test_matchUpdatedBy() { + // Create a change Event with updatedBy username + ChangeEvent changeEvent = new ChangeEvent(); + changeEvent.setUserName("test"); + + // Test if the username is in list or not + AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); + EvaluationContext evaluationContext = new StandardEvaluationContext(alertsRuleEvaluator); + assertTrue(evaluateExpression("matchUpdatedBy('test')", evaluationContext)); + assertFalse(evaluateExpression("matchUpdatedBy('test1')", evaluationContext)); + } + + @Test + void test_matchAnyFieldChange() { + // Create a change Event with some Change Description and Field Change + ChangeDescription changeDescription = new ChangeDescription(); + changeDescription.setFieldsUpdated( + List.of(new FieldChange().withName("test").withOldValue("test1").withNewValue("test2"))); + + ChangeEvent changeEvent = new ChangeEvent(); + changeEvent.setChangeDescription(changeDescription); + + // Test if the updated field matches from the list or not + AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); + EvaluationContext evaluationContext = new StandardEvaluationContext(alertsRuleEvaluator); + assertTrue(evaluateExpression("matchAnyFieldChange('test')", evaluationContext)); + assertFalse(evaluateExpression("matchAnyFieldChange('temp')", evaluationContext)); + } + + private Boolean evaluateExpression(String condition, EvaluationContext evaluationContext) { return parseExpression(condition).getValue(evaluationContext, Boolean.class); } - - private List getTags(String... tags) { - List tagLabels = new ArrayList<>(); - for (String tag : tags) { - tagLabels.add(new TagLabel().withTagFQN(tag)); - } - return tagLabels; - } }