diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index ff4009d..d6254ca 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -57,3 +57,26 @@ func (f *Fetcher) Fetch(taskID string) (taskstatus.TaskStatus, error) { return task.GetStatus(), nil } + +// IsHealthy returns true if the user has access to the repo +func IsHealthy(issueTracker config.IssueTracker, url string) bool { + switch issueTracker { + case config.IssueTrackerGithub: + return healthCheck(url) + default: + return true + } +} + +func healthCheck(url string) bool { + res, err := http.Head(url) + if err != nil { + return false + } + + if res.StatusCode != http.StatusOK { + return false + } + + return true +} diff --git a/issuetracker/issuetracker.go b/issuetracker/issuetracker.go index 597bb20..2fbc8eb 100644 --- a/issuetracker/issuetracker.go +++ b/issuetracker/issuetracker.go @@ -11,6 +11,9 @@ import ( "github.com/preslavmihaylov/todocheck/issuetracker/models" ) +// ErrUnsupportedHealthCheck is returned when the health check doesn't support the given issue tracker +var ErrUnsupportedHealthCheck = errors.New("unsupported issue tracker for health check") + // TaskFor gets the corresponding task model, based on the issue tracker type func TaskFor(issueTracker config.IssueTracker) models.Task { switch issueTracker { @@ -53,12 +56,7 @@ func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error) case config.IssueTrackerJira: return fmt.Sprintf("%s/rest/api/latest/issue/", origin), nil case config.IssueTrackerGithub: - tokens := common.RemoveEmptyTokens(strings.Split(origin, "/")) - if tokens[0] == "github.com" { - tokens = append([]string{"https:"}, tokens...) - } - - scheme, owner, repo := tokens[0], tokens[2], tokens[3] + scheme, owner, repo := parseGithubDetails(origin) return fmt.Sprintf("%s//api.github.com/repos/%s/%s/issues/", scheme, owner, repo), nil case config.IssueTrackerGitlab: tokens := common.RemoveEmptyTokens(strings.Split(origin, "/")) @@ -87,3 +85,24 @@ func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error) return "", errors.New("unknown issue tracker " + string(issueTracker)) } } + +// HealthcheckURL returns the health check base url given the issue tracker type and the site origin +func HealthcheckURL(issueTracker config.IssueTracker, origin string) (string, error) { + switch issueTracker { + case config.IssueTrackerGithub: + scheme, owner, repo := parseGithubDetails(origin) + return fmt.Sprintf("%s//api.github.com/repos/%s/%s", scheme, owner, repo), nil + default: + return "", ErrUnsupportedHealthCheck + } +} + +func parseGithubDetails(origin string) (scheme, owner, repo string) { + tokens := common.RemoveEmptyTokens(strings.Split(origin, "/")) + if !strings.HasPrefix(tokens[0], "http") { + tokens = append([]string{"https:"}, tokens...) + } + + scheme, owner, repo = tokens[0], tokens[2], tokens[3] + return +} diff --git a/testing/scenarios/repo_health_checks/.todocheck.yaml b/testing/scenarios/repo_health_checks/.todocheck.yaml new file mode 100644 index 0000000..03e2f2e --- /dev/null +++ b/testing/scenarios/repo_health_checks/.todocheck.yaml @@ -0,0 +1,2 @@ +origin: https://github.com/preslavmihaylov/todocheck +issue_tracker: GITHUB diff --git a/testing/scenarios/repo_health_checks/main.go b/testing/scenarios/repo_health_checks/main.go new file mode 100644 index 0000000..100f38f --- /dev/null +++ b/testing/scenarios/repo_health_checks/main.go @@ -0,0 +1,3 @@ +package main + +// This is intentionally left blank since we're testing repo access and not todo files diff --git a/testing/test_configs/invalid_github_access.yaml b/testing/test_configs/invalid_github_access.yaml new file mode 100644 index 0000000..caa613c --- /dev/null +++ b/testing/test_configs/invalid_github_access.yaml @@ -0,0 +1,2 @@ +origin: https://github.com/user/repo +issue_tracker: GITHUB diff --git a/testing/test_configs/valid_github_access.yaml b/testing/test_configs/valid_github_access.yaml new file mode 100644 index 0000000..03e2f2e --- /dev/null +++ b/testing/test_configs/valid_github_access.yaml @@ -0,0 +1,2 @@ +origin: https://github.com/preslavmihaylov/todocheck +issue_tracker: GITHUB diff --git a/testing/test_configs/valid_github_https.yaml b/testing/test_configs/valid_github_https.yaml index 41c6772..03e2f2e 100644 --- a/testing/test_configs/valid_github_https.yaml +++ b/testing/test_configs/valid_github_https.yaml @@ -1,2 +1,2 @@ -origin: https://github.com/user/project +origin: https://github.com/preslavmihaylov/todocheck issue_tracker: GITHUB diff --git a/testing/test_configs/valid_github_origin.yaml b/testing/test_configs/valid_github_origin.yaml index b9e1eb7..e5a45e7 100644 --- a/testing/test_configs/valid_github_origin.yaml +++ b/testing/test_configs/valid_github_origin.yaml @@ -1,2 +1,2 @@ -origin: github.com/user/project +origin: github.com/preslavmihaylov/todocheck issue_tracker: GITHUB diff --git a/testing/test_configs/valid_github_www.yaml b/testing/test_configs/valid_github_www.yaml index 95b4c22..5226fe7 100644 --- a/testing/test_configs/valid_github_www.yaml +++ b/testing/test_configs/valid_github_www.yaml @@ -1,2 +1,2 @@ -origin: www.github.com/user/project +origin: www.github.com/preslavmihaylov/todocheck issue_tracker: GITHUB diff --git a/testing/todocheck_test.go b/testing/todocheck_test.go index c7e6da8..0a480e3 100644 --- a/testing/todocheck_test.go +++ b/testing/todocheck_test.go @@ -332,6 +332,28 @@ func TestInvalidIssueTracker(t *testing.T) { } } +func TestValidGithubAccess(t *testing.T) { + err := scenariobuilder.NewScenario(). + WithBinary("../todocheck"). + WithBasepath("./scenarios/repo_health_checks"). + WithTestEnvConfig("./test_configs/valid_github_access.yaml"). + Run() + if err != nil { + t.Errorf("%s", err) + } +} + +func TestInvalidGithubAccess(t *testing.T) { + err := scenariobuilder.NewScenario(). + WithBinary("../todocheck"). + WithTestEnvConfig("./test_configs/invalid_github_access.yaml"). + ExpectExecutionError(). + Run() + if err != nil { + t.Errorf("%s", err) + } +} + func TestInvalidOrigins(t *testing.T) { invalidConfigPaths := []string{ "./test_configs/invalid_github_https.yaml", @@ -463,7 +485,7 @@ func TestConfigAutoDetectWithSSHGitConfig(t *testing.T) { WithBinary("../todocheck"). WithBasepath("./scenarios/auto_detect_config"). WithTestEnvConfig("./scenarios/auto_detect_config/expected_config.yaml"). - WithGitConfig("git@github.com:username/repo.git"). + WithGitConfig("git@github.com:preslavmihaylov/todocheck.git"). ExpectTodoErr( scenariobuilder.NewTodoErr(). WithType(errors.TODOErrTypeMalformed). @@ -480,7 +502,7 @@ func TestConfigAutoDetectWithHTTPSGitConfig(t *testing.T) { WithBinary("../todocheck"). WithBasepath("./scenarios/auto_detect_config"). WithTestEnvConfig("./scenarios/auto_detect_config/expected_config.yaml"). - WithGitConfig("https://github.com/username/repo"). + WithGitConfig("https://github.com/preslavmihaylov/todocheck"). ExpectTodoErr( scenariobuilder.NewTodoErr(). WithType(errors.TODOErrTypeMalformed). diff --git a/validation/validation.go b/validation/validation.go index 320590b..c972931 100644 --- a/validation/validation.go +++ b/validation/validation.go @@ -1,27 +1,34 @@ package validation import ( + "errors" "fmt" "net/url" "github.com/fatih/color" "github.com/preslavmihaylov/todocheck/config" + "github.com/preslavmihaylov/todocheck/fetcher" + "github.com/preslavmihaylov/todocheck/issuetracker" ) // Validate validates the values of given configuration func Validate(cfg *config.Local) []error { - var errors []error + var errs []error if err := validateIssueTracker(cfg); err != nil { - errors = append(errors, err) + errs = append(errs, err) } if err := validateAuthOfflineURL(cfg); err != nil { - errors = append(errors, err) + errs = append(errs, err) } if err := validateIssueTrackerOrigin(cfg); err != nil { - errors = append(errors, err) + errs = append(errs, err) + } + + if err := validateIssueTrackerExists(cfg); err != nil { + errs = append(errs, err) } if cfg.Auth.Token == "" && cfg.IssueTracker == config.IssueTrackerGithub { @@ -31,7 +38,7 @@ func Validate(cfg *config.Local) []error { " Go to https://developer.github.com/v3/#rate-limiting for more information.")) } - return errors + return errs } func validateIssueTracker(cfg *config.Local) error { @@ -57,3 +64,20 @@ func validateIssueTrackerOrigin(cfg *config.Local) error { return nil } + +func validateIssueTrackerExists(cfg *config.Local) error { + url, err := issuetracker.HealthcheckURL(cfg.IssueTracker, cfg.Origin) + if err != nil { + if errors.Is(err, issuetracker.ErrUnsupportedHealthCheck) { + return nil + } + + return err + } + + if !fetcher.IsHealthy(cfg.IssueTracker, url) { + return fmt.Errorf("repository %s not found. Is the repository private? More info: https://github.com/preslavmihaylov/todocheck#github", url) + } + + return nil +}