Repository: ctreminiom/go-atlassian
Pull request #80 (2021-11-15 19:15:37)

:rocket: Added the Epic.Move() method


Patch:
diff --git a/jira/agile/epic.go b/jira/agile/epic.go
index eae0470..a13e9bb 100644
--- a/jira/agile/epic.go
+++ b/jira/agile/epic.go
@@ -94,3 +94,41 @@ func (e *EpicService) Issues(ctx context.Context, epicIDOrKey string, startAt, m
 
 	return
 }
+
+// Move moves issues to an epic, for a given epic id.
+// Issues can be only in a single epic at the same time.
+// That means that already assigned issues to an epic, will not be assigned to the previous epic anymore.
+// The user needs to have the edit issue permission for all issue they want to move and to the epic.
+// The maximum number of issues that can be moved in one operation is 50.
+// Docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-epic/#api-agile-1-0-epic-epicidorkey-issue-post
+func (e *EpicService) Move(ctx context.Context, epicIDOrKey string, issues []string) (response *ResponseScheme, err error) {
+
+	if len(epicIDOrKey) == 0 {
+		return nil, model.ErrNoEpicIDError
+	}
+
+	payload := struct {
+		Issues []string `json:"issues,omitempty"`
+	}{
+		Issues: issues,
+	}
+
+	var (
+		payloadAsReader, _ = transformStructToReader(&payload)
+		endpoint           = fmt.Sprintf("rest/agile/1.0/epic/%v/issue", epicIDOrKey)
+	)
+
+	request, err := e.client.newRequest(ctx, http.MethodPost, endpoint, payloadAsReader)
+	if err != nil {
+		return
+	}
+
+	request.Header.Set("Content-Type", "application/json")
+
+	response, err = e.client.Call(request, nil)
+	if err != nil {
+		return response, err
+	}
+
+	return
+}


Test patch:
diff --git a/jira/agile/epic_test.go b/jira/agile/epic_test.go
index 3955c3f..0c99398 100644
--- a/jira/agile/epic_test.go
+++ b/jira/agile/epic_test.go
@@ -394,3 +394,128 @@ func TestEpicService_Issues(t *testing.T) {
 	}
 
 }
+
+func TestEpicService_Move(t *testing.T) {
+
+	testCases := []struct {
+		name               string
+		epicIDOrKey        string
+		issues             []string
+		mockFile           string
+		wantHTTPMethod     string
+		endpoint           string
+		context            context.Context
+		wantHTTPCodeReturn int
+		wantErr            bool
+	}{
+		{
+			name:               "MoveIssuesToEpicWhenTheParametersAreCorrect",
+			epicIDOrKey:        "EPIC-1",
+			issues:             []string{"STORY-1"},
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/rest/agile/1.0/epic/EPIC-1/issue",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            false,
+		},
+		{
+			name:               "MoveIssuesToEpicWhenTheEpicKeyIsNotProvided",
+			epicIDOrKey:        "",
+			issues:             []string{"STORY-1"},
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/rest/agile/1.0/epic/EPIC-1/issue",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            true,
+		},
+
+		{
+			name:               "MoveIssuesToEpicWhenTheContextIsNotProvided",
+			epicIDOrKey:        "EPIC-1",
+			issues:             []string{"STORY-1"},
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/rest/agile/1.0/epic/EPIC-1/issue",
+			context:            nil,
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            true,
+		},
+
+		{
+			name:               "MoveIssuesToEpicWhenTheRequestMethodIsIncorrect",
+			epicIDOrKey:        "EPIC-1",
+			issues:             []string{"STORY-1"},
+			wantHTTPMethod:     http.MethodPut,
+			endpoint:           "/rest/agile/1.0/epic/EPIC-1/issue",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusNoContent,
+			wantErr:            true,
+		},
+
+		{
+			name:               "MoveIssuesToEpicWhenTheResponseStatusCodeIsIncorrect",
+			epicIDOrKey:        "EPIC-1",
+			issues:             []string{"STORY-1"},
+			wantHTTPMethod:     http.MethodPost,
+			endpoint:           "/rest/agile/1.0/epic/EPIC-1/issue",
+			context:            context.Background(),
+			wantHTTPCodeReturn: http.StatusBadRequest,
+			wantErr:            true,
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			//Init a new HTTP mock server
+			mockOptions := mockServerOptions{
+				Endpoint:           testCase.endpoint,
+				MockFilePath:       testCase.mockFile,
+				MethodAccepted:     testCase.wantHTTPMethod,
+				ResponseCodeWanted: testCase.wantHTTPCodeReturn,
+			}
+
+			mockServer, err := startMockServer(&mockOptions)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			defer mockServer.Close()
+
+			//Init the library instance
+			mockClient, err := startMockClient(mockServer.URL)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			service := &EpicService{client: mockClient}
+			gotResponse, err := service.Move(testCase.context, testCase.epicIDOrKey, testCase.issues)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.Error(t, err)
+
+				if gotResponse != nil {
+					t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				}
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+
+				apiEndpoint, err := url.Parse(gotResponse.Endpoint)
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				t.Logf("HTTP Endpoint Wanted: %v, HTTP Endpoint Returned: %v", testCase.endpoint, apiEndpoint.Path)
+				assert.Equal(t, testCase.endpoint, apiEndpoint.Path)
+
+				t.Logf("HTTP Code Wanted: %v, HTTP Code Returned: %v", testCase.wantHTTPCodeReturn, gotResponse.Code)
+				assert.Equal(t, gotResponse.Code, testCase.wantHTTPCodeReturn)
+			}
+		})
+	}
+}