Files
atlantis/server/events_controller_e2e_test.go
2018-06-16 18:11:30 +01:00

305 lines
10 KiB
Go

package server_test
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/google/go-github/github"
. "github.com/petergtz/pegomock"
"github.com/runatlantis/atlantis/server"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/locking"
"github.com/runatlantis/atlantis/server/events/locking/boltdb"
"github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/mocks/matchers"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/runtime"
"github.com/runatlantis/atlantis/server/events/terraform"
vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks"
"github.com/runatlantis/atlantis/server/events/webhooks"
"github.com/runatlantis/atlantis/server/events/yaml"
"github.com/runatlantis/atlantis/server/logging"
. "github.com/runatlantis/atlantis/testing"
)
/*
flows:
- pull request opened autoplan
- comment to apply
github/gitlab
locking
merging pull requests
different repo organizations
atlantis.yaml
*/
func TestGitHubWorkflow(t *testing.T) {
RegisterMockTestingT(t)
cases := []struct {
Description string
// RepoDir is relative to testfixtures/test-repos.
RepoDir string
ModifiedFiles []string
ExpAutoplanCommentFile string
ExpMergeCommentFile string
CommentAndReplies []string
}{
{
Description: "simple",
RepoDir: "simple",
ModifiedFiles: []string{"main.tf"},
ExpAutoplanCommentFile: "exp-output-autoplan.txt",
CommentAndReplies: []string{
"atlantis apply", "exp-output-apply.txt",
},
ExpMergeCommentFile: "exp-output-merge.txt",
},
}
for _, c := range cases {
ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t)
t.Run(c.Description, func(t *testing.T) {
// Set the repo to be cloned through the testing backdoor.
repoDir, err := filepath.Abs(filepath.Join("testfixtures", "test-repos", c.RepoDir))
Ok(t, err)
atlantisWorkspace.TestingOverrideCloneURL = fmt.Sprintf("file://%s", repoDir)
// Setup test dependencies.
w := httptest.NewRecorder()
When(githubGetter.GetPullRequest(AnyRepo(), AnyInt())).ThenReturn(GitHubPullRequestParsed(), nil)
When(vcsClient.GetModifiedFiles(AnyRepo(), matchers.AnyModelsPullRequest())).ThenReturn(c.ModifiedFiles, nil)
// First, send the open pull request event and trigger an autoplan.
pullOpenedReq := GitHubPullRequestOpenedEvent(t)
ctrl.Post(w, pullOpenedReq)
responseContains(t, w, 200, "Processing...")
_, _, autoplanComment := vcsClient.VerifyWasCalledOnce().CreateComment(AnyRepo(), AnyInt(), AnyString()).GetCapturedArguments()
exp, err := ioutil.ReadFile(filepath.Join(repoDir, c.ExpAutoplanCommentFile))
Ok(t, err)
Equals(t, string(exp), autoplanComment)
// Now send any other comments.
for i := 0; i < len(c.CommentAndReplies); i += 2 {
comment := c.CommentAndReplies[i]
expOutputFile := c.CommentAndReplies[i+1]
commentReq := GitHubCommentEvent(t, comment)
w = httptest.NewRecorder()
ctrl.Post(w, commentReq)
responseContains(t, w, 200, "Processing...")
_, _, autoplanComment = vcsClient.VerifyWasCalled(Twice()).CreateComment(AnyRepo(), AnyInt(), AnyString()).GetCapturedArguments()
exp, err = ioutil.ReadFile(filepath.Join(repoDir, expOutputFile))
Ok(t, err)
// Replace all 'ID: 1111818181' strings with * so we can do a comparison.
idRegex := regexp.MustCompile(`\(ID: [0-9]+\)`)
autoplanComment = idRegex.ReplaceAllString(autoplanComment, "(ID: ******************)")
Equals(t, string(exp), autoplanComment)
}
// Finally, send the pull request merged event.
pullClosedReq := GitHubPullRequestClosedEvent(t)
w = httptest.NewRecorder()
ctrl.Post(w, pullClosedReq)
responseContains(t, w, 200, "Pull request cleaned successfully")
_, _, pullClosedComment := vcsClient.VerifyWasCalled(Times(3)).CreateComment(AnyRepo(), AnyInt(), AnyString()).GetCapturedArguments()
exp, err = ioutil.ReadFile(filepath.Join(repoDir, c.ExpMergeCommentFile))
Ok(t, err)
Equals(t, string(exp), pullClosedComment)
})
}
}
func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClientProxy, *mocks.MockGithubPullGetter, *events.FileWorkspace) {
allowForkPRs := false
dataDir, cleanup := TempDir(t)
defer cleanup()
testRepoDir, err := filepath.Abs("testfixtures/test-repos/simple")
Ok(t, err)
// Mocks.
e2eVCSClient := vcsmocks.NewMockClientProxy()
e2eStatusUpdater := mocks.NewMockCommitStatusUpdater()
e2eGithubGetter := mocks.NewMockGithubPullGetter()
e2eGitlabGetter := mocks.NewMockGitlabMergeRequestGetter()
// Real dependencies.
logger := logging.NewSimpleLogger("server", nil, true, logging.Debug)
eventParser := &events.EventParser{
GithubUser: "github-user",
GithubToken: "github-token",
GitlabUser: "gitlab-user",
GitlabToken: "gitlab-token",
}
commentParser := &events.CommentParser{
GithubUser: "github-user",
GithubToken: "github-token",
GitlabUser: "gitlab-user",
GitlabToken: "gitlab-token",
}
terraformClient, err := terraform.NewClient(dataDir)
Ok(t, err)
boltdb, err := boltdb.New(dataDir)
Ok(t, err)
lockingClient := locking.NewClient(boltdb)
projectLocker := &events.DefaultProjectLocker{
Locker: lockingClient,
}
atlantisWorkspace := &events.FileWorkspace{
DataDir: dataDir,
TestingOverrideCloneURL: testRepoDir,
}
defaultTFVersion := terraformClient.Version()
commandHandler := &events.CommandHandler{
EventParser: eventParser,
VCSClient: e2eVCSClient,
GithubPullGetter: e2eGithubGetter,
GitlabMergeRequestGetter: e2eGitlabGetter,
CommitStatusUpdater: e2eStatusUpdater,
AtlantisWorkspaceLocker: events.NewDefaultAtlantisWorkspaceLocker(),
MarkdownRenderer: &events.MarkdownRenderer{},
Logger: logger,
AllowForkPRs: allowForkPRs,
AllowForkPRsFlag: "allow-fork-prs",
PullRequestOperator: &events.DefaultPullRequestOperator{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
ParserValidator: &yaml.ParserValidator{},
ProjectFinder: &events.DefaultProjectFinder{},
VCSClient: e2eVCSClient,
Workspace: atlantisWorkspace,
ProjectOperator: events.ProjectOperator{
Locker: projectLocker,
LockURLGenerator: &mockLockURLGenerator{},
InitStepOperator: runtime.InitStepOperator{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
},
PlanStepOperator: runtime.PlanStepOperator{
TerraformExecutor: terraformClient,
DefaultTFVersion: defaultTFVersion,
},
ApplyStepOperator: runtime.ApplyStepOperator{
TerraformExecutor: terraformClient,
},
RunStepOperator: runtime.RunStepOperator{},
ApprovalOperator: runtime.ApprovalOperator{
VCSClient: e2eVCSClient,
},
Workspace: atlantisWorkspace,
Webhooks: &mockWebhookSender{},
},
},
}
ctrl := server.EventsController{
TestingMode: true,
CommandRunner: commandHandler,
PullCleaner: &events.PullClosedExecutor{
Locker: lockingClient,
VCSClient: e2eVCSClient,
Workspace: atlantisWorkspace,
},
Logger: logger,
Parser: eventParser,
CommentParser: commentParser,
GithubWebHookSecret: nil,
GithubRequestValidator: &server.DefaultGithubRequestValidator{},
GitlabRequestParser: &server.DefaultGitlabRequestParser{},
GitlabWebHookSecret: nil,
RepoWhitelist: &events.RepoWhitelist{
Whitelist: "*",
},
SupportedVCSHosts: []models.VCSHostType{models.Gitlab, models.Github},
VCSClient: e2eVCSClient,
AtlantisGithubUser: models.User{
Username: "atlantisbot",
},
AtlantisGitlabUser: models.User{
Username: "atlantisbot",
},
}
return ctrl, e2eVCSClient, e2eGithubGetter, atlantisWorkspace
}
type mockLockURLGenerator struct{}
func (m *mockLockURLGenerator) GenerateLockURL(lockID string) string {
return "lock-url"
}
type mockWebhookSender struct{}
func (w *mockWebhookSender) Send(log *logging.SimpleLogger, result webhooks.ApplyResult) error {
return nil
}
func GitHubCommentEvent(t *testing.T, comment string) *http.Request {
requestJSON, err := ioutil.ReadFile(filepath.Join("testfixtures", "githubIssueCommentEvent.json"))
Ok(t, err)
requestJSON = []byte(strings.Replace(string(requestJSON), "###comment body###", comment, 1))
req, err := http.NewRequest("POST", "/events", bytes.NewBuffer(requestJSON))
Ok(t, err)
req.Header.Set("Content-Type", "application/json")
req.Header.Set(githubHeader, "issue_comment")
return req
}
func GitHubPullRequestOpenedEvent(t *testing.T) *http.Request {
requestJSON, err := ioutil.ReadFile(filepath.Join("testfixtures", "githubPullRequestOpenedEvent.json"))
Ok(t, err)
req, err := http.NewRequest("POST", "/events", bytes.NewBuffer(requestJSON))
Ok(t, err)
req.Header.Set("Content-Type", "application/json")
req.Header.Set(githubHeader, "pull_request")
return req
}
func GitHubPullRequestClosedEvent(t *testing.T) *http.Request {
requestJSON, err := ioutil.ReadFile(filepath.Join("testfixtures", "githubPullRequestClosedEvent.json"))
Ok(t, err)
req, err := http.NewRequest("POST", "/events", bytes.NewBuffer(requestJSON))
Ok(t, err)
req.Header.Set("Content-Type", "application/json")
req.Header.Set(githubHeader, "pull_request")
return req
}
func GitHubPullRequestParsed() *github.PullRequest {
return &github.PullRequest{
Number: github.Int(1),
State: github.String("open"),
HTMLURL: github.String("htmlurl"),
Head: &github.PullRequestBranch{
Repo: &github.Repository{
FullName: github.String("runatlantis/atlantis-tests"),
CloneURL: github.String("/runatlantis/atlantis-tests.git"),
},
SHA: github.String("sha"),
Ref: github.String("branch"),
},
Base: &github.PullRequestBranch{
Repo: &github.Repository{
FullName: github.String("runatlantis/atlantis-tests"),
CloneURL: github.String("/runatlantis/atlantis-tests.git"),
},
},
User: &github.User{
Login: github.String("atlantisbot"),
},
}
}