diff --git a/errormapper/errormapper.go b/errormapper/errormapper.go
new file mode 100644
index 00000000..50c888fa
--- /dev/null
+++ b/errormapper/errormapper.go
@@ -0,0 +1,68 @@
+package errormapper
+
+import (
+ "regexp"
+
+ "github.com/bitrise-io/bitrise-init/step"
+)
+
+const (
+ // UnknownParam ...
+ UnknownParam = "::unknown::"
+ // DetailedErrorRecKey ...
+ DetailedErrorRecKey = "DetailedError"
+)
+
+// DetailedError ...
+type DetailedError struct {
+ Title string
+ Description string
+}
+
+// NewDetailedErrorRecommendation ...
+func NewDetailedErrorRecommendation(detailedError DetailedError) step.Recommendation {
+ return step.Recommendation{
+ DetailedErrorRecKey: detailedError,
+ }
+}
+
+// DetailedErrorBuilder ...
+type DetailedErrorBuilder = func(...string) DetailedError
+
+// GetParamAt ...
+func GetParamAt(index int, params []string) string {
+ res := UnknownParam
+ if index >= 0 && len(params) > index {
+ res = params[index]
+ }
+ return res
+}
+
+// PatternToDetailedErrorBuilder ...
+type PatternToDetailedErrorBuilder map[string]DetailedErrorBuilder
+
+// PatternErrorMatcher ...
+type PatternErrorMatcher struct {
+ DefaultBuilder DetailedErrorBuilder
+ PatternToBuilder PatternToDetailedErrorBuilder
+}
+
+// Run ...
+func (m *PatternErrorMatcher) Run(msg string) step.Recommendation {
+ for pattern, builder := range m.PatternToBuilder {
+ re := regexp.MustCompile(pattern)
+ if re.MatchString(msg) {
+ // [search_string, match1, match2, ...]
+ matches := re.FindStringSubmatch((msg))
+ // Drop the first item, which is always the search_string itself
+ // [search_string] -> []
+ // [search_string, match1, ...] -> [match1, ...]
+ params := matches[1:]
+ detail := builder(params...)
+ return NewDetailedErrorRecommendation(detail)
+ }
+ }
+
+ detail := m.DefaultBuilder(msg)
+ return NewDetailedErrorRecommendation(detail)
+}
diff --git a/errormapper/errormapper_test.go b/errormapper/errormapper_test.go
new file mode 100644
index 00000000..6b3afac8
--- /dev/null
+++ b/errormapper/errormapper_test.go
@@ -0,0 +1,225 @@
+package errormapper
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/bitrise-io/bitrise-init/step"
+)
+
+func Test_newDetailedErrorRecommendation(t *testing.T) {
+ type args struct {
+ detailedError DetailedError
+ }
+ tests := []struct {
+ name string
+ args args
+ want step.Recommendation
+ }{
+ {
+ name: "newDetailedErrorRecommendation with nil",
+ args: args{
+ detailedError: DetailedError{
+ Title: "TestTitle",
+ Description: "TestDesciption",
+ },
+ },
+ want: step.Recommendation{
+ DetailedErrorRecKey: DetailedError{
+ Title: "TestTitle",
+ Description: "TestDesciption",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := NewDetailedErrorRecommendation(tt.args.detailedError); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("newDetailedErrorRecommendation() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_getParamAt(t *testing.T) {
+ type args struct {
+ index int
+ params []string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "getParamsAt(0, nil)",
+ args: args{
+ index: 0,
+ params: nil,
+ },
+ want: UnknownParam,
+ },
+ {
+ name: "getParamsAt(0, [])",
+ args: args{
+ index: 0,
+ params: []string{},
+ },
+ want: UnknownParam,
+ },
+ {
+ name: "getParamsAt(-1, ['1', '2', '3', '4', '5'])",
+ args: args{
+ index: -1,
+ params: []string{"1", "2", "3", "4", "5"},
+ },
+ want: UnknownParam,
+ },
+ {
+ name: "getParamsAt(5, ['1', '2', '3', '4', '5'])",
+ args: args{
+ index: 5,
+ params: []string{"1", "2", "3", "4", "5"},
+ },
+ want: UnknownParam,
+ },
+ {
+ name: "getParamsAt(0, ['1', '2', '3', '4', '5'])",
+ args: args{
+ index: 0,
+ params: []string{"1", "2", "3", "4", "5"},
+ },
+ want: "1",
+ },
+ {
+ name: "getParamsAt(4, ['1', '2', '3', '4', '5'])",
+ args: args{
+ index: 4,
+ params: []string{"1", "2", "3", "4", "5"},
+ },
+ want: "5",
+ },
+ {
+ name: "getParamsAt(2, ['1', '2', '3', '4', '5'])",
+ args: args{
+ index: 2,
+ params: []string{"1", "2", "3", "4", "5"},
+ },
+ want: "3",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := GetParamAt(tt.args.index, tt.args.params); got != tt.want {
+ t.Errorf("getParamAt() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestPatternErrorMatcher_Run(t *testing.T) {
+ type fields struct {
+ defaultBuilder DetailedErrorBuilder
+ patternToBuilder PatternToDetailedErrorBuilder
+ }
+ type args struct {
+ msg string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want step.Recommendation
+ }{
+ {
+ name: "Run with defaultBuilder",
+ fields: fields{
+ defaultBuilder: func(params ...string) DetailedError {
+ return DetailedError{
+ Title: "T",
+ Description: "D",
+ }
+ },
+ patternToBuilder: map[string]DetailedErrorBuilder{},
+ },
+ args: args{
+ msg: "Test",
+ },
+ want: step.Recommendation{
+ DetailedErrorRecKey: DetailedError{
+ Title: "T",
+ Description: "D",
+ },
+ },
+ },
+ {
+ name: "Run with patternBuilder",
+ fields: fields{
+ defaultBuilder: func(params ...string) DetailedError {
+ return DetailedError{
+ Title: "DefaultTitle",
+ Description: "DefaultDesc",
+ }
+ },
+ patternToBuilder: map[string]DetailedErrorBuilder{
+ "Test": func(params ...string) DetailedError {
+ return DetailedError{
+ Title: "PatternTitle",
+ Description: "PatternDesc",
+ }
+ },
+ },
+ },
+ args: args{
+ msg: "Test",
+ },
+ want: step.Recommendation{
+ DetailedErrorRecKey: DetailedError{
+ Title: "PatternTitle",
+ Description: "PatternDesc",
+ },
+ },
+ },
+ {
+ name: "Run with patternBuilder with param",
+ fields: fields{
+ defaultBuilder: func(params ...string) DetailedError {
+ return DetailedError{
+ Title: "DefaultTitle",
+ Description: "DefaultDesc",
+ }
+ },
+ patternToBuilder: map[string]DetailedErrorBuilder{
+ "Test (.+)!": func(params ...string) DetailedError {
+ p := GetParamAt(0, params)
+ return DetailedError{
+ Title: "PatternTitle",
+ Description: fmt.Sprintf("PatternDesc: '%s'", p),
+ }
+ },
+ },
+ },
+ args: args{
+ msg: "Test WithPatternParam!",
+ },
+ want: step.Recommendation{
+ DetailedErrorRecKey: DetailedError{
+ Title: "PatternTitle",
+ Description: "PatternDesc: 'WithPatternParam'",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ m := &PatternErrorMatcher{
+ DefaultBuilder: tt.fields.defaultBuilder,
+ PatternToBuilder: tt.fields.patternToBuilder,
+ }
+ if got := m.Run(tt.args.msg); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("PatternErrorMatcher.Run() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/gitclone/git.go b/gitclone/git.go
index 46d6e637..63b6b5cb 100644
--- a/gitclone/git.go
+++ b/gitclone/git.go
@@ -21,6 +21,13 @@ import (
"github.com/bitrise-io/go-utils/pathutil"
"github.com/bitrise-io/go-utils/retry"
"github.com/bitrise-io/go-utils/sliceutil"
+ "github.com/bitrise-steplib/steps-git-clone/errormapper"
+)
+
+const (
+ checkoutFailedTag = "checkout_failed"
+ fetchFailedTag = "fetch_failed"
+ branchRecKey = "BranchRecommendation"
)
func isOriginPresent(gitCmd git.Git, dir, repoURL string) (bool, error) {
@@ -291,14 +298,14 @@ func manualMerge(gitCmd git.Git, repoURL, prRepoURL, branch, commit, branchDest
func parseListBranchesOutput(output string) map[string][]string {
lines := strings.Split(output, "\n")
- branchesByRemote := map[string][]string {}
+ branchesByRemote := map[string][]string{}
for _, line := range lines {
line = strings.Trim(line, " ")
split := strings.Split(line, "/")
remote := split[0]
branch := ""
- if (len(split) > 1) {
+ if len(split) > 1 {
branch = strings.Join(split[1:], "/")
branches := branchesByRemote[remote]
branches = append(branches, branch)
@@ -339,17 +346,18 @@ func checkout(gitCmd git.Git, arg, branch string, depth int, isTag bool) *step.E
branches := branchesByRemote[defaultRemoteName]
if branchesErr == nil && !sliceutil.IsStringInSlice(branch, branches) {
return newStepErrorWithRecommendations(
- "fetch_failed",
+ fetchFailedTag,
fmt.Errorf("fetch failed: invalid branch selected: %s, available branches: %s: %v", branch, strings.Join(branches, ", "), err),
"Fetching repository has failed",
step.Recommendation{
- "BranchRecommendation": branches,
+ branchRecKey: branches,
+ errormapper.DetailedErrorRecKey: newFetchFailedInvalidBranchDetailedError(branch),
},
)
}
}
return newStepError(
- "fetch_failed",
+ fetchFailedTag,
fmt.Errorf("fetch failed, error: %v", err),
"Fetching repository has failed",
)
@@ -358,7 +366,7 @@ func checkout(gitCmd git.Git, arg, branch string, depth int, isTag bool) *step.E
if err := run(gitCmd.Checkout(arg)); err != nil {
if depth == 0 {
return newStepError(
- "checkout_failed",
+ checkoutFailedTag,
fmt.Errorf("checkout failed (%s), error: %v", arg, err),
"Checkout has failed",
)
diff --git a/gitclone/gitclone.go b/gitclone/gitclone.go
index 97f88b32..811f71ef 100644
--- a/gitclone/gitclone.go
+++ b/gitclone/gitclone.go
@@ -32,8 +32,9 @@ type Config struct {
}
const (
- trimEnding = "..."
- defaultRemoteName = "origin"
+ trimEnding = "..."
+ defaultRemoteName = "origin"
+ updateSubmodelFailedTag = "update_submodule_failed"
)
func printLogAndExportEnv(gitCmd git.Git, format, env string, maxEnvLength int) error {
@@ -160,7 +161,7 @@ func Execute(cfg Config) *step.Error {
if cfg.UpdateSubmodules {
if err := run(gitCmd.SubmoduleUpdate()); err != nil {
return newStepError(
- "update_submodule_failed",
+ updateSubmodelFailedTag,
fmt.Errorf("submodule update: %v", err),
"Updating submodules has failed",
)
diff --git a/gitclone/steperror.go b/gitclone/steperror.go
index 73e1cba7..840c36f2 100644
--- a/gitclone/steperror.go
+++ b/gitclone/steperror.go
@@ -1,11 +1,131 @@
package gitclone
-import "github.com/bitrise-io/bitrise-init/step"
+import (
+ "fmt"
+
+ "github.com/bitrise-io/bitrise-init/step"
+ "github.com/bitrise-steplib/steps-git-clone/errormapper"
+)
+
+func mapRecommendation(tag, errMsg string) step.Recommendation {
+ switch tag {
+ case checkoutFailedTag:
+ matcher := newCheckoutFailedPatternErrorMatcher()
+ return matcher.Run(errMsg)
+ case updateSubmodelFailedTag: // update_submodule_failed could have the same errors as fetch
+ fallthrough
+ case fetchFailedTag:
+ fetchFailedMatcher := newFetchFailedPatternErrorMatcher()
+ return fetchFailedMatcher.Run(errMsg)
+ }
+ return nil
+}
func newStepError(tag string, err error, shortMsg string) *step.Error {
+ recommendation := mapRecommendation(tag, err.Error())
+ if recommendation != nil {
+ return step.NewErrorWithRecommendations("git-clone", tag, err, shortMsg, recommendation)
+ }
+
return step.NewError("git-clone", tag, err, shortMsg)
}
func newStepErrorWithRecommendations(tag string, err error, shortMsg string, recommendations step.Recommendation) *step.Error {
return step.NewErrorWithRecommendations("git-clone", tag, err, shortMsg, recommendations)
}
+
+func newCheckoutFailedPatternErrorMatcher() *errormapper.PatternErrorMatcher {
+ return &errormapper.PatternErrorMatcher{
+ DefaultBuilder: newCheckoutFailedGenericDetailedError,
+ PatternToBuilder: nil,
+ }
+}
+
+func newFetchFailedPatternErrorMatcher() *errormapper.PatternErrorMatcher {
+ return &errormapper.PatternErrorMatcher{
+ DefaultBuilder: newFetchFailedGenericDetailedError,
+ PatternToBuilder: errormapper.PatternToDetailedErrorBuilder{
+ `Permission denied \((.+)\)`: newFetchFailedSSHAccessErrorDetailedError,
+ `fatal: repository '(.+)' not found`: newFetchFailedCouldNotFindGitRepoDetailedError,
+ `fatal: '(.+)' does not appear to be a git repository`: newFetchFailedCouldNotFindGitRepoDetailedError,
+ `fatal: (.+)/info/refs not valid: is this a git repository?`: newFetchFailedCouldNotFindGitRepoDetailedError,
+ `remote: HTTP Basic: Access denied[\n]*fatal: Authentication failed for '(.+)'`: newFetchFailedHTTPAccessErrorDetailedError,
+ `remote: Invalid username or password\(\.\)[\n]*fatal: Authentication failed for '(.+)'`: newFetchFailedHTTPAccessErrorDetailedError,
+ `Unauthorized`: newFetchFailedHTTPAccessErrorDetailedError,
+ `Forbidden`: newFetchFailedHTTPAccessErrorDetailedError,
+ `remote: Unauthorized LoginAndPassword`: newFetchFailedHTTPAccessErrorDetailedError,
+ // `fatal: unable to access '(.+)': Failed to connect to .+ port \d+: Connection timed out
+ // `fatal: unable to access '(.+)': The requested URL returned error: 400`
+ // `fatal: unable to access '(.+)': The requested URL returned error: 403`
+ `fatal: unable to access '(.+)': (Failed|The requested URL returned error: \d+)`: newFetchFailedHTTPAccessErrorDetailedError,
+ // `ssh: connect to host (.+) port \d+: Connection timed out`
+ // `ssh: connect to host (.+) port \d+: Connection refused`
+ // `ssh: connect to host (.+) port \d+: Network is unreachable`
+ `ssh: connect to host (.+) port \d+:`: newFetchFailedCouldConnectErrorDetailedError,
+ `ssh: Could not resolve hostname (.+): Name or service not known`: newFetchFailedCouldConnectErrorDetailedError,
+ `fatal: unable to access '.+': Could not resolve host: (\S+)`: newFetchFailedCouldConnectErrorDetailedError,
+ `ERROR: The \x60(.+)' organization has enabled or enforced SAML SSO`: newFetchFailedSamlSSOEnforcedDetailedError,
+ },
+ }
+}
+
+func newCheckoutFailedGenericDetailedError(params ...string) errormapper.DetailedError {
+ err := errormapper.GetParamAt(0, params)
+ return errormapper.DetailedError{
+ Title: "We couldn’t checkout your branch.",
+ Description: fmt.Sprintf("Our auto-configurator returned the following error:\n%s", err),
+ }
+}
+
+func newFetchFailedGenericDetailedError(params ...string) errormapper.DetailedError {
+ err := errormapper.GetParamAt(0, params)
+ return errormapper.DetailedError{
+ Title: "We couldn’t fetch your repository.",
+ Description: fmt.Sprintf("Our auto-configurator returned the following error:\n%s", err),
+ }
+}
+
+func newFetchFailedSSHAccessErrorDetailedError(params ...string) errormapper.DetailedError {
+ return errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process, double-check your SSH key and try again.",
+ }
+}
+
+func newFetchFailedCouldNotFindGitRepoDetailedError(params ...string) errormapper.DetailedError {
+ repoURL := errormapper.GetParamAt(0, params)
+ return errormapper.DetailedError{
+ Title: fmt.Sprintf("We couldn’t find a git repository at '%s'.", repoURL),
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }
+}
+
+func newFetchFailedHTTPAccessErrorDetailedError(params ...string) errormapper.DetailedError {
+ return errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process and try again, by providing the repository with SSH URL.",
+ }
+}
+
+func newFetchFailedCouldConnectErrorDetailedError(params ...string) errormapper.DetailedError {
+ host := errormapper.GetParamAt(0, params)
+ return errormapper.DetailedError{
+ Title: fmt.Sprintf("We couldn’t connect to '%s'.", host),
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }
+}
+
+func newFetchFailedSamlSSOEnforcedDetailedError(params ...string) errormapper.DetailedError {
+ return errormapper.DetailedError{
+ Title: "To access this repository, you need to use SAML SSO.",
+ Description: `Please abort the process, update your SSH settings and try again. You can find out more about using SAML SSO in the Github docs.`,
+ }
+}
+
+func newFetchFailedInvalidBranchDetailedError(params ...string) errormapper.DetailedError {
+ branch := errormapper.GetParamAt(0, params)
+ return errormapper.DetailedError{
+ Title: fmt.Sprintf("We couldn't find the branch '%s'.", branch),
+ Description: "Please choose another branch and try again.",
+ }
+}
diff --git a/gitclone/steperror_test.go b/gitclone/steperror_test.go
new file mode 100644
index 00000000..8d0bcebb
--- /dev/null
+++ b/gitclone/steperror_test.go
@@ -0,0 +1,357 @@
+package gitclone
+
+import (
+ "errors"
+ "reflect"
+ "testing"
+
+ "github.com/bitrise-io/bitrise-init/step"
+ "github.com/bitrise-steplib/steps-git-clone/errormapper"
+)
+
+var mapRecommendationMock func(tag, errMsg string) step.Recommendation
+
+func Test_mapRecommendation(t *testing.T) {
+ type args struct {
+ tag string
+ errMsg string
+ }
+ tests := []struct {
+ name string
+ args args
+ want step.Recommendation
+ }{
+ {
+ name: "checkout_failed generic error mapping",
+ args: args{
+ tag: checkoutFailedTag,
+ errMsg: "error: pathspec 'master' did not match any file(s) known to git.",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t checkout your branch.",
+ Description: "Our auto-configurator returned the following error:\nerror: pathspec 'master' did not match any file(s) known to git.",
+ }),
+ },
+ {
+ name: "fetch_failed generic error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "fetch failed, error: exit status 128",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t fetch your repository.",
+ Description: "Our auto-configurator returned the following error:\nfetch failed, error: exit status 128",
+ }),
+ },
+ {
+ name: "fetch_failed permission denied (publickey) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "Permission denied (publickey).",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process, double-check your SSH key and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed permission denied (publickey,publickey,gssapi-keyex,gssapi-with-mic,password) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process, double-check your SSH key and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed could not find repository (fatal: repository 'http://localhost/repo.git' not found) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "fatal: repository 'http://localhost/repo.git' not found",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t find a git repository at 'http://localhost/repo.git'.",
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed could not find repository (fatal: 'totally.not.made.up' does not appear to be a git repository) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "fatal: 'totally.not.made.up' does not appear to be a git repository",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t find a git repository at 'totally.not.made.up'.",
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed could not find repository (fatal: https://www.youtube.com/channel/UCh0BVQAUkD3vr3WzmINFO5A/info/refs not valid: is this a git repository?) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "fatal: https://www.youtube.com/channel/UCh0BVQAUkD3vr3WzmINFO5A/info/refs not valid: is this a git repository?",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t find a git repository at 'https://www.youtube.com/channel/UCh0BVQAUkD3vr3WzmINFO5A'.",
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed could not access repository (remote: HTTP Basic: Access denied\nfatal: Authentication failed for 'https://localhost/repo.git') error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "remote: HTTP Basic: Access denied\nfatal: Authentication failed for 'https://localhost/repo.git'",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process and try again, by providing the repository with SSH URL.",
+ }),
+ },
+ {
+ name: "fetch_failed could not access repository (remote: Invalid username or password(.)\nfatal: Authentication failed for 'https://localhost/repo.git') error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "remote: Invalid username or password(.)\nfatal: Authentication failed for 'https://localhost/repo.git'",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process and try again, by providing the repository with SSH URL.",
+ }),
+ },
+ {
+ name: "fetch_failed could not access repository (Unauthorized) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "Unauthorized",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process and try again, by providing the repository with SSH URL.",
+ }),
+ },
+ {
+ name: "fetch_failed could not access repository (Forbidden) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "Forbidden",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process and try again, by providing the repository with SSH URL.",
+ }),
+ },
+ {
+ name: "fetch_failed could not access repository (fatal: unable to access 'https://git.something.com/group/repo.git/': Failed to connect to git.something.com port 443: Connection timed out) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "fatal: unable to access 'https://git.something.com/group/repo.git/': Failed to connect to git.something.com port 443: Connection timed out",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process and try again, by providing the repository with SSH URL.",
+ }),
+ },
+ {
+ name: "fetch_failed could not access repository (fatal: unable to access 'https://github.com/group/repo.git)/': The requested URL returned error: 400) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "fatal: unable to access 'https://github.com/group/repo.git)/': The requested URL returned error: 400",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process and try again, by providing the repository with SSH URL.",
+ }),
+ },
+ {
+ name: "fetch_failed could not connect (ssh: connect to host git.something.com.outer port 22: Connection timed out) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "ssh: connect to host git.something.com port 22: Connection timed out) error mapping",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t connect to 'git.something.com'.",
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed could not connect (ssh: connect to host git.something.com.outer port 22: Connection refused) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "ssh: connect to host git.something.com port 22: Connection refused) error mapping",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t connect to 'git.something.com'.",
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed could not connect (ssh: connect to host git.something.com.outer port 22: Network is unreachable) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "ssh: connect to host git.something.com port 22: Network is unreachable) error mapping",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t connect to 'git.something.com'.",
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed could not connect (ssh: Could not resolve hostname git.something.com: Name or service not known) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "ssh: Could not resolve hostname git.something.com: Name or service not known",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t connect to 'git.something.com'.",
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed could not connect (fatal: unable to access 'https://site.google.com/view/something/': Could not resolve host: site.google.com) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "fatal: unable to access 'https://site.google.com/view/something/': Could not resolve host: site.google.com"},
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t connect to 'site.google.com'.",
+ Description: "Please abort the process, double-check your repository URL and try again.",
+ }),
+ },
+ {
+ name: "fetch_failed SAML SSO enforced (ERROR: The `my-company' organization has enabled or enforced SAML SSO) error mapping",
+ args: args{
+ tag: fetchFailedTag,
+ errMsg: "ERROR: The `my-company' organization has enabled or enforced SAML SSO",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "To access this repository, you need to use SAML SSO.",
+ Description: `Please abort the process, update your SSH settings and try again. You can find out more about using SAML SSO in the Github docs.`,
+ }),
+ },
+ {
+ name: "update_submodule_failed generic (fatal: no submodule mapping found in .gitmodules for path 'web') error mapping",
+ args: args{
+ tag: updateSubmodelFailedTag,
+ errMsg: "fatal: no submodule mapping found in .gitmodules for path 'web'",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t fetch your repository.",
+ Description: "Our auto-configurator returned the following error:\nfatal: no submodule mapping found in .gitmodules for path 'web'",
+ }),
+ },
+ {
+ name: "update_submodule_failed (remote: Unauthorized LoginAndPassword(Username for 'https/***): User not found) error mapping",
+ args: args{
+ tag: updateSubmodelFailedTag,
+ errMsg: "remote: Unauthorized LoginAndPassword(Username for 'https/***): User not found",
+ },
+ want: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process and try again, by providing the repository with SSH URL.",
+ }),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := mapRecommendation(tt.args.tag, tt.args.errMsg); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("mapRecommendation() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_newStepError(t *testing.T) {
+ type args struct {
+ tag string
+ err error
+ shortMsg string
+ }
+ tests := []struct {
+ name string
+ args args
+ want *step.Error
+ }{
+ {
+ name: "newStepError without recommendation",
+ args: args{
+ tag: "test_tag",
+ err: errors.New("fatal error"),
+ shortMsg: "unknown error",
+ },
+ want: &step.Error{
+ StepID: "git-clone",
+ Tag: "test_tag",
+ Err: errors.New("fatal error"),
+ ShortMsg: "unknown error",
+ },
+ },
+ {
+ name: "newStepError with recommendation",
+ args: args{
+ tag: "fetch_failed",
+ err: errors.New("Permission denied (publickey)"),
+ shortMsg: "unknown error",
+ },
+ want: &step.Error{
+ StepID: "git-clone",
+ Tag: "fetch_failed",
+ Err: errors.New("Permission denied (publickey)"),
+ ShortMsg: "unknown error",
+ Recommendations: errormapper.NewDetailedErrorRecommendation(errormapper.DetailedError{
+ Title: "We couldn’t access your repository.",
+ Description: "Please abort the process, double-check your SSH key and try again.",
+ }),
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := newStepError(tt.args.tag, tt.args.err, tt.args.shortMsg); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("newStepError() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_newStepErrorWithRecommendations(t *testing.T) {
+ type args struct {
+ tag string
+ err error
+ shortMsg string
+ recommendations step.Recommendation
+ }
+ tests := []struct {
+ name string
+ args args
+ want *step.Error
+ }{
+ {
+ name: "newStepErrorWithRecommendations",
+ args: args{
+ tag: "test_tag",
+ err: errors.New("fatal error"),
+ shortMsg: "unknown error",
+ recommendations: step.Recommendation{
+ "Test": "Passed",
+ },
+ },
+ want: &step.Error{
+ StepID: "git-clone",
+ Tag: "test_tag",
+ Err: errors.New("fatal error"),
+ ShortMsg: "unknown error",
+ Recommendations: step.Recommendation{
+ "Test": "Passed",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := newStepErrorWithRecommendations(tt.args.tag, tt.args.err, tt.args.shortMsg, tt.args.recommendations); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("newStepErrorWithRecommendations() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}