This is an automated email from the ASF dual-hosted git repository.

likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new c293c943a feat: nullable DataFlowTester (#4618)
c293c943a is described below

commit c293c943ae50e880340711b247334a67af9524ba
Author: mindlesscloud <[email protected]>
AuthorDate: Thu Mar 9 09:48:29 2023 +0800

    feat: nullable DataFlowTester (#4618)
---
 backend/helpers/e2ehelper/data_flow_tester.go      | 52 ++++++++++++---
 backend/helpers/e2ehelper/nullable_test.go         | 78 ++++++++++++++++++++++
 .../e2ehelper/testdata/issue_changelogs.csv        | 22 +++---
 3 files changed, 131 insertions(+), 21 deletions(-)

diff --git a/backend/helpers/e2ehelper/data_flow_tester.go 
b/backend/helpers/e2ehelper/data_flow_tester.go
index e52a27726..d4b862383 100644
--- a/backend/helpers/e2ehelper/data_flow_tester.go
+++ b/backend/helpers/e2ehelper/data_flow_tester.go
@@ -89,6 +89,8 @@ type TableOptions struct {
        // IgnoreTypes similar to IgnoreFields, this will ignore the fields 
contained in the type. Useful for ignoring embedded
        // types and their fields in the target model
        IgnoreTypes []interface{}
+       // if Nullable is set to be true, only the string `NULL` will be taken 
as NULL
+       Nullable bool
 }
 
 // NewDataFlowTester create a *DataFlowTester to help developer test their 
subtasks data flow
@@ -135,8 +137,7 @@ func (t *DataFlowTester) ImportCsvIntoRawTable(csvRelPath 
string, rawTableName s
        }
 }
 
-// ImportCsvIntoTabler imports records from specified csv file into target 
tabler, note that existing data would be deleted first.
-func (t *DataFlowTester) ImportCsvIntoTabler(csvRelPath string, dst 
schema.Tabler) {
+func (t *DataFlowTester) importCsv(csvRelPath string, dst schema.Tabler, 
nullable bool) {
        csvIter, _ := pluginhelper.NewCsvFileIterator(csvRelPath)
        defer csvIter.Close()
        t.FlushTabler(dst)
@@ -144,8 +145,14 @@ func (t *DataFlowTester) ImportCsvIntoTabler(csvRelPath 
string, dst schema.Table
        for csvIter.HasNext() {
                toInsertValues := csvIter.Fetch()
                for i := range toInsertValues {
-                       if toInsertValues[i].(string) == `` {
-                               toInsertValues[i] = nil
+                       if nullable {
+                               if toInsertValues[i].(string) == `NULL` {
+                                       toInsertValues[i] = nil
+                               }
+                       } else {
+                               if toInsertValues[i].(string) == `` {
+                                       toInsertValues[i] = nil
+                               }
                        }
                }
                result := t.Db.Model(dst).Create(toInsertValues)
@@ -156,6 +163,16 @@ func (t *DataFlowTester) ImportCsvIntoTabler(csvRelPath 
string, dst schema.Table
        }
 }
 
+// ImportCsvIntoTabler imports records from specified csv file into target 
tabler, the empty string will be taken as NULL. note that existing data would 
be deleted first.
+func (t *DataFlowTester) ImportCsvIntoTabler(csvRelPath string, dst 
schema.Tabler) {
+       t.importCsv(csvRelPath, dst, false)
+}
+
+// ImportNullableCsvIntoTabler imports records from specified csv file into 
target tabler, the `NULL` will be taken as NULL. note that existing data would 
be deleted first.
+func (t *DataFlowTester) ImportNullableCsvIntoTabler(csvRelPath string, dst 
schema.Tabler) {
+       t.importCsv(csvRelPath, dst, true)
+}
+
 // FlushRawTable migrate table and deletes all records from specified table
 func (t *DataFlowTester) FlushRawTable(rawTableName string) {
        // flush target table
@@ -272,7 +289,11 @@ func (t *DataFlowTester) CreateSnapshot(dst schema.Tabler, 
opts TableOptions) {
                                if value.Valid {
                                        values[i] = 
value.Time.In(location).Format("2006-01-02T15:04:05.000-07:00")
                                } else {
-                                       values[i] = ``
+                                       if opts.Nullable {
+                                               values[i] = "NULL"
+                                       } else {
+                                               values[i] = ""
+                                       }
                                }
                        case *bool:
                                if *forScanValues[i].(*bool) {
@@ -285,14 +306,22 @@ func (t *DataFlowTester) CreateSnapshot(dst 
schema.Tabler, opts TableOptions) {
                                if value.Valid {
                                        values[i] = value.String
                                } else {
-                                       values[i] = ``
+                                       if opts.Nullable {
+                                               values[i] = "NULL"
+                                       } else {
+                                               values[i] = ""
+                                       }
                                }
                        case *sql.NullInt64:
                                value := *forScanValues[i].(*sql.NullInt64)
                                if value.Valid {
                                        values[i] = 
strconv.FormatInt(value.Int64, 10)
                                } else {
-                                       values[i] = ``
+                                       if opts.Nullable {
+                                               values[i] = "NULL"
+                                       } else {
+                                               values[i] = ""
+                                       }
                                }
                        case *string:
                                values[i] = 
fmt.Sprint(*forScanValues[i].(*string))
@@ -333,7 +362,10 @@ func (t *DataFlowTester) ExportRawTable(rawTableName 
string, csvRelPath string)
        }
 }
 
-func formatDbValue(value interface{}) string {
+func formatDbValue(value interface{}, nullable bool) string {
+       if nullable && value == nil {
+               return "NULL"
+       }
        location, _ := time.LoadLocation(`UTC`)
        switch value := value.(type) {
        case time.Time:
@@ -457,7 +489,7 @@ func (t *DataFlowTester) VerifyTableWithOptions(dst 
schema.Tabler, opts TableOpt
                actualTotal++
                pkValues := make([]string, 0, len(pkColumns))
                for _, pkc := range pkColumns {
-                       pkValues = append(pkValues, 
formatDbValue(actual[pkc.Name()]))
+                       pkValues = append(pkValues, 
formatDbValue(actual[pkc.Name()], opts.Nullable))
                }
                expected, ok := csvMap[strings.Join(pkValues, `-`)]
                assert.True(t.T, ok, fmt.Sprintf(`%s not found (with params 
from csv %s)`, dst.TableName(), pkValues))
@@ -465,7 +497,7 @@ func (t *DataFlowTester) VerifyTableWithOptions(dst 
schema.Tabler, opts TableOpt
                        continue
                }
                for _, field := range targetFields {
-                       assert.Equal(t.T, expected[field], 
formatDbValue(actual[field]), fmt.Sprintf(`%s.%s not match (with params from 
csv %s)`, dst.TableName(), field, pkValues))
+                       assert.Equal(t.T, expected[field], 
formatDbValue(actual[field], opts.Nullable), fmt.Sprintf(`%s.%s not match (with 
params from csv %s)`, dst.TableName(), field, pkValues))
                }
        }
 
diff --git a/backend/helpers/e2ehelper/nullable_test.go 
b/backend/helpers/e2ehelper/nullable_test.go
new file mode 100644
index 000000000..84d6386df
--- /dev/null
+++ b/backend/helpers/e2ehelper/nullable_test.go
@@ -0,0 +1,78 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+*/
+
+package e2ehelper
+
+import (
+       "testing"
+)
+
+func TestNullableFlow(t *testing.T) {
+       dataflowTester := NewDataFlowTester(t, "", nil)
+
+       // nullable as false
+       dataflowTester.ImportCsvIntoTabler("./testdata/issue_changelogs.csv", 
&nullStringTest{})
+       dataflowTester.VerifyTableWithOptions(
+               &nullStringTest{},
+               TableOptions{
+                       CSVRelPath: "./testdata/issue_changelogs.csv",
+                       TargetFields: ColumnWithRawData(
+                               "id",
+                               "issue_id",
+                               "author_id",
+                               "author_name",
+                               "field_id",
+                               "field_name",
+                               "original_from_value",
+                               "original_to_value",
+                               "from_value",
+                               "to_value",
+                               "created_date",
+                               "_raw_data_params",
+                               "_raw_data_table",
+                               "_raw_data_id",
+                               "_raw_data_remark",
+                       ),
+               },
+       )
+       // nullable as true
+       
dataflowTester.ImportNullableCsvIntoTabler("./testdata/issue_changelogs.csv", 
&nullStringTest{})
+       dataflowTester.VerifyTableWithOptions(
+               &nullStringTest{},
+               TableOptions{
+                       Nullable:   true,
+                       CSVRelPath: "./testdata/issue_changelogs.csv",
+                       TargetFields: ColumnWithRawData(
+                               "id",
+                               "issue_id",
+                               "author_id",
+                               "author_name",
+                               "field_id",
+                               "field_name",
+                               "original_from_value",
+                               "original_to_value",
+                               "from_value",
+                               "to_value",
+                               "created_date",
+                               "_raw_data_params",
+                               "_raw_data_table",
+                               "_raw_data_id",
+                               "_raw_data_remark",
+                       ),
+               },
+       )
+}
diff --git a/backend/helpers/e2ehelper/testdata/issue_changelogs.csv 
b/backend/helpers/e2ehelper/testdata/issue_changelogs.csv
index cb8150b37..d9d66efd3 100644
--- a/backend/helpers/e2ehelper/testdata/issue_changelogs.csv
+++ b/backend/helpers/e2ehelper/testdata/issue_changelogs.csv
@@ -1,15 +1,15 @@
 
id,issue_id,author_id,author_name,field_id,field_name,original_from_value,original_to_value,from_value,to_value,created_date,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
-jira:JiraIssueChangelogItems:2:10645:Rank,jira:JiraIssue:2:10067,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Rank,,Ranked 
lower,,,2020-06-12T00:17:32.778+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12445,
-jira:JiraIssueChangelogItems:2:10646:Fix 
Version,jira:JiraIssue:2:10064,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,,,2020-06-12T00:17:56.609+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12442,
-jira:JiraIssueChangelogItems:2:10647:Fix 
Version,jira:JiraIssue:2:10065,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,,,2020-06-12T00:17:56.680+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12443,
-jira:JiraIssueChangelogItems:2:10648:Fix 
Version,jira:JiraIssue:2:10066,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,,,2020-06-12T00:17:56.735+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12444,
-jira:JiraIssueChangelogItems:2:10649:Fix 
Version,jira:JiraIssue:2:10068,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,,,2020-06-12T00:17:56.787+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12446,
-jira:JiraIssueChangelogItems:2:10650:Fix 
Version,jira:JiraIssue:2:10067,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,,,2020-06-12T00:18:00.255+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12445,
-jira:JiraIssueChangelogItems:2:10655:Fix 
Version,jira:JiraIssue:2:10072,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,,,2020-06-12T00:19:34.103+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12449,
-jira:JiraIssueChangelogItems:2:10656:Fix 
Version,jira:JiraIssue:2:10070,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,,,2020-06-12T00:19:34.130+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12447,
-jira:JiraIssueChangelogItems:2:10657:Fix 
Version,jira:JiraIssue:2:10071,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,,,2020-06-12T00:19:34.157+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12448,
-jira:JiraIssueChangelogItems:2:10659:Epic 
Link,jira:JiraIssue:2:10063,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin 
Zheng,,Epic 
Link,,EE-11,,,2020-06-12T00:21:20.929+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12441,
-jira:JiraIssueChangelogItems:2:10660:Epic 
Link,jira:JiraIssue:2:10064,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin 
Zheng,,Epic 
Link,,EE-11,,,2020-06-12T00:21:20.980+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12442,
+jira:JiraIssueChangelogItems:2:10645:Rank,jira:JiraIssue:2:10067,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Rank,,Ranked 
lower,NULL,,2020-06-12T00:17:32.778+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12445,
+jira:JiraIssueChangelogItems:2:10646:Fix 
Version,jira:JiraIssue:2:10064,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,NULL,,2020-06-12T00:17:56.609+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12442,
+jira:JiraIssueChangelogItems:2:10647:Fix 
Version,jira:JiraIssue:2:10065,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,NULL,,2020-06-12T00:17:56.680+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12443,
+jira:JiraIssueChangelogItems:2:10648:Fix 
Version,jira:JiraIssue:2:10066,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,NULL,,2020-06-12T00:17:56.735+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12444,
+jira:JiraIssueChangelogItems:2:10649:Fix 
Version,jira:JiraIssue:2:10068,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,NULL,,2020-06-12T00:17:56.787+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12446,
+jira:JiraIssueChangelogItems:2:10650:Fix 
Version,jira:JiraIssue:2:10067,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,NULL,,2020-06-12T00:18:00.255+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12445,
+jira:JiraIssueChangelogItems:2:10655:Fix 
Version,jira:JiraIssue:2:10072,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,NULL,,2020-06-12T00:19:34.103+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12449,
+jira:JiraIssueChangelogItems:2:10656:Fix 
Version,jira:JiraIssue:2:10070,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,NULL,,2020-06-12T00:19:34.130+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12447,
+jira:JiraIssueChangelogItems:2:10657:Fix 
Version,jira:JiraIssue:2:10071,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin
 Zheng,,Fix 
Version,,v2.7.0,NULL,,2020-06-12T00:19:34.157+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12448,
+jira:JiraIssueChangelogItems:2:10659:Epic 
Link,jira:JiraIssue:2:10063,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin 
Zheng,,Epic 
Link,,EE-11,NULL,,2020-06-12T00:21:20.929+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12441,
+jira:JiraIssueChangelogItems:2:10660:Epic 
Link,jira:JiraIssue:2:10064,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin 
Zheng,,Epic 
Link,,EE-11,NULL,,2020-06-12T00:21:20.980+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12442,
 jira:JiraIssueChangelogItems:2:10661:Epic 
Link,jira:JiraIssue:2:10065,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin 
Zheng,,Epic 
Link,,EE-11,,,2020-06-12T00:21:21.038+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12443,
 jira:JiraIssueChangelogItems:2:10662:Epic 
Link,jira:JiraIssue:2:10066,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin 
Zheng,,Epic 
Link,,EE-11,,,2020-06-12T00:21:21.089+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12444,
 jira:JiraIssueChangelogItems:2:10665:Epic 
Link,jira:JiraIssue:2:10068,jira:JiraAccount:2:5e9711ba34f7b90c0fbc37d3,Rankin 
Zheng,,Epic 
Link,,EE-12,,,2020-06-12T00:22:49.463+00:00,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12446,

Reply via email to