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

warren 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 8e5562f06 test(tapd): add unit test for shared (#4770)
8e5562f06 is described below

commit 8e5562f0663de06bb14d656e3d92d54208c03a45
Author: Warren Chen <[email protected]>
AuthorDate: Tue Mar 28 13:26:51 2023 +0800

    test(tapd): add unit test for shared (#4770)
---
 backend/plugins/tapd/tasks/shared.go      |  56 +++++-
 backend/plugins/tapd/tasks/shared_test.go | 301 ++++++++++++++++++++++++++++++
 2 files changed, 350 insertions(+), 7 deletions(-)

diff --git a/backend/plugins/tapd/tasks/shared.go 
b/backend/plugins/tapd/tasks/shared.go
index a0117a5fa..9a5726efb 100644
--- a/backend/plugins/tapd/tasks/shared.go
+++ b/backend/plugins/tapd/tasks/shared.go
@@ -96,9 +96,12 @@ func GetTotalPagesFromResponse(r *http.Response, args 
*api.ApiCollectorArgs) (in
        return totalPage, err
 }
 
+// parseIterationChangelog function is used to parse the iteration changelog
 func parseIterationChangelog(taskCtx plugin.SubTaskContext, old string, new 
string) (iterationFromId uint64, iterationToId uint64, err errors.Error) {
        data := taskCtx.GetData().(*TapdTaskData)
        db := taskCtx.GetDal()
+
+       // Find the iteration with the old name
        iterationFrom := &models.TapdIteration{}
        clauses := []dal.Clause{
                dal.From(&models.TapdIteration{}),
@@ -110,6 +113,7 @@ func parseIterationChangelog(taskCtx plugin.SubTaskContext, 
old string, new stri
                return 0, 0, err
        }
 
+       // Find the iteration with the new name
        iterationTo := &models.TapdIteration{}
        clauses = []dal.Clause{
                dal.From(&models.TapdIteration{}),
@@ -120,15 +124,19 @@ func parseIterationChangelog(taskCtx 
plugin.SubTaskContext, old string, new stri
        if err != nil && !db.IsErrorNotFound(err) {
                return 0, 0, err
        }
+
        return iterationFrom.Id, iterationTo.Id, nil
 }
 
+// GetRawMessageDirectFromResponse extracts the raw message from an HTTP 
response
 func GetRawMessageDirectFromResponse(res *http.Response) ([]json.RawMessage, 
errors.Error) {
+       // Read the response body
        body, err := io.ReadAll(res.Body)
        res.Body.Close()
        if err != nil {
                return nil, errors.Convert(err)
        }
+       // Return the response body as a slice of json.RawMessage
        return []json.RawMessage{body}, nil
 }
 
@@ -146,83 +154,111 @@ type TapdApiParams struct {
        WorkspaceId  uint64
 }
 
+// CreateRawDataSubTaskArgs creates a new instance of api.RawDataSubTaskArgs 
based on the provided
+// task context, raw table name, and a flag to determine if the company ID 
should be used.
+// It returns a pointer to the created api.RawDataSubTaskArgs and a pointer to 
the filtered TapdTaskData.
 func CreateRawDataSubTaskArgs(taskCtx plugin.SubTaskContext, rawTable string, 
useCompanyId bool) (*api.RawDataSubTaskArgs, *TapdTaskData) {
+       // Retrieve task data from the provided task context and cast it to 
TapdTaskData
        data := taskCtx.GetData().(*TapdTaskData)
+       // Create a filtered copy of the original data
        filteredData := *data
        filteredData.Options = &TapdOptions{}
        *filteredData.Options = *data.Options
+       // Set up TapdApiParams based on the original data
        var params = TapdApiParams{
                ConnectionId: data.Options.ConnectionId,
                WorkspaceId:  data.Options.WorkspaceId,
        }
+       // Check if the company ID should be used and if it is not zero, add it 
to the params
        if data.Options.CompanyId != 0 && useCompanyId {
                params.CompanyId = data.Options.CompanyId
        } else {
                filteredData.Options.CompanyId = 0
        }
+       // Create the RawDataSubTaskArgs with the task context, params, and raw 
table name
        rawDataSubTaskArgs := &api.RawDataSubTaskArgs{
                Ctx:    taskCtx,
                Params: params,
                Table:  rawTable,
        }
+       // Return the created RawDataSubTaskArgs and the filtered TapdTaskData
        return rawDataSubTaskArgs, &filteredData
 }
 
-// getTapdTypeMappings will map story types in _tool_tapd_workitem_types to 
our typeMapping
+// getTapdTypeMappings retrieves story types from _tool_tapd_workitem_types 
and maps them to
+// typeMapping. It takes TapdTaskData, a Dal interface, and a system string as 
arguments.
+// It returns a map of type ID to type name and an error, if any.
 func getTapdTypeMappings(data *TapdTaskData, db dal.Dal, system string) 
(map[uint64]string, errors.Error) {
        typeIdMapping := make(map[uint64]string)
        issueTypes := make([]models.TapdWorkitemType, 0)
+       // Create clauses for querying the database
        clauses := []dal.Clause{
                dal.From(&models.TapdWorkitemType{}),
                dal.Where("connection_id = ? and workspace_id = ? and 
entity_type = ?",
                        data.Options.ConnectionId, data.Options.WorkspaceId, 
system),
        }
+       // Query the database for issue types
        err := db.All(&issueTypes, clauses...)
        if err != nil {
                return nil, err
        }
+       // Map the retrieved issue types
        for _, issueType := range issueTypes {
                typeIdMapping[issueType.Id] = issueType.Name
        }
        return typeIdMapping, nil
 }
 
+// getStdTypeMappings creates a map of user type to standard type based on the 
provided TapdTaskData.
+// It returns the created map.
 func getStdTypeMappings(data *TapdTaskData) map[string]string {
        stdTypeMappings := make(map[string]string)
+       // Map user types to standard types
        for userType, stdType := range 
data.Options.TransformationRules.TypeMappings {
                stdTypeMappings[userType] = 
strings.ToUpper(stdType.StandardType)
        }
        return stdTypeMappings
 }
 
+// getStatusMapping creates a map of original status values to standard status 
values
+// based on the provided TapdTaskData. It returns the created map.
 func getStatusMapping(data *TapdTaskData) map[string]string {
        statusMapping := make(map[string]string)
        mapping := data.Options.TransformationRules.StatusMappings
+       // Map original status values to standard status values
        for std, orig := range mapping {
                for _, v := range orig {
                        statusMapping[v] = std
                }
        }
-
        return statusMapping
 }
 
+// getDefaultStdStatusMapping retrieves default standard status mappings for 
the given TapdTaskData and status list.
+// It takes TapdTaskData, a Dal interface, and a statusList of type S 
(models.TapdStatus).
+// It returns a map of English to Chinese status names, a function to get 
standard status from status key, and an error, if any.
 func getDefaultStdStatusMapping[S models.TapdStatus](data *TapdTaskData, db 
dal.Dal, statusList []S) (map[string]string, func(statusKey string) string, 
errors.Error) {
+       // Create clauses for querying the database
        clauses := []dal.Clause{
                dal.Where("connection_id = ? and workspace_id = ?", 
data.Options.ConnectionId, data.Options.WorkspaceId),
        }
+       // Query the database for status list
        err := db.All(&statusList, clauses...)
        if err != nil {
                return nil, nil, err
        }
 
+       // Create status language and last step maps
        statusLanguageMap := make(map[string]string, len(statusList))
        statusLastStepMap := make(map[string]bool, len(statusList))
 
+       // Populate status maps
        for _, v := range statusList {
                statusLanguageMap[v.GetEnglish()] = v.GetChinese()
                statusLastStepMap[v.GetChinese()] = v.GetIsLastStep()
        }
+
+       // Define function to get standard status from status key
        getStdStatus := func(statusKey string) string {
                if statusLastStepMap[statusKey] {
                        return ticket.DONE
@@ -235,8 +271,9 @@ func getDefaultStdStatusMapping[S models.TapdStatus](data 
*TapdTaskData, db dal.
        return statusLanguageMap, getStdStatus, nil
 }
 
+// unicodeToZh converts a string containing Unicode escape sequences to a 
Chinese string.
+// It returns the converted string and an error if the conversion fails.
 func unicodeToZh(s string) (string, error) {
-       // strconv.Quote(s) will add additional `"`, so we need to do `Unquote` 
again
        str, err := strconv.Unquote(strings.Replace(strconv.Quote(s), `\\u`, 
`\u`, -1))
        if err != nil {
                return "", err
@@ -244,6 +281,8 @@ func unicodeToZh(s string) (string, error) {
        return str, nil
 }
 
+// convertUnicode converts the ValueAfterParsed and ValueBeforeParsed fields 
of a struct to Chinese text.
+// It takes a pointer to a struct and returns an error if the conversion fails.
 func convertUnicode(p interface{}) errors.Error {
        var err errors.Error
        pType := reflect.TypeOf(p)
@@ -268,7 +307,7 @@ func convertUnicode(p interface{}) errors.Error {
        if before == "--" {
                before = ""
        }
-       // set ValueAfterParsed
+       // Set ValueAfterParsed and ValueBeforeParsed fields
        valueAfterField := pValue.FieldByName("ValueAfterParsed")
        valueAfterField.SetString(after)
        valueBeforeField := pValue.FieldByName("ValueBeforeParsed")
@@ -276,14 +315,17 @@ func convertUnicode(p interface{}) errors.Error {
        return nil
 }
 
-// replace ";" with ","
+// replaceSemicolonWithComma replaces all semicolons with commas in the given 
string
+// and trims any trailing commas. It returns the modified string.
 func replaceSemicolonWithComma(str string) string {
        res := strings.ReplaceAll(str, ";", ",")
        return strings.TrimRight(res, ",")
 }
 
-// generate domain account id for each user
-// param is a string with format "user1,user2,user3"
+// generateDomainAccountIdForUsers generates domain account IDs for a list of 
users.
+// The input 'param' is a string with format "user1,user2,user3".
+// The function takes a string containing a list of users separated by commas 
and a connectionId.
+// It returns a string containing the generated domain account IDs for each 
user, separated by commas.
 func generateDomainAccountIdForUsers(param string, connectionId uint64) string 
{
        if param == "" {
                return ""
diff --git a/backend/plugins/tapd/tasks/shared_test.go 
b/backend/plugins/tapd/tasks/shared_test.go
new file mode 100644
index 000000000..9569704a8
--- /dev/null
+++ b/backend/plugins/tapd/tasks/shared_test.go
@@ -0,0 +1,301 @@
+/*
+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 tasks
+
+import (
+       "encoding/json"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/plugin"
+       mockdal "github.com/apache/incubator-devlake/mocks/core/dal"
+       mockplugin "github.com/apache/incubator-devlake/mocks/core/plugin"
+       "github.com/apache/incubator-devlake/plugins/tapd/models"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/mock"
+       "io/ioutil"
+       "net/http"
+       "reflect"
+       "strings"
+       "testing"
+)
+
+// TestParseIterationChangelog tests the parseIterationChangelog function
+func TestParseIterationChangelog(t *testing.T) {
+       data := &TapdTaskData{Options: &TapdOptions{CompanyId: 1, WorkspaceId: 
1, ConnectionId: 1}}
+
+       // Set up the required data for testing
+       mockCtx := new(mockplugin.SubTaskContext)
+       mockDal := new(mockdal.Dal)
+
+       mockCtx.On("GetData").Return(data)
+       mockCtx.On("GetDal").Return(mockDal)
+       // Set up the required data for testing
+       mockIterationFrom := &models.TapdIteration{
+               ConnectionId: 1,
+               Id:           1,
+               Name:         "",
+       }
+       mockIterationTo := &models.TapdIteration{
+               ConnectionId: 1,
+               Id:           2,
+               Name:         "",
+       }
+       mockDal.On("First", mock.Anything, mock.Anything).Run(func(args 
mock.Arguments) {
+               dst := args.Get(0).(*models.TapdIteration)
+               *dst = *mockIterationFrom
+       }).Return(nil).Once()
+       mockDal.On("First", mock.Anything, mock.Anything).Run(func(args 
mock.Arguments) {
+               dst := args.Get(0).(*models.TapdIteration)
+               *dst = *mockIterationTo
+       }).Return(nil).Once()
+       // Test case 2: success scenario
+       iterationFromId, iterationToId, err := parseIterationChangelog(mockCtx, 
"old", "new")
+       assert.Equal(t, uint64(1), iterationFromId)
+       assert.Equal(t, uint64(2), iterationToId)
+       assert.Nil(t, err)
+}
+
+func TestGetRawMessageDirectFromResponse(t *testing.T) {
+       // Create a mock HTTP response
+       body := `{"data": {"count": 10}}`
+       res := &http.Response{
+               StatusCode: http.StatusOK,
+               Body:       ioutil.NopCloser(strings.NewReader(body)),
+       }
+
+       // Call the function and check the result
+       rawMessages, err := GetRawMessageDirectFromResponse(res)
+       if err != nil {
+               t.Errorf("Unexpected error: %v", err)
+       }
+       if len(rawMessages) != 1 {
+               t.Errorf("Expected 1 raw message, got %d", len(rawMessages))
+       }
+       var page Page
+       err = errors.Convert(json.Unmarshal(rawMessages[0], &page))
+       if err != nil {
+               t.Errorf("Unexpected error: %v", err)
+       }
+       if page.Data.Count != 10 {
+               t.Errorf("Expected count to be 10, got %d", page.Data.Count)
+       }
+}
+
+func TestGetTapdTypeMappings(t *testing.T) {
+       // create a mock database connection
+       db := new(mockdal.Dal)
+       // create some test data
+       data := &TapdTaskData{
+               Options: &TapdOptions{
+                       ConnectionId: 1,
+                       WorkspaceId:  2,
+               },
+       }
+
+       issueTypes := make([]models.TapdWorkitemType, 0)
+       issueTypes = append(issueTypes, models.TapdWorkitemType{
+               ConnectionId: 1,
+               WorkspaceId:  2,
+               Id:           1,
+               Name:         "Story",
+       })
+       issueTypes = append(issueTypes, models.TapdWorkitemType{
+               ConnectionId: 1,
+               WorkspaceId:  2,
+               Id:           2,
+               Name:         "Bug",
+       })
+       db.On("All", mock.Anything, mock.Anything).Run(func(args 
mock.Arguments) {
+               dst := args.Get(0).(*[]models.TapdWorkitemType)
+               *dst = issueTypes
+       }).Return(nil).Once()
+       // call the function being tested
+       result, err := getTapdTypeMappings(data, db, "story")
+
+       // check if the result is correct
+       if err != nil {
+               t.Errorf("getTapdTypeMappings returned an error: %v", err)
+       }
+       if len(result) != 2 {
+               t.Errorf("getTapdTypeMappings returned %d items, expected 2", 
len(result))
+       }
+       if result[1] != "Story" {
+               t.Errorf("getTapdTypeMappings returned incorrect value for ID 
1: %s", result[1])
+       }
+       if result[2] != "Bug" {
+               t.Errorf("getTapdTypeMappings returned incorrect value for ID 
2: %s", result[2])
+       }
+}
+
+func TestGetStdTypeMappings(t *testing.T) {
+       data := &TapdTaskData{
+               Options: &TapdOptions{
+                       TransformationRules: TransformationRules{
+                               TypeMappings: map[string]TypeMapping{
+                                       "userType": {
+                                               StandardType: "stdType",
+                                       },
+                               },
+                       },
+               },
+       }
+       expected := map[string]string{
+               "userType": "STDTYPE",
+       }
+       result := getStdTypeMappings(data)
+       if !reflect.DeepEqual(result, expected) {
+               t.Errorf("Expected %v but got %v", expected, result)
+       }
+}
+
+func TestGetStatusMapping(t *testing.T) {
+       data := &TapdTaskData{
+               Options: &TapdOptions{
+                       TransformationRules: TransformationRules{
+                               StatusMappings: StatusMappings{
+                                       "Done":        {"已完成", "已关闭"},
+                                       "In Progress": {"进行中", "处理中"},
+                                       "To Do":       {"未开始", "草稿"},
+                               },
+                       },
+               },
+       }
+
+       expected := map[string]string{
+               "已完成": "Done",
+               "已关闭": "Done",
+               "进行中": "In Progress",
+               "处理中": "In Progress",
+               "未开始": "To Do",
+               "草稿":  "To Do",
+       }
+
+       result := getStatusMapping(data)
+
+       if !reflect.DeepEqual(result, expected) {
+               t.Errorf("Expected %v, but got %v", expected, result)
+       }
+}
+
+// test case for when the status list is empty
+func TestGetDefaultStdStatusMappingEmptyStatusList(t *testing.T) {
+       data := &TapdTaskData{
+               Options: &TapdOptions{
+                       ConnectionId: 123,
+                       WorkspaceId:  456,
+               },
+       }
+       db := new(mockdal.Dal)
+       statusList := []models.TapdStoryStatus{
+               {
+                       ConnectionId: 123,
+                       WorkspaceId:  456,
+                       EnglishName:  "Done",
+                       ChineseName:  "已完成",
+                       IsLastStep:   true,
+               },
+               {
+                       ConnectionId: 123,
+                       WorkspaceId:  456,
+                       EnglishName:  "In Progress",
+                       ChineseName:  "进行中",
+               },
+       }
+       db.On("All", mock.Anything, mock.Anything).Run(func(args 
mock.Arguments) {
+               dst := args.Get(0).(*[]models.TapdStoryStatus)
+               *dst = statusList
+       }).Return(nil).Once()
+       statusLanguageMap, getStdStatus, err := 
getDefaultStdStatusMapping(data, db, statusList)
+       if err != nil {
+               t.Errorf("getDefaultStdStatusMapping returned an error: %v", 
err)
+       }
+       expectedStatusLanguageMap := map[string]string{
+               "Done":        "已完成",
+               "In Progress": "进行中",
+       }
+       if !reflect.DeepEqual(statusLanguageMap, expectedStatusLanguageMap) {
+               t.Errorf("getDefaultStdStatusMapping returned unexpected 
statusLanguageMap: got %v, want %v", statusLanguageMap, 
expectedStatusLanguageMap)
+       }
+       expectedGetStdStatus := map[string]string{
+               "已完成": "DONE",
+               "进行中": "IN_PROGRESS",
+       }
+       for k, v := range expectedGetStdStatus {
+               if getStdStatus(k) != v {
+                       t.Errorf("getDefaultStdStatusMapping returned 
unexpected getStdStatus for %v: got %v, want %v", k, getStdStatus(k), v)
+               }
+       }
+}
+
+func TestUnicodeToZh(t *testing.T) {
+       input := "\\u4e2d\\u6587"
+       expected := "中文"
+       output, err := unicodeToZh(input)
+       if err != nil {
+               t.Errorf("unicodeToZh(%q) returned error %v", input, err)
+       }
+       if output != expected {
+               t.Errorf("unicodeToZh(%q) = %q, want %q", input, output, 
expected)
+       }
+}
+
+func TestConvertUnicode(t *testing.T) {
+       testStruct := struct {
+               ValueBeforeParsed string
+               ValueAfterParsed  string
+       }{
+               ValueBeforeParsed: "Hello, \\u4e16\\u754c!",
+               ValueAfterParsed:  "--",
+       }
+
+       err := convertUnicode(&testStruct)
+       if err != nil {
+               t.Errorf("Unexpected error: %v", err)
+       }
+
+       expectedBefore := "Hello, 世界!"
+       if testStruct.ValueBeforeParsed != expectedBefore {
+               t.Errorf("Expected ValueBeforeParsed to be %q, but got %q", 
expectedBefore, testStruct.ValueBeforeParsed)
+       }
+
+       expectedAfter := ""
+       if testStruct.ValueAfterParsed != expectedAfter {
+               t.Errorf("Expected ValueAfterParsed to be %q, but got %q", 
expectedAfter, testStruct.ValueAfterParsed)
+       }
+}
+
+func TestGenerateDomainAccountIdForUsers(t *testing.T) {
+       connectionId := uint64(123)
+       testCases := []struct {
+               param    string
+               expected string
+       }{
+               {"user1,user2,user3", 
"tapd:TapdAccount:123:user1,tapd:TapdAccount:123:user2,tapd:TapdAccount:123:user3"},
+               {"user4;user5;user6", 
"tapd:TapdAccount:123:user4,tapd:TapdAccount:123:user5,tapd:TapdAccount:123:user6"},
+               {"", ""},
+       }
+       mockMeta := mockplugin.NewPluginMeta(t)
+       
mockMeta.On("RootPkgPath").Return("github.com/apache/incubator-devlake/plugins/tapd")
+       err := plugin.RegisterPlugin("tapd", mockMeta)
+       assert.Nil(t, err)
+       for _, testCase := range testCases {
+               result := generateDomainAccountIdForUsers(testCase.param, 
connectionId)
+               if result != testCase.expected {
+                       t.Errorf("generateDomainAccountIdForUsers(%s, %d) = %s; 
expected %s", testCase.param, connectionId, result, testCase.expected)
+               }
+       }
+}

Reply via email to