mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-03 15:08:49 +00:00
While using a PR from a fork and the "Github allow mergeable bypass apply" flag, the mergeable checks were run with the wrong owner in the request, leading to 404. By choosing the owner from the head repo data it should work both, for fork PRs and in-repo PRs. Co-authored-by: Dylan Page <dylan.page@autodesk.com>
1369 lines
38 KiB
Go
1369 lines
38 KiB
Go
package vcs_test
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/runatlantis/atlantis/server/events/command"
|
|
"github.com/runatlantis/atlantis/server/events/models"
|
|
"github.com/runatlantis/atlantis/server/events/vcs"
|
|
"github.com/runatlantis/atlantis/server/logging"
|
|
. "github.com/runatlantis/atlantis/testing"
|
|
|
|
"github.com/shurcooL/githubv4"
|
|
)
|
|
|
|
// GetModifiedFiles should make multiple requests if more than one page
|
|
// and concat results.
|
|
func TestGithubClient_GetModifiedFiles(t *testing.T) {
|
|
logger := logging.NewNoopLogger(t)
|
|
respTemplate := `[
|
|
{
|
|
"sha": "bbcd538c8e72b8c175046e27cc8f907076331401",
|
|
"filename": "%s",
|
|
"status": "added",
|
|
"additions": 103,
|
|
"deletions": 21,
|
|
"changes": 124,
|
|
"blob_url": "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt",
|
|
"raw_url": "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt",
|
|
"contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/file1.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|
"patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"
|
|
}
|
|
]`
|
|
firstResp := fmt.Sprintf(respTemplate, "file1.txt")
|
|
secondResp := fmt.Sprintf(respTemplate, "file2.txt")
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.RequestURI {
|
|
// The first request should hit this URL.
|
|
case "/api/v3/repos/owner/repo/pulls/1/files?per_page=300":
|
|
// We write a header that means there's an additional page.
|
|
w.Header().Add("Link", `<https://api.github.com/resource?page=2>; rel="next",
|
|
<https://api.github.com/resource?page=2>; rel="last"`)
|
|
w.Write([]byte(firstResp)) // nolint: errcheck
|
|
return
|
|
// The second should hit this URL.
|
|
case "/api/v3/repos/owner/repo/pulls/1/files?page=2&per_page=300":
|
|
w.Write([]byte(secondResp)) // 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{}, logger)
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
files, err := client.GetModifiedFiles(models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
}, models.PullRequest{
|
|
Num: 1,
|
|
})
|
|
Ok(t, err)
|
|
Equals(t, []string{"file1.txt", "file2.txt"}, files)
|
|
}
|
|
|
|
// GetModifiedFiles should include the source and destination of a moved
|
|
// file.
|
|
func TestGithubClient_GetModifiedFilesMovedFile(t *testing.T) {
|
|
resp := `[
|
|
{
|
|
"sha": "bbcd538c8e72b8c175046e27cc8f907076331401",
|
|
"filename": "new/filename.txt",
|
|
"previous_filename": "previous/filename.txt",
|
|
"status": "renamed",
|
|
"additions": 103,
|
|
"deletions": 21,
|
|
"changes": 124,
|
|
"blob_url": "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt",
|
|
"raw_url": "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt",
|
|
"contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/file1.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|
"patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"
|
|
}
|
|
]`
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.RequestURI {
|
|
// The first request should hit this URL.
|
|
case "/api/v3/repos/owner/repo/pulls/1/files?per_page=300":
|
|
w.Write([]byte(resp)) // nolint: errcheck
|
|
return
|
|
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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
files, err := client.GetModifiedFiles(models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
}, models.PullRequest{
|
|
Num: 1,
|
|
})
|
|
Ok(t, err)
|
|
Equals(t, []string{"new/filename.txt", "previous/filename.txt"}, files)
|
|
}
|
|
|
|
func TestGithubClient_PaginatesComments(t *testing.T) {
|
|
calls := 0
|
|
issueResps := []string{
|
|
`[
|
|
{"node_id": "1", "body": "asd\nplan\nasd", "user": {"login": "someone-else"}},
|
|
{"node_id": "2", "body": "asd plan\nasd", "user": {"login": "user"}}
|
|
]`,
|
|
`[
|
|
{"node_id": "3", "body": "asd", "user": {"login": "someone-else"}},
|
|
{"node_id": "4", "body": "asdasd", "user": {"login": "someone-else"}}
|
|
]`,
|
|
`[
|
|
{"node_id": "5", "body": "asd plan", "user": {"login": "someone-else"}},
|
|
{"node_id": "6", "body": "asd\nplan", "user": {"login": "user"}}
|
|
]`,
|
|
`[
|
|
{"node_id": "7", "body": "asd", "user": {"login": "user"}},
|
|
{"node_id": "8", "body": "asd plan \n asd", "user": {"login": "user"}}
|
|
]`,
|
|
}
|
|
minimizeResp := "{}"
|
|
type graphQLCall struct {
|
|
Variables struct {
|
|
Input githubv4.MinimizeCommentInput `json:"input"`
|
|
} `json:"variables"`
|
|
}
|
|
gotMinimizeCalls := make([]graphQLCall, 0, 2)
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method + " " + r.RequestURI {
|
|
case "POST /api/graphql":
|
|
defer r.Body.Close() // nolint: errcheck
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Errorf("read body error: %v", err)
|
|
http.Error(w, "server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
call := graphQLCall{}
|
|
err = json.Unmarshal(body, &call)
|
|
if err != nil {
|
|
t.Errorf("parse body error: %v", err)
|
|
http.Error(w, "server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
gotMinimizeCalls = append(gotMinimizeCalls, call)
|
|
w.Write([]byte(minimizeResp)) // nolint: errcheck
|
|
return
|
|
default:
|
|
if r.Method != "GET" || !strings.HasPrefix(r.RequestURI, "/api/v3/repos/owner/repo/issues/123/comments") {
|
|
t.Errorf("got unexpected request at %q", r.RequestURI)
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
if (calls + 1) < len(issueResps) {
|
|
w.Header().Add(
|
|
"Link",
|
|
fmt.Sprintf(
|
|
`<http://%s/api/v3/repos/owner/repo/issues/123/comments?page=%d&per_page=100>; rel="next"`,
|
|
r.Host,
|
|
calls+1,
|
|
),
|
|
)
|
|
}
|
|
w.Write([]byte(issueResps[calls])) // nolint: errcheck
|
|
calls++
|
|
}
|
|
}),
|
|
)
|
|
|
|
testServerURL, err := url.Parse(testServer.URL)
|
|
Ok(t, err)
|
|
|
|
client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
err = client.HidePrevCommandComments(
|
|
models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Hostname: "github.com",
|
|
Type: models.Github,
|
|
},
|
|
},
|
|
123,
|
|
command.Plan.TitleString(),
|
|
)
|
|
Ok(t, err)
|
|
Equals(t, 2, len(gotMinimizeCalls))
|
|
Equals(t, "2", gotMinimizeCalls[0].Variables.Input.SubjectID)
|
|
Equals(t, "8", gotMinimizeCalls[1].Variables.Input.SubjectID)
|
|
Equals(t, githubv4.ReportedContentClassifiersOutdated, gotMinimizeCalls[0].Variables.Input.Classifier)
|
|
Equals(t, githubv4.ReportedContentClassifiersOutdated, gotMinimizeCalls[1].Variables.Input.Classifier)
|
|
}
|
|
|
|
func TestGithubClient_HideOldComments(t *testing.T) {
|
|
// Only comment 6 should be minimized, because it's by the same Atlantis bot user
|
|
// and it has "plan" in the first line of the comment body.
|
|
issueResp := `[
|
|
{"node_id": "1", "body": "asd\nplan\nasd", "user": {"login": "someone-else"}},
|
|
{"node_id": "2", "body": "asd plan\nasd", "user": {"login": "someone-else"}},
|
|
{"node_id": "3", "body": "asdasdasd\nasdasdasd", "user": {"login": "someone-else"}},
|
|
{"node_id": "4", "body": "asdasdasd\nasdasdasd", "user": {"login": "user"}},
|
|
{"node_id": "5", "body": "asd\nplan\nasd", "user": {"login": "user"}},
|
|
{"node_id": "6", "body": "asd plan\nasd", "user": {"login": "user"}},
|
|
{"node_id": "7", "body": "asdasdasd", "user": {"login": "user"}},
|
|
{"node_id": "8", "body": "asd plan\nasd", "user": {"login": "user"}},
|
|
{"node_id": "9", "body": "Continued Plan from previous comment\nasd", "user": {"login": "user"}}
|
|
]`
|
|
minimizeResp := "{}"
|
|
type graphQLCall struct {
|
|
Variables struct {
|
|
Input githubv4.MinimizeCommentInput `json:"input"`
|
|
} `json:"variables"`
|
|
}
|
|
gotMinimizeCalls := make([]graphQLCall, 0, 1)
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method + " " + r.RequestURI {
|
|
// This gets the pull request's comments.
|
|
case "GET /api/v3/repos/owner/repo/issues/123/comments?direction=asc&sort=created":
|
|
w.Write([]byte(issueResp)) // nolint: errcheck
|
|
return
|
|
case "POST /api/graphql":
|
|
defer r.Body.Close() // nolint: errcheck
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Errorf("read body error: %v", err)
|
|
http.Error(w, "server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
call := graphQLCall{}
|
|
err = json.Unmarshal(body, &call)
|
|
if err != nil {
|
|
t.Errorf("parse body error: %v", err)
|
|
http.Error(w, "server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
gotMinimizeCalls = append(gotMinimizeCalls, call)
|
|
w.Write([]byte(minimizeResp)) // nolint: errcheck
|
|
return
|
|
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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
err = client.HidePrevCommandComments(
|
|
models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Hostname: "github.com",
|
|
Type: models.Github,
|
|
},
|
|
},
|
|
123,
|
|
command.Plan.TitleString(),
|
|
)
|
|
Ok(t, err)
|
|
Equals(t, 3, len(gotMinimizeCalls))
|
|
Equals(t, "6", gotMinimizeCalls[0].Variables.Input.SubjectID)
|
|
Equals(t, "9", gotMinimizeCalls[2].Variables.Input.SubjectID)
|
|
Equals(t, githubv4.ReportedContentClassifiersOutdated, gotMinimizeCalls[0].Variables.Input.Classifier)
|
|
}
|
|
|
|
func TestGithubClient_UpdateStatus(t *testing.T) {
|
|
cases := []struct {
|
|
status models.CommitStatus
|
|
expState string
|
|
}{
|
|
{
|
|
models.PendingCommitStatus,
|
|
"pending",
|
|
},
|
|
{
|
|
models.SuccessCommitStatus,
|
|
"success",
|
|
},
|
|
{
|
|
models.FailedCommitStatus,
|
|
"failure",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.status.String(), func(t *testing.T) {
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.RequestURI {
|
|
case "/api/v3/repos/owner/repo/statuses/":
|
|
body, err := io.ReadAll(r.Body)
|
|
Ok(t, err)
|
|
exp := fmt.Sprintf(`{"state":"%s","target_url":"https://google.com","description":"description","context":"src"}%s`, c.expState, "\n")
|
|
Equals(t, exp, string(body))
|
|
defer r.Body.Close() // nolint: errcheck
|
|
w.WriteHeader(http.StatusOK)
|
|
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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
err = client.UpdateStatus(models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
}, models.PullRequest{
|
|
Num: 1,
|
|
}, c.status, "src", "description", "https://google.com")
|
|
Ok(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_PullIsApproved(t *testing.T) {
|
|
respTemplate := `[
|
|
{
|
|
"id": %d,
|
|
"node_id": "MDE3OlB1bGxSZXF1ZXN0UmV2aWV3ODA=",
|
|
"user": {
|
|
"login": "octocat",
|
|
"id": 1,
|
|
"node_id": "MDQ6VXNlcjE=",
|
|
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
|
"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
|
|
},
|
|
"body": "Here is the body for the review.",
|
|
"commit_id": "ecdd80bb57125d7ba9641ffaa4d7d2c19d3f3091",
|
|
"state": "CHANGES_REQUESTED",
|
|
"html_url": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-%d",
|
|
"pull_request_url": "https://api.github.com/repos/octocat/Hello-World/pulls/12",
|
|
"_links": {
|
|
"html": {
|
|
"href": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-%d"
|
|
},
|
|
"pull_request": {
|
|
"href": "https://api.github.com/repos/octocat/Hello-World/pulls/12"
|
|
}
|
|
}
|
|
}
|
|
]`
|
|
firstResp := fmt.Sprintf(respTemplate, 80, 80, 80)
|
|
secondResp := fmt.Sprintf(respTemplate, 81, 81, 81)
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.RequestURI {
|
|
// The first request should hit this URL.
|
|
case "/api/v3/repos/owner/repo/pulls/1/reviews?per_page=300":
|
|
// We write a header that means there's an additional page.
|
|
w.Header().Add("Link", `<https://api.github.com/resource?page=2>; rel="next",
|
|
<https://api.github.com/resource?page=2>; rel="last"`)
|
|
w.Write([]byte(firstResp)) // nolint: errcheck
|
|
return
|
|
// The second should hit this URL.
|
|
case "/api/v3/repos/owner/repo/pulls/1/reviews?page=2&per_page=300":
|
|
w.Write([]byte(secondResp)) // 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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
approvalStatus, err := client.PullIsApproved(models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
}, models.PullRequest{
|
|
Num: 1,
|
|
})
|
|
Ok(t, err)
|
|
Equals(t, false, approvalStatus.IsApproved)
|
|
}
|
|
|
|
func TestGithubClient_PullIsMergeable(t *testing.T) {
|
|
vcsStatusName := "atlantis-test"
|
|
cases := []struct {
|
|
state string
|
|
expMergeable bool
|
|
}{
|
|
{
|
|
"dirty",
|
|
false,
|
|
},
|
|
{
|
|
"unknown",
|
|
false,
|
|
},
|
|
{
|
|
"blocked",
|
|
false,
|
|
},
|
|
{
|
|
"behind",
|
|
false,
|
|
},
|
|
{
|
|
"random",
|
|
false,
|
|
},
|
|
{
|
|
"unstable",
|
|
true,
|
|
},
|
|
{
|
|
"has_hooks",
|
|
true,
|
|
},
|
|
{
|
|
"clean",
|
|
true,
|
|
},
|
|
{
|
|
"",
|
|
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)
|
|
|
|
//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,
|
|
`"mergeable_state": "clean"`,
|
|
fmt.Sprintf(`"mergeable_state": "%s"`, c.state),
|
|
1,
|
|
)
|
|
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.RequestURI {
|
|
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)
|
|
return
|
|
}
|
|
}))
|
|
testServerURL, err := url.Parse(testServer.URL)
|
|
Ok(t, err)
|
|
client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
actMergeable, err := client.PullIsMergeable(models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
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_PullIsMergeableWithAllowMergeableBypassApply(t *testing.T) {
|
|
vcsStatusName := "atlantis"
|
|
cases := []struct {
|
|
state string
|
|
reviewDecision string
|
|
expMergeable bool
|
|
}{
|
|
{
|
|
"dirty",
|
|
`"REVIEW_REQUIRED"`,
|
|
false,
|
|
},
|
|
{
|
|
"unknown",
|
|
`"REVIEW_REQUIRED"`,
|
|
false,
|
|
},
|
|
{
|
|
"blocked",
|
|
`"REVIEW_REQUIRED"`,
|
|
false,
|
|
},
|
|
{
|
|
"blocked",
|
|
`"APPROVED"`,
|
|
true,
|
|
},
|
|
{
|
|
"blocked",
|
|
"null",
|
|
true,
|
|
},
|
|
{
|
|
"behind",
|
|
`"REVIEW_REQUIRED"`,
|
|
false,
|
|
},
|
|
{
|
|
"random",
|
|
`"REVIEW_REQUIRED"`,
|
|
false,
|
|
},
|
|
{
|
|
"unstable",
|
|
`"REVIEW_REQUIRED"`,
|
|
true,
|
|
},
|
|
{
|
|
"has_hooks",
|
|
`"APPROVED"`,
|
|
true,
|
|
},
|
|
{
|
|
"clean",
|
|
`"APPROVED"`,
|
|
true,
|
|
},
|
|
{
|
|
"",
|
|
`"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}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
actMergeable, err := client.PullIsMergeable(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_MergePullHandlesError(t *testing.T) {
|
|
cases := []struct {
|
|
code int
|
|
message string
|
|
merged string
|
|
expErr string
|
|
}{
|
|
{
|
|
code: 200,
|
|
message: "Pull Request successfully merged",
|
|
merged: "true",
|
|
expErr: "",
|
|
},
|
|
{
|
|
code: 405,
|
|
message: "Pull Request is not mergeable",
|
|
expErr: "405 Pull Request is not mergeable []",
|
|
},
|
|
{
|
|
code: 409,
|
|
message: "Head branch was modified. Review and try the merge again.",
|
|
expErr: "409 Head branch was modified. Review and try the merge again. []",
|
|
},
|
|
}
|
|
|
|
jsBytes, err := os.ReadFile("testdata/github-repo.json")
|
|
Ok(t, err)
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.message, func(t *testing.T) {
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.RequestURI {
|
|
case "/api/v3/repos/owner/repo":
|
|
w.Write(jsBytes) // nolint: errcheck
|
|
return
|
|
case "/api/v3/repos/owner/repo/pulls/1/merge":
|
|
body, err := io.ReadAll(r.Body)
|
|
Ok(t, err)
|
|
exp := "{\"merge_method\":\"merge\"}\n"
|
|
Equals(t, exp, string(body))
|
|
var resp string
|
|
if c.code == 200 {
|
|
resp = fmt.Sprintf(`{"message":"%s","merged":%s}%s`, c.message, c.merged, "\n")
|
|
} else {
|
|
resp = fmt.Sprintf(`{"message":"%s"}%s`, c.message, "\n")
|
|
}
|
|
defer r.Body.Close() // nolint: errcheck
|
|
w.WriteHeader(c.code)
|
|
w.Write([]byte(resp)) // 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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
err = client.MergePull(
|
|
models.PullRequest{
|
|
BaseRepo: models.Repo{
|
|
FullName: "owner/repo",
|
|
Owner: "owner",
|
|
Name: "repo",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
},
|
|
Num: 1,
|
|
}, models.PullRequestOptions{
|
|
DeleteSourceBranchOnMerge: false,
|
|
})
|
|
|
|
if c.expErr == "" {
|
|
Ok(t, err)
|
|
} else {
|
|
ErrContains(t, c.expErr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test that if the pull request only allows a certain merge method that we
|
|
// use that method
|
|
func TestGithubClient_MergePullCorrectMethod(t *testing.T) {
|
|
cases := map[string]struct {
|
|
allowMerge bool
|
|
allowRebase bool
|
|
allowSquash bool
|
|
expMethod string
|
|
}{
|
|
"all true": {
|
|
allowMerge: true,
|
|
allowRebase: true,
|
|
allowSquash: true,
|
|
expMethod: "merge",
|
|
},
|
|
"all false (edge case)": {
|
|
allowMerge: false,
|
|
allowRebase: false,
|
|
allowSquash: false,
|
|
expMethod: "merge",
|
|
},
|
|
"merge: false rebase: true squash: true": {
|
|
allowMerge: false,
|
|
allowRebase: true,
|
|
allowSquash: true,
|
|
expMethod: "rebase",
|
|
},
|
|
"merge: false rebase: false squash: true": {
|
|
allowMerge: false,
|
|
allowRebase: false,
|
|
allowSquash: true,
|
|
expMethod: "squash",
|
|
},
|
|
"merge: false rebase: true squash: false": {
|
|
allowMerge: false,
|
|
allowRebase: true,
|
|
allowSquash: false,
|
|
expMethod: "rebase",
|
|
},
|
|
}
|
|
|
|
for name, c := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
// Modify response.
|
|
jsBytes, err := os.ReadFile("testdata/github-repo.json")
|
|
Ok(t, err)
|
|
resp := string(jsBytes)
|
|
resp = strings.Replace(resp,
|
|
`"allow_squash_merge": true`,
|
|
fmt.Sprintf(`"allow_squash_merge": %t`, c.allowSquash),
|
|
-1)
|
|
resp = strings.Replace(resp,
|
|
`"allow_merge_commit": true`,
|
|
fmt.Sprintf(`"allow_merge_commit": %t`, c.allowMerge),
|
|
-1)
|
|
resp = strings.Replace(resp,
|
|
`"allow_rebase_merge": true`,
|
|
fmt.Sprintf(`"allow_rebase_merge": %t`, c.allowRebase),
|
|
-1)
|
|
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.RequestURI {
|
|
case "/api/v3/repos/runatlantis/atlantis":
|
|
w.Write([]byte(resp)) // nolint: errcheck
|
|
return
|
|
case "/api/v3/repos/runatlantis/atlantis/pulls/1/merge":
|
|
body, err := io.ReadAll(r.Body)
|
|
Ok(t, err)
|
|
defer r.Body.Close() // nolint: errcheck
|
|
type bodyJSON struct {
|
|
MergeMethod string `json:"merge_method"`
|
|
}
|
|
expBody := bodyJSON{
|
|
MergeMethod: c.expMethod,
|
|
}
|
|
expBytes, err := json.Marshal(expBody)
|
|
Ok(t, err)
|
|
Equals(t, string(expBytes)+"\n", string(body))
|
|
|
|
resp := `{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","merged":true,"message":"Pull Request successfully merged"}`
|
|
w.Write([]byte(resp)) // 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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
err = client.MergePull(
|
|
models.PullRequest{
|
|
BaseRepo: models.Repo{
|
|
FullName: "runatlantis/atlantis",
|
|
Owner: "runatlantis",
|
|
Name: "atlantis",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
},
|
|
Num: 1,
|
|
}, models.PullRequestOptions{
|
|
DeleteSourceBranchOnMerge: false,
|
|
})
|
|
|
|
Ok(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_MarkdownPullLink(t *testing.T) {
|
|
client, err := vcs.NewGithubClient("hostname", &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
pull := models.PullRequest{Num: 1}
|
|
s, _ := client.MarkdownPullLink(pull)
|
|
exp := "#1"
|
|
Equals(t, exp, s)
|
|
}
|
|
|
|
// disableSSLVerification disables ssl verification for the global http client
|
|
// and returns a function to be called in a defer that will re-enable it.
|
|
func disableSSLVerification() func() {
|
|
orig := http.DefaultTransport.(*http.Transport).TLSClientConfig
|
|
// nolint: gosec
|
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
|
return func() {
|
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = orig
|
|
}
|
|
}
|
|
|
|
func TestGithubClient_SplitComments(t *testing.T) {
|
|
type githubComment struct {
|
|
Body string `json:"body"`
|
|
}
|
|
githubComments := make([]githubComment, 0, 1)
|
|
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
switch r.Method + " " + r.RequestURI {
|
|
case "POST /api/v3/repos/runatlantis/atlantis/issues/1/comments":
|
|
defer r.Body.Close() // nolint: errcheck
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Errorf("read body error: %v", err)
|
|
http.Error(w, "server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
requestBody := githubComment{}
|
|
err = json.Unmarshal(body, &requestBody)
|
|
if err != nil {
|
|
t.Errorf("parse body error: %v", err)
|
|
http.Error(w, "server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
githubComments = append(githubComments, requestBody)
|
|
return
|
|
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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
pull := models.PullRequest{Num: 1}
|
|
repo := models.Repo{
|
|
FullName: "runatlantis/atlantis",
|
|
Owner: "runatlantis",
|
|
Name: "atlantis",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
}
|
|
// create an extra long string
|
|
comment := strings.Repeat("a", 65537)
|
|
err = client.CreateComment(repo, pull.Num, comment, command.Plan.String())
|
|
Ok(t, err)
|
|
err = client.CreateComment(repo, pull.Num, comment, "")
|
|
Ok(t, err)
|
|
|
|
body := strings.Split(githubComments[1].Body, "\n")
|
|
firstSplit := strings.ToLower(body[0])
|
|
body = strings.Split(githubComments[3].Body, "\n")
|
|
secondSplit := strings.ToLower(body[0])
|
|
|
|
Equals(t, 4, len(githubComments))
|
|
Assert(t, strings.Contains(firstSplit, command.Plan.String()), fmt.Sprintf("comment should contain the command name but was %q", firstSplit))
|
|
Assert(t, strings.Contains(secondSplit, "continued from previous comment"), fmt.Sprintf("comment should contain no reference to the command name but was %q", secondSplit))
|
|
}
|
|
|
|
// Test that we retry the get pull request call if it 404s.
|
|
func TestGithubClient_Retry404(t *testing.T) {
|
|
var numCalls = 0
|
|
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
switch r.Method + " " + r.RequestURI {
|
|
case "GET /api/v3/repos/runatlantis/atlantis/pulls/1":
|
|
defer r.Body.Close() // nolint: errcheck
|
|
numCalls++
|
|
if numCalls < 3 {
|
|
w.WriteHeader(404)
|
|
} else {
|
|
w.WriteHeader(200)
|
|
}
|
|
return
|
|
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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
repo := models.Repo{
|
|
FullName: "runatlantis/atlantis",
|
|
Owner: "runatlantis",
|
|
Name: "atlantis",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
}
|
|
_, err = client.GetPullRequest(repo, 1)
|
|
Ok(t, err)
|
|
Equals(t, 3, numCalls)
|
|
}
|
|
|
|
// Test that we retry the get pull request files call if it 404s.
|
|
func TestGithubClient_Retry404Files(t *testing.T) {
|
|
var numCalls = 0
|
|
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
switch r.Method + " " + r.RequestURI {
|
|
case "GET /api/v3/repos/runatlantis/atlantis/pulls/1/files?per_page=300":
|
|
defer r.Body.Close() // nolint: errcheck
|
|
numCalls++
|
|
if numCalls < 3 {
|
|
w.WriteHeader(404)
|
|
} else {
|
|
w.WriteHeader(200)
|
|
}
|
|
return
|
|
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{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
repo := models.Repo{
|
|
FullName: "runatlantis/atlantis",
|
|
Owner: "runatlantis",
|
|
Name: "atlantis",
|
|
CloneURL: "",
|
|
SanitizedCloneURL: "",
|
|
VCSHost: models.VCSHost{
|
|
Type: models.Github,
|
|
Hostname: "github.com",
|
|
},
|
|
}
|
|
pr := models.PullRequest{Num: 1}
|
|
_, err = client.GetModifiedFiles(repo, pr)
|
|
Ok(t, err)
|
|
Equals(t, 3, numCalls)
|
|
}
|
|
|
|
// GetTeamNamesForUser returns a list of team names for a user.
|
|
func TestGithubClient_GetTeamNamesForUser(t *testing.T) {
|
|
logger := logging.NewNoopLogger(t)
|
|
// Mocked GraphQL response for two teams
|
|
resp := `{
|
|
"data":{
|
|
"organization": {
|
|
"teams":{
|
|
"edges":[
|
|
{"node":{"name": "Frontend Developers", "slug":"frontend-developers"}},
|
|
{"node":{"name": "Employees", "slug":"employees"}}
|
|
],
|
|
"pageInfo":{
|
|
"endCursor":"Y3Vyc29yOnYyOpHOAFMoLQ==",
|
|
"hasNextPage":false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.RequestURI {
|
|
case "/api/graphql":
|
|
w.Write([]byte(resp)) // 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{}, logger)
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
|
|
teams, err := client.GetTeamNamesForUser(models.Repo{
|
|
Owner: "testrepo",
|
|
}, models.User{
|
|
Username: "testuser",
|
|
})
|
|
Ok(t, err)
|
|
Equals(t, []string{"Frontend Developers", "frontend-developers", "Employees", "employees"}, teams)
|
|
}
|
|
|
|
func TestGithubClient_DiscardReviews(t *testing.T) {
|
|
type ResponseDef struct {
|
|
httpCode int
|
|
body string
|
|
}
|
|
type fields struct {
|
|
responses []ResponseDef
|
|
}
|
|
type args struct {
|
|
repo models.Repo
|
|
pull models.PullRequest
|
|
}
|
|
|
|
queryResponseSingleReview := `{
|
|
"data": {
|
|
"repository": {
|
|
"pullRequest": {
|
|
"reviewDecision": "APPROVED",
|
|
"reviews": {
|
|
"nodes": [
|
|
{
|
|
"id": "PRR_kwDOFxULt85HBb7A",
|
|
"submittedAt": "2022-11-23T12:28:30Z",
|
|
"author": {
|
|
"login": "atlantis-test"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
queryResponseMultipleReviews := `{
|
|
"data": {
|
|
"repository": {
|
|
"pullRequest": {
|
|
"reviewDecision": "APPROVED",
|
|
"reviews": {
|
|
"nodes": [
|
|
{
|
|
"id": "PRR_kwDOFxULt85HBb7A",
|
|
"submittedAt": "2022-11-23T12:28:30Z",
|
|
"author": {
|
|
"login": "atlantis-test"
|
|
}
|
|
},
|
|
{
|
|
"id": "PRR_kwDOFxULt85HBb7B",
|
|
"submittedAt": "2022-11-23T14:28:30Z",
|
|
"author": {
|
|
"login": "atlantis-test2"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
mutationResponseSingleReviewDismissal := `{
|
|
"data": {
|
|
"dismissPullRequestReview": {
|
|
"pullRequestReview": {
|
|
"id": "PRR_kwDOFxULt85HBb7A"
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "return no error if dismissing a single approval",
|
|
fields: fields{
|
|
responses: []ResponseDef{
|
|
{
|
|
httpCode: 200,
|
|
body: queryResponseSingleReview,
|
|
},
|
|
{
|
|
httpCode: 200,
|
|
body: mutationResponseSingleReviewDismissal,
|
|
},
|
|
},
|
|
},
|
|
args: args{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "return no error if dismissing multiple reviews",
|
|
fields: fields{
|
|
responses: []ResponseDef{
|
|
{
|
|
httpCode: 200,
|
|
body: queryResponseMultipleReviews,
|
|
},
|
|
{
|
|
httpCode: 200,
|
|
body: mutationResponseSingleReviewDismissal,
|
|
},
|
|
{
|
|
httpCode: 200,
|
|
body: mutationResponseSingleReviewDismissal,
|
|
},
|
|
},
|
|
},
|
|
args: args{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "return error if query fails",
|
|
fields: fields{
|
|
responses: []ResponseDef{
|
|
{
|
|
httpCode: 500,
|
|
body: "",
|
|
},
|
|
},
|
|
},
|
|
args: args{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "return error if mutating fails",
|
|
fields: fields{
|
|
responses: []ResponseDef{
|
|
{
|
|
httpCode: 200,
|
|
body: queryResponseSingleReview,
|
|
},
|
|
{
|
|
httpCode: 500,
|
|
body: "",
|
|
},
|
|
},
|
|
},
|
|
args: args{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "return error if dismissing fails after already dismissing one",
|
|
fields: fields{
|
|
responses: []ResponseDef{
|
|
{
|
|
httpCode: 200,
|
|
body: queryResponseMultipleReviews,
|
|
},
|
|
{
|
|
httpCode: 200,
|
|
body: mutationResponseSingleReviewDismissal,
|
|
},
|
|
{
|
|
httpCode: 500,
|
|
body: "",
|
|
},
|
|
},
|
|
},
|
|
args: args{},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Mocked GraphQL response for two teams
|
|
responseIndex := 0
|
|
responseLength := len(tt.fields.responses)
|
|
testServer := httptest.NewTLSServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.RequestURI != "/api/graphql" {
|
|
t.Errorf("got unexpected request at %q", r.RequestURI)
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
Assert(t, responseIndex < responseLength, "requesting more responses than are defined")
|
|
response := tt.fields.responses[responseIndex]
|
|
responseIndex++
|
|
w.WriteHeader(response.httpCode)
|
|
w.Write([]byte(response.body)) // nolint: errcheck
|
|
}))
|
|
testServerURL, err := url.Parse(testServer.URL)
|
|
Ok(t, err)
|
|
client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{}, logging.NewNoopLogger(t))
|
|
Ok(t, err)
|
|
defer disableSSLVerification()()
|
|
if err := client.DiscardReviews(tt.args.repo, tt.args.pull); (err != nil) != tt.wantErr {
|
|
t.Errorf("DiscardReviews() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
Equals(t, responseLength, responseIndex) // check if all defined requests have been used
|
|
})
|
|
}
|
|
}
|