From 7eacabb06db14a5012ea20bcd2ec941d3b31df3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C3=A1nchez=20Navarro?= Date: Tue, 28 May 2024 17:18:07 +0200 Subject: [PATCH] test: jira test cases --- internal/issue_trackers/github/github_test.go | 3 - internal/issue_trackers/issue_trackers.go | 92 ++--- internal/issue_trackers/jira/issue.go | 81 +++++ internal/issue_trackers/jira/jira.go | 207 +++++------ internal/issue_trackers/jira/jira_test.go | 328 +++++++++--------- 5 files changed, 365 insertions(+), 346 deletions(-) create mode 100644 internal/issue_trackers/jira/issue.go diff --git a/internal/issue_trackers/github/github_test.go b/internal/issue_trackers/github/github_test.go index e5211c8..fb6b34e 100644 --- a/internal/issue_trackers/github/github_test.go +++ b/internal/issue_trackers/github/github_test.go @@ -112,7 +112,6 @@ func Test_CheckConfiguration(t *testing.T) { } } -// TODO: MOVE THIS TEST WHERE IS NEEDED func TestGetIssueType(t *testing.T) { createIssue := func(labelNames ...string) domain.Issue { @@ -226,8 +225,6 @@ func TestGithub_FormatIssueId(t *testing.T) { } } -//TODO: MOVE THIS TEST WHERE IS NEDED - func TestGetIssueTypeLabel(t *testing.T) { createIssue := func(labelNames ...string) domain.Issue { diff --git a/internal/issue_trackers/issue_trackers.go b/internal/issue_trackers/issue_trackers.go index 36e8f7e..4a602db 100644 --- a/internal/issue_trackers/issue_trackers.go +++ b/internal/issue_trackers/issue_trackers.go @@ -6,18 +6,18 @@ import ( "github.com/InditexTech/gh-sherpa/internal/config" "github.com/InditexTech/gh-sherpa/internal/domain" "github.com/InditexTech/gh-sherpa/internal/issue_trackers/github" + "github.com/InditexTech/gh-sherpa/internal/issue_trackers/jira" "github.com/InditexTech/gh-sherpa/internal/logging" ) type Configuration struct { - //TODO: UNCOMMENT JIRA - // Jira jira.Configuration + Jira jira.Configuration Github github.Configuration } type Provider struct { cfg Configuration github github.Github - // jira jira.Jira + jira jira.Jira } var _ domain.IssueTrackerProvider = (*Provider)(nil) @@ -29,27 +29,25 @@ func New(cfg Configuration) (*Provider, error) { return nil, err } - //TODO: UNCOMMENT JIRA - // j, err := jira.New(cfg.Jira) - // if err != nil { - // return nil, err - // } + j, err := jira.New(cfg.Jira) + if err != nil { + return nil, err + } return &Provider{ cfg: cfg, github: *g, - // jira: *j, + jira: *j, }, nil } // NewConfiguration returns a new configuration from the given global configuration func NewFromConfiguration(globalConfig config.Configuration) (*Provider, error) { return New(Configuration{ - //TODO: UNCOMMENT JIRA - // Jira: jira.Configuration{ - // Jira: globalConfig.Jira, - // IssueTypeLabels: globalConfig.Github.IssueLabels, - // }, + Jira: jira.Configuration{ + Jira: globalConfig.Jira, + IssueTypeLabels: globalConfig.Github.IssueLabels, + }, Github: github.Configuration{ Github: globalConfig.Github, }, @@ -63,13 +61,11 @@ func (p Provider) GetIssue(identifier string) (domain.Issue, error) { return p.github.GetIssue(identifier) } - //TODO: UNCOMMENT JIRA - // if p.jira.IdentifyIssue(identifier) { - // logging.Debugf("Issue %s identified as a Jira issue", identifier) - // // return &p.jira, nil - // return p.jira.GetIssue(identifier) - - // } + if p.jira.IdentifyIssue(identifier) { + logging.Debugf("Issue %s identified as a Jira issue", identifier) + // return &p.jira, nil + return p.jira.GetIssue(identifier) + } return nil, fmt.Errorf("could not identify issue %s", identifier) } @@ -80,56 +76,10 @@ func (p Provider) ParseIssueId(identifier string) (issueId string) { return p.github.ParseRawIssueId(identifier) } - //TODO: UNCOMMENT JIRA - - // if p.jira.IdentifyIssue(identifier) { - // logging.Debugf("Issue %s identified as a Jira issue", identifier) - // return p.jira.ParseRawIssueId(identifier) - // } + if p.jira.IdentifyIssue(identifier) { + logging.Debugf("Issue %s identified as a Jira issue", identifier) + return p.jira.ParseRawIssueId(identifier) + } return } - -//TODO: MOVE THIS TO THE CORRESPONDING PLACE -// GetIssueTitle returns the issue title -// func (p *Provider) GetIssueTitle(issue domain.Issue) (title string, err error) { -// switch issue.IssueTracker { -// case domain.IssueTrackerTypeGithub: -// title = issue.Title -// case domain.IssueTrackerTypeJira: -// title = fmt.Sprintf("[%s] %s", issue.ID, issue.Title) -// default: -// err = fmt.Errorf("issue tracker %s is not supported", issue.IssueTracker) -// } - -// return -// } - -//TODO: MOVE THIS TO THE CORRESPONDING PLACE -// GetIssueBody returns the issue body -// func (p *Provider) GetIssueBody(issue domain.Issue, noCloseIssue bool) (body string, err error) { -// switch issue.IssueTracker { -// case domain.IssueTrackerTypeGithub: -// keyword := "Closes" -// if noCloseIssue { -// keyword = "Related to" -// } - -// body = fmt.Sprintf("%s #%s", keyword, issue.ID) - -// case domain.IssueTrackerTypeJira: -// jiraHost := p.cfg.Jira.Auth.Host -// jiraUrlBrowseIssue := jiraHost -// if !strings.HasSuffix(jiraHost, "/") { -// jiraUrlBrowseIssue += "/" -// } - -// jiraUrlBrowseIssue += "browse/" + issue.ID - -// body = fmt.Sprintf("Relates to [%s](%s)", issue.ID, jiraUrlBrowseIssue) -// default: -// err = fmt.Errorf("issue tracker %s is not supported", issue.IssueTracker) -// } - -// return -// } diff --git a/internal/issue_trackers/jira/issue.go b/internal/issue_trackers/jira/issue.go new file mode 100644 index 0000000..90ba8e0 --- /dev/null +++ b/internal/issue_trackers/jira/issue.go @@ -0,0 +1,81 @@ +package jira + +import ( + "github.com/InditexTech/gh-sherpa/internal/config" + "github.com/InditexTech/gh-sherpa/internal/domain" + "github.com/InditexTech/gh-sherpa/internal/domain/issue_types" +) + +type Issue struct { + id string + title string + body string + url string + issueType IssueType + issueTypesConfig config.JiraIssueTypes + labelsConfig map[issue_types.IssueType][]string +} + +var _ domain.Issue = (*Issue)(nil) + +type IssueType struct { + Id string + Name string + Description string +} + +// Body implements domain.Issue. +func (i Issue) Body() string { + panic("unimplemented") +} + +// FormatID implements domain.Issue. +func (i Issue) FormatID() string { + return i.id +} + +// ID implements domain.Issue. +func (i Issue) ID() string { + return i.id +} + +// Title implements domain.Issue. +func (i Issue) Title() string { + return i.title +} + +// TrackerType implements domain.Issue. +func (i Issue) TrackerType() domain.IssueTrackerType { + return domain.IssueTrackerTypeJira +} + +// Type implements domain.Issue. +func (i Issue) Type() issue_types.IssueType { + for issueType, ids := range i.issueTypesConfig { + for _, id := range ids { + if id == i.issueType.Id { + return issueType + } + } + } + + return issue_types.Unknown +} + +// TypeLabel implements domain.Issue. +func (i Issue) TypeLabel() string { + issueType := i.Type() + + for mappedIssueType, labels := range i.labelsConfig { + if issueType == mappedIssueType && len(labels) > 0 { + return labels[0] + } + } + + return "" +} + +// URL implements domain.Issue. +func (i Issue) URL() string { + return i.url +} diff --git a/internal/issue_trackers/jira/jira.go b/internal/issue_trackers/jira/jira.go index 2656340..b6a3c80 100644 --- a/internal/issue_trackers/jira/jira.go +++ b/internal/issue_trackers/jira/jira.go @@ -1,78 +1,78 @@ package jira -// import ( -// "crypto/tls" -// "errors" -// "fmt" -// "net/http" -// "regexp" - -// "github.com/InditexTech/gh-sherpa/internal/config" -// "github.com/InditexTech/gh-sherpa/internal/domain" -// "github.com/InditexTech/gh-sherpa/internal/domain/issue_types" -// gojira "github.com/andygrunwald/go-jira" -// ) - -// var issuePattern = regexp.MustCompile(`^(?P\w+)-(?P\d+)$`) - -// type Jira struct { -// cfg Configuration -// client JiraClient -// } +import ( + "crypto/tls" + "errors" + "fmt" + "net/http" + "regexp" -// type JiraClient struct { -// gojira.Client -// } + "github.com/InditexTech/gh-sherpa/internal/config" + "github.com/InditexTech/gh-sherpa/internal/domain" + "github.com/InditexTech/gh-sherpa/internal/domain/issue_types" + gojira "github.com/andygrunwald/go-jira" +) -// type Configuration struct { -// config.Jira -// IssueTypeLabels map[issue_types.IssueType][]string -// } +var issuePattern = regexp.MustCompile(`^(?P\w+)-(?P\d+)$`) -// // New returns a new Jira issue tracker with the given configuration -// func New(cfg Configuration) (jira *Jira, err error) { +type Jira struct { + cfg Configuration + client JiraClient +} -// jira = &Jira{cfg: cfg} +type JiraClient struct { + gojira.Client +} -// gojiraClient, err := createBearerClient(cfg.Auth.Token, cfg.Auth.Host, cfg.Auth.SkipTLSVerify) -// if err != nil { -// return nil, fmt.Errorf("could not create a Jira client: %s", err) -// } +type Configuration struct { + config.Jira + IssueTypeLabels map[issue_types.IssueType][]string +} -// jira.client = *gojiraClient +// New returns a new Jira issue tracker with the given configuration +func New(cfg Configuration) (jira *Jira, err error) { -// return -// } + jira = &Jira{cfg: cfg} + + gojiraClient, err := createBearerClient(cfg.Auth.Token, cfg.Auth.Host, cfg.Auth.SkipTLSVerify) + if err != nil { + return nil, fmt.Errorf("could not create a Jira client: %s", err) + } + + jira.client = *gojiraClient + + return +} // var _ domain.IssueTracker = (*Jira)(nil) -// func (j *Jira) GetIssue(identifier string) (issue domain.Issue, err error) { -// issueGot, res, err := j.client.Issue.Get(identifier, &gojira.GetQueryOptions{Fields: "issuetype,summary"}) +func (j *Jira) GetIssue(identifier string) (issue domain.Issue, err error) { + issueGot, res, err := j.client.Issue.Get(identifier, &gojira.GetQueryOptions{Fields: "issuetype,summary"}) -// if err != nil { -// if res == nil { -// err = fmt.Errorf("could not get response from host '%s'. Check your jira configuration", j.cfg.Auth.Host) -// return -// } + if err != nil { + if res == nil { + err = fmt.Errorf("could not get response from host '%s'. Check your jira configuration", j.cfg.Auth.Host) + return + } -// switch res.StatusCode { -// case http.StatusUnauthorized: -// err = errors.New("your PAT is invalid or revoked") -// case http.StatusForbidden: -// err = errors.New("you do not have permission to get this issue") -// case http.StatusNotFound: -// err = errors.New("the issue was not found") -// default: -// err = fmt.Errorf("could not get issue: %s", err) -// } + switch res.StatusCode { + case http.StatusUnauthorized: + err = errors.New("your PAT is invalid or revoked") + case http.StatusForbidden: + err = errors.New("you do not have permission to get this issue") + case http.StatusNotFound: + err = errors.New("the issue was not found") + default: + err = fmt.Errorf("could not get issue: %s", err) + } -// return -// } + return + } -// issue = j.goJiraIssueToIssue(*issueGot) + issue = j.goJiraIssueToIssue(*issueGot) -// return -// } + return +} // func (j *Jira) GetIssueType(issue domain.Issue) (issueType issue_types.IssueType) { // for issueType, ids := range j.cfg.Jira.IssueTypes { @@ -86,14 +86,14 @@ package jira // return issue_types.Unknown // } -// func (j *Jira) IdentifyIssue(identifier string) bool { -// return issuePattern.MatchString(identifier) -// } - -// func (j *Jira) CheckConfiguration() (err error) { +func (j *Jira) IdentifyIssue(identifier string) bool { + return issuePattern.MatchString(identifier) +} -// return -// } +func (j *Jira) CheckConfiguration() (err error) { + // TODO: Check if configuration is valid + return +} // func (j *Jira) FormatIssueId(issueId string) (formattedIssueId string) { // return issueId @@ -103,44 +103,45 @@ package jira // return domain.IssueTrackerTypeJira // } -// func createBearerClient(token string, host string, skipTLSVerify bool) (client *JiraClient, err error) { -// customTransport := http.DefaultTransport.(*http.Transport).Clone() -// customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: skipTLSVerify} - -// tp := gojira.BearerAuthTransport{ -// Token: token, -// Transport: customTransport, -// } - -// gojiraClient, err := gojira.NewClient(tp.Client(), host) - -// if err != nil { -// return -// } - -// client = &JiraClient{*gojiraClient} - -// return -// } - -// func (j *Jira) ParseRawIssueId(identifier string) (issueId string) { -// return identifier -// } - -// func (j *Jira) goJiraIssueToIssue(issue gojira.Issue) domain.Issue { -// return domain.Issue{ -// ID: issue.Key, -// Title: issue.Fields.Summary, -// Body: issue.Fields.Description, -// Url: fmt.Sprintf("%s/browse/%s", j.cfg.Auth.Host, issue.Key), -// Type: domain.IssueType{ -// Id: issue.Fields.Type.ID, -// Name: issue.Fields.Type.Name, -// Description: issue.Fields.Type.Description, -// }, -// IssueTracker: domain.IssueTrackerTypeJira, -// } -// } +func createBearerClient(token string, host string, skipTLSVerify bool) (client *JiraClient, err error) { + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: skipTLSVerify} + + tp := gojira.BearerAuthTransport{ + Token: token, + Transport: customTransport, + } + + gojiraClient, err := gojira.NewClient(tp.Client(), host) + + if err != nil { + return + } + + client = &JiraClient{*gojiraClient} + + return +} + +func (j *Jira) ParseRawIssueId(identifier string) (issueId string) { + return identifier +} + +func (j *Jira) goJiraIssueToIssue(issue gojira.Issue) domain.Issue { + return Issue{ + id: issue.Key, + title: issue.Fields.Summary, + body: issue.Fields.Description, + url: fmt.Sprintf("%s/browse/%s", j.cfg.Auth.Host, issue.Key), + issueType: IssueType{ + Id: issue.Fields.Type.ID, + Name: issue.Fields.Type.Name, + Description: issue.Fields.Type.Description, + }, + issueTypesConfig: j.cfg.Jira.IssueTypes, + labelsConfig: j.cfg.IssueTypeLabels, + } +} // func (j *Jira) GetIssueTypeLabel(issue domain.Issue) string { // issueType := j.GetIssueType(issue) diff --git a/internal/issue_trackers/jira/jira_test.go b/internal/issue_trackers/jira/jira_test.go index d5d22d9..27822da 100644 --- a/internal/issue_trackers/jira/jira_test.go +++ b/internal/issue_trackers/jira/jira_test.go @@ -1,171 +1,161 @@ package jira -// import ( -// "reflect" -// "testing" - -// "github.com/InditexTech/gh-sherpa/internal/config" -// "github.com/InditexTech/gh-sherpa/internal/domain" -// "github.com/InditexTech/gh-sherpa/internal/domain/issue_types" -// gojira "github.com/andygrunwald/go-jira" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" -// "github.com/stretchr/testify/suite" -// ) - -// type JiraTestSuite struct { -// suite.Suite -// jira *Jira -// } - -// func TestJiraTestSuite(t *testing.T) { -// suite.Run(t, new(JiraTestSuite)) -// } - -// func (s *JiraTestSuite) SetupSuite() { -// cfg := Configuration{ -// Jira: config.Jira{ -// Auth: config.JiraAuth{ -// Host: "https://jira.example.com/jira", -// }, -// }, -// } - -// j, err := New(cfg) -// s.Require().NoError(err) - -// s.jira = j -// } - -// func (s *JiraTestSuite) TestGojiraIssueToDomainIssue() { -// s.Run("Should convert a gojira issue to a domain issue", func() { -// issue := gojira.Issue{ -// Key: "ISSUE-1", -// Fields: &gojira.IssueFields{ -// Summary: "Summary", -// Description: "Description", -// Type: gojira.IssueType{ -// ID: "1", -// Name: "Bug", -// Description: "Fixes a bug", -// }, -// }, -// } - -// result := s.jira.goJiraIssueToIssue(issue) - -// expected := domain.Issue{ -// ID: "ISSUE-1", -// Title: "Summary", -// Body: "Description", -// Type: domain.IssueType{ -// Id: "1", -// Name: "Bug", -// Description: "Fixes a bug", -// }, -// Url: "https://jira.example.com/jira/browse/ISSUE-1", -// IssueTracker: domain.IssueTrackerTypeJira, -// } - -// s.Truef(reflect.DeepEqual(expected, result), "expected: %v, got: %v", expected, result) -// }) -// } - -// func TestGetIssueType(t *testing.T) { - -// createIssue := func(issueTypeId string) domain.Issue { -// return domain.Issue{Type: domain.IssueType{Id: issueTypeId}} -// } - -// cfg := Configuration{ -// Jira: config.Jira{ -// IssueTypes: config.JiraIssueTypes{ -// issue_types.Bug: {"1"}, -// issue_types.Feature: {"3", "5"}, -// issue_types.Improvement: {}, -// }, -// }, -// } - -// j, err := New(cfg) -// require.NoError(t, err) - -// for _, tc := range []struct { -// name string -// issue domain.Issue -// want issue_types.IssueType -// }{ -// { -// name: "GetIssueType bug", -// issue: createIssue("1"), -// want: issue_types.Bug, -// }, -// { -// name: "GetIssueType feature", -// issue: createIssue("3"), -// want: issue_types.Feature, -// }, -// { -// name: "GetIssueType unknown", -// issue: createIssue("-1"), -// want: issue_types.Unknown, -// }, -// } { -// tc := tc -// t.Run(tc.name, func(t *testing.T) { -// got := j.GetIssueType(tc.issue) -// assert.Equal(t, tc.want, got) -// }) -// } -// } - -// func TestGetIssueTypeLabel(t *testing.T) { -// createIssue := func(issueTypeId string) domain.Issue { -// return domain.Issue{Type: domain.IssueType{Id: issueTypeId}} -// } - -// cfg := Configuration{ -// Jira: config.Jira{ -// IssueTypes: config.JiraIssueTypes{ -// issue_types.Bug: {"1"}, -// issue_types.Feature: {"3", "5"}, -// issue_types.Improvement: {}, -// }, -// }, -// IssueTypeLabels: map[issue_types.IssueType][]string{ -// issue_types.Bug: {"kind/bug", "kind/bugfix"}, -// issue_types.Feature: {"kind/feat"}, -// }, -// } - -// j, err := New(cfg) -// require.NoError(t, err) - -// for _, tc := range []struct { -// name string -// issue domain.Issue -// want string -// }{ -// { -// name: "Get issue type label with single mapped label", -// issue: createIssue("5"), -// want: "kind/feat", -// }, -// { -// name: "Returns first issue label if multiple labels are mapped to the same issue type", -// issue: createIssue("1"), -// want: "kind/bug", -// }, -// { -// name: "Returns empty string if no kind is present in the issue", -// issue: createIssue("-1"), -// want: "", -// }, -// } { -// tc := tc -// t.Run(tc.name, func(t *testing.T) { -// got := j.GetIssueTypeLabel(tc.issue) -// assert.Equal(t, tc.want, got) -// }) -// } -// } +import ( + "reflect" + "testing" + + "github.com/InditexTech/gh-sherpa/internal/config" + "github.com/InditexTech/gh-sherpa/internal/domain" + "github.com/InditexTech/gh-sherpa/internal/domain/issue_types" + gojira "github.com/andygrunwald/go-jira" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type JiraTestSuite struct { + suite.Suite + jira *Jira +} + +func TestJiraTestSuite(t *testing.T) { + suite.Run(t, new(JiraTestSuite)) +} + +func (s *JiraTestSuite) SetupSuite() { + cfg := Configuration{ + Jira: config.Jira{ + Auth: config.JiraAuth{ + Host: "https://jira.example.com/jira", + }, + }, + } + + j, err := New(cfg) + s.Require().NoError(err) + + s.jira = j +} + +func (s *JiraTestSuite) TestGojiraIssueToDomainIssue() { + s.Run("Should convert a gojira issue to a domain issue", func() { + issue := gojira.Issue{ + Key: "ISSUE-1", + Fields: &gojira.IssueFields{ + Summary: "Summary", + Description: "Description", + Type: gojira.IssueType{ + ID: "1", + Name: "Bug", + Description: "Fixes a bug", + }, + }, + } + + result := s.jira.goJiraIssueToIssue(issue) + + expected := Issue{ + id: "ISSUE-1", + title: "Summary", + body: "Description", + issueType: IssueType{ + Id: "1", + Name: "Bug", + Description: "Fixes a bug", + }, + url: "https://jira.example.com/jira/browse/ISSUE-1", + } + + s.Truef(reflect.DeepEqual(expected, result), "expected: %v, got: %v", expected, result) + }) +} + +func TestGetIssueType(t *testing.T) { + + createIssue := func(issueTypeId string) domain.Issue { + return Issue{ + issueType: IssueType{ + Id: issueTypeId, + }, + issueTypesConfig: config.JiraIssueTypes{ + issue_types.Bug: {"1"}, + issue_types.Feature: {"3", "5"}, + issue_types.Improvement: {}, + }, + } + } + + for _, tc := range []struct { + name string + issue domain.Issue + want issue_types.IssueType + }{ + { + name: "GetIssueType bug", + issue: createIssue("1"), + want: issue_types.Bug, + }, + { + name: "GetIssueType feature", + issue: createIssue("3"), + want: issue_types.Feature, + }, + { + name: "GetIssueType unknown", + issue: createIssue("-1"), + want: issue_types.Unknown, + }, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + got := tc.issue.Type() + assert.Equal(t, tc.want, got) + }) + } +} + +func TestGetIssueTypeLabel(t *testing.T) { + createIssue := func(issueTypeId string) domain.Issue { + return Issue{ + issueType: IssueType{ + Id: issueTypeId, + }, + issueTypesConfig: config.JiraIssueTypes{ + issue_types.Bug: {"1"}, + issue_types.Feature: {"3", "5"}, + issue_types.Improvement: {}, + }, + labelsConfig: map[issue_types.IssueType][]string{ + issue_types.Bug: {"kind/bug", "kind/bugfix"}, + issue_types.Feature: {"kind/feat"}, + }, + } + } + + for _, tc := range []struct { + name string + issue domain.Issue + want string + }{ + { + name: "Get issue type label with single mapped label", + issue: createIssue("5"), + want: "kind/feat", + }, + { + name: "Returns first issue label if multiple labels are mapped to the same issue type", + issue: createIssue("1"), + want: "kind/bug", + }, + { + name: "Returns empty string if no kind is present in the issue", + issue: createIssue("-1"), + want: "", + }, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + got := tc.issue.TypeLabel() + assert.Equal(t, tc.want, got) + }) + } +}