Repository: ctreminiom/go-atlassian
Pull request #203 (2023-05-25 03:27:46)

:sparkles: Added the Jira Roadmaps Team service
1. Added the possibility to interacts with the Jira Roadmaps Teams and gets and creates teams on the Jira instance.


Patch:
diff --git a/jira/agile/internal/board_backlog_impl.go b/jira/agile/internal/board_backlog_impl.go
index d80806f..a53a436 100644
--- a/jira/agile/internal/board_backlog_impl.go
+++ b/jira/agile/internal/board_backlog_impl.go
@@ -43,7 +43,7 @@ func (b *BoardBacklogService) Move(ctx context.Context, issues []string) (*model
 //
 // POST /rest/agile/1.0/backlog/{boardId}/issue
 //
-// https://docs.go-atlassian.io/jira-agile/boards/backlog#move-issues-to-backlog
+// https://docs.go-atlassian.io/jira-agile/boards/backlog#move-issues-to-a-board-backlog
 func (b *BoardBacklogService) MoveTo(ctx context.Context, boardID int, payload *model.BoardBacklogPayloadScheme) (*model.ResponseScheme, error) {
 	return b.internalClient.MoveTo(ctx, boardID, payload)
 }
diff --git a/jira/internal/team_impl.go b/jira/internal/team_impl.go
new file mode 100644
index 0000000..86ce03b
--- /dev/null
+++ b/jira/internal/team_impl.go
@@ -0,0 +1,102 @@
+package internal
+
+import (
+	"context"
+	model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/service"
+	"github.com/ctreminiom/go-atlassian/service/jira"
+	"net/http"
+)
+
+func NewTeamService(client service.Client) *TeamService {
+
+	return &TeamService{
+		internalClient: &internalTeamServiceImpl{c: client},
+	}
+}
+
+type TeamService struct {
+	internalClient jira.TeamConnector
+}
+
+// Gets gets the Teams information from the Jira Advanced Roadmaps application.
+//
+// Teams in Advanced Roadmaps are different from the teams found in the rest of Jira Software Cloud.
+//
+// In Advanced Roadmaps, they act as a label applied to issues that designates which team will eventually.
+//
+// pick up the work on your timeline. By adding the Team field to your Jira issues.
+//
+// you can save this value back to your Jira issues, which makes sprint planning easier.
+//
+// POST /rest/teams/1.0/teams/find
+//
+// https://docs.go-atlassian.io/jira-software-cloud/teams#get-teams
+func (t *TeamService) Gets(ctx context.Context, maxResults int) (*model.JiraTeamPageScheme, *model.ResponseScheme, error) {
+	return t.internalClient.Gets(ctx, maxResults)
+}
+
+// Create creates a team on the Advanced Roadmaps
+//
+// POST /rest/teams/1.0/teams/create
+//
+// https://docs.go-atlassian.io/jira-software-cloud/teams#create-team
+func (t *TeamService) Create(ctx context.Context, payload *model.JiraTeamCreatePayloadScheme) (*model.JiraTeamCreateResponseScheme, *model.ResponseScheme, error) {
+	return t.internalClient.Create(ctx, payload)
+}
+
+type internalTeamServiceImpl struct {
+	c service.Client
+}
+
+func (i *internalTeamServiceImpl) Gets(ctx context.Context, maxResults int) (*model.JiraTeamPageScheme, *model.ResponseScheme, error) {
+
+	payload := struct {
+		MaxResults int `json:"maxResults"`
+	}{
+		MaxResults: maxResults,
+	}
+
+	reader, err := i.c.TransformStructToReader(&payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	endpoint := "rest/teams/1.0/teams/find"
+
+	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, reader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	teams := new(model.JiraTeamPageScheme)
+	response, err := i.c.Call(request, teams)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return teams, response, nil
+}
+
+func (i *internalTeamServiceImpl) Create(ctx context.Context, payload *model.JiraTeamCreatePayloadScheme) (*model.JiraTeamCreateResponseScheme, *model.ResponseScheme, error) {
+
+	reader, err := i.c.TransformStructToReader(payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	endpoint := "rest/teams/1.0/teams/create"
+
+	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, reader)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	team := new(model.JiraTeamCreateResponseScheme)
+	response, err := i.c.Call(request, team)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return team, response, nil
+}
diff --git a/jira/v2/api_client_impl.go b/jira/v2/api_client_impl.go
index 5c99f52..68c4df7 100644
--- a/jira/v2/api_client_impl.go
+++ b/jira/v2/api_client_impl.go
@@ -370,6 +370,7 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
 	client.Workflow = workflow
 	client.JQL = jql
 	client.NotificationScheme = projectNotificationScheme
+	client.Team = internal.NewTeamService(client)
 
 	return client, nil
 }
@@ -395,6 +396,7 @@ type Client struct {
 	Workflow           *internal.WorkflowService
 	JQL                *internal.JQLService
 	NotificationScheme *internal.NotificationSchemeService
+	Team               *internal.TeamService
 }
 
 func (c *Client) NewFormRequest(ctx context.Context, method, apiEndpoint, contentType string, payload io.Reader) (*http.Request, error) {
diff --git a/jira/v3/api_client_impl.go b/jira/v3/api_client_impl.go
index 1df1899..b8555c8 100644
--- a/jira/v3/api_client_impl.go
+++ b/jira/v3/api_client_impl.go
@@ -370,6 +370,7 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
 	client.Workflow = workflow
 	client.JQL = jql
 	client.NotificationScheme = projectNotificationScheme
+	client.Team = internal.NewTeamService(client)
 
 	return client, nil
 }
@@ -395,6 +396,7 @@ type Client struct {
 	Workflow           *internal.WorkflowService
 	JQL                *internal.JQLService
 	NotificationScheme *internal.NotificationSchemeService
+	Team               *internal.TeamService
 }
 
 func (c *Client) NewFormRequest(ctx context.Context, method, apiEndpoint, contentType string, payload io.Reader) (*http.Request, error) {
diff --git a/pkg/infra/models/jira_teams.go b/pkg/infra/models/jira_teams.go
new file mode 100644
index 0000000..86543a7
--- /dev/null
+++ b/pkg/infra/models/jira_teams.go
@@ -0,0 +1,44 @@
+package models
+
+type JiraTeamPageScheme struct {
+	MoreResultsAvailable bool                    `json:"moreResultsAvailable,omitempty"`
+	Teams                []*JiraTeamScheme       `json:"teams,omitempty"`
+	Persons              []*JiraTeamPersonScheme `json:"persons,omitempty"`
+	ZeroMemberTeamsCount int                     `json:"zeroMemberTeamsCount,omitempty"`
+}
+
+type JiraTeamScheme struct {
+	Id         int                       `json:"id,omitempty"`
+	ExternalId string                    `json:"externalId,omitempty"`
+	Title      string                    `json:"title,omitempty"`
+	Shareable  bool                      `json:"shareable,omitempty"`
+	Resources  []*JiraTeamResourceScheme `json:"resources,omitempty"`
+}
+
+type JiraTeamResourceScheme struct {
+	Id       int `json:"id,omitempty"`
+	PersonId int `json:"personId,omitempty"`
+}
+
+type JiraTeamPersonScheme struct {
+	PersonId int                 `json:"personId,omitempty"`
+	JiraUser *JiraTeamUserScheme `json:"jiraUser,omitempty"`
+}
+
+type JiraTeamUserScheme struct {
+	Title     string `json:"title,omitempty"`
+	Email     string `json:"email,omitempty"`
+	AvatarUrl string `json:"avatarUrl,omitempty"`
+}
+
+type JiraTeamCreatePayloadScheme struct {
+	Title     string                    `json:"title,omitempty"`
+	Shareable bool                      `json:"shareable,omitempty"`
+	Resources []*JiraTeamResourceScheme `json:"resources,omitempty"`
+}
+
+type JiraTeamCreateResponseScheme struct {
+	Id      int                     `json:"id,omitempty"`
+	Team    *JiraTeamScheme         `json:"team,omitempty"`
+	Persons []*JiraTeamPersonScheme `json:"persons,omitempty"`
+}
diff --git a/service/agile/backlog.go b/service/agile/backlog.go
index 07b1c1a..36c7af5 100644
--- a/service/agile/backlog.go
+++ b/service/agile/backlog.go
@@ -30,6 +30,6 @@ type BoardBacklogConnector interface {
 	//
 	// POST /rest/agile/1.0/backlog/{boardId}/issue
 	//
-	// https://docs.go-atlassian.io/jira-agile/boards/backlog#move-issues-to-backlog
+	// https://docs.go-atlassian.io/jira-agile/boards/backlog#move-issues-to-a-board-backlog
 	MoveTo(ctx context.Context, boardID int, payload *models.BoardBacklogPayloadScheme) (*models.ResponseScheme, error)
 }
diff --git a/service/jira/team.go b/service/jira/team.go
new file mode 100644
index 0000000..992ca18
--- /dev/null
+++ b/service/jira/team.go
@@ -0,0 +1,32 @@
+package jira
+
+import (
+	"context"
+	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
+)
+
+// TeamConnector is an interface that defines the methods available from Roadmap Teams API.
+type TeamConnector interface {
+
+	// Gets gets the Teams information from the Jira Advanced Roadmaps application.
+	//
+	// Teams in Advanced Roadmaps are different from the teams found in the rest of Jira Software Cloud.
+	//
+	// In Advanced Roadmaps, they act as a label applied to issues that designates which team will eventually.
+	//
+	// pick up the work on your timeline. By adding the Team field to your Jira issues.
+	//
+	// you can save this value back to your Jira issues, which makes sprint planning easier.
+	//
+	// POST /rest/teams/1.0/teams/find
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/teams#get-teams
+	Gets(ctx context.Context, maxResults int) (*models.JiraTeamPageScheme, *models.ResponseScheme, error)
+
+	// Create creates a team on the Advanced Roadmaps
+	//
+	// POST /rest/teams/1.0/teams/create
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/teams#create-team
+	Create(ctx context.Context, payload *models.JiraTeamCreatePayloadScheme) (*models.JiraTeamCreateResponseScheme, *models.ResponseScheme, error)
+}


Test patch:
diff --git a/jira/internal/team_impl_test.go b/jira/internal/team_impl_test.go
new file mode 100644
index 0000000..8eb8cc6
--- /dev/null
+++ b/jira/internal/team_impl_test.go
@@ -0,0 +1,327 @@
+package internal
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/service"
+	"github.com/ctreminiom/go-atlassian/service/mocks"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"testing"
+)
+
+func Test_internalTeamServiceImpl_Gets(t *testing.T) {
+
+	payloadMocked := &struct {
+		MaxResults int "json:\"maxResults\""
+	}{MaxResults: 1000}
+
+	type fields struct {
+		c service.Client
+	}
+
+	type args struct {
+		ctx        context.Context
+		maxResults int
+	}
+
+	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.TODO(),
+				maxResults: 1000,
+			},
+			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/teams/1.0/teams/find",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.JiraTeamPageScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+			},
+			wantErr: false,
+			Err:     nil,
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:        context.TODO(),
+				maxResults: 1000,
+			},
+			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/teams/1.0/teams/find",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the http call returns an error",
+			args: args{
+				ctx:        context.TODO(),
+				maxResults: 1000,
+			},
+			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/teams/1.0/teams/find",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.JiraTeamPageScheme{}).
+					Return(&model.ResponseScheme{}, errors.New("error, unable to execute the http call"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to execute the http call"),
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			teamService := NewTeamService(testCase.fields.c)
+
+			gotResult, gotResponse, err := teamService.Gets(testCase.args.ctx, testCase.args.maxResults)
+
+			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)
+				assert.NotEqual(t, gotResult, nil)
+			}
+		})
+	}
+}
+
+func Test_internalTeamServiceImpl_Create(t *testing.T) {
+
+	payloadMocked := &model.JiraTeamCreatePayloadScheme{
+		Title:     "Team Name Sample",
+		Shareable: true,
+		Resources: []*model.JiraTeamResourceScheme{
+			{
+				PersonId: 6,
+			},
+		},
+	}
+
+	type fields struct {
+		c service.Client
+	}
+
+	type args struct {
+		ctx     context.Context
+		payload *model.JiraTeamCreatePayloadScheme
+	}
+
+	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.TODO(),
+				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/teams/1.0/teams/create",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.JiraTeamCreateResponseScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+			},
+			wantErr: false,
+			Err:     nil,
+		},
+
+		{
+			name: "when the payload is not provided",
+			args: args{
+				ctx:     context.TODO(),
+				payload: nil,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewClient(t)
+
+				client.On("TransformStructToReader",
+					(*model.JiraTeamCreatePayloadScheme)(nil)).
+					Return(nil, model.ErrNilPayloadError)
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     model.ErrNilPayloadError,
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:     context.TODO(),
+				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/teams/1.0/teams/create",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the http call cannot be executed",
+			args: args{
+				ctx:     context.TODO(),
+				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/teams/1.0/teams/create",
+					bytes.NewReader([]byte{})).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.JiraTeamCreateResponseScheme{}).
+					Return(&model.ResponseScheme{}, errors.New("error, unable to execute the http transition"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to execute the http transition"),
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			teamService := NewTeamService(testCase.fields.c)
+
+			gotResult, gotResponse, err := teamService.Create(testCase.args.ctx, 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)
+				assert.NotEqual(t, gotResult, nil)
+			}
+		})
+	}
+}