diff --git a/server/events/vcs/github_client.go b/server/events/vcs/github_client.go index 64eaa29d52..2af7c6d98b 100644 --- a/server/events/vcs/github_client.go +++ b/server/events/vcs/github_client.go @@ -18,6 +18,7 @@ import ( "encoding/base64" "fmt" "net/http" + "strconv" "strings" "time" @@ -39,6 +40,40 @@ var ( pullRequestDismissalMessage = *githubv4.NewString("Dismissing reviews because of plan changes") ) +type GithubRepoIdCacheEntry struct { + RepoId githubv4.Int + LookupTime time.Time +} + +type GitHubRepoIdCache struct { + cache map[githubv4.String]GithubRepoIdCacheEntry +} + +func NewGitHubRepoIdCache() GitHubRepoIdCache { + return GitHubRepoIdCache{ + cache: make(map[githubv4.String]GithubRepoIdCacheEntry), + } +} + +func (c *GitHubRepoIdCache) Get(key githubv4.String) (githubv4.Int, bool) { + entry, ok := c.cache[key] + if !ok { + return githubv4.Int(0), false + } + if time.Since(entry.LookupTime) > time.Hour { + delete(c.cache, key) + return githubv4.Int(0), false + } + return entry.RepoId, true +} + +func (c *GitHubRepoIdCache) Set(key githubv4.String, value githubv4.Int) { + c.cache[key] = GithubRepoIdCacheEntry{ + RepoId: value, + LookupTime: time.Now(), + } +} + // GithubClient is used to perform GitHub actions. type GithubClient struct { user string @@ -47,6 +82,7 @@ type GithubClient struct { ctx context.Context config GithubConfig maxCommentsPerCommand int + repoIdCache GitHubRepoIdCache } // GithubAppTemporarySecrets holds app credentials obtained from github after creation. @@ -109,6 +145,7 @@ func NewGithubClient(hostname string, credentials GithubCredentials, config Gith if err != nil { return nil, errors.Wrap(err, "getting user") } + return &GithubClient{ user: user, client: client, @@ -116,6 +153,7 @@ func NewGithubClient(hostname string, credentials GithubCredentials, config Gith ctx: context.Background(), config: config, maxCommentsPerCommand: maxCommentsPerCommand, + repoIdCache: NewGitHubRepoIdCache(), }, nil } @@ -410,119 +448,348 @@ func (g *GithubClient) DiscardReviews(repo models.Repo, pull models.PullRequest) return nil } -// isRequiredCheck is a helper function to determine if a check is required or not -func isRequiredCheck(check string, required []string) bool { - //in go1.18 can prob replace this with slices.Contains - for _, r := range required { - if r == check { - return true - } - } +type PageInfo struct { + EndCursor *githubv4.String + HasNextPage githubv4.Boolean +} - return false +type WorkflowFileReference struct { + Path githubv4.String + RepositoryId githubv4.Int + Sha *githubv4.String } -// GetCombinedStatusMinusApply checks Statuses for PR, excluding atlantis apply. Returns true if all other statuses are not in failure. -func (g *GithubClient) GetCombinedStatusMinusApply(logger logging.SimpleLogging, repo models.Repo, pull *github.PullRequest, vcstatusname string) (bool, error) { - logger.Debug("Checking if GitHub pull request %d has successful status checks", pull.GetNumber()) - //check combined status api - status, resp, err := g.client.Repositories.GetCombinedStatus(g.ctx, *pull.Head.Repo.Owner.Login, repo.Name, *pull.Head.Ref, nil) - if resp != nil { - logger.Debug("GET /repos/%v/%v/commits/%s/status returned: %v", *pull.Head.Repo.Owner.Login, repo.Name, *pull.Head.Ref, resp.StatusCode) +func (original WorkflowFileReference) Copy() WorkflowFileReference { + copy := WorkflowFileReference{ + Path: original.Path, + RepositoryId: original.RepositoryId, + Sha: new(githubv4.String), } - if err != nil { - return false, errors.Wrap(err, "getting combined status") + if original.Sha != nil { + *copy.Sha = *original.Sha } + return copy +} - //iterate over statuses - return false if we find one that isn't "apply" and doesn't have state = "success" - for _, r := range status.Statuses { - if strings.HasPrefix(*r.Context, fmt.Sprintf("%s/%s", vcstatusname, command.Apply.String())) { - continue - } - if *r.State != "success" { - return false, nil - } +type WorkflowRun struct { + File struct { + Path githubv4.String + RepositoryFileUrl githubv4.String + RepositoryName githubv4.String } +} - //get required status checks - required, resp, err := g.client.Repositories.GetBranchProtection(context.Background(), repo.Owner, repo.Name, *pull.Base.Ref) - if resp != nil { - logger.Debug("GET /repos/%v/%v/branches/%s/protection returned: %v", repo.Owner, repo.Name, *pull.Base.Ref, resp.StatusCode) +type CheckRun struct { + Name githubv4.String + Conclusion githubv4.String + // Not currently used: GitHub API classifies as required if coming from ruleset, even when the ruleset is not enforced! + IsRequired githubv4.Boolean `graphql:"isRequired(pullRequestNumber: $number)"` + CheckSuite struct { + WorkflowRun *WorkflowRun } - if err != nil { - return false, errors.Wrap(err, "getting required status checks") +} + +func (original CheckRun) Copy() CheckRun { + copy := CheckRun{ + Name: original.Name, + Conclusion: original.Conclusion, + IsRequired: original.IsRequired, + CheckSuite: original.CheckSuite, } + if original.CheckSuite.WorkflowRun != nil { + copy.CheckSuite.WorkflowRun = new(WorkflowRun) + *copy.CheckSuite.WorkflowRun = *original.CheckSuite.WorkflowRun + } + return copy +} - if required.RequiredStatusChecks == nil { - return true, nil +type StatusContext struct { + Context githubv4.String + State githubv4.String + // Not currently used: GitHub API classifies as required if coming from ruleset, even when the ruleset is not enforced! + IsRequired githubv4.Boolean `graphql:"isRequired(pullRequestNumber: $number)"` +} + +func (g *GithubClient) LookupRepoId(repo githubv4.String) (githubv4.Int, error) { + // This function may get many calls for the same repo, and repo names are not often changed + // Utilize caching to reduce the number of API calls to GitHub + if repoId, ok := g.repoIdCache.Get(repo); ok { + return repoId, nil } - //check check suite/check run api - checksuites, resp, err := g.client.Checks.ListCheckSuitesForRef(context.Background(), *pull.Head.Repo.Owner.Login, repo.Name, *pull.Head.Ref, nil) - if resp != nil { - logger.Debug("GET /repos/%v/%v/commits/%s/check-suites returned: %v", *pull.Head.Repo.Owner.Login, repo.Name, *pull.Head.Ref, resp.StatusCode) + repoSplit := strings.Split(string(repo), "/") + if len(repoSplit) != 2 { + return githubv4.Int(0), fmt.Errorf("invalid repository name: %s", repo) } + + var query struct { + Repository struct { + DatabaseId githubv4.Int + } `graphql:"repository(owner: $owner, name: $name)"` + } + variables := map[string]interface{}{ + "owner": githubv4.String(repoSplit[0]), + "name": githubv4.String(repoSplit[1]), + } + + err := g.v4Client.Query(g.ctx, &query, variables) + if err != nil { - return false, errors.Wrap(err, "getting check suites for ref") + return githubv4.Int(0), errors.Wrap(err, "getting repository id from GraphQL") } - //iterate over check completed check suites - return false if we find one that doesnt have conclusion = "success" - for _, c := range checksuites.CheckSuites { - if *c.Status == "completed" { - //iterate over the runs inside the suite - suite, resp, err := g.client.Checks.ListCheckRunsCheckSuite(context.Background(), *pull.Head.Repo.Owner.Login, repo.Name, *c.ID, nil) - if resp != nil { - logger.Debug("GET /repos/%v/%v/check-suites/%d/check-runs returned: %v", *pull.Head.Repo.Owner.Login, repo.Name, *c.ID, resp.StatusCode) - } - if err != nil { - return false, errors.Wrap(err, "getting check runs for check suite") - } + g.repoIdCache.Set(repo, query.Repository.DatabaseId) - for _, r := range suite.CheckRuns { - //check to see if the check is required - if isRequiredCheck(*r.Name, required.RequiredStatusChecks.Contexts) { - if *c.Conclusion == "success" { - continue - } - return false, nil - } - //ignore checks that arent required - continue - } - } + return query.Repository.DatabaseId, nil +} + +func (g *GithubClient) WorkflowRunMatchesWorkflowFileReference(workflowRun WorkflowRun, workflowFileReference WorkflowFileReference) (bool, error) { + // Unfortunately, the GitHub API doesn't expose the repositoryId for the WorkflowRunFile from the statusCheckRollup. + // Conversely, it doesn't expose the repository name for the WorkflowFileReference from the RepositoryRuleConnection. + // Therefore, a second query is required to lookup the association between repositoryId and repositoryName. + repoId, err := g.LookupRepoId(workflowRun.File.RepositoryName) + if err != nil { + return false, err } - return true, nil + if !(repoId == workflowFileReference.RepositoryId && workflowRun.File.Path == workflowFileReference.Path) { + return false, nil + } else if workflowFileReference.Sha != nil { + return strings.Contains(string(workflowRun.File.RepositoryFileUrl), string(*workflowFileReference.Sha)), nil + } else { + return true, nil + } } -// GetPullReviewDecision gets the pull review decision, which takes into account CODEOWNERS -func (g *GithubClient) GetPullReviewDecision(repo models.Repo, pull models.PullRequest) (approvalStatus bool, err error) { +func (g *GithubClient) GetPullRequestMergeabilityInfo( + repo models.Repo, + pull *github.PullRequest, +) ( + reviewDecision githubv4.String, + requiredChecks []githubv4.String, + requiredWorkflows []WorkflowFileReference, + checkRuns []CheckRun, + statusContexts []StatusContext, + err error, +) { var query struct { Repository struct { PullRequest struct { - ReviewDecision string + ReviewDecision githubv4.String + BaseRef struct { + BranchProtectionRule struct { + RequiredStatusChecks []struct { + Context githubv4.String + } + } + Rules struct { + PageInfo PageInfo + Nodes []struct { + Type githubv4.String + RepositoryRuleset struct { + Enforcement githubv4.String + } + Parameters struct { + RequiredStatusChecksParameters struct { + RequiredStatusChecks []struct { + Context githubv4.String + } + } `graphql:"... on RequiredStatusChecksParameters"` + WorkflowsParameters struct { + Workflows []WorkflowFileReference + } `graphql:"... on WorkflowsParameters"` + } + } + } `graphql:"rules(first: 100, after: $ruleCursor)"` + } + Commits struct { + Nodes []struct { + Commit struct { + StatusCheckRollup struct { + Contexts struct { + PageInfo PageInfo + Nodes []struct { + Typename githubv4.String `graphql:"__typename"` + CheckRun CheckRun `graphql:"... on CheckRun"` + StatusContext StatusContext `graphql:"... on StatusContext"` + } + } `graphql:"contexts(first: 100, after: $contextCursor)"` + } + } + } + } `graphql:"commits(last: 1)"` } `graphql:"pullRequest(number: $number)"` } `graphql:"repository(owner: $owner, name: $name)"` } variables := map[string]interface{}{ - "owner": githubv4.String(repo.Owner), - "name": githubv4.String(repo.Name), - "number": githubv4.Int(pull.Num), + "owner": githubv4.String(repo.Owner), + "name": githubv4.String(repo.Name), + "number": githubv4.Int(*pull.Number), + "ruleCursor": (*githubv4.String)(nil), + "contextCursor": (*githubv4.String)(nil), + } + + requiredChecksSet := make(map[githubv4.String]any) + +pagination: + for { + err = g.v4Client.Query(g.ctx, &query, variables) + + if err != nil { + break pagination + } + + reviewDecision = query.Repository.PullRequest.ReviewDecision + + for _, rule := range query.Repository.PullRequest.BaseRef.BranchProtectionRule.RequiredStatusChecks { + requiredChecksSet[rule.Context] = struct{}{} + } + + for _, rule := range query.Repository.PullRequest.BaseRef.Rules.Nodes { + if rule.RepositoryRuleset.Enforcement != "ACTIVE" { + continue + } + switch rule.Type { + case "REQUIRED_STATUS_CHECKS": + for _, context := range rule.Parameters.RequiredStatusChecksParameters.RequiredStatusChecks { + requiredChecksSet[context.Context] = struct{}{} + } + case "WORKFLOWS": + for _, workflow := range rule.Parameters.WorkflowsParameters.Workflows { + requiredWorkflows = append(requiredWorkflows, workflow.Copy()) + } + default: + continue + } + } + + if len(query.Repository.PullRequest.Commits.Nodes) == 0 { + err = errors.New("no commits found on PR") + break pagination + } + + for _, context := range query.Repository.PullRequest.Commits.Nodes[0].Commit.StatusCheckRollup.Contexts.Nodes { + switch context.Typename { + case "CheckRun": + checkRuns = append(checkRuns, context.CheckRun.Copy()) + case "StatusContext": + statusContexts = append(statusContexts, context.StatusContext) + default: + err = fmt.Errorf("unknown type of status check, %q", context.Typename) + break pagination + } + } + + if !query.Repository.PullRequest.BaseRef.Rules.PageInfo.HasNextPage && + !query.Repository.PullRequest.Commits.Nodes[0].Commit.StatusCheckRollup.Contexts.PageInfo.HasNextPage { + break pagination + } + + if query.Repository.PullRequest.BaseRef.Rules.PageInfo.EndCursor != nil { + variables["ruleCursor"] = query.Repository.PullRequest.BaseRef.Rules.PageInfo.EndCursor + } + if query.Repository.PullRequest.Commits.Nodes[0].Commit.StatusCheckRollup.Contexts.PageInfo.EndCursor != nil { + variables["contextCursor"] = query.Repository.PullRequest.Commits.Nodes[0].Commit.StatusCheckRollup.Contexts.PageInfo.EndCursor + } } - err = g.v4Client.Query(g.ctx, &query, variables) if err != nil { - return approvalStatus, errors.Wrap(err, "getting reviewDecision") + return "", nil, nil, nil, nil, errors.Wrap(err, "fetching rulesets, branch protections and status checks from GraphQL") } - if query.Repository.PullRequest.ReviewDecision == "APPROVED" || len(query.Repository.PullRequest.ReviewDecision) == 0 { - return true, nil + for context := range requiredChecksSet { + requiredChecks = append(requiredChecks, context) + } + + return reviewDecision, requiredChecks, requiredWorkflows, checkRuns, statusContexts, nil +} + +func CheckRunPassed(checkRun CheckRun) bool { + return checkRun.Conclusion == "SUCCESS" || checkRun.Conclusion == "SKIPPED" || checkRun.Conclusion == "NEUTRAL" +} + +func StatusContextPassed(statusContext StatusContext, vcsstatusname string) bool { + return strings.HasPrefix(string(statusContext.Context), fmt.Sprintf("%s/%s", vcsstatusname, command.Apply.String())) || + statusContext.State == "SUCCESS" +} + +func ExpectedCheckPassed(expectedContext githubv4.String, checkRuns []CheckRun, statusContexts []StatusContext, vcsstatusname string) bool { + for _, checkRun := range checkRuns { + if checkRun.Name == expectedContext { + return CheckRunPassed(checkRun) + } + } + + for _, statusContext := range statusContexts { + if statusContext.Context == expectedContext { + return StatusContextPassed(statusContext, vcsstatusname) + } + } + + return false +} + +func (g *GithubClient) ExpectedWorkflowPassed(expectedWorkflow WorkflowFileReference, checkRuns []CheckRun) (bool, error) { + for _, checkRun := range checkRuns { + if checkRun.CheckSuite.WorkflowRun == nil { + continue + } + match, err := g.WorkflowRunMatchesWorkflowFileReference(*checkRun.CheckSuite.WorkflowRun, expectedWorkflow) + if err != nil { + return false, err + } + if match { + return CheckRunPassed(checkRun), nil + } } return false, nil } +// IsMergeableMinusApply checks review decision (which takes into account CODEOWNERS) and required checks for PR (excluding the atlantis apply check). +func (g *GithubClient) IsMergeableMinusApply(logger logging.SimpleLogging, repo models.Repo, pull *github.PullRequest, vcsstatusname string) (bool, error) { + if pull.Number == nil { + return false, errors.New("pull request number is nil") + } + reviewDecision, requiredChecks, requiredWorkflows, checkRuns, statusContexts, err := g.GetPullRequestMergeabilityInfo(repo, pull) + if err != nil { + return false, err + } + + notMergeablePrefix := fmt.Sprintf("Pull Request %s/%s:%s is not mergeable", repo.Owner, repo.Name, strconv.Itoa(*pull.Number)) + + // Review decision takes CODEOWNERS into account + // Empty review decision means review is not required + if reviewDecision != "APPROVED" && len(reviewDecision) != 0 { + logger.Debug("%s: Review Decision: %s", notMergeablePrefix, reviewDecision) + return false, nil + } + + // The statusCheckRollup does not always contain all required checks + // For example, if a check was made required after the pull request was opened, it would be missing + // Go through all checks and workflows required by branch protection or rulesets + // Make sure that they can all be found in the statusCheckRollup and that they all pass + for _, requiredCheck := range requiredChecks { + if !ExpectedCheckPassed(requiredCheck, checkRuns, statusContexts, vcsstatusname) { + logger.Debug("%s: Expected Required Check: %s", notMergeablePrefix, requiredCheck) + return false, nil + } + } + for _, requiredWorkflow := range requiredWorkflows { + passed, err := g.ExpectedWorkflowPassed(requiredWorkflow, checkRuns) + if err != nil { + return false, err + } + if !passed { + logger.Debug("%s: Expected Required Workflow: RepositoryId: %d Path: %s", notMergeablePrefix, requiredWorkflow.RepositoryId, requiredWorkflow.Path) + return false, nil + } + } + + return true, nil +} + // PullIsMergeable returns true if the pull request is mergeable. func (g *GithubClient) PullIsMergeable(logger logging.SimpleLogging, repo models.Repo, pull models.PullRequest, vcsstatusname string) (bool, error) { logger.Debug("Checking if GitHub pull request %d is mergeable", pull.Num) @@ -531,7 +798,6 @@ func (g *GithubClient) PullIsMergeable(logger logging.SimpleLogging, repo models return false, errors.Wrap(err, "getting pull request") } - state := githubPR.GetMergeableState() // We map our mergeable check to when the GitHub merge button is clickable. // This corresponds to the following states: // clean: No conflicts, all requirements satisfied. @@ -541,33 +807,22 @@ func (g *GithubClient) PullIsMergeable(logger logging.SimpleLogging, repo models // has_hooks: GitHub Enterprise only, if a repo has custom pre-receive // hooks. Merging is allowed (green box). // See: https://github.com/octokit/octokit.net/issues/1763 - if state != "clean" && state != "unstable" && state != "has_hooks" { - //mergeable bypass apply code hidden by feature flag + switch githubPR.GetMergeableState() { + case "clean", "unstable", "has_hooks": + return true, nil + case "blocked": if g.config.AllowMergeableBypassApply { logger.Debug("AllowMergeableBypassApply feature flag is enabled - attempting to bypass apply from mergeable requirements") - if state == "blocked" { - //check status excluding atlantis apply - status, err := g.GetCombinedStatusMinusApply(logger, repo, githubPR, vcsstatusname) - if err != nil { - return false, errors.Wrap(err, "getting pull request status") - } - - //check to see if pr is approved using reviewDecision - approved, err := g.GetPullReviewDecision(repo, pull) - if err != nil { - return false, errors.Wrap(err, "getting pull request reviewDecision") - } - - //if all other status checks EXCEPT atlantis/apply are successful, and the PR is approved based on reviewDecision, let it proceed - if status && approved { - return true, nil - } + isMergeableMinusApply, err := g.IsMergeableMinusApply(logger, repo, githubPR, vcsstatusname) + if err != nil { + return false, errors.Wrap(err, "getting pull request status") } + return isMergeableMinusApply, nil } - + return false, nil + default: return false, nil } - return true, nil } // GetPullRequest returns the pull request. diff --git a/server/events/vcs/github_client_test.go b/server/events/vcs/github_client_test.go index 44ad33a1c4..ef97c64245 100644 --- a/server/events/vcs/github_client_test.go +++ b/server/events/vcs/github_client_test.go @@ -569,22 +569,6 @@ func TestGithubClient_PullIsMergeable(t *testing.T) { Ok(t, err) prJSON := string(jsBytes) - // Status Check Response - jsBytes, err = os.ReadFile("testdata/github-commit-status-full.json") - Ok(t, err) - commitJSON := string(jsBytes) - - //reviewDecision Response - reviewDecision := `{ - "data": { - "repository": { - "pullRequest": { - "reviewDecision": "REVIEW_REQUIRED" - } - } - } - }` - for _, c := range cases { t.Run(c.state, func(t *testing.T) { response := strings.Replace(prJSON, @@ -599,13 +583,6 @@ func TestGithubClient_PullIsMergeable(t *testing.T) { case "/api/v3/repos/owner/repo/pulls/1": w.Write([]byte(response)) // nolint: errcheck return - case "/api/v3/repos/owner/repo/pulls/1/reviews?per_page=300": - w.Write([]byte("[]")) // nolint: errcheck - return - case "/api/v3/repos/owner/repo/commits/new-topic/status": - w.Write([]byte(commitJSON)) // nolint: errcheck - case "/api/graphql": - w.Write([]byte(reviewDecision)) // nolint: errcheck default: t.Errorf("got unexpected request at %q", r.RequestURI) http.Error(w, "not found", http.StatusNotFound) @@ -643,167 +620,177 @@ func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApply(t *testing.T) logger := logging.NewNoopLogger(t) vcsStatusName := "atlantis" cases := []struct { - state string - reviewDecision string - expMergeable bool + state string + statusCheckRollupFilePath string + reviewDecision string + expMergeable bool }{ { "dirty", + "ruleset-atlantis-apply-pending.json", `"REVIEW_REQUIRED"`, false, }, { "unknown", + "ruleset-atlantis-apply-pending.json", `"REVIEW_REQUIRED"`, false, }, { "blocked", + "ruleset-atlantis-apply-pending.json", `"REVIEW_REQUIRED"`, false, }, { "blocked", + "ruleset-atlantis-apply-pending.json", `"APPROVED"`, true, }, { "blocked", + "ruleset-atlantis-apply-pending.json", "null", true, }, { "behind", + "ruleset-atlantis-apply-pending.json", `"REVIEW_REQUIRED"`, false, }, { "random", + "ruleset-atlantis-apply-pending.json", `"REVIEW_REQUIRED"`, false, }, { "unstable", + "ruleset-atlantis-apply-pending.json", `"REVIEW_REQUIRED"`, true, }, { "has_hooks", + "ruleset-atlantis-apply-pending.json", `"APPROVED"`, true, }, { "clean", + "ruleset-atlantis-apply-pending.json", `"APPROVED"`, true, }, { "", + "ruleset-atlantis-apply-pending.json", `"APPROVED"`, false, }, - } - - // Use a real GitHub json response and edit the mergeable_state field. - jsBytes, err := os.ReadFile("testdata/github-pull-request.json") - Ok(t, err) - prJSON := string(jsBytes) - - // Status Check Response - jsBytes, err = os.ReadFile("testdata/github-commit-status-full.json") - Ok(t, err) - commitJSON := string(jsBytes) - - // Branch protection Response - jsBytes, err = os.ReadFile("testdata/github-branch-protection-required-checks.json") - Ok(t, err) - branchProtectionJSON := string(jsBytes) - - // List check suites Response - jsBytes, err = os.ReadFile("testdata/github-commit-check-suites.json") - Ok(t, err) - checkSuites := string(jsBytes) - - for _, c := range cases { - t.Run(c.state, func(t *testing.T) { - response := strings.Replace(prJSON, - `"mergeable_state": "clean"`, - fmt.Sprintf(`"mergeable_state": "%s"`, c.state), - 1, - ) - - // reviewDecision Response - reviewDecision := fmt.Sprintf(`{ - "data": { - "repository": { - "pullRequest": { - "reviewDecision": %s - } - } - } - }`, c.reviewDecision) - - testServer := httptest.NewTLSServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.RequestURI { - case "/api/v3/repos/octocat/repo/pulls/1": - w.Write([]byte(response)) // nolint: errcheck - return - case "/api/v3/repos/octocat/repo/pulls/1/reviews?per_page=300": - w.Write([]byte("[]")) // nolint: errcheck - return - case "/api/v3/repos/octocat/repo/commits/new-topic/status": - w.Write([]byte(commitJSON)) // nolint: errcheck - case "/api/graphql": - w.Write([]byte(reviewDecision)) // nolint: errcheck - case "/api/v3/repos/octocat/repo/branches/main/protection": - w.Write([]byte(branchProtectionJSON)) // nolint: errcheck - case "/api/v3/repos/octocat/repo/commits/new-topic/check-suites": - w.Write([]byte(checkSuites)) // nolint: errcheck - default: - t.Errorf("got unexpected request at %q", r.RequestURI) - http.Error(w, "not found", http.StatusNotFound) - return - } - })) - testServerURL, err := url.Parse(testServer.URL) - Ok(t, err) - client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{AllowMergeableBypassApply: true}, 0, logging.NewNoopLogger(t)) - Ok(t, err) - defer disableSSLVerification()() - - actMergeable, err := client.PullIsMergeable( - logger, - models.Repo{ - FullName: "octocat/repo", - Owner: "octocat", - Name: "repo", - CloneURL: "", - SanitizedCloneURL: "", - VCSHost: models.VCSHost{ - Type: models.Github, - Hostname: "github.com", - }, - }, models.PullRequest{ - Num: 1, - }, vcsStatusName) - Ok(t, err) - Equals(t, c.expMergeable, actMergeable) - }) - } -} - -func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApplyButWithNoBranchProtectionChecks(t *testing.T) { - logger := logging.NewNoopLogger(t) - vcsStatusName := "atlantis" - cases := []struct { - state string - reviewDecision string - expMergeable bool - }{ { "blocked", - `"REVIEW_REQUIRED"`, + "ruleset-optional-check-failed.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "ruleset-optional-status-failed.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "ruleset-check-pending.json", + `"APPROVED"`, + false, + }, + { + "blocked", + "ruleset-check-skipped.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "ruleset-check-neutral.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "ruleset-evaluate-workflow-failed.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "branch-protection-expected.json", + `"APPROVED"`, + false, + }, + { + "blocked", + "branch-protection-failed.json", + `"APPROVED"`, + false, + }, + { + "blocked", + "branch-protection-passed.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "ruleset-check-expected.json", + `"APPROVED"`, + false, + }, + { + "blocked", + "ruleset-check-failed.json", + `"APPROVED"`, + false, + }, + { + "blocked", + "ruleset-check-passed.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "ruleset-workflow-expected.json", + `"APPROVED"`, + false, + }, + { + "blocked", + "ruleset-workflow-failed.json", + `"APPROVED"`, + false, + }, + { + "blocked", + "ruleset-workflow-passed.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "ruleset-workflow-passed-sha-match.json", + `"APPROVED"`, + true, + }, + { + "blocked", + "ruleset-workflow-passed-sha-mismatch.json", + `"APPROVED"`, false, }, } @@ -813,25 +800,9 @@ func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApplyButWithNoBranc Ok(t, err) prJSON := string(jsBytes) - // Status Check Response - jsBytes, err = os.ReadFile("testdata/github-commit-status-full.json") - Ok(t, err) - commitJSON := string(jsBytes) - - // Branch protection Response - jsBytes, err = os.ReadFile("testdata/github-branch-protection-no-required-checks.json") + jsBytes, err = os.ReadFile("testdata/github-pull-request-mergeability/repository-id.json") Ok(t, err) - branchProtectionJSON := string(jsBytes) - - // List check suites Response - jsBytes, err = os.ReadFile("testdata/github-commit-check-suites-completed.json") - Ok(t, err) - checkSuites := string(jsBytes) - - // List check runs in a check suite - jsBytes, err = os.ReadFile("testdata/github-commit-check-suites-check-runs-completed.json") - Ok(t, err) - checkRuns := string(jsBytes) + repoIdJSON := string(jsBytes) for _, c := range cases { t.Run(c.state, func(t *testing.T) { @@ -841,36 +812,41 @@ func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApplyButWithNoBranc 1, ) - // reviewDecision Response - reviewDecision := fmt.Sprintf(`{ - "data": { - "repository": { - "pullRequest": { - "reviewDecision": %s - } - } - } - }`, c.reviewDecision) + // PR review decision and checks statuses Response + jsBytes, err = os.ReadFile("testdata/github-pull-request-mergeability/" + c.statusCheckRollupFilePath) + Ok(t, err) + prMergeableStatusJSON := string(jsBytes) + + // PR review decision and checks statuses Response + prMergeableStatus := strings.Replace(prMergeableStatusJSON, + `"reviewDecision": null,`, + fmt.Sprintf(`"reviewDecision": %s,`, c.reviewDecision), + 1, + ) testServer := httptest.NewTLSServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.RequestURI { - case "/api/v3/repos/octocat/Hello-World/pulls/1": + case "/api/v3/repos/octocat/repo/pulls/1": w.Write([]byte(response)) // nolint: errcheck return - case "/api/v3/repos/octocat/Hello-World/pulls/1/reviews?per_page=300": - w.Write([]byte("[]")) // nolint: errcheck - return - case "/api/v3/repos/octocat/Hello-World/commits/new-topic/status": - w.Write([]byte(commitJSON)) // nolint: errcheck case "/api/graphql": - w.Write([]byte(reviewDecision)) // nolint: errcheck - case "/api/v3/repos/octocat/Hello-World/branches/main/protection": - w.Write([]byte(branchProtectionJSON)) // nolint: errcheck - case "/api/v3/repos/octocat/Hello-World/commits/new-topic/check-suites": - w.Write([]byte(checkSuites)) // nolint: errcheck - case "/api/v3/repos/octocat/Hello-World/check-suites/1234567890/check-runs": - w.Write([]byte(checkRuns)) // nolint: errcheck + body, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("read body error: %v", err) + http.Error(w, "", http.StatusInternalServerError) + return + } + if strings.Contains(string(body), "pullRequest(") { + w.Write([]byte(prMergeableStatus)) // nolint: errcheck + return + } else if strings.Contains(string(body), "databaseId") { + w.Write([]byte(repoIdJSON)) // nolint: errcheck + return + } + t.Errorf("got unexpected request at %q", r.RequestURI) + http.Error(w, "not found", http.StatusNotFound) + return default: t.Errorf("got unexpected request at %q", r.RequestURI) http.Error(w, "not found", http.StatusNotFound) @@ -886,9 +862,9 @@ func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApplyButWithNoBranc actMergeable, err := client.PullIsMergeable( logger, models.Repo{ - FullName: "octocat/Hello-World", + FullName: "octocat/repo", Owner: "octocat", - Name: "Hello-World", + Name: "repo", CloneURL: "", SanitizedCloneURL: "", VCSHost: models.VCSHost{ diff --git a/server/events/vcs/testdata/github-branch-protection-no-required-checks.json b/server/events/vcs/testdata/github-branch-protection-no-required-checks.json deleted file mode 100644 index 4dd1496b79..0000000000 --- a/server/events/vcs/testdata/github-branch-protection-no-required-checks.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://api.github.com/repos/octocat/repo/branches/master/protection", - "required_pull_request_reviews": { - "url": "https://api.github.com/repos/octocat/repo/branches/master/protection/required_pull_request_reviews", - "dismiss_stale_reviews": false, - "require_code_owner_reviews": false, - "require_last_push_approval": false, - "required_approving_review_count": 1 - } -} diff --git a/server/events/vcs/testdata/github-branch-protection-required-checks.json b/server/events/vcs/testdata/github-branch-protection-required-checks.json deleted file mode 100644 index 9f422db9ea..0000000000 --- a/server/events/vcs/testdata/github-branch-protection-required-checks.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "url": "https://api.github.com/repos/octocat/Hello-World/branches/master/protection", - "required_status_checks": { - "url": "https://api.github.com/repos/octocat/Hello-World/branches/master/protection/required_status_checks", - "strict": true, - "contexts": [ - "atlantis/apply" - ], - "contexts_url": "https://api.github.com/repos/octocat/Hello-World/branches/master/protection/required_status_checks/contexts", - "checks": [ - { - "context": "atlantis/apply", - "app_id": 123456 - } - ] - } -} diff --git a/server/events/vcs/testdata/github-commit-check-suites-check-runs-completed.json b/server/events/vcs/testdata/github-commit-check-suites-check-runs-completed.json deleted file mode 100644 index 125e4d1ddf..0000000000 --- a/server/events/vcs/testdata/github-commit-check-suites-check-runs-completed.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "total_count": 1, - "check_runs": [ - { - "id": 4, - "head_sha": "ce587453ced02b1526dfb4cb910479d431683101", - "node_id": "MDg6Q2hlY2tSdW40", - "external_id": "", - "url": "https://api.github.com/repos/github/hello-world/check-runs/4", - "html_url": "https://github.com/github/hello-world/runs/4", - "details_url": "https://example.com", - "status": "completed", - "conclusion": "success", - "started_at": "2018-05-04T01:14:52Z", - "completed_at": "2018-05-04T01:14:52Z", - "output": { - "title": "Mighty Readme report", - "summary": "There are 0 failures, 2 warnings, and 1 notice.", - "text": "You may have some misspelled words on lines 2 and 4. You also may want to add a section in your README about how to install your app.", - "annotations_count": 2, - "annotations_url": "https://api.github.com/repos/github/hello-world/check-runs/4/annotations" - }, - "name": "mighty_readme", - "check_suite": { - "id": 5 - }, - "app": { - "id": 1, - "slug": "octoapp", - "node_id": "MDExOkludGVncmF0aW9uMQ==", - "owner": { - "login": "github", - "id": 1, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", - "url": "https://api.github.com/orgs/github", - "repos_url": "https://api.github.com/orgs/github/repos", - "events_url": "https://api.github.com/orgs/github/events", - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "gravatar_id": "", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": true - }, - "name": "Octocat App", - "description": "", - "external_url": "https://example.com", - "html_url": "https://github.com/apps/octoapp", - "created_at": "2017-07-08T16:18:44-04:00", - "updated_at": "2017-07-08T16:18:44-04:00", - "permissions": { - "metadata": "read", - "contents": "read", - "issues": "write", - "single_file": "write" - }, - "events": [ - "push", - "pull_request" - ] - }, - "pull_requests": [ - { - "url": "https://api.github.com/repos/github/hello-world/pulls/1", - "id": 1934, - "number": 3956, - "head": { - "ref": "say-hello", - "sha": "3dca65fa3e8d4b3da3f3d056c59aee1c50f41390", - "repo": { - "id": 526, - "url": "https://api.github.com/repos/github/hello-world", - "name": "hello-world" - } - }, - "base": { - "ref": "master", - "sha": "e7fdf7640066d71ad16a86fbcbb9c6a10a18af4f", - "repo": { - "id": 526, - "url": "https://api.github.com/repos/github/hello-world", - "name": "hello-world" - } - } - } - ] - } - ] -} diff --git a/server/events/vcs/testdata/github-commit-check-suites-completed.json b/server/events/vcs/testdata/github-commit-check-suites-completed.json deleted file mode 100644 index b8af9c32a9..0000000000 --- a/server/events/vcs/testdata/github-commit-check-suites-completed.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "total_count": 1, - "check_suites": [ - { - "id": 1234567890, - "node_id": "CS_kwDOHE7PYM8AAAAB2iIZfQ", - "head_branch": "atlantis-patch-2", - "head_sha": "4273e07c528292222f119a040079093bf1f11232", - "status": "completed", - "conclusion": null, - "url": "https://api.github.com/repos/octocat/Hello-World/check-suites/1234567890", - "before": "0000000000000000000000000000000000000000", - "after": "4273e07c528292222f119a040079093bf1f11232", - "pull_requests": [ - { - "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1", - "id": 1035065545, - "number": 1, - "head": { - "ref": "atlantis-patch-2", - "sha": "4273e07c528292222f119a040079093bf1f11232", - "repo": { - "id": 474926944, - "url": "https://api.github.com/repos/octocat/Hello-World", - "name": "Hello-World" - } - }, - "base": { - "ref": "main", - "sha": "6f5744874b33ceb6a5c91edc91085991dbd1f61a", - "repo": { - "id": 474926944, - "url": "https://api.github.com/repos/octocat/Hello-World", - "name": "Hello-World" - } - } - } - ], - "app": { - "id": 184783, - "slug": "atlantis", - "node_id": "A_kwHOBbMkBs4AAtHP", - "owner": { - "login": "octocat", - "id": 95626246, - "node_id": "O_kgDOBbMkBg", - "avatar_url": "https://avatars.githubusercontent.com/u/95626246?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "Organization", - "site_admin": false - }, - "name": "atlantis", - "description": "", - "external_url": "https://atlantis.localhost/", - "html_url": "https://github.com/apps/atlantis", - "created_at": "2022-03-29T08:51:26Z", - "updated_at": "2022-07-01T11:35:37Z", - "permissions": { - "administration": "read", - "checks": "write", - "contents": "write", - "issues": "write", - "metadata": "read", - "pull_requests": "write", - "statuses": "write" - }, - "events": [] - }, - "created_at": "2022-08-24T07:06:21Z", - "updated_at": "2022-08-24T07:06:21Z", - "rerequestable": true, - "runs_rerequestable": true, - "latest_check_runs_count": 0, - "check_runs_url": "https://api.github.com/repos/octocat/Hello-World/check-suites/1234567890/check-runs", - "head_commit": { - "id": "4273e07c528292222f119a040079093bf1f11232", - "tree_id": "56781332464aabdfae51b7f37f72ffc6ce8ce54e", - "message": "test atlantis", - "timestamp": "2022-08-24T07:06:21Z", - "author": { - "name": "octocat", - "email": "octocat@noreply.github.com" - }, - "committer": { - "name": "GitHub", - "email": "noreply@github.com" - } - }, - "repository": { - "id": 474926944, - "node_id": "R_kgDOHE7PYA", - "name": "Hello-World", - "full_name": "octocat/Hello-World", - "private": true, - "owner": { - "login": "octocat", - "id": 95626246, - "node_id": "O_kgDOBbMkBg", - "avatar_url": "https://avatars.githubusercontent.com/u/95626246?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/octocat/Hello-World", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/octocat/Hello-World", - "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", - "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", - "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", - "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", - "events_url": "https://api.github.com/repos/octocat/Hello-World/events", - "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", - "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", - "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", - "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", - "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", - "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", - "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", - "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", - "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", - "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", - "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", - "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", - "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", - "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", - "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", - "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", - "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", - "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments" - } - } - ] -} diff --git a/server/events/vcs/testdata/github-commit-check-suites.json b/server/events/vcs/testdata/github-commit-check-suites.json deleted file mode 100644 index 017aee7e01..0000000000 --- a/server/events/vcs/testdata/github-commit-check-suites.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "total_count": 1, - "check_suites": [ - { - "id": 1234567890, - "node_id": "CS_kwDOHE7PYM8AAAAB2iIZfQ", - "head_branch": "atlantis-patch-2", - "head_sha": "4273e07c528292222f119a040079093bf1f11232", - "status": "queued", - "conclusion": null, - "url": "https://api.github.com/repos/octocat/Hello-World/check-suites/1234567890", - "before": "0000000000000000000000000000000000000000", - "after": "4273e07c528292222f119a040079093bf1f11232", - "pull_requests": [ - { - "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1", - "id": 1035065545, - "number": 1, - "head": { - "ref": "atlantis-patch-2", - "sha": "4273e07c528292222f119a040079093bf1f11232", - "repo": { - "id": 474926944, - "url": "https://api.github.com/repos/octocat/Hello-World", - "name": "Hello-World" - } - }, - "base": { - "ref": "main", - "sha": "6f5744874b33ceb6a5c91edc91085991dbd1f61a", - "repo": { - "id": 474926944, - "url": "https://api.github.com/repos/octocat/Hello-World", - "name": "Hello-World" - } - } - } - ], - "app": { - "id": 184783, - "slug": "atlantis", - "node_id": "A_kwHOBbMkBs4AAtHP", - "owner": { - "login": "octocat", - "id": 95626246, - "node_id": "O_kgDOBbMkBg", - "avatar_url": "https://avatars.githubusercontent.com/u/95626246?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "Organization", - "site_admin": false - }, - "name": "atlantis", - "description": "", - "external_url": "https://atlantis.localhost/", - "html_url": "https://github.com/apps/atlantis", - "created_at": "2022-03-29T08:51:26Z", - "updated_at": "2022-07-01T11:35:37Z", - "permissions": { - "administration": "read", - "checks": "write", - "contents": "write", - "issues": "write", - "metadata": "read", - "pull_requests": "write", - "statuses": "write" - }, - "events": [] - }, - "created_at": "2022-08-24T07:06:21Z", - "updated_at": "2022-08-24T07:06:21Z", - "rerequestable": true, - "runs_rerequestable": true, - "latest_check_runs_count": 0, - "check_runs_url": "https://api.github.com/repos/octocat/Hello-World/check-suites/1234567890/check-runs", - "head_commit": { - "id": "4273e07c528292222f119a040079093bf1f11232", - "tree_id": "56781332464aabdfae51b7f37f72ffc6ce8ce54e", - "message": "test atlantis", - "timestamp": "2022-08-24T07:06:21Z", - "author": { - "name": "octocat", - "email": "octocat@noreply.github.com" - }, - "committer": { - "name": "GitHub", - "email": "noreply@github.com" - } - }, - "repository": { - "id": 474926944, - "node_id": "R_kgDOHE7PYA", - "name": "Hello-World", - "full_name": "octocat/Hello-World", - "private": true, - "owner": { - "login": "octocat", - "id": 95626246, - "node_id": "O_kgDOBbMkBg", - "avatar_url": "https://avatars.githubusercontent.com/u/95626246?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/octocat/Hello-World", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/octocat/Hello-World", - "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", - "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", - "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", - "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", - "events_url": "https://api.github.com/repos/octocat/Hello-World/events", - "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", - "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", - "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", - "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", - "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", - "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", - "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", - "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", - "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", - "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", - "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", - "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", - "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", - "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", - "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", - "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", - "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", - "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments" - } - } - ] -} diff --git a/server/events/vcs/testdata/github-commit-status-full.json b/server/events/vcs/testdata/github-commit-status-full.json deleted file mode 100644 index a042101c40..0000000000 --- a/server/events/vcs/testdata/github-commit-status-full.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "state": "blocked", - "statuses": [ - { - "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", - "id": 16230299674, - "node_id": "SC_kwDOFRFvL88AAAADx2a4Gg", - "state": "success", - "description": "Plan succeeded.", - "target_url": "https://localhost/jobs/octocat/Hello-World/1/project1", - "context": "atlantis/plan: project1", - "created_at": "2022-02-10T15:26:01Z", - "updated_at": "2022-02-10T15:26:01Z" - }, - { - "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", - "id": 16230303174, - "node_id": "SC_kwDOFRFvL88AAAADx2bFxg", - "state": "success", - "description": "Plan succeeded.", - "target_url": "https://localhost/jobs/octocat/Hello-World/1/project2", - "context": "atlantis/plan: project2", - "created_at": "2022-02-10T15:26:12Z", - "updated_at": "2022-02-10T15:26:12Z" - }, - { - "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", - "id": 16230303679, - "node_id": "SC_kwDOFRFvL88AAAADx2bHvw", - "state": "success", - "description": "2/2 projects planned successfully.", - "target_url": "", - "context": "atlantis/plan", - "created_at": "2022-02-10T15:26:13Z", - "updated_at": "2022-02-10T15:26:13Z" - }, - { - "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", - "id": 16230307923, - "node_id": "SC_kwDOFRFvL88AAAADx2bYUw", - "state": "failure", - "description": "Apply failed.", - "target_url": "https://localhost/jobs/octocat/Hello-World/1/project1", - "context": "atlantis/apply: project1", - "created_at": "2022-02-10T15:26:27Z", - "updated_at": "2022-02-10T15:26:27Z" - }, - { - "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", - "id": 16230308153, - "node_id": "SC_kwDOFRFvL88AAAADx2bZOQ", - "state": "failure", - "description": "Apply failed.", - "target_url": "https://localhost/jobs/octocat/Hello-World/1/project2", - "context": "atlantis/apply: project2", - "created_at": "2022-02-10T15:26:27Z", - "updated_at": "2022-02-10T15:26:27Z" - }, - { - "url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", - "id": 16230308528, - "node_id": "SC_kwDOFRFvL88AAAADx2basA", - "state": "failure", - "description": "0/2 projects applied successfully.", - "target_url": "", - "context": "atlantis/apply", - "created_at": "2022-02-10T15:26:28Z", - "updated_at": "2022-02-10T15:26:28Z" - } - ], - "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", - "total_count": 0, - "repository": { - "id": 1296269, - "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", - "name": "Hello-World", - "full_name": "octocat/Hello-World", - "private": false, - "owner": { - "login": "octocat", - "id": 583231, - "node_id": "MDQ6VXNlcjU4MzIzMQ==", - "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/octocat", - "html_url": "https://github.com/octocat", - "followers_url": "https://api.github.com/users/octocat/followers", - "following_url": "https://api.github.com/users/octocat/following{/other_user}", - "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", - "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", - "organizations_url": "https://api.github.com/users/octocat/orgs", - "repos_url": "https://api.github.com/users/octocat/repos", - "events_url": "https://api.github.com/users/octocat/events{/privacy}", - "received_events_url": "https://api.github.com/users/octocat/received_events", - "type": "User", - "site_admin": false - }, - "html_url": "https://github.com/octocat/Hello-World", - "description": "My first repository on GitHub!", - "fork": false, - "url": "https://api.github.com/repos/octocat/Hello-World", - "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", - "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", - "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", - "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", - "events_url": "https://api.github.com/repos/octocat/Hello-World/events", - "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", - "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", - "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", - "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", - "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", - "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", - "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", - "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", - "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", - "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", - "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", - "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", - "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", - "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", - "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", - "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", - "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", - "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments" - }, - "commit_url": "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", - "url": "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/status" - } \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-expected.json b/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-expected.json new file mode 100644 index 0000000000..ad84fee1f3 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-expected.json @@ -0,0 +1,58 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-failed.json b/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-failed.json new file mode 100644 index 0000000000..8b1ee9c1b5 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-failed.json @@ -0,0 +1,64 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "FAILED", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-passed.json b/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-passed.json new file mode 100644 index 0000000000..3dd40fcf2a --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-passed.json @@ -0,0 +1,64 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "SUCCESS", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/repository-id.json b/server/events/vcs/testdata/github-pull-request-mergeability/repository-id.json new file mode 100644 index 0000000000..9e6d02e114 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/repository-id.json @@ -0,0 +1,7 @@ +{ + "data": { + "repository": { + "databaseId": 120519269 + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-atlantis-apply-pending.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-atlantis-apply-pending.json new file mode 100644 index 0000000000..4ce1799bcd --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-atlantis-apply-pending.json @@ -0,0 +1,65 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-expected.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-expected.json new file mode 100644 index 0000000000..d0914e6395 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-expected.json @@ -0,0 +1,68 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-failed.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-failed.json new file mode 100644 index 0000000000..6bc4f55c25 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-failed.json @@ -0,0 +1,74 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "FAILED", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-neutral.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-neutral.json new file mode 100644 index 0000000000..f6b3183780 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-neutral.json @@ -0,0 +1,74 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my-required-expected-check", + "conclusion": "NEUTRAL", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-passed.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-passed.json new file mode 100644 index 0000000000..95b7b5350c --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-passed.json @@ -0,0 +1,74 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "SUCCESS", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-pending.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-pending.json new file mode 100644 index 0000000000..60d70332de --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-pending.json @@ -0,0 +1,74 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "PENDING", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-skipped.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-skipped.json new file mode 100644 index 0000000000..8427088fe0 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-skipped.json @@ -0,0 +1,74 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my-required-expected-check", + "conclusion": "SKIPPED", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-evaluate-workflow-failed.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-evaluate-workflow-failed.json new file mode 100644 index 0000000000..ddde1d8ac5 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-evaluate-workflow-failed.json @@ -0,0 +1,95 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + }, + { + "type": "WORKFLOWS", + "repositoryRuleset": { + "enforcement": "EVALUATE" + }, + "parameters": { + "workflows": [ + { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryId": 120519269, + "sha": null + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my required (evaluate-enforcement) check", + "conclusion": "FAILURE", + "isRequired": true, + "checkSuite": { + "workflowRun": { + "file": { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryFileUrl": "https://github.com/runatlantis/atlantis/blob/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/.github/workflows/my-required-workflow.yaml", + "repositoryName": "runatlantis/atlantis" + } + } + } + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-optional-check-failed.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-optional-check-failed.json new file mode 100644 index 0000000000..6368cbe69e --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-optional-check-failed.json @@ -0,0 +1,71 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my-optional-check", + "conclusion": "FAILURE", + "isRequired": false + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-optional-status-failed.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-optional-status-failed.json new file mode 100644 index 0000000000..111ff4337a --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-optional-status-failed.json @@ -0,0 +1,71 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "StatusContext", + "context": "my-optional-check", + "state": "FAILURE", + "isRequired": false + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-expected.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-expected.json new file mode 100644 index 0000000000..9b21af9390 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-expected.json @@ -0,0 +1,80 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + }, + { + "type": "WORKFLOWS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "workflows": [ + { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryId": 120519269, + "sha": null + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-failed.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-failed.json new file mode 100644 index 0000000000..570fdb9276 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-failed.json @@ -0,0 +1,95 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + }, + { + "type": "WORKFLOWS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "workflows": [ + { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryId": 120519269, + "sha": null + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my required check", + "conclusion": "FAILURE", + "isRequired": true, + "checkSuite": { + "workflowRun": { + "file": { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryFileUrl": "https://github.com/runatlantis/atlantis/blob/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/.github/workflows/my-required-workflow.yaml", + "repositoryName": "runatlantis/atlantis" + } + } + } + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-sha-match.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-sha-match.json new file mode 100644 index 0000000000..e40b014ab3 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-sha-match.json @@ -0,0 +1,95 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + }, + { + "type": "WORKFLOWS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "workflows": [ + { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryId": 120519269, + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my required check", + "conclusion": "SUCCESS", + "isRequired": true, + "checkSuite": { + "workflowRun": { + "file": { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryFileUrl": "https://github.com/runatlantis/atlantis/blob/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/.github/workflows/my-required-workflow.yaml", + "repositoryName": "runatlantis/atlantis" + } + } + } + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-sha-mismatch.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-sha-mismatch.json new file mode 100644 index 0000000000..5a79ce3e33 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-sha-mismatch.json @@ -0,0 +1,95 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + }, + { + "type": "WORKFLOWS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "workflows": [ + { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryId": 120519269, + "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my required check", + "conclusion": "SUCCESS", + "isRequired": true, + "checkSuite": { + "workflowRun": { + "file": { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryFileUrl": "https://github.com/runatlantis/atlantis/blob/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/.github/workflows/my-required-workflow.yaml", + "repositoryName": "runatlantis/atlantis" + } + } + } + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed.json new file mode 100644 index 0000000000..6a88b4c8c5 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed.json @@ -0,0 +1,95 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + }, + { + "type": "WORKFLOWS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "workflows": [ + { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryId": 120519269, + "sha": null + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my required check", + "conclusion": "SUCCESS", + "isRequired": true, + "checkSuite": { + "workflowRun": { + "file": { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryFileUrl": "https://github.com/runatlantis/atlantis/blob/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/.github/workflows/my-required-workflow.yaml", + "repositoryName": "runatlantis/atlantis" + } + } + } + } + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file