From a8a2cdf1b3dceef54951ab62d88a988e66053eea Mon Sep 17 00:00:00 2001 From: priyabhatnagar Date: Wed, 21 Jun 2023 22:25:25 +0530 Subject: [PATCH] wrangler e2e tests --- .../features/Wrangler/ParseAsCsv.feature | 2 +- .../features/Wrangler/ParseAsExcel.feature | 42 ++ .../features/Wrangler/ParseAsJson.feature | 43 +++ .../Wrangler/ParseAsXmlToJson.feature | 43 +++ .../common/stepsdesign/TestSetupHooks.java | 52 ++- .../Directive_parse_excel | 2 + .../Directive_parse_json | 3 + .../Directive_parse_xmltojson | 6 + .../BigQuery/BigQueryCreateTableQueryXml.txt | 1 + .../BigQuery/BigQueryInsertDataQueryXml.txt | 5 + .../BigQueryInsertDataQueryparsejson.txt | 6 + .../BigQuery/BigQuerycreateTableQueryjson.txt | 1 + .../resources/BQtesdata/BigQuery/test1.xlsx | Bin 0 -> 5228 bytes .../resources/pluginParameters.properties | 11 + ...xcel_wrangler_copy-cdap-data-pipeline.json | 186 +++++++++ ...e_json_Wrangle-cdap-data-pipeline (1).json | 180 +++++++++ ..._xmltojson_wrangle-cdap-data-pipeline.json | 362 ++++++++++++++++++ 17 files changed, 943 insertions(+), 2 deletions(-) create mode 100644 wrangler-transform/src/e2e-test/features/Wrangler/ParseAsExcel.feature create mode 100644 wrangler-transform/src/e2e-test/features/Wrangler/ParseAsJson.feature create mode 100644 wrangler-transform/src/e2e-test/features/Wrangler/ParseAsXmlToJson.feature create mode 100644 wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_excel create mode 100644 wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_json create mode 100644 wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_xmltojson create mode 100644 wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryCreateTableQueryXml.txt create mode 100644 wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryInsertDataQueryXml.txt create mode 100644 wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryInsertDataQueryparsejson.txt create mode 100644 wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQuerycreateTableQueryjson.txt create mode 100644 wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/test1.xlsx create mode 100644 wrangler-transform/src/e2e-test/resources/testData/Wrangler/parse_excel_wrangler_copy-cdap-data-pipeline.json create mode 100644 wrangler-transform/src/e2e-test/resources/testData/Wrangler/parse_json_Wrangle-cdap-data-pipeline (1).json create mode 100644 wrangler-transform/src/e2e-test/resources/testData/Wrangler/parse_xmltojson_wrangle-cdap-data-pipeline.json diff --git a/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsCsv.feature b/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsCsv.feature index fa59cb54c..7cb04063d 100644 --- a/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsCsv.feature +++ b/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsCsv.feature @@ -13,7 +13,7 @@ # the License. @Wrangler -Feature: Wrangler - Run time scenarios +Feature: Wrangler - Run time scenarios for parse csv @BQ_SOURCE_CSV_TEST @BQ_SINK_TEST Scenario: To verify User is able to run a pipeline using parse csv directive diff --git a/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsExcel.feature b/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsExcel.feature new file mode 100644 index 000000000..849924a0c --- /dev/null +++ b/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsExcel.feature @@ -0,0 +1,42 @@ +# Copyright © 2023 Cask Data, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +@Wrangler +Feature: parse as excel + + @GCS_SOURCE_TEST @BQ_SINK_TEST + Scenario: To verify User is able to run a pipeline using parse excel directive + Given Open Datafusion Project to configure pipeline + Then Click on the Plus Green Button to import the pipelines + Then Select the file for importing the pipeline for the plugin "Directive_parse_excel" + Then Navigate to the properties page of plugin: "GCSFile" + Then Replace input plugin property: "project" with value: "projectId" + Then Replace input plugin property: "path" with value: "gcsSourceBucket" + Then Click on the Get Schema button + Then Click on the Validate button + Then Close the Plugin Properties page + Then Navigate to the properties page of plugin: "BigQuery" + Then Replace input plugin property: "project" with value: "projectId" + Then Replace input plugin property: "table" with value: "bqTargetTable" + Then Replace input plugin property: "dataset" with value: "dataset" + Then Click on the Validate button + Then Close the Plugin Properties page + Then Rename the pipeline + Then Deploy the pipeline + Then Run the Pipeline in Runtime + Then Wait till pipeline is in running state + Then Open and capture logs + Then Verify the pipeline status is "Succeeded" + Then Close the pipeline logs + Then Validate The Data From BQ To BQ With Actual And Expected File for: "ExpectedDirective_parse_excel" diff --git a/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsJson.feature b/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsJson.feature new file mode 100644 index 000000000..79d0069fa --- /dev/null +++ b/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsJson.feature @@ -0,0 +1,43 @@ +# Copyright © 2023 Cask Data, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +@Wrangler +Feature: parse as json + + @BQ_SOURCE_JSON_TEST @BQ_SINK_TEST + Scenario: To verify User is able to run a pipeline using parse json directive + Given Open Datafusion Project to configure pipeline + Then Click on the Plus Green Button to import the pipelines + Then Select the file for importing the pipeline for the plugin "Directive_parse_json" + Then Navigate to the properties page of plugin: "BigQueryTable" + Then Replace input plugin property: "project" with value: "projectId" + Then Replace input plugin property: "dataset" with value: "dataset" + Then Replace input plugin property: "table" with value: "bqSourceTable" + Then Click on the Get Schema button + Then Click on the Validate button + Then Close the Plugin Properties page + Then Navigate to the properties page of plugin: "BigQuery2" + Then Replace input plugin property: "project" with value: "projectId" + Then Replace input plugin property: "table" with value: "bqTargetTable" + Then Replace input plugin property: "dataset" with value: "dataset" + Then Click on the Validate button + Then Close the Plugin Properties page + Then Rename the pipeline + Then Deploy the pipeline + Then Run the Pipeline in Runtime + Then Wait till pipeline is in running state + Then Open and capture logs + Then Verify the pipeline status is "Succeeded" + Then Close the pipeline logs + Then Validate The Data From BQ To BQ With Actual And Expected File for: "ExpectedDirective_parse_json" diff --git a/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsXmlToJson.feature b/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsXmlToJson.feature new file mode 100644 index 000000000..77485db35 --- /dev/null +++ b/wrangler-transform/src/e2e-test/features/Wrangler/ParseAsXmlToJson.feature @@ -0,0 +1,43 @@ +# Copyright © 2023 Cask Data, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +@Wrangler +Feature: parse as XmlToJson + + @BQ_SOURCE_XML_TEST @BQ_SINK_TEST + Scenario: To verify User is able to run a pipeline using parse XmlToJson directive + Given Open Datafusion Project to configure pipeline + Then Click on the Plus Green Button to import the pipelines + Then Select the file for importing the pipeline for the plugin "Directive_parse_xml" + Then Navigate to the properties page of plugin: "BigQueryTable" + Then Replace input plugin property: "project" with value: "projectId" + Then Replace input plugin property: "dataset" with value: "dataset" + Then Replace input plugin property: "table" with value: "bqSourceTable" + Then Click on the Get Schema button + Then Click on the Validate button + Then Close the Plugin Properties page + Then Navigate to the properties page of plugin: "BigQuery2" + Then Replace input plugin property: "project" with value: "projectId" + Then Replace input plugin property: "table" with value: "bqTargetTable" + Then Replace input plugin property: "dataset" with value: "dataset" + Then Click on the Validate button + Then Close the Plugin Properties page + Then Rename the pipeline + Then Deploy the pipeline + Then Run the Pipeline in Runtime + Then Wait till pipeline is in running state + Then Open and capture logs + Then Verify the pipeline status is "Succeeded" + Then Close the pipeline logs + Then Validate The Data From BQ To BQ With Actual And Expected File for: "ExpectedDirective_parse_xml" diff --git a/wrangler-transform/src/e2e-test/java/io/cdap/plugin/common/stepsdesign/TestSetupHooks.java b/wrangler-transform/src/e2e-test/java/io/cdap/plugin/common/stepsdesign/TestSetupHooks.java index 0243dc4ed..89ed879a4 100644 --- a/wrangler-transform/src/e2e-test/java/io/cdap/plugin/common/stepsdesign/TestSetupHooks.java +++ b/wrangler-transform/src/e2e-test/java/io/cdap/plugin/common/stepsdesign/TestSetupHooks.java @@ -17,8 +17,11 @@ package io.cdap.plugin.common.stepsdesign; import com.google.cloud.bigquery.BigQueryException; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.StorageException; import io.cdap.e2e.utils.BigQueryClient; import io.cdap.e2e.utils.PluginPropertyUtils; +import io.cdap.e2e.utils.StorageClient; import io.cucumber.java.After; import io.cucumber.java.Before; import org.apache.commons.lang3.StringUtils; @@ -26,6 +29,7 @@ import stepsdesign.BeforeActions; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -36,6 +40,7 @@ * Setup BQ for Wrangler tests. */ public class TestSetupHooks { + public static String gcsSourceBucketName = StringUtils.EMPTY; @Before(order = 1, value = "@BQ_SOURCE_CSV_TEST") public static void createTempSourceBQTable() throws IOException, InterruptedException { @@ -66,7 +71,7 @@ public static void deleteTempTargetBQTable() throws IOException, InterruptedExce } /** - * Create BigQuery table. + * Create BigQuery table test. */ @Before(order = 1, value = "@BQ_SOURCE_FXDLEN_TEST") public static void createTempSourceBQTableFxdLen() throws IOException, InterruptedException { @@ -96,6 +101,51 @@ public static void deleteTempSourceBQTable() throws IOException, InterruptedExce BeforeActions.scenario.write("BQ source Table " + bqSourceTable + " deleted successfully"); PluginPropertyUtils.removePluginProp("bqSourceTable"); } + @Before(order = 1, value = "@BQ_SOURCE_JSON_TEST") + public static void createTempSourceBQTableJson() throws IOException, InterruptedException { + createSourceBQTableWithQueries(PluginPropertyUtils.pluginProp("CreateBQTableQueryFileJson"), + PluginPropertyUtils.pluginProp("InsertBQDataQueryFileJson")); + } + @Before(order = 1, value = "@BQ_SOURCE_XML_TEST") + public static void createTempSourceBQTableXml() throws IOException, InterruptedException { + createSourceBQTableWithQueries(PluginPropertyUtils.pluginProp("CreateBQDataQueryFileXml"), + PluginPropertyUtils.pluginProp("InsertBQDataQueryFileXml")); + } + + @Before(order = 1, value = "@GCS_SOURCE_TEST") + public static void createBucketWithEXCELFile() throws IOException, URISyntaxException { + gcsSourceBucketName = createGCSBucketWithFile(PluginPropertyUtils.pluginProp("testFile")); + PluginPropertyUtils.addPluginProp("gcsSourceBucket", "gs://" + gcsSourceBucketName + "/" + + PluginPropertyUtils.pluginProp("testFile")); + BeforeActions.scenario.write("GCS source bucket1 name - " + gcsSourceBucketName); + } + private static String createGCSBucketWithFile(String filePath) + throws IOException, URISyntaxException { + String bucketName = StorageClient.createBucket("e2e-test-" + UUID.randomUUID()).getName(); + StorageClient.uploadObject(bucketName, filePath, filePath); + return bucketName; + } + @After(order = 1, value = "@GCS_SOURCE_TEST") + public static void deleteSourceBucketWithFile() { + deleteGCSBucket(gcsSourceBucketName); + gcsSourceBucketName = StringUtils.EMPTY; + } + private static void deleteGCSBucket(String bucketName) { + try { + for (Blob blob : StorageClient.listObjects(bucketName).iterateAll()) { + StorageClient.deleteObject(bucketName, blob.getName()); + } + StorageClient.deleteBucket(bucketName); + BeforeActions.scenario.write("Deleted GCS Bucket " + bucketName); + } catch (StorageException | IOException e) { + if (e.getMessage().contains("The specified bucket does not exist")) { + BeforeActions.scenario.write("GCS Bucket " + bucketName + " does not exist."); + } else { + Assert.fail(e.getMessage()); + } + } + } + private static void createSourceBQTableWithQueries(String bqCreateTableQueryFile, String bqInsertDataQueryFile) throws IOException, InterruptedException { diff --git a/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_excel b/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_excel new file mode 100644 index 000000000..3c3ae5154 --- /dev/null +++ b/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_excel @@ -0,0 +1,2 @@ +{"copiedname":"very","id":0,"name":"very","phone":"8838.0","rollno":"3.0","uniquenum":"very,0"} +{"copiedname":"hello","id":2,"name":"hell","phone":"12345.0","rollno":"1.0","uniquenum":"hello,2"} \ No newline at end of file diff --git a/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_json b/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_json new file mode 100644 index 000000000..b8cac65cb --- /dev/null +++ b/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_json @@ -0,0 +1,3 @@ +{"Body":"hello abc","copied":{"first":"Root","last":"joy"},"desc":"nick, hello abc","id":22,"json_age":"{\"json_id\":22,\"copied\":{\"first\":\"Root\",\"last\":\"joy\"},\"json_age\":1,\"json_name\":{\"first\":\"Root\",\"last\":\"joy\"},\"json_pet\":\"testing\",\"json_id_json_name\":\"22,{\\\"first\\\":\\\"Root\\\",\\\"last\\\":\\\"joy\\\"}\",\"body\":\"hello abc\",\"desc\":\"nick, hello abc\"}","json_id_json_name":"22,{\"first\":\"Root\",\"last\":\"joy\"}","json_name":{"first":"Root","last":"joy"},"json_pet":"testing"} +{"Body":"hello def","copied":{"first":"dded","last":"share"},"desc":"hello, hello def","id":23,"json_age":"{\"json_id\":23,\"copied\":{\"first\":\"dded\",\"last\":\"share\"},\"json_age\":2,\"json_name\":{\"first\":\"dded\",\"last\":\"share\"},\"json_pet\":\"testing\",\"json_id_json_name\":\"23,{\\\"first\\\":\\\"dded\\\",\\\"last\\\":\\\"share\\\"}\",\"body\":\"hello def\",\"desc\":\"hello, hello def\"}","json_id_json_name":"23,{\"first\":\"dded\",\"last\":\"share\"}","json_name":{"first":"dded","last":"share"},"json_pet":"testing"} +{"Body":"hello ghi","copied":{"first":"Root","last":"Joltie"},"desc":"doms, hello ghi","id":24,"json_age":"{\"json_id\":24,\"copied\":{\"first\":\"Root\",\"last\":\"Joltie\"},\"json_age\":3,\"json_name\":{\"first\":\"Root\",\"last\":\"Joltie\"},\"json_pet\":\"testing\",\"json_id_json_name\":\"24,{\\\"first\\\":\\\"Root\\\",\\\"last\\\":\\\"Joltie\\\"}\",\"body\":\"hello ghi\",\"desc\":\"doms, hello ghi\"}","json_id_json_name":"24,{\"first\":\"Root\",\"last\":\"Joltie\"}","json_name":{"first":"Root","last":"Joltie"},"json_pet":"testing"} \ No newline at end of file diff --git a/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_xmltojson b/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_xmltojson new file mode 100644 index 000000000..2f9d3ba92 --- /dev/null +++ b/wrangler-transform/src/e2e-test/resources/BQValidationExpectedFiles/Directive_parse_xmltojson @@ -0,0 +1,6 @@ +{"Email":"abc01@mail.com","distance":2.0,"distance2":0.3571428656578064,"email_domain":{"distance":2.0,"email_account":"abc01"},"email_porter":["abc","mail","com"],"id":"1","xmldata_note":{"body":"Dont forget me this week!","from":"Tani","heading":"Reminder","to":"Tove"}} +{"Email":"def02@mail.com","distance":2.0,"distance2":0.3571428656578064,"email_domain":{"distance":2.0,"email_account":"def02"},"email_porter":["def","mail","com"],"id":"2","xmldata_note":{"body":"Dont forget us this holiday!","from":"joy","heading":"Reminder","to":"Tove"}} +{"Email":"ghi03@mail.com","distance":2.0,"distance2":0.3571428656578064,"email_domain":{"distance":2.0,"email_account":"ghi03"},"email_porter":["ghi","mail","com"],"id":"3","xmldata_note":{"body":"Dont forget him this weekend!","from":"shree","heading":"Reminder","to":"Tove"}} +{"Email":"abc01@mail.com","distance":2.0,"distance2":0.3571428656578064,"email_domain":{"distance":2.0,"email_account":"abc01"},"email_porter":["abc","mail","com"],"id":"abc","xmldata_note":{"body":"Dont forget me this week!","from":"Tani","heading":"Reminder","to":"Tove"}} +{"Email":"def02@mail.com","distance":2.0,"distance2":0.3571428656578064,"email_domain":{"distance":2.0,"email_account":"def02"},"email_porter":["def","mail","com"],"id":"def","xmldata_note":{"body":"Dont forget us this holiday!","from":"joy","heading":"Reminder","to":"Tove"}} +{"Email":"ghi03@mail.com","distance":2.0,"distance2":0.3571428656578064,"email_domain":{"distance":2.0,"email_account":"ghi03"},"email_porter":["ghi","mail","com"],"id":"ghi","xmldata_note":{"body":"Dont forget him this weekend!","from":"shree","heading":"Reminder","to":"Tove"}} \ No newline at end of file diff --git a/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryCreateTableQueryXml.txt b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryCreateTableQueryXml.txt new file mode 100644 index 000000000..a711921e2 --- /dev/null +++ b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryCreateTableQueryXml.txt @@ -0,0 +1 @@ +create table `DATASET.TABLE_NAME` (email STRING, xmldata STRING) diff --git a/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryInsertDataQueryXml.txt b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryInsertDataQueryXml.txt new file mode 100644 index 000000000..0dc9608ce --- /dev/null +++ b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryInsertDataQueryXml.txt @@ -0,0 +1,5 @@ +INSERT INTO DATASET.TABLE_NAME (email,xmldata) +VALUES +('abc01@mail.com',' Tove Tani Reminder Dont forget me this week! '), +('def02@mail.com',' Tove joy Reminder Dont forget us this holiday! '), +('ghi03@mail.com',' Tove shree Reminder Dont forget him this weekend! '); diff --git a/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryInsertDataQueryparsejson.txt b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryInsertDataQueryparsejson.txt new file mode 100644 index 000000000..dc9fa7d17 --- /dev/null +++ b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQueryInsertDataQueryparsejson.txt @@ -0,0 +1,6 @@ +INSERT INTO DATASET.TABLE_NAME (body,json) +VALUES +(' hello abc', '{"id": 1, "name": {"first": "Root", "last": "joy"}, "age": 22, "pet": "nick", "height": 5.8}'), +('hello def', '{"id": 2, "name": {"first": "dded", "last": "share"}, "age": 23, "pet": "hello", "height": 6.8}'), +('hello ghi', '{"id": 3, "name": {"first": "Root", "last": "Joltie"}, "age": 24, "pet": "doms", "height": 7.8}'); + diff --git a/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQuerycreateTableQueryjson.txt b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQuerycreateTableQueryjson.txt new file mode 100644 index 000000000..be6b585ea --- /dev/null +++ b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/BigQuerycreateTableQueryjson.txt @@ -0,0 +1 @@ +create table `DATASET.TABLE_NAME` (body STRING, json STRING) \ No newline at end of file diff --git a/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/test1.xlsx b/wrangler-transform/src/e2e-test/resources/BQtesdata/BigQuery/test1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..adaa5291b024baf3415f6081d3fdca5369b6bbb8 GIT binary patch literal 5228 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