Repository: ctreminiom/go-atlassian
Pull request #17 (2021-04-08 02:21:31)

Enable the interaction with the Field Operations on the Jira issue edit method


Patch:
diff --git a/jira/examples/advanced/time-in-status/main.go b/jira/examples/advanced/time-in-status/main.go
index f3d2214..12b24d9 100644
--- a/jira/examples/advanced/time-in-status/main.go
+++ b/jira/examples/advanced/time-in-status/main.go
@@ -130,8 +130,7 @@ func main() {
 
 				csvRows = append(csvRows, csvRow{
 					Key:     issue,
-					From:    record.FromString,
-					To:      record.ToString,
+					Status:  record.FromString,
 					Pretty:  durafmt.ParseShort(diff).LimitFirstN(4).String(),
 					Days:    diff.Hours() / 24,
 					Hours:   diff.Hours(),
@@ -139,6 +138,8 @@ func main() {
 					Seconds: diff.Seconds(),
 				})
 
+				log.Println(index, durafmt.ParseShort(diff).LimitFirstN(4).String())
+
 			}
 
 			if index == len(records)-1 {
@@ -148,8 +149,7 @@ func main() {
 
 				csvRows = append(csvRows, csvRow{
 					Key:     issue,
-					From:    record.FromString,
-					To:      record.ToString,
+					Status:  record.ToString,
 					Pretty:  durafmt.ParseShort(diff).LimitFirstN(4).String(),
 					Days:    diff.Hours() / 24,
 					Hours:   diff.Hours(),
@@ -157,6 +157,8 @@ func main() {
 					Seconds: diff.Seconds(),
 				})
 
+				log.Println(index == len(records)-1, durafmt.ParseShort(diff).LimitFirstN(4).String())
+
 				break
 			}
 
@@ -165,14 +167,16 @@ func main() {
 
 			csvRows = append(csvRows, csvRow{
 				Key:     issue,
-				From:    record.FromString,
-				To:      record.ToString,
+				Status:  record.ToString,
 				Pretty:  durafmt.ParseShort(diff).LimitFirstN(4).String(),
 				Days:    diff.Hours() / 24,
 				Hours:   diff.Hours(),
 				Minutes: diff.Minutes(),
 				Seconds: diff.Seconds(),
 			})
+
+			log.Println(index, durafmt.ParseShort(diff).LimitFirstN(4).String())
+
 		}
 
 	}
@@ -191,7 +195,7 @@ func main() {
 	defer writer.Flush()
 
 	//Write the csv headers
-	if err = writer.Write([]string{"issue-key", "status-from", "status-to", "pretty", "days", "hours", "minutes", "seconds"}); err != nil {
+	if err = writer.Write([]string{"issue-key", "status", "pretty", "days", "hours", "minutes", "seconds"}); err != nil {
 		log.Fatal(err)
 	}
 
@@ -199,8 +203,7 @@ func main() {
 
 		if err = writer.Write([]string{
 			row.Key,
-			row.From,
-			row.To,
+			row.Status,
 			row.Pretty,
 			fmt.Sprintf("%.2f", row.Days),
 			fmt.Sprintf("%.2f", row.Hours),
@@ -234,8 +237,7 @@ type changelogRecord struct {
 
 type csvRow struct {
 	Key     string
-	From    string
-	To      string
+	Status  string
 	Pretty  string
 	Days    float64
 	Hours   float64
diff --git a/jira/examples/advanced/time-in-status/time-in-status.csv b/jira/examples/advanced/time-in-status/time-in-status.csv
index 2a416d2..298f1cb 100644
--- a/jira/examples/advanced/time-in-status/time-in-status.csv
+++ b/jira/examples/advanced/time-in-status/time-in-status.csv
@@ -1,14 +1,4 @@
-issue-key,status-from,status-to,pretty,days,hours,minutes,seconds
-DESK-12,Needs Approval,Waiting for support,9 minutes 43 seconds 216 milliseconds,0.01,0.16,9.72,583.22
-DESK-12,Needs Approval,Waiting for support,1 week 1 day 19 hours 56 minutes,8.83,211.94,12716.66,762999.59
-DESK-12,Waiting for support,Resolved,2 weeks 1 day 19 hours 10 minutes,15.80,379.17,22750.06,1365003.41
-DESK-11,Waiting for support,Resolved,2 hours 35 minutes 16 seconds 106 milliseconds,0.11,2.59,155.27,9316.11
-DESK-11,Waiting for support,Resolved,3 weeks 3 days 15 hours 25 minutes,24.64,591.43,35485.79,2129147.33
-KP-2,Backlog,Backlog,3 weeks 4 days 12 hours 54 minutes,25.54,612.90,36774.18,2206450.56
-KP-2,Backlog,Backlog,4 weeks 5 days 51 minutes 37 seconds,33.04,792.86,47571.63,2854297.91
-KP-1,Backlog,Done,10 seconds 616 milliseconds,0.00,0.00,0.18,10.62
-KP-1,Backlog,Done,11 weeks 5 days 16 hours 14 minutes,82.68,1984.25,119054.94,7143296.42
-DESK-5,Waiting for support,Resolved,856 milliseconds,0.00,0.00,0.01,0.86
-DESK-5,Waiting for support,Resolved,12 weeks 5 days 19 hours 1 minute,89.79,2155.02,129301.16,7758069.46
-DESK-3,Waiting for support,Escalated,10 weeks 3 days 2 hours 14 minutes,73.09,1754.24,105254.13,6315248.07
-DESK-3,Waiting for support,Escalated,2 weeks 2 days 16 hours 47 minutes,16.70,400.78,24047.05,1442823.25
+issue-key,status,pretty,days,hours,minutes,seconds
+DESK-12,Needs Approval,9 minutes 43 seconds 216 milliseconds,0.01,0.16,9.72,583.22
+DESK-12,Waiting for support,1 week 1 day 19 hours 56 minutes,8.83,211.94,12716.66,762999.59
+DESK-12,Resolved,3 weeks 18 hours 10 minutes 32 seconds,21.76,522.18,31330.55,1879832.99
diff --git a/jira/examples/issue/edit/edit.go b/jira/examples/issue/edit/edit.go
index 0db3c76..f97bffe 100644
--- a/jira/examples/issue/edit/edit.go
+++ b/jira/examples/issue/edit/edit.go
@@ -40,7 +40,26 @@ func main() {
 		log.Fatal(err)
 	}
 
-	response, err := atlassian.Issue.Update(context.Background(), "KP-2", false, &payload, &customFields)
+	//Issue Update Operations
+	var operations = &jira.UpdateOperations{}
+
+	err = operations.AddArrayOperation("labels", map[string]string{
+		"triaged":   "remove",
+		"triaged-2": "remove",
+		"triaged-1": "remove",
+		"blocker":   "remove",
+	})
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	err = operations.AddStringOperation("summary", "set", "new summary using operation")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	response, err := atlassian.Issue.Update(context.Background(), "KP-2", false, &payload, &customFields, operations)
 	if err != nil {
 		if response != nil {
 			log.Println("Response HTTP Response", string(response.BodyAsBytes))
diff --git a/jira/issue.go b/jira/issue.go
index 4827c2d..c0fc5a0 100644
--- a/jira/issue.go
+++ b/jira/issue.go
@@ -53,7 +53,7 @@ type IssueFieldsScheme struct {
 	Labels                   []string                  `json:"labels,omitempty"`
 }
 
-func (i *IssueScheme) Merge(fields *CustomFields) (result map[string]interface{}, err error) {
+func (i *IssueScheme) MergeCustomFields(fields *CustomFields) (result map[string]interface{}, err error) {
 
 	if fields == nil {
 		return nil, fmt.Errorf("error, please provide a value *CustomFields pointer")
@@ -82,7 +82,11 @@ func (i *IssueScheme) Merge(fields *CustomFields) (result map[string]interface{}
 	return issueSchemeAsMap, nil
 }
 
-func (i *IssueScheme) ToMap() (result map[string]interface{}, err error) {
+func (i *IssueScheme) MergeOperations(operations *UpdateOperations) (result map[string]interface{}, err error) {
+
+	if operations == nil {
+		return nil, fmt.Errorf("error, please provide a value *CustomFields pointer")
+	}
 
 	//Convert the IssueScheme struct to map[string]interface{}
 	issueSchemeAsBytes, err := json.Marshal(i)
@@ -91,11 +95,29 @@ func (i *IssueScheme) ToMap() (result map[string]interface{}, err error) {
 	}
 
 	issueSchemeAsMap := make(map[string]interface{})
-	err = json.Unmarshal(issueSchemeAsBytes, &issueSchemeAsMap)
-	if err != nil {
+	if err = json.Unmarshal(issueSchemeAsBytes, &issueSchemeAsMap); err != nil {
 		return nil, err
 	}
 
+	//For each customField created, merge it into the eAsMap
+	for _, customField := range operations.Fields {
+		if err = mergo.Merge(&issueSchemeAsMap, customField, mergo.WithOverride); err != nil {
+			return nil, err
+		}
+	}
+
+	return issueSchemeAsMap, nil
+}
+
+func (i *IssueScheme) ToMap() (result map[string]interface{}, err error) {
+
+	//Convert the IssueScheme struct to map[string]interface{}
+	issueSchemeAsBytes, _ := json.Marshal(i)
+
+	issueSchemeAsMap := make(map[string]interface{})
+	if err = json.Unmarshal(issueSchemeAsBytes, &issueSchemeAsMap); err != nil {
+		return nil, err
+	}
 	return issueSchemeAsMap, err
 }
 
@@ -442,7 +464,7 @@ func (i *IssueService) Create(ctx context.Context, payload *IssueScheme, customF
 
 	var endpoint = "rest/api/3/issue"
 
-	payloadWithCustomFields, err := payload.Merge(customFields)
+	payloadWithCustomFields, err := payload.MergeCustomFields(customFields)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -512,7 +534,7 @@ func (i *IssueService) Creates(ctx context.Context, payload []*IssueBulkScheme)
 		}
 
 		//Convert the issueScheme struct to map
-		newIssueAsMap, err := newIssue.Payload.Merge(newIssue.CustomFields)
+		newIssueAsMap, err := newIssue.Payload.MergeCustomFields(newIssue.CustomFields)
 		if err != nil {
 			return nil, nil, err
 		}
@@ -615,9 +637,68 @@ func (i *IssueService) Get(ctx context.Context, issueKeyOrID string, fields []st
 	return
 }
 
+type UpdateOperations struct{ Fields []map[string]interface{} }
+
+func (u *UpdateOperations) AddArrayOperation(customFieldID string, mapping map[string]string) (err error) {
+
+	if len(customFieldID) == 0 {
+		return fmt.Errorf("error, please provide a valid customFieldID value")
+	}
+
+	var operations []map[string]interface{}
+	for value, operation := range mapping {
+
+		var operationNode = map[string]interface{}{}
+		operationNode[operation] = value
+
+		operations = append(operations, operationNode)
+	}
+
+	var fieldNode = map[string]interface{}{}
+	fieldNode[customFieldID] = operations
+
+	var updateNode = map[string]interface{}{}
+	updateNode["update"] = fieldNode
+
+	u.Fields = append(u.Fields, updateNode)
+	return
+}
+
+func (u *UpdateOperations) AddStringOperation(customFieldID, operation, value string) (err error) {
+
+	if len(customFieldID) == 0 {
+		return fmt.Errorf("error, please provide a valid customFieldID value")
+	}
+
+	if len(operation) == 0 {
+		return fmt.Errorf("error, please provide a valid operation value")
+	}
+
+	if len(value) == 0 {
+		return fmt.Errorf("error, please provide a valid value value")
+	}
+
+	var operations []map[string]interface{}
+
+	var operationNode = map[string]interface{}{}
+	operationNode[operation] = value
+
+	operations = append(operations, operationNode)
+
+	var fieldNode = map[string]interface{}{}
+	fieldNode[customFieldID] = operations
+
+	var updateNode = map[string]interface{}{}
+	updateNode["update"] = fieldNode
+
+	u.Fields = append(u.Fields, updateNode)
+
+	return
+}
+
 // Edits an issue.
 // Docs: https://docs.go-atlassian.io/jira-software-cloud/issues#edit-issue
-func (i *IssueService) Update(ctx context.Context, issueKeyOrID string, notify bool, payload *IssueScheme, customFields *CustomFields) (response *Response, err error) {
+func (i *IssueService) Update(ctx context.Context, issueKeyOrID string, notify bool, payload *IssueScheme, customFields *CustomFields, operations *UpdateOperations) (response *Response, err error) {
 
 	if len(issueKeyOrID) == 0 {
 		return nil, fmt.Errorf("error, please provide a valid issueKeyOrID value")
@@ -639,14 +720,42 @@ func (i *IssueService) Update(ctx context.Context, issueKeyOrID string, notify b
 		endpoint = fmt.Sprintf("rest/api/3/issue/%v", issueKeyOrID)
 	}
 
-	payloadWithCustomFields, err := payload.Merge(customFields)
-	if err != nil {
-		return nil, err
+	//Check if the method contais custom fields
+	var payloadAsMap map[string]interface{}
+
+	if customFields != nil {
+
+		payloadWithCustomFields, err := payload.MergeCustomFields(customFields)
+		if err != nil {
+			return nil, err
+		}
+
+		payloadAsMap = payloadWithCustomFields
+
 	}
 
-	request, err := i.client.newRequest(ctx, http.MethodPut, endpoint, payloadWithCustomFields)
-	if err != nil {
-		return nil, err
+	if operations != nil {
+
+		payloadWithOperations, err := payload.MergeOperations(operations)
+		if err != nil {
+			return nil, err
+		}
+
+		payloadAsMap = payloadWithOperations
+	}
+
+	var request *http.Request
+
+	if payloadAsMap != nil {
+		request, err = i.client.newRequest(ctx, http.MethodPut, endpoint, payloadAsMap)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		request, err = i.client.newRequest(ctx, http.MethodPut, endpoint, payload)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	request.Header.Set("Accept", "application/json")


Test patch:
diff --git a/jira/auth_test.go b/jira/auth_test.go
new file mode 100644
index 0000000..780a7d3
--- /dev/null
+++ b/jira/auth_test.go
@@ -0,0 +1,92 @@
+package jira
+
+import "testing"
+
+func TestAuthenticationService_SetBasicAuth(t *testing.T) {
+
+	mockedClient, err := startMockClient("")
+	if err != nil {
+		t.Log(err)
+	}
+
+	type fields struct {
+		client      *Client
+		token, mail string
+		agent       string
+	}
+	type args struct {
+		mail, token string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		args   args
+	}{
+		{
+			name: "SetAccessTokenWhenTheParamsAreCorrect",
+			fields: fields{
+				client: mockedClient,
+			},
+			args: args{
+				token: "$TOKEN_ID",
+				mail:  "$MAIL_ID",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &AuthenticationService{
+				client: tt.fields.client,
+				mail:   tt.fields.mail,
+				token:  tt.fields.token,
+				agent:  tt.fields.agent,
+			}
+
+			a.SetBasicAuth(tt.args.mail, tt.args.token)
+		})
+	}
+}
+
+func TestAuthenticationService_SetUserAgent(t *testing.T) {
+
+	mockedClient, err := startMockClient("")
+	if err != nil {
+		t.Log(err)
+	}
+
+	type fields struct {
+		client *Client
+		agent  string
+	}
+	type args struct {
+		userAgent string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		args   args
+	}{
+		{
+			name: "SetUserAgentWhenTheParamsAreCorrect",
+			fields: fields{
+				client: mockedClient,
+			},
+			args: args{
+				userAgent: "$USER_AGENT_SAMPLE",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &AuthenticationService{
+				client: tt.fields.client,
+				agent:  tt.fields.agent,
+			}
+
+			a.SetUserAgent(tt.args.userAgent)
+		})
+	}
+
+}
diff --git a/jira/issue_test.go b/jira/issue_test.go
index 43cd31c..2eabed9 100644
--- a/jira/issue_test.go
+++ b/jira/issue_test.go
@@ -726,7 +726,7 @@ func TestCustomFields_Users(t *testing.T) {
 
 }
 
-func TestIssueScheme_Merge(t *testing.T) {
+func TestIssueScheme_MergeCustomFields(t *testing.T) {
 
 	var customFieldMockedWithFields = CustomFields{}
 
@@ -741,6 +741,10 @@ func TestIssueScheme_Merge(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	var customFieldMockedWithOutFields = CustomFields{
+		nil,
+	}
+
 	testCases := []struct {
 		name    string
 		fields  *CustomFields
@@ -751,6 +755,13 @@ func TestIssueScheme_Merge(t *testing.T) {
 			fields:  &customFieldMockedWithFields,
 			wantErr: false,
 		},
+
+		{
+			name:    "MergeCustomFieldsWhenTheCustomFieldsAreEmpty",
+			fields:  &customFieldMockedWithOutFields,
+			wantErr: false,
+		},
+
 		{
 			name:    "MergeCustomFieldsWhenTheCustomFieldsIsNil",
 			fields:  nil,
@@ -758,6 +769,76 @@ func TestIssueScheme_Merge(t *testing.T) {
 		},
 	}
 
+	for _, testCase := range testCases {
+
+		t.Run(testCase.name, func(t *testing.T) {
+
+			/*
+				issueScheme := &IssueScheme{
+					Fields: &IssueFieldsScheme{
+						Summary:   "New summary test",
+						Project:   &ProjectScheme{ID: "10000"},
+						IssueType: &IssueTypeScheme{Name: "Story"},
+					},
+				}
+
+			*/
+
+			issueScheme := &IssueScheme{}
+
+			issueSchemeWithCustomFields, err := issueScheme.MergeCustomFields(testCase.fields)
+
+			if testCase.wantErr {
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+				assert.Error(t, err)
+			}
+
+			empJSON, err := json.MarshalIndent(issueSchemeWithCustomFields, "", "  ")
+			if err != nil {
+				log.Fatalf(err.Error())
+			}
+
+			t.Log(string(empJSON))
+		})
+
+	}
+
+}
+
+func TestIssueScheme_MergeOperations(t *testing.T) {
+
+	var operations = &UpdateOperations{}
+
+	err := operations.AddArrayOperation("labels", map[string]string{
+		"triaged":   "remove",
+		"triaged-2": "remove",
+		"triaged-1": "remove",
+		"blocker":   "remove",
+	})
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	testCases := []struct {
+		name       string
+		operations *UpdateOperations
+		wantErr    bool
+	}{
+		{
+			name:       "MergeOperationsWhenTheOperationsAreCorrect",
+			operations: operations,
+			wantErr:    false,
+		},
+		{
+			name:       "MergeOperationsWhenTheOperationsAreNil",
+			operations: nil,
+			wantErr:    true,
+		},
+	}
+
 	for _, testCase := range testCases {
 
 		t.Run(testCase.name, func(t *testing.T) {
@@ -770,7 +851,7 @@ func TestIssueScheme_Merge(t *testing.T) {
 				},
 			}
 
-			issueSchemeWithCustomFields, err := issueScheme.Merge(testCase.fields)
+			issueSchemeWithOperations, err := issueScheme.MergeOperations(testCase.operations)
 
 			if testCase.wantErr {
 				if err != nil {
@@ -779,7 +860,7 @@ func TestIssueScheme_Merge(t *testing.T) {
 				assert.Error(t, err)
 			}
 
-			empJSON, err := json.MarshalIndent(issueSchemeWithCustomFields, "", "  ")
+			empJSON, err := json.MarshalIndent(issueSchemeWithOperations, "", "  ")
 			if err != nil {
 				log.Fatalf(err.Error())
 			}
@@ -2369,12 +2450,26 @@ func TestIssueService_Update(t *testing.T) {
 
 	var customFieldMockedWithOutFields = CustomFields{}
 
+	var operationMocked = UpdateOperations{}
+
+	err = operationMocked.AddArrayOperation("labels", map[string]string{
+		"triaged":   "remove",
+		"triaged-2": "remove",
+		"triaged-1": "remove",
+		"blocker":   "remove",
+	})
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	testCases := []struct {
 		name               string
 		issueKeyOrID       string
 		notify             bool
 		payload            *IssueScheme
 		customFields       *CustomFields
+		operations         *UpdateOperations
 		mockFile           string
 		wantHTTPMethod     string
 		endpoint           string
@@ -2391,6 +2486,25 @@ func TestIssueService_Update(t *testing.T) {
 					Summary: "New summary test",
 				},
 			},
+			operations:         nil,
+			customFields:       &customFieldMockedWithFields,
+			wantHTTPMethod:     http.MethodPut,
+			endpoint:           "/rest/api/3/issue/DUMMY-3?notifyUsers=false",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            false,
+		},
+
+		{
+			name:         "UpdateIssueWhenTheOperationsAreProvided",
+			issueKeyOrID: "DUMMY-3",
+			notify:       false,
+			payload: &IssueScheme{
+				Fields: &IssueFieldsScheme{
+					Summary: "New summary test",
+				},
+			},
+			operations:         &operationMocked,
 			customFields:       &customFieldMockedWithFields,
 			wantHTTPMethod:     http.MethodPut,
 			endpoint:           "/rest/api/3/issue/DUMMY-3?notifyUsers=false",
@@ -2399,6 +2513,42 @@ func TestIssueService_Update(t *testing.T) {
 			wantErr:            false,
 		},
 
+		{
+			name:         "UpdateIssueWhenTheOperationsAndCustomFieldsAreNotSet",
+			issueKeyOrID: "DUMMY-3",
+			notify:       false,
+			payload: &IssueScheme{
+				Fields: &IssueFieldsScheme{
+					Summary: "New summary test",
+				},
+			},
+			operations:         nil,
+			customFields:       nil,
+			wantHTTPMethod:     http.MethodPut,
+			endpoint:           "/rest/api/3/issue/DUMMY-3?notifyUsers=false",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            false,
+		},
+
+		{
+			name:         "UpdateIssueWhenTheOperationsAndCustomFieldsAreNotSetAndContextIsNil",
+			issueKeyOrID: "DUMMY-3",
+			notify:       false,
+			payload: &IssueScheme{
+				Fields: &IssueFieldsScheme{
+					Summary: "New summary test",
+				},
+			},
+			operations:         nil,
+			customFields:       nil,
+			wantHTTPMethod:     http.MethodPut,
+			endpoint:           "/rest/api/3/issue/DUMMY-3?notifyUsers=false",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            true,
+		},
+
 		{
 			name:         "UpdateIssueWhenTheNotifyParamIsTrue",
 			issueKeyOrID: "DUMMY-3",
@@ -2447,7 +2597,7 @@ func TestIssueService_Update(t *testing.T) {
 			endpoint:           "/rest/api/3/issue/DUMMY-3?notifyUsers=false",
 			context:            context.Background(),
 			wantHTTPCodeReturn: http.StatusNoContent,
-			wantErr:            true,
+			wantErr:            false,
 		},
 
 		{
@@ -2578,6 +2728,7 @@ func TestIssueService_Update(t *testing.T) {
 				testCase.notify,
 				testCase.payload,
 				testCase.customFields,
+				nil,
 			)
 
 			if testCase.wantErr {
@@ -2614,3 +2765,175 @@ func TestIssueService_Update(t *testing.T) {
 	}
 
 }
+
+func TestUpdateOperations_AddArrayOperation(t *testing.T) {
+
+	testCases := []struct {
+		name          string
+		customFieldID string
+		mapping       map[string]string
+		wantErr       bool
+	}{
+		{
+			name:          "AddArrayOperationWhenTheParametersAreSet",
+			customFieldID: "customfield_000",
+			mapping: map[string]string{
+				"triaged":   "remove",
+				"triaged-2": "remove",
+				"triaged-1": "remove",
+				"blocker":   "remove",
+			},
+			wantErr: false,
+		},
+
+		{
+			name:          "AddArrayOperationWhenTheCustomFieldIDIsNotSet",
+			customFieldID: "",
+			mapping: map[string]string{
+				"triaged":   "remove",
+				"triaged-2": "remove",
+				"triaged-1": "remove",
+				"blocker":   "remove",
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+
+		t.Run(testCase.name, func(t *testing.T) {
+
+			var operations = UpdateOperations{}
+			err := operations.AddArrayOperation(testCase.customFieldID, testCase.mapping)
+
+			if testCase.wantErr {
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+				assert.Error(t, err)
+			}
+
+		})
+
+	}
+
+}
+
+func TestUpdateOperations_AddStringOperation(t *testing.T) {
+
+	testCases := []struct {
+		name                            string
+		customFieldID, operation, value string
+		wantErr                         bool
+	}{
+		{
+			name:          "AddStringOperationWhenTheParametersAreSet",
+			customFieldID: "summary",
+			operation:     "set",
+			value:         "new summary using operation",
+			wantErr:       false,
+		},
+
+		{
+			name:          "AddStringOperationWhenTheCustomFieldIDIsNotSet",
+			customFieldID: "",
+			operation:     "set",
+			value:         "new summary using operation",
+			wantErr:       true,
+		},
+
+		{
+			name:          "AddStringOperationWhenTheOperationIsNotSet",
+			customFieldID: "summary",
+			operation:     "",
+			value:         "new summary using operation",
+			wantErr:       true,
+		},
+
+		{
+			name:          "AddStringOperationWhenTheValueIsNotSet",
+			customFieldID: "summary",
+			operation:     "set",
+			value:         "",
+			wantErr:       true,
+		},
+	}
+
+	for _, testCase := range testCases {
+
+		t.Run(testCase.name, func(t *testing.T) {
+
+			var operations = UpdateOperations{}
+			err := operations.AddStringOperation(testCase.customFieldID, testCase.operation, testCase.value)
+
+			if testCase.wantErr {
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+				assert.Error(t, err)
+			}
+
+		})
+
+	}
+
+}
+
+func TestIssueScheme_ToMap(t *testing.T) {
+
+	testCases := []struct {
+		name    string
+		issue   *IssueScheme
+		wantErr bool
+	}{
+		{
+			name: "ConvertIssueStructToMapWhenTheParametersAreCorrect",
+			issue: &IssueScheme{
+				Fields: &IssueFieldsScheme{
+					Summary:   "New summary test",
+					Project:   &ProjectScheme{ID: "10000"},
+					IssueType: &IssueTypeScheme{Name: "Story"},
+				},
+			},
+			wantErr: false,
+		},
+
+		{
+			name:    "ConvertIssueStructToMapWhenTheIssueStructIsNil",
+			issue:   nil,
+			wantErr: false,
+		},
+
+		{
+			name: "ConvertIssueStructToMapWhenTheParametersAreCorrect",
+			issue: &IssueScheme{
+				Fields: &IssueFieldsScheme{
+					Summary:   "New summary test",
+					Project:   &ProjectScheme{ID: "10000"},
+					IssueType: &IssueTypeScheme{Name: "Story"},
+				},
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, testCase := range testCases {
+
+		t.Run(testCase.name, func(t *testing.T) {
+
+			result, err := testCase.issue.ToMap()
+
+			if testCase.wantErr {
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+				assert.Error(t, err)
+			} else {
+				t.Log(result)
+			}
+
+		})
+
+	}
+
+}