Repository: ctreminiom/go-atlassian
Pull request #162 (2022-11-08 05:47:14)

:sparkles: Added the ability to move issues to a sprint ID


Patch:
diff --git a/jira/agile/internal/sprint_impl.go b/jira/agile/internal/sprint_impl.go
index 2942320..beda1b2 100644
--- a/jira/agile/internal/sprint_impl.go
+++ b/jira/agile/internal/sprint_impl.go
@@ -119,11 +119,45 @@ func (s *SprintService) Close(ctx context.Context, sprintID int) (*model.Respons
 	return s.internalClient.Close(ctx, sprintID)
 }
 
+// Move moves issues to a sprint, for a given sprint ID.
+//
+// Issues can only be moved to open or active sprints.
+//
+// The maximum number of issues that can be moved in one operation is 50.
+//
+// POST /rest/agile/1.0/sprint/{sprintId}/issue
+//
+// https://docs.go-atlassian.io/jira-agile/sprints#move-issues-to-sprint
+func (s *SprintService) Move(ctx context.Context, sprintID int, payload *model.SprintMovePayloadScheme) (*model.ResponseScheme, error) {
+	return s.internalClient.Move(ctx, sprintID, payload)
+}
+
 type internalSprintImpl struct {
 	c       service.Client
 	version string
 }
 
+func (i *internalSprintImpl) Move(ctx context.Context, sprintID int, payload *model.SprintMovePayloadScheme) (*model.ResponseScheme, error) {
+
+	if sprintID == 0 {
+		return nil, model.ErrNoSprintIDError
+	}
+
+	reader, err := i.c.TransformStructToReader(payload)
+	if err != nil {
+		return nil, err
+	}
+
+	endpoint := fmt.Sprintf("/rest/agile/%v/sprint/%v/issue", i.version, sprintID)
+
+	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, reader)
+	if err != nil {
+		return nil, err
+	}
+
+	return i.c.Call(request, nil)
+}
+
 func (i *internalSprintImpl) Get(ctx context.Context, sprintID int) (*model.SprintScheme, *model.ResponseScheme, error) {
 
 	if sprintID == 0 {
diff --git a/pkg/infra/models/agile_sprint.go b/pkg/infra/models/agile_sprint.go
index 0962d92..5e82bf2 100644
--- a/pkg/infra/models/agile_sprint.go
+++ b/pkg/infra/models/agile_sprint.go
@@ -37,3 +37,10 @@ type SprintIssueScheme struct {
 	Self   string `json:"self,omitempty"`
 	Key    string `json:"key,omitempty"`
 }
+
+type SprintMovePayloadScheme struct {
+	Issues            []string `json:"issues,omitempty"`
+	RankBeforeIssue   string   `json:"rankBeforeIssue,omitempty"`
+	RankAfterIssue    string   `json:"rankAfterIssue,omitempty"`
+	RankCustomFieldId int      `json:"rankCustomFieldId,omitempty"`
+}
diff --git a/service/agile/sprint.go b/service/agile/sprint.go
index e80d585..31bfc08 100644
--- a/service/agile/sprint.go
+++ b/service/agile/sprint.go
@@ -82,4 +82,15 @@ type SprintConnector interface {
 	//
 	// https://docs.go-atlassian.io/jira-agile/sprints#close-sprint
 	Close(ctx context.Context, sprintID int) (*models.ResponseScheme, error)
+
+	// Move moves issues to a sprint, for a given sprint ID.
+	//
+	// Issues can only be moved to open or active sprints.
+	//
+	// The maximum number of issues that can be moved in one operation is 50.
+	//
+	// POST /rest/agile/1.0/sprint/{sprintId}/issue
+	//
+	// https://docs.go-atlassian.io/jira-agile/sprints#move-issues-to-sprint
+	Move(ctx context.Context, sprintID int, payload *models.SprintMovePayloadScheme) (*models.ResponseScheme, error)
 }


Test patch:
diff --git a/jira/agile/internal/sprint_impl_test.go b/jira/agile/internal/sprint_impl_test.go
index bf48395..2450279 100644
--- a/jira/agile/internal/sprint_impl_test.go
+++ b/jira/agile/internal/sprint_impl_test.go
@@ -1353,3 +1353,165 @@ func Test_SprintService_Close(t *testing.T) {
 		})
 	}
 }
+
+func Test_SprintService_Move(t *testing.T) {
+
+	payloadMocked := &model.SprintMovePayloadScheme{
+		Issues:            []string{"DUMMY-1", "DUMMY-2"},
+		RankBeforeIssue:   "DUMMY-4",
+		RankAfterIssue:    "DUMMY-12",
+		RankCustomFieldId: 10521,
+	}
+
+	type fields struct {
+		c service.Client
+	}
+
+	type args struct {
+		ctx      context.Context
+		sprintId int
+		payload  *model.SprintMovePayloadScheme
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:      context.Background(),
+				sprintId: 1001,
+				payload:  payloadMocked,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("TransformStructToReader",
+					payloadMocked).
+					Return(bytes.NewReader([]byte{}), nil)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"/rest/agile/1.0/sprint/1001/issue",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+			},
+		},
+
+		{
+			name: "when the sprintId is not provided",
+			args: args{
+				ctx: context.Background(),
+			},
+			on: func(fields *fields) {
+				fields.c = mocks.NewClient(t)
+			},
+			Err:     model.ErrNoSprintIDError,
+			wantErr: true,
+		},
+
+		{
+			name: "when the api cannot be executed",
+			args: args{
+				ctx:      context.Background(),
+				sprintId: 1001,
+				payload:  payloadMocked,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("TransformStructToReader",
+					payloadMocked).
+					Return(bytes.NewReader([]byte{}), nil)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"/rest/agile/1.0/sprint/1001/issue",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, errors.New("error, unable to execute the http call"))
+
+				fields.c = client
+			},
+			Err:     errors.New("error, unable to execute the http call"),
+			wantErr: true,
+		},
+
+		{
+			name: "when the request cannot be created",
+			args: args{
+				ctx:      context.Background(),
+				sprintId: 1001,
+				payload:  payloadMocked,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("TransformStructToReader",
+					payloadMocked).
+					Return(bytes.NewReader([]byte{}), nil)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"/rest/agile/1.0/sprint/1001/issue",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, errors.New("unable to create the http request"))
+
+				fields.c = client
+			},
+			Err:     errors.New("unable to create the http request"),
+			wantErr: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			sprintService, err := NewSprintService(testCase.fields.c, "1.0")
+			assert.NoError(t, err)
+
+			gotResponse, err := sprintService.Move(testCase.args.ctx, testCase.args.sprintId, testCase.args.payload)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+			}
+
+		})
+	}
+}