diff --git a/internal/branches/branches.go b/internal/branches/branches.go index 8747228..0856def 100644 --- a/internal/branches/branches.go +++ b/internal/branches/branches.go @@ -95,7 +95,7 @@ func normalizeBranch(branchSlug string) string { return branchSlug } -// formatBranchName formats a branch name based on the issue type and the issue identifier. +// formatBranchName formats a branch name based on the branch type and the issue identifier. // It overrides the branch prefix if the issue type is present in the branchPrefixOverride map. // If the prefix is empty, it uses the branch type as the prefix. func (b BranchProvider) formatBranchName(repoNameWithOwner string, branchType string, issueId string, issueContext string) (branchName string) { diff --git a/internal/branches/branches_test.go b/internal/branches/branches_test.go index 10597ee..04ef819 100644 --- a/internal/branches/branches_test.go +++ b/internal/branches/branches_test.go @@ -33,7 +33,8 @@ func (s *BranchTestSuite) SetupSubTest() { cfg: Configuration{ Branches: config.Branches{ Prefixes: map[issue_types.IssueType]string{ - issue_types.Bug: "bugfix", + issue_types.Bug: "bugfix", + issue_types.Feature: "feature", }, MaxLength: 0, }, @@ -74,7 +75,7 @@ func (s *BranchTestSuite) TestGetBranchName() { s.Equal(expectedBrachName, branchName) }) - s.Run("should return expected branch name when interactive", func() { + s.Run("should return expected branch name when title is changed in interactive", func() { expectedBrachName := "bugfix/GH-1-fake-title-from-interactive" s.userInteractionProvider.EXPECT().SelectOrInputPrompt(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() @@ -106,6 +107,57 @@ func (s *BranchTestSuite) TestGetBranchName() { s.NoError(err) s.Equal(expectedBrachName, branchName) }) + + s.Run("should return branch name with when type is changed to bug issue type in interactive", func() { + expectedBrachName := "bugfix/GH-1-fake-title" + + s.fakeIssue.SetType(issue_types.Feature) + s.fakeIssue.SetTypeLabel("kind/feature") + + s.userInteractionProvider.EXPECT().SelectOrInputPrompt(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(message string, validValues []string, variable *string, required bool) { + *variable = "other" + }).Return(nil).Once() + s.userInteractionProvider.EXPECT().SelectOrInput(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(message string, validValues []string, variable *string, required bool) { + *variable = "bugfix" + }).Return(nil).Once() + s.userInteractionProvider.EXPECT().SelectOrInput(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + + s.b.cfg.IsInteractive = true + + branchName, err := s.b.GetBranchName(s.fakeIssue, *s.defaultRepository) + + s.NoError(err) + s.Equal(expectedBrachName, branchName) + }) + + s.Run("should return branch name with when type is changed from bug issue type in interactive", func() { + expectedBrachName := "feature/GH-1-fake-title" + + s.userInteractionProvider.EXPECT().SelectOrInputPrompt(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(message string, validValues []string, variable *string, required bool) { + *variable = "other" + }).Return(nil).Once() + s.userInteractionProvider.EXPECT().SelectOrInput(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(message string, validValues []string, variable *string, required bool) { + *variable = "feature" + }).Return(nil).Once() + s.userInteractionProvider.EXPECT().SelectOrInput(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + + s.b.cfg.IsInteractive = true + + branchName, err := s.b.GetBranchName(s.fakeIssue, *s.defaultRepository) + + s.NoError(err) + s.Equal(expectedBrachName, branchName) + }) + + s.Run("should return error when issue type could not be determined", func() { + + s.fakeIssue.SetType("undetermined-type") + + branchName, err := s.b.GetBranchName(s.fakeIssue, *s.defaultRepository) + + s.ErrorIs(err, ErrUndeterminedIssueType) + s.Empty(branchName) + }) } func TestParseIssueContext(t *testing.T) { diff --git a/internal/branches/interactive.go b/internal/branches/interactive.go index 4c1177b..6f85f94 100644 --- a/internal/branches/interactive.go +++ b/internal/branches/interactive.go @@ -10,6 +10,9 @@ import ( "github.com/InditexTech/gh-sherpa/internal/logging" ) +// ErrUndeterminedIssueType is returned when the issue type can't be determined +var ErrUndeterminedIssueType = errors.New("undetermined issue type") + // GetBranchName asks the user for a branch name in an interactive way func (b BranchProvider) GetBranchName(issue domain.Issue, repo domain.Repository) (branchName string, err error) { issueType := issue.Type() @@ -42,13 +45,14 @@ func (b BranchProvider) GetBranchName(issue domain.Issue, repo domain.Repository issueSlug = normalizeBranch(issueSlug) } else { - if issueType == issue_types.Other || issueType == issue_types.Unknown { - return "", errors.New("undetermined issue type") - } - // remap bug to bugfix if issueType == issue_types.Bug { branchType = b.getBugFixBranchType() + issueType = issue_types.Bugfix + } + + if !issueType.Valid() || issueType == issue_types.Other || issueType == issue_types.Unknown { + return "", ErrUndeterminedIssueType } } @@ -92,7 +96,7 @@ func (b BranchProvider) getBranchType(issueType issue_types.IssueType, issueTrac return } - if issueType == issue_types.Other || issueType == issue_types.Unknown { + if branchType == issue_types.Other.String() || branchType == issue_types.Unknown.String() { err = askBranchTypeOther(&branchType, b.UserInteraction) if err != nil { return diff --git a/internal/domain/issue_types/issue_type.go b/internal/domain/issue_types/issue_type.go index f51bdae..dbbccdf 100644 --- a/internal/domain/issue_types/issue_type.go +++ b/internal/domain/issue_types/issue_type.go @@ -1,11 +1,18 @@ package issue_types +import "slices" + type IssueType string func (it IssueType) String() string { return string(it) } +// Valid returns true if the IssueType is a valid one. +func (it IssueType) Valid() bool { + return slices.Contains(GetValidIssueTypes(), it) +} + const ( Bug IssueType = "bug" Bugfix IssueType = "bugfix" @@ -52,22 +59,7 @@ func GetBugValues() []IssueType { } func GetAllValues() []IssueType { - return []IssueType{ - Bugfix, - Dependency, - Deprecation, - Documentation, - Feature, - Hotfix, - Improvement, - Internal, - Refactoring, - Release, - Removal, - Revert, - Security, - Other, - } + return append(GetValidIssueTypes(), Other) } // ParseIssueType parses a string into an IssueType. diff --git a/internal/fakes/domain/fake_issue.go b/internal/fakes/domain/fake_issue.go index 7e24d0d..c77a0ff 100644 --- a/internal/fakes/domain/fake_issue.go +++ b/internal/fakes/domain/fake_issue.go @@ -23,6 +23,14 @@ func (f *FakeIssue) SetTitle(title string) { f.title = title } +func (f *FakeIssue) SetType(issueType issue_types.IssueType) { + f.issueType = issueType +} + +func (f *FakeIssue) SetTypeLabel(label string) { + f.typeLabel = label +} + func NewFakeIssue(id string, issueType issue_types.IssueType, issueTrackerType domain.IssueTrackerType) *FakeIssue { return &FakeIssue{ id: id, diff --git a/internal/interactive/interactive.go b/internal/interactive/interactive.go index 2a7f654..6457bd2 100644 --- a/internal/interactive/interactive.go +++ b/internal/interactive/interactive.go @@ -70,7 +70,7 @@ func (u UserInteractionProvider) SelectOrInputPrompt(message string, validValues return handleSurveyError(err) } -// TODO: Do not use "kind/*" here, use the actual config to retriebe the label +// TODO: Do not use "kind/*" here, use the actual config to retrieve the label func GetPromptMessageBranchType(branchType string, issueTrackerType domain.IssueTrackerType) string { if issueTrackerType == domain.IssueTrackerTypeJira { return fmt.Sprintf("Issue type '%s' found. What type of branch name do you want to create?", branchType)