From df585b56c74da9856ad83bb325e3e1020fe843a7 Mon Sep 17 00:00:00 2001 From: Teddy Crepineau Date: Thu, 24 Oct 2024 11:22:11 +0200 Subject: [PATCH 1/7] fix: entity link accepted characters --- .../openmetadata/service/resources/EntityResourceTest.java | 4 ++-- .../service/resources/feeds/SuggestionsResourceTest.java | 2 +- .../src/main/resources/json/schema/type/basic.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/EntityResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/EntityResourceTest.java index 6ae4af2a8f87..e770acf8b9e1 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/EntityResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/EntityResourceTest.java @@ -238,7 +238,7 @@ public abstract class EntityResourceTest$\"]"; + "[entityLink must match \"(?U)^<#E::\\w+::[\\[\\]\\w'\\- .&/:+\"\\\\()$#%]+>$\"]"; // Random unicode string generator to test entity name accepts all the unicode characters protected static final RandomStringGenerator RANDOM_STRING_GENERATOR = diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/SuggestionsResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/SuggestionsResourceTest.java index f35d6810e38c..d9b144c9c0c8 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/SuggestionsResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/SuggestionsResourceTest.java @@ -153,7 +153,7 @@ void post_suggestionWithInvalidAbout_4xx() { CreateSuggestion create = create().withEntityLink("<>"); // Invalid EntityLink String failureReason = - "[entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\"]"; + "[entityLink must match \"(?U)^<#E::\\w+::[\\[\\]\\w'\\- .&/:+\"\\\\()$#%]+>$\"]"; assertResponseContains( () -> createSuggestion(create, USER_AUTH_HEADERS), BAD_REQUEST, failureReason); diff --git a/openmetadata-spec/src/main/resources/json/schema/type/basic.json b/openmetadata-spec/src/main/resources/json/schema/type/basic.json index 4c2528391daf..b72389d01990 100644 --- a/openmetadata-spec/src/main/resources/json/schema/type/basic.json +++ b/openmetadata-spec/src/main/resources/json/schema/type/basic.json @@ -113,7 +113,7 @@ "entityLink": { "description": "Link to an entity or field within an entity using this format `<#E::{entities}::{entityType}::{field}::{arrayFieldName}::{arrayFieldValue}`.", "type": "string", - "pattern": "(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$" + "pattern": "(?U)^<#E::\\w+::[\\[\\]\\w'\\- .&/:+\"\\\\()$#%]+>$" }, "entityName": { "description": "Name that identifies an entity.", From 6c5ffa3cb3a259bd358606cb1d08d2e15853429f Mon Sep 17 00:00:00 2001 From: Teddy Crepineau Date: Mon, 28 Oct 2024 13:09:08 +0100 Subject: [PATCH 2/7] fix: match all characters but `<>|::` --- .../dqtests/TestCaseResourceTest.java | 40 +++++ .../events/EventSubscriptionResourceTest.java | 146 ++++++++------- .../service/util/EntityUtilTest.java | 166 ++++++++++++++++++ .../resources/json/schema/type/basic.json | 2 +- 4 files changed, 289 insertions(+), 65 deletions(-) diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java index d4c0138c3573..2c5c84308927 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openmetadata.common.utils.CommonUtil.listOf; import static org.openmetadata.schema.type.ColumnDataType.BIGINT; @@ -3188,6 +3189,45 @@ void test_listTestCaseFromSearch(TestInfo testInfo) throws HttpResponseException }); } + @Test + void test_testCaseInvalidEntityLinkTest(TestInfo testInfo) throws IOException { + // Invalid entity link as not parsable by antlr parser + String entityLink = "<#E::table::special!@#$%^&*()_+[]{}|;:\\'\",./?>"; + CreateTestCase create = createRequest(testInfo); + create + .withEntityLink(entityLink) + .withTestSuite(TEST_SUITE1.getFullyQualifiedName()) + .withTestDefinition(TEST_DEFINITION3.getFullyQualifiedName()) + .withParameterValues( + List.of(new TestCaseParameterValue().withValue("100").withName("missingCountValue"))); + + assertThrows( + HttpResponseException.class, + () -> createAndCheckEntity(create, ADMIN_AUTH_HEADERS), + "entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\""); + + entityLink = "<#E::table::user::column>"; + create.setEntityLink(entityLink); + assertThrows( + HttpResponseException.class, + () -> createAndCheckEntity(create, ADMIN_AUTH_HEADERS), + "entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\""); + + entityLink = "<#E::table::user>name::column>"; + create.setEntityLink(entityLink); + assertThrows( + HttpResponseException.class, + () -> createAndCheckEntity(create, ADMIN_AUTH_HEADERS), + "entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\""); + + entityLink = "<#E::table::foo<>bar::baz>\");"; + create.setEntityLink(entityLink); + assertThrows( + HttpResponseException.class, + () -> createAndCheckEntity(create, ADMIN_AUTH_HEADERS), + "entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\""); + } + private void putInspectionQuery(TestCase testCase, String sql) throws IOException { TestCase putResponse = putInspectionQuery(testCase.getId(), sql, ADMIN_AUTH_HEADERS); assertEquals(sql, putResponse.getInspectionQuery()); diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/events/EventSubscriptionResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/events/EventSubscriptionResourceTest.java index 8ee7bc81242f..c69cf3e7e68f 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/events/EventSubscriptionResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/events/EventSubscriptionResourceTest.java @@ -98,8 +98,9 @@ public EventSubscriptionResourceTest() { @Test void post_alertActionWithEnabledStateChange(TestInfo test) throws IOException { String webhookName = getEntityName(test); + String endpoint = test.getDisplayName(); LOG.info("creating webhook in disabled state"); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/webhook/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/webhook/" + endpoint; // Create a Disabled Generic Webhook CreateEventSubscription genericWebhookActionRequest = createRequest(webhookName).withEnabled(false).withDestinations(getWebhook(uri)); @@ -108,7 +109,7 @@ void post_alertActionWithEnabledStateChange(TestInfo test) throws IOException { SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(DISABLED, status.getStatus()); WebhookCallbackResource.EventDetails details = - webhookCallbackResource.getEventDetails(webhookName); + webhookCallbackResource.getEventDetails(endpoint); assertNull(details); // // Now enable the webhook @@ -143,7 +144,7 @@ void post_alertActionWithEnabledStateChange(TestInfo test) throws IOException { assertEquals(SubscriptionStatus.Status.ACTIVE, status2.getStatus()); // Ensure the call back notification has started - details = waitForFirstEvent(alert.getId(), webhookName, 25); + details = waitForFirstEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); SubscriptionStatus successDetails = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); @@ -454,7 +455,8 @@ void testDifferentTypesOfAlerts() throws IOException { @Test public void post_createAndValidateEventSubscription(TestInfo test) throws IOException { String webhookName = getEntityName(test); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/webhook/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/webhook/" + endpoint; CreateEventSubscription enabledWebhookRequest = new CreateEventSubscription() @@ -491,7 +493,8 @@ public void post_createAndValidateEventSubscription(TestInfo test) throws IOExce @Test public void post_duplicateAlertsAreNotAllowed(TestInfo test) throws IOException { String webhookName = getEntityName(test); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/webhook/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/webhook/" + endpoint; CreateEventSubscription genericWebhookRequest = new CreateEventSubscription() @@ -599,7 +602,8 @@ public void get_fetchEventSubscription_ByInvalidId_returns404() { @Test public void post_createAndValidateEventSubscription_SLACK(TestInfo test) throws IOException { String webhookName = getEntityName(test); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; CreateEventSubscription enabledWebhookRequest = new CreateEventSubscription() @@ -614,7 +618,7 @@ public void post_createAndValidateEventSubscription_SLACK(TestInfo test) throws EventSubscription alert = createEntity(enabledWebhookRequest, ADMIN_AUTH_HEADERS); waitForAllEventToComplete(alert.getId()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); ConcurrentLinkedQueue events = details.getEvents(); for (SlackMessage event : events) { validateSlackMessage(alert, event); @@ -675,8 +679,9 @@ private ArgumentsInput createFilterByNameArgumentsInput( @Test void post_tableResource_filterByOwner_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); + String endpoint = test.getDisplayName(); TableResourceTest tableResourceTest = new TableResourceTest(); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; // Create the event subscription request with the Slack destination and table as the resource CreateEventSubscription genericWebhookActionRequest = @@ -700,7 +705,7 @@ void post_tableResource_filterByOwner_alertAction(TestInfo test) throws IOExcept assertEquals(ACTIVE, status.getStatus()); // Verify no Slack events triggered initially - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); assertNull(details); // Create a table with a different owner (USER2), expect no alert @@ -709,14 +714,14 @@ void post_tableResource_filterByOwner_alertAction(TestInfo test) throws IOExcept .createRequest(test.getClass().getName() + generateUniqueNumberAsString()) .withOwners(List.of(USER2.getEntityReference())); tableResourceTest.createEntity(createTable1, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertNull(details); // Create another table with the owner that matches the filter (USER_TEAM21), expect an alert CreateTable createTable = tableResourceTest.createRequest(test).withOwners(List.of(USER_TEAM21.getEntityReference())); Table table = tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); // Clean up: delete the event subscription and created tables @@ -728,7 +733,8 @@ void post_tableResource_filterByOwner_alertAction(TestInfo test) throws IOExcept void post_tableResource_filterByDomain_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); TableResourceTest tableResourceTest = new TableResourceTest(); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; // Create the event subscription request with the Slack destination and table resource with // filterByDomain @@ -754,7 +760,7 @@ void post_tableResource_filterByDomain_alertAction(TestInfo test) throws IOExcep SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); assertNull(details); CreateDomain createDomain2 = domainResourceTest.createRequest("Engineering_2"); @@ -762,7 +768,7 @@ void post_tableResource_filterByDomain_alertAction(TestInfo test) throws IOExcep CreateTable createTable = tableResourceTest.createRequest(test).withDomain(domainSecond.getName()); tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); // changeEvent on the table with correct domain will result in alerts assertNull(details); @@ -771,7 +777,7 @@ void post_tableResource_filterByDomain_alertAction(TestInfo test) throws IOExcep .createRequest(test.getClass().getName() + "_secondTable") .withDomain(domain.getName()); tableResourceTest.createEntity(createTable2, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @@ -779,7 +785,8 @@ void post_tableResource_filterByDomain_alertAction(TestInfo test) throws IOExcep void post_tableResource_fqnFilter_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); TableResourceTest tableResourceTest = new TableResourceTest(); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; // Create the event subscription request with slack destination and table as the resource with // filterByFqn @@ -798,20 +805,20 @@ void post_tableResource_fqnFilter_alertAction(TestInfo test) throws IOException SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // alerts will trigger on the changeEvent of table resource assertNull(details); CreateTable createTable = tableResourceTest.createRequest(test.getClass().getName() + generateUniqueNumberAsString()); Table table1 = tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); // alerts will trigger on the changeEvent of table resource with correct fqn assertNull(details); CreateTable createTable2 = tableResourceTest.createRequest(test).withName("test"); Table table2 = tableResourceTest.createEntity(createTable2, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); tableResourceTest.deleteEntity(table1.getId(), ADMIN_AUTH_HEADERS); @@ -821,7 +828,8 @@ void post_tableResource_fqnFilter_alertAction(TestInfo test) throws IOException @Test void post_excluded_filters_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; // Create the event subscription request with the Slack destination and table resource CreateEventSubscription genericWebhookActionRequest = @@ -841,7 +849,7 @@ void post_excluded_filters_alertAction(TestInfo test) throws IOException { SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); assertNull(details); TableResourceTest tableResourceTest = new TableResourceTest(); @@ -851,7 +859,7 @@ void post_excluded_filters_alertAction(TestInfo test) throws IOException { tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS); // Wait for the slack event and verify its details - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); // filters are EXCLUDED assertNull(details); @@ -862,7 +870,8 @@ void post_excluded_filters_alertAction(TestInfo test) throws IOException { void post_tableResource_filterByOwner_AND_filterByDomain_Filter_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; // Create the event subscription request with the Slack destination and table resource with // filterByOwner AND filterByDomain @@ -894,7 +903,7 @@ void post_tableResource_filterByOwner_AND_filterByDomain_Filter_alertAction(Test SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); assertNull(details); // wrong owner and correct domain -> no alerts @@ -906,7 +915,7 @@ void post_tableResource_filterByOwner_AND_filterByDomain_Filter_alertAction(Test .withOwners(List.of(USER1.getEntityReference())) .withDomain(domain.getName()); tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertNull(details); // correct owner and wrong domain -> no alerts @@ -920,7 +929,7 @@ void post_tableResource_filterByOwner_AND_filterByDomain_Filter_alertAction(Test .withOwners(List.of(USER1.getEntityReference())) .withDomain(domain2.getName()); tableResourceTest.createEntity(createTable2, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertNull(details); // correcr owner and correct domain -> alerts @@ -933,7 +942,7 @@ void post_tableResource_filterByOwner_AND_filterByDomain_Filter_alertAction(Test Table table = tableResourceTest.createEntity(createTable3, ADMIN_AUTH_HEADERS); // Wait for the slack event and verify its details - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); tableResourceTest.deleteEntity(table.getId(), ADMIN_AUTH_HEADERS); } @@ -942,7 +951,8 @@ void post_tableResource_filterByOwner_AND_filterByDomain_Filter_alertAction(Test void post_tableResource_noFilters_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); TableResourceTest tableResourceTest = new TableResourceTest(); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; // Create the event subscription request with the Slack destination and table as the resource // without any filters @@ -955,7 +965,7 @@ void post_tableResource_noFilters_alertAction(TestInfo test) throws IOException SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // alerts will trigger on the changeEvent of table resource assertNull(details); @@ -964,14 +974,15 @@ void post_tableResource_noFilters_alertAction(TestInfo test) throws IOException tableResourceTest.createEntity(createTable, ADMIN_AUTH_HEADERS); // Wait for the slack event and verify its details - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @Test void post_topicResource_noFilters_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; CreateEventSubscription genericWebhookActionRequest = createRequest(webhookName) @@ -981,7 +992,7 @@ void post_topicResource_noFilters_alertAction(TestInfo test) throws IOException SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // Alerts are triggered only by ChangeEvent occurrences related to resources as topic assertNull(details); @@ -993,15 +1004,16 @@ void post_topicResource_noFilters_alertAction(TestInfo test) throws IOException .withMessageSchema(TopicResourceTest.SCHEMA.withSchemaFields(TopicResourceTest.fields)); topicResourceTest.createEntity(topicRequest, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @Test void post_topicResource_filterByFqn_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); + String endpoint = test.getDisplayName(); TopicResourceTest topicResourceTest = new TopicResourceTest(); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; CreateEventSubscription genericWebhookActionRequest = createRequest(webhookName) @@ -1020,7 +1032,7 @@ void post_topicResource_filterByFqn_alertAction(TestInfo test) throws IOExceptio SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // Alerts are triggered only by changeEvent occurrences related to resources as topic with // filterByFqn @@ -1031,7 +1043,7 @@ void post_topicResource_filterByFqn_alertAction(TestInfo test) throws IOExceptio .createRequest(test) .withMessageSchema(TopicResourceTest.SCHEMA.withSchemaFields(TopicResourceTest.fields)); topicResourceTest.createEntity(topicRequest, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); // changeEvents associated with correct fqn will result in alerts assertNull(details); @@ -1041,15 +1053,16 @@ void post_topicResource_filterByFqn_alertAction(TestInfo test) throws IOExceptio .withMessageSchema(TopicResourceTest.SCHEMA.withSchemaFields(TopicResourceTest.fields)); topicResourceTest.createEntity(topicRequest2, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @Test void post_topicResource_domain_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); + String endpoint = test.getDisplayName(); TopicResourceTest topicResourceTest = new TopicResourceTest(); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; CreateEventSubscription genericWebhookActionRequest = createRequest(webhookName) @@ -1072,7 +1085,7 @@ void post_topicResource_domain_alertAction(TestInfo test) throws IOException { EventSubscription alert = createAndCheckEntity(genericWebhookActionRequest, ADMIN_AUTH_HEADERS); SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // Alerts are triggered only by ChangeEvent occurrences related to resources as topic by // filterByDomain @@ -1085,15 +1098,16 @@ void post_topicResource_domain_alertAction(TestInfo test) throws IOException { .withMessageSchema(TopicResourceTest.SCHEMA.withSchemaFields(TopicResourceTest.fields)); topicResourceTest.createEntity(topicRequest, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @Test void post_topicResource_owner_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); + String endpoint = test.getDisplayName(); TopicResourceTest topicResourceTest = new TopicResourceTest(); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; CreateEventSubscription genericWebhookActionRequest = createRequest(webhookName) @@ -1110,7 +1124,7 @@ void post_topicResource_owner_alertAction(TestInfo test) throws IOException { EventSubscription alert = createAndCheckEntity(genericWebhookActionRequest, ADMIN_AUTH_HEADERS); SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // Alerts are triggered only by changeEvent occurrences related to resources as topic by // filerByOwner @@ -1123,7 +1137,7 @@ void post_topicResource_owner_alertAction(TestInfo test) throws IOException { .withMessageSchema(TopicResourceTest.SCHEMA.withSchemaFields(TopicResourceTest.fields)); topicResourceTest.createEntity(topicRequest, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @@ -1131,8 +1145,9 @@ void post_topicResource_owner_alertAction(TestInfo test) throws IOException { void post_topicResource_owner_AND_domain_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); DomainResourceTest domainResourceTest = new DomainResourceTest(); + String endpoint = test.getDisplayName(); TopicResourceTest topicResourceTest = new TopicResourceTest(); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; CreateEventSubscription genericWebhookActionRequest = createRequest(webhookName) @@ -1160,7 +1175,7 @@ void post_topicResource_owner_AND_domain_alertAction(TestInfo test) throws IOExc EventSubscription alert = createAndCheckEntity(genericWebhookActionRequest, ADMIN_AUTH_HEADERS); SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // Alerts are triggered only by ChangeEvent occurrences related to resources as topic by // filterByDomain @@ -1175,7 +1190,7 @@ void post_topicResource_owner_AND_domain_alertAction(TestInfo test) throws IOExc .withDomain(domain.getName()) .withMessageSchema(TopicResourceTest.SCHEMA.withSchemaFields(TopicResourceTest.fields)); topicResourceTest.createEntity(topicRequest, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertNull(details); // correct owner AND wrong domain -> no alerts @@ -1190,7 +1205,7 @@ void post_topicResource_owner_AND_domain_alertAction(TestInfo test) throws IOExc .withDomain(domain2.getName()) .withMessageSchema(TopicResourceTest.SCHEMA.withSchemaFields(TopicResourceTest.fields)); topicResourceTest.createEntity(topicRequest2, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertNull(details); // correct owner AND correct domain -> alerts @@ -1202,14 +1217,15 @@ void post_topicResource_owner_AND_domain_alertAction(TestInfo test) throws IOExc .withMessageSchema(TopicResourceTest.SCHEMA.withSchemaFields(TopicResourceTest.fields)); topicResourceTest.createEntity(topicRequest3, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @Test void post_ingestionPiplelineResource_noFilter_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; CreateEventSubscription genericWebhookActionRequest = createRequest(webhookName) @@ -1220,7 +1236,7 @@ void post_ingestionPiplelineResource_noFilter_alertAction(TestInfo test) throws SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // Alerts are triggered only by ChangeEvent occurrences related to resources as // ingestionPipeline by domain filter @@ -1244,15 +1260,16 @@ void post_ingestionPiplelineResource_noFilter_alertAction(TestInfo test) throws ingestionPipelineResourceTest.createEntity(request, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @Test void post_ingestionPiplelineResource_owner_alertAction(TestInfo test) throws IOException { String webhookName = getEntityName(test); + String endpoint = test.getDisplayName(); LOG.info("creating webhook in disabled state"); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; CreateEventSubscription genericWebhookActionRequest = createRequest(webhookName) @@ -1272,7 +1289,7 @@ void post_ingestionPiplelineResource_owner_alertAction(TestInfo test) throws IOE SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(ACTIVE, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); // Alerts are triggered only by ChangeEvent occurrences related to resources as // ingestionPipeline @@ -1296,7 +1313,7 @@ void post_ingestionPiplelineResource_owner_alertAction(TestInfo test) throws IOE ingestionPipelineResourceTest.createEntity(request, ADMIN_AUTH_HEADERS); - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); } @@ -1307,8 +1324,9 @@ public static String generateUniqueNumberAsString() { @Test void post_alertActionWithEnabledStateChange_SLACK(TestInfo test) throws IOException { String webhookName = getEntityName(test); + String endpoint = test.getDisplayName(); LOG.info("creating webhook in disabled state"); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/slack/" + endpoint; // Create a Disabled Generic Webhook CreateEventSubscription genericWebhookActionRequest = @@ -1318,7 +1336,7 @@ void post_alertActionWithEnabledStateChange_SLACK(TestInfo test) throws IOExcept // For the DISABLED Publisher are not available, so it will have no status SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(DISABLED, status.getStatus()); - SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(webhookName); + SlackCallbackResource.EventDetails details = slackCallbackResource.getEventDetails(endpoint); assertNull(details); LOG.info("Enabling webhook Action"); @@ -1339,7 +1357,7 @@ void post_alertActionWithEnabledStateChange_SLACK(TestInfo test) throws IOExcept assertEquals(SubscriptionStatus.Status.ACTIVE, status2.getStatus()); // Ensure the call back notification has started - details = waitForFirstSlackEvent(alert.getId(), webhookName, 25); + details = waitForFirstSlackEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); ConcurrentLinkedQueue messages = details.getEvents(); for (SlackMessage sm : messages) { @@ -1451,7 +1469,8 @@ void testDifferentTypesOfAlerts_SLACK() throws IOException { @Test public void post_createAndValidateEventSubscription_MSTEAMS(TestInfo test) throws IOException { String webhookName = getEntityName(test); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/msteams/" + webhookName; + String endpoint = test.getDisplayName(); + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/msteams/" + endpoint; CreateEventSubscription enabledWebhookRequest = new CreateEventSubscription() @@ -1466,8 +1485,7 @@ public void post_createAndValidateEventSubscription_MSTEAMS(TestInfo test) throw EventSubscription alert = createEntity(enabledWebhookRequest, ADMIN_AUTH_HEADERS); waitForAllEventToComplete(alert.getId()); - MSTeamsCallbackResource.EventDetails details = - teamsCallbackResource.getEventDetails(webhookName); + MSTeamsCallbackResource.EventDetails details = teamsCallbackResource.getEventDetails(endpoint); Awaitility.await() .pollInterval(Duration.ofMillis(100L)) @@ -1495,8 +1513,9 @@ public void post_createAndValidateEventSubscription_MSTEAMS(TestInfo test) throw @Test void post_alertActionWithEnabledStateChange_MSTeams(TestInfo test) throws IOException { String webhookName = getEntityName(test); + String endpoint = test.getDisplayName(); LOG.info("creating webhook in disabled state"); - String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/msteams/" + webhookName; + String uri = "http://localhost:" + APP.getLocalPort() + "/api/v1/test/msteams/" + endpoint; // Create a Disabled Generic Webhook CreateEventSubscription genericWebhookActionRequest = @@ -1506,8 +1525,7 @@ void post_alertActionWithEnabledStateChange_MSTeams(TestInfo test) throws IOExce // For the DISABLED Publisher are not available, so it will have no status SubscriptionStatus status = getStatus(alert.getId(), Response.Status.OK.getStatusCode()); assertEquals(DISABLED, status.getStatus()); - MSTeamsCallbackResource.EventDetails details = - teamsCallbackResource.getEventDetails(webhookName); + MSTeamsCallbackResource.EventDetails details = teamsCallbackResource.getEventDetails(endpoint); assertNull(details); LOG.info("Enabling webhook Action"); @@ -1528,7 +1546,7 @@ void post_alertActionWithEnabledStateChange_MSTeams(TestInfo test) throws IOExce assertEquals(SubscriptionStatus.Status.ACTIVE, status2.getStatus()); // Ensure the call back notification has started - details = waitForFirstMSTeamsEvent(alert.getId(), webhookName, 25); + details = waitForFirstMSTeamsEvent(alert.getId(), endpoint, 25); assertEquals(1, details.getEvents().size()); ConcurrentLinkedQueue messages = details.getEvents(); for (TeamsMessage teamsMessage : messages) { diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityUtilTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityUtilTest.java index 15413aca948f..74d51041ef42 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityUtilTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/util/EntityUtilTest.java @@ -2,9 +2,12 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; import org.openmetadata.schema.entity.data.GlossaryTerm; import org.openmetadata.schema.entity.data.Table; +import org.openmetadata.service.resources.feeds.MessageParser; class EntityUtilTest { @Test @@ -15,4 +18,167 @@ void test_isDescriptionRequired() { EntityUtil.isDescriptionRequired( GlossaryTerm.class)); // GlossaryTerm entity requires description } + + @Test + void test_entityLinkParser() { + + // Valid entity links + Map expected = new HashMap<>(); + expected.put("entityLink", "<#E::table::users>"); + expected.put("arrayFieldName", null); + expected.put("arrayFieldValue", null); + expected.put("fieldName", null); + expected.put("entityFQN", "users"); + expected.put("entityType", "table"); + expected.put("linkType", "ENTITY"); + expected.put("fullyQualifiedFieldType", "table"); + expected.put("fullyQualifiedFieldValue", "users"); + verifyEntityLinkParser(expected); + + expected.clear(); + expected.put("entityLink", "<#E::table::users.foo.\"bar.baz\">"); + expected.put("arrayFieldName", null); + expected.put("arrayFieldValue", null); + expected.put("fieldName", null); + expected.put("entityFQN", "users.foo.\"bar.baz\""); + expected.put("entityType", "table"); + expected.put("linkType", "ENTITY"); + expected.put("fullyQualifiedFieldType", "table"); + expected.put("fullyQualifiedFieldValue", "users.foo.\"bar.baz\""); + verifyEntityLinkParser(expected); + + expected.clear(); + expected.put("entityLink", "<#E::db::customers>"); + expected.put("arrayFieldName", null); + expected.put("arrayFieldValue", null); + expected.put("fieldName", null); + expected.put("entityFQN", "customers"); + expected.put("entityType", "db"); + expected.put("linkType", "ENTITY"); + expected.put("fullyQualifiedFieldType", "db"); + expected.put("fullyQualifiedFieldValue", "customers"); + verifyEntityLinkParser(expected); + + expected.clear(); + expected.put("entityLink", "<#E::table::users::column::id>"); + expected.put("arrayFieldName", "id"); + expected.put("arrayFieldValue", null); + expected.put("fieldName", "column"); + expected.put("entityFQN", "users"); + expected.put("entityType", "table"); + expected.put("linkType", "ENTITY_ARRAY_FIELD"); + expected.put("fullyQualifiedFieldType", "table.column.member"); + expected.put("fullyQualifiedFieldValue", "users.id"); + verifyEntityLinkParser(expected); + + expected.clear(); + expected.put("entityLink", "<#E::table::orders::column::status::type::enum>"); + expected.put("arrayFieldName", "status"); + expected.put("arrayFieldValue", "type::enum"); + expected.put("fieldName", "column"); + expected.put("entityFQN", "orders"); + expected.put("entityType", "table"); + expected.put("linkType", "ENTITY_ARRAY_FIELD"); + expected.put("fullyQualifiedFieldType", "table.column.member"); + expected.put("fullyQualifiedFieldValue", "orders.status.type::enum"); + verifyEntityLinkParser(expected); + + expected.clear(); + expected.put("entityLink", "<#E::db::schema::table::view::column>"); + expected.put("arrayFieldName", "view"); + expected.put("arrayFieldValue", "column"); + expected.put("fieldName", "table"); + expected.put("entityFQN", "schema"); + expected.put("entityType", "db"); + expected.put("linkType", "ENTITY_ARRAY_FIELD"); + expected.put("fullyQualifiedFieldType", "db.table.member"); + expected.put("fullyQualifiedFieldValue", "schema.view.column"); + verifyEntityLinkParser(expected); + + expected.clear(); + expected.put("entityLink", "<#E::table::foo@bar>"); + expected.put("arrayFieldName", null); + expected.put("arrayFieldValue", null); + expected.put("fieldName", null); + expected.put("entityFQN", "foo@bar"); + expected.put("entityType", "table"); + expected.put("linkType", "ENTITY"); + expected.put("fullyQualifiedFieldType", "table"); + expected.put("fullyQualifiedFieldValue", "foo@bar"); + verifyEntityLinkParser(expected); + + expected.clear(); + expected.put("entityLink", "<#E::table::foo[bar]>"); + expected.put("arrayFieldName", null); + expected.put("arrayFieldValue", null); + expected.put("fieldName", null); + expected.put("entityFQN", "foo[bar]"); + expected.put("entityType", "table"); + expected.put("linkType", "ENTITY"); + expected.put("fullyQualifiedFieldType", "table"); + expected.put("fullyQualifiedFieldValue", "foo[bar]"); + verifyEntityLinkParser(expected); + + expected.clear(); + expected.put("entityLink", "<#E::table::special!@#$%^&*()_+[]{};:\\'\",./?>"); + expected.put("arrayFieldName", null); + expected.put("arrayFieldValue", null); + expected.put("fieldName", null); + expected.put("entityFQN", "special!@#$%^&*()_+[]{};:\\'\",./?"); + expected.put("entityType", "table"); + expected.put("linkType", "ENTITY"); + expected.put("fullyQualifiedFieldType", "table"); + expected.put("fullyQualifiedFieldValue", "special!@#$%^&*()_+[]{};:\\'\",./?"); + verifyEntityLinkParser(expected); + + // Invalid entity link + expected.clear(); + expected.put("entityLink", "<#E::table::special!@#$%^&*()_+[]{}|;:\\'\",./?>"); + // EntityLink with `|` character will not be parsed correctly and everything after `|` will be + // ignored + org.opentest4j.AssertionFailedError exception = + assertThrows( + org.opentest4j.AssertionFailedError.class, () -> verifyEntityLinkParser(expected)); + assertEquals( + "expected: <<#E::table::special!@#$%^&*()_+[]{}|;:\\'\",./?>> but was: <<#E::table::special!@#$%^&*()_+[]{}>>", + exception.getMessage()); + + expected.clear(); + expected.put("entityLink", "<#E::table::user::column>"); + IllegalArgumentException argException = + assertThrows(IllegalArgumentException.class, () -> verifyEntityLinkParser(expected)); + assertEquals( + "Entity link was not found in <#E::table::user::column>", argException.getMessage()); + + expected.clear(); + expected.put("entityLink", "<#E::table::user>name::column>"); + exception = + assertThrows( + org.opentest4j.AssertionFailedError.class, () -> verifyEntityLinkParser(expected)); + assertEquals( + "expected: <<#E::table::user>name::column>> but was: <<#E::table::user>>", + exception.getMessage()); + + expected.clear(); + expected.put("entityLink", "<#E::table::foo<>bar::baz>"); + argException = + assertThrows(IllegalArgumentException.class, () -> verifyEntityLinkParser(expected)); + assertEquals( + "Entity link was not found in <#E::table::foo<>bar::baz>", argException.getMessage()); + } + + void verifyEntityLinkParser(Map expected) { + MessageParser.EntityLink entityLink = + MessageParser.EntityLink.parse(expected.get("entityLink")); + assertEquals(expected.get("entityLink"), entityLink.getLinkString()); + assertEquals(expected.get("arrayFieldName"), entityLink.getArrayFieldName()); + assertEquals(expected.get("arrayFieldValue"), entityLink.getArrayFieldValue()); + assertEquals(expected.get("entityType"), entityLink.getEntityType()); + assertEquals(expected.get("fieldName"), entityLink.getFieldName()); + assertEquals(expected.get("entityFQN"), entityLink.getEntityFQN()); + assertEquals(expected.get("linkType"), entityLink.getLinkType().toString()); + assertEquals(expected.get("fullyQualifiedFieldType"), entityLink.getFullyQualifiedFieldType()); + assertEquals( + expected.get("fullyQualifiedFieldValue"), entityLink.getFullyQualifiedFieldValue()); + } } diff --git a/openmetadata-spec/src/main/resources/json/schema/type/basic.json b/openmetadata-spec/src/main/resources/json/schema/type/basic.json index b72389d01990..7bb83ed2d353 100644 --- a/openmetadata-spec/src/main/resources/json/schema/type/basic.json +++ b/openmetadata-spec/src/main/resources/json/schema/type/basic.json @@ -113,7 +113,7 @@ "entityLink": { "description": "Link to an entity or field within an entity using this format `<#E::{entities}::{entityType}::{field}::{arrayFieldName}::{arrayFieldValue}`.", "type": "string", - "pattern": "(?U)^<#E::\\w+::[\\[\\]\\w'\\- .&/:+\"\\\\()$#%]+>$" + "pattern": "(?U)^<#E::\\w+::(?:[^:<>|]|:[^:<>|])+(?:::(?:[^:<>|]|:[^:<>|])+)*(?$" }, "entityName": { "description": "Name that identifies an entity.", From 9628ae85ec4d6c0e191af564d15d532869c7232f Mon Sep 17 00:00:00 2001 From: Teddy Crepineau Date: Mon, 28 Oct 2024 15:21:39 +0100 Subject: [PATCH 3/7] fix: remove unnecessary negative lookbehind --- .../service/resources/dqtests/TestCaseResourceTest.java | 7 +++++++ .../src/main/resources/json/schema/type/basic.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java index 2c5c84308927..43aeeb472774 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java @@ -3226,6 +3226,13 @@ void test_testCaseInvalidEntityLinkTest(TestInfo testInfo) throws IOException { HttpResponseException.class, () -> createAndCheckEntity(create, ADMIN_AUTH_HEADERS), "entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\""); + + entityLink = "<#E::table::::baz>"; + create.setEntityLink(entityLink); + assertThrows( + HttpResponseException.class, + () -> createAndCheckEntity(create, ADMIN_AUTH_HEADERS), + "entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\""); } private void putInspectionQuery(TestCase testCase, String sql) throws IOException { diff --git a/openmetadata-spec/src/main/resources/json/schema/type/basic.json b/openmetadata-spec/src/main/resources/json/schema/type/basic.json index 7bb83ed2d353..314954800e07 100644 --- a/openmetadata-spec/src/main/resources/json/schema/type/basic.json +++ b/openmetadata-spec/src/main/resources/json/schema/type/basic.json @@ -113,7 +113,7 @@ "entityLink": { "description": "Link to an entity or field within an entity using this format `<#E::{entities}::{entityType}::{field}::{arrayFieldName}::{arrayFieldValue}`.", "type": "string", - "pattern": "(?U)^<#E::\\w+::(?:[^:<>|]|:[^:<>|])+(?:::(?:[^:<>|]|:[^:<>|])+)*(?$" + "pattern": "(?U)^<#E::\\w+::(?:[^:<>|]|:[^:<>|])+(?:::(?:[^:<>|]|:[^:<>|])+)*>$" }, "entityName": { "description": "Name that identifies an entity.", From 9b948ff27d7bb84804f7f80f118a9215694b01de Mon Sep 17 00:00:00 2001 From: Aniket Katkar Date: Mon, 28 Oct 2024 21:00:39 +0530 Subject: [PATCH 4/7] fix the test case search not working on add ingestion page in case of special characters in FQN --- .../AddDataQualityTest/components/AddTestSuitePipeline.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/AddTestSuitePipeline.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/AddTestSuitePipeline.tsx index 9c33d7f2e7c9..ce65038745dc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/AddTestSuitePipeline.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/AddTestSuitePipeline.tsx @@ -25,6 +25,7 @@ import { FormItemLayout, } from '../../../../interface/FormUtils.interface'; import { generateFormFields } from '../../../../utils/formUtils'; +import { escapeESReservedCharacters } from '../../../../utils/StringsUtils'; import ScheduleInterval from '../../../Settings/Services/AddIngestion/Steps/ScheduleInterval'; import { WorkflowExtraConfig } from '../../../Settings/Services/AddIngestion/Steps/ScheduleInterval.interface'; import { AddTestCaseList } from '../../AddTestCaseList/AddTestCaseList.component'; @@ -152,9 +153,9 @@ const AddTestSuitePipeline = ({ ]} valuePropName="selectedTest"> From c7dd821e26d583585cf31a647a03fa6c9e156353 Mon Sep 17 00:00:00 2001 From: Teddy Crepineau Date: Mon, 28 Oct 2024 17:03:00 +0100 Subject: [PATCH 5/7] style: ran java linting --- .../service/resources/dqtests/TestCaseResourceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java index 43aeeb472774..e2f40cb61dd3 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java @@ -3230,9 +3230,9 @@ void test_testCaseInvalidEntityLinkTest(TestInfo testInfo) throws IOException { entityLink = "<#E::table::::baz>"; create.setEntityLink(entityLink); assertThrows( - HttpResponseException.class, - () -> createAndCheckEntity(create, ADMIN_AUTH_HEADERS), - "entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\""); + HttpResponseException.class, + () -> createAndCheckEntity(create, ADMIN_AUTH_HEADERS), + "entityLink must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\""); } private void putInspectionQuery(TestCase testCase, String sql) throws IOException { From f8cca503b0b515bd6c2383faa95d72af91a7a7fe Mon Sep 17 00:00:00 2001 From: Teddy Crepineau Date: Tue, 29 Oct 2024 07:18:11 +0100 Subject: [PATCH 6/7] fix: failing testst --- .../org/openmetadata/service/resources/EntityResourceTest.java | 2 +- .../openmetadata/service/resources/feeds/FeedResourceTest.java | 3 ++- .../service/resources/feeds/SuggestionsResourceTest.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/EntityResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/EntityResourceTest.java index e770acf8b9e1..ad387a12b48d 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/EntityResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/EntityResourceTest.java @@ -252,7 +252,7 @@ public abstract class EntityResourceTest$\"]"; + "[entityLink must match \"(?U)^<#E::\\w+::(?:[^:<>|]|:[^:<>|])+(?:::(?:[^:<>|]|:[^:<>|])+)*>$\"]"; // Random unicode string generator to test entity name accepts all the unicode characters protected static final RandomStringGenerator RANDOM_STRING_GENERATOR = diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/FeedResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/FeedResourceTest.java index 0b1c15056eef..3f8c92ca05d8 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/FeedResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/FeedResourceTest.java @@ -219,7 +219,8 @@ void post_feedWithInvalidAbout_4xx() { // Create thread without addressed to entity in the request CreateThread create = create().withFrom(USER.getName()).withAbout("<>"); // Invalid EntityLink - String failureReason = "[about must match \"(?U)^<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\"]"; + String failureReason = + "[about must match \"(?U)^<#E::\\w+::(?:[^:<>|]|:[^:<>|])+(?:::(?:[^:<>|]|:[^:<>|])+)*>$\"]"; assertResponseContains( () -> createThread(create, USER_AUTH_HEADERS), BAD_REQUEST, failureReason); diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/SuggestionsResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/SuggestionsResourceTest.java index d9b144c9c0c8..a3375627643d 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/SuggestionsResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/feeds/SuggestionsResourceTest.java @@ -153,7 +153,7 @@ void post_suggestionWithInvalidAbout_4xx() { CreateSuggestion create = create().withEntityLink("<>"); // Invalid EntityLink String failureReason = - "[entityLink must match \"(?U)^<#E::\\w+::[\\[\\]\\w'\\- .&/:+\"\\\\()$#%]+>$\"]"; + "[entityLink must match \"(?U)^<#E::\\w+::(?:[^:<>|]|:[^:<>|])+(?:::(?:[^:<>|]|:[^:<>|])+)*>$\"]"; assertResponseContains( () -> createSuggestion(create, USER_AUTH_HEADERS), BAD_REQUEST, failureReason); From a619f16390b282110717e6a306a4cc603dc29bd6 Mon Sep 17 00:00:00 2001 From: Teddy Crepineau Date: Tue, 29 Oct 2024 08:22:00 +0100 Subject: [PATCH 7/7] style: ran java linting --- .../src/main/java/org/openmetadata/csv/CsvUtil.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/csv/CsvUtil.java b/openmetadata-service/src/main/java/org/openmetadata/csv/CsvUtil.java index 96640909c550..ec6e1fa9cb2e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/csv/CsvUtil.java +++ b/openmetadata-service/src/main/java/org/openmetadata/csv/CsvUtil.java @@ -134,13 +134,15 @@ public static List fieldToExtensionStrings(String field) throws IOExcept preprocessedField = preprocessedField.replace("\n", "\\n").replace("\"", "\\\""); CSVFormat format = - CSVFormat.DEFAULT.builder() + CSVFormat.DEFAULT + .builder() .setDelimiter(';') .setQuote('"') .setRecordSeparator(null) .setIgnoreSurroundingSpaces(true) .setIgnoreEmptyLines(true) - .setEscape('\\').build(); // Use backslash for escaping special characters + .setEscape('\\') + .build(); // Use backslash for escaping special characters try (CSVParser parser = CSVParser.parse(new StringReader(preprocessedField), format)) { return parser.getRecords().stream() @@ -180,7 +182,7 @@ public static List fieldToColumns(String field) throws IOException { preprocessedField = preprocessedField.replace("\n", "\\n").replace("\"", "\\\""); CSVFormat format = - CSVFormat.DEFAULT.builder().setDelimiter(',').setQuote('"').setEscape('\\').build(); + CSVFormat.DEFAULT.builder().setDelimiter(',').setQuote('"').setEscape('\\').build(); List columns; try (CSVParser parser = CSVParser.parse(new StringReader(preprocessedField), format)) {