You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by wa...@apache.org on 2023/03/28 05:26:55 UTC

[incubator-devlake] branch main updated: test(tapd): add unit test for shared (#4770)

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 <yi...@merico.dev>
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)
+		}
+	}
+}