From e97b98e4a04c71c5b2d1f7ef3a215e22a40d0cca Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Fri, 15 Jun 2018 17:52:14 +0100 Subject: [PATCH] Add first e2e test with mocks. --- server/events/project_operator.go | 7 +- server/events_controller.go | 14 +- server/events_controller_e2e_test.go | 210 ++++++++++++++++++ .../testfixtures/githubIssueCommentEvent.json | 207 +++++++++++++++++ .../simple/exp-output-atlantis-plan.txt | 23 ++ server/testfixtures/test-repos/simple/main.tf | 3 + 6 files changed, 459 insertions(+), 5 deletions(-) create mode 100644 server/events_controller_e2e_test.go create mode 100644 server/testfixtures/githubIssueCommentEvent.json create mode 100644 server/testfixtures/test-repos/simple/exp-output-atlantis-plan.txt create mode 100644 server/testfixtures/test-repos/simple/main.tf diff --git a/server/events/project_operator.go b/server/events/project_operator.go index 37dfcb5f0..2c6c5c558 100644 --- a/server/events/project_operator.go +++ b/server/events/project_operator.go @@ -22,6 +22,7 @@ import ( "github.com/runatlantis/atlantis/server/events/runtime" "github.com/runatlantis/atlantis/server/events/webhooks" "github.com/runatlantis/atlantis/server/events/yaml/valid" + "github.com/runatlantis/atlantis/server/logging" ) //go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_lock_url_generator.go LockURLGenerator @@ -30,6 +31,10 @@ type LockURLGenerator interface { GenerateLockURL(lockID string) string } +type WebhooksSender interface { + Send(log *logging.SimpleLogger, result webhooks.ApplyResult) error +} + // PlanSuccess is the result of a successful plan. type PlanSuccess struct { TerraformOutput string @@ -45,7 +50,7 @@ type ProjectOperator struct { RunStepOperator runtime.RunStepOperator ApprovalOperator runtime.ApprovalOperator Workspace AtlantisWorkspace - Webhooks *webhooks.MultiWebhookSender + Webhooks WebhooksSender } func (p *ProjectOperator) Plan(ctx models.ProjectCommandContext, projAbsPathPtr *string) ProjectResult { diff --git a/server/events_controller.go b/server/events_controller.go index b5b3c7330..b205f2eb8 100644 --- a/server/events_controller.go +++ b/server/events_controller.go @@ -55,6 +55,7 @@ type EventsController struct { AtlantisGithubUser models.User // AtlantisGitlabUser is the user that atlantis is running as for Gitlab. AtlantisGitlabUser models.User + TestingMode bool } // Post handles POST webhook requests. @@ -254,11 +255,16 @@ func (e *EventsController) handleCommentEvent(w http.ResponseWriter, baseRepo mo return } - // Respond with success and then actually execute the command asynchronously. - // We use a goroutine so that this function returns and the connection is - // closed. fmt.Fprintln(w, "Processing...") - go e.CommandRunner.ExecuteCommand(baseRepo, headRepo, user, pullNum, parseResult.Command) + if !e.TestingMode { + // Respond with success and then actually execute the command asynchronously. + // We use a goroutine so that this function returns and the connection is + // closed. + go e.CommandRunner.ExecuteCommand(baseRepo, headRepo, user, pullNum, parseResult.Command) + } else { + // When testing we want to wait for everything to complete. + e.CommandRunner.ExecuteCommand(baseRepo, headRepo, user, pullNum, parseResult.Command) + } } // HandleGitlabMergeRequestEvent will delete any locks associated with the pull diff --git a/server/events_controller_e2e_test.go b/server/events_controller_e2e_test.go new file mode 100644 index 000000000..97883c0a1 --- /dev/null +++ b/server/events_controller_e2e_test.go @@ -0,0 +1,210 @@ +package server_test + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "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" +) + +func Test(t *testing.T) { + RegisterMockTestingT(t) + + // Config. + allowForkPRs := false + dataDir, cleanup := TempDir(t) + defer cleanup() + + // Mocks. + e2eVCSClient := vcsmocks.NewMockClientProxy() + e2eStatusUpdater := mocks.NewMockCommitStatusUpdater() + e2eGithubGetter := mocks.NewMockGithubPullGetter() + e2eGitlabGetter := mocks.NewMockGitlabMergeRequestGetter() + e2eWorkspace := mocks.NewMockAtlantisWorkspace() + + // 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, + } + + 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: e2eWorkspace, + 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: e2eWorkspace, + Webhooks: &mockWebhookSender{}, + }, + }, + } + + ctrl := server.EventsController{ + TestingMode: true, + CommandRunner: commandHandler, + PullCleaner: nil, + 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", + }, + } + + // Test GitHub Post + req := GitHubCommentEvent(t, "atlantis plan") + w := httptest.NewRecorder() + When(e2eGithubGetter.GetPullRequest(AnyRepo(), AnyInt())).ThenReturn(GitHubPullRequestParsed(), nil) + testRepoDir, err := filepath.Abs("testfixtures/test-repos/simple") + Ok(t, err) + When(e2eWorkspace.Clone(matchers.AnyPtrToLoggingSimpleLogger(), AnyRepo(), AnyRepo(), matchers.AnyModelsPullRequest(), AnyString())).ThenReturn(testRepoDir, nil) + // Clean up .terraform and plan files when we're done. + defer func() { + os.RemoveAll(filepath.Join(testRepoDir, ".terraform")) + planFiles, _ := filepath.Glob(testRepoDir + "/*.tfplan") + for _, file := range planFiles { + os.Remove(file) + } + }() + + ctrl.Post(w, req) + responseContains(t, w, 200, "Processing...") + _, _, comment := e2eVCSClient.VerifyWasCalledOnce().CreateComment(AnyRepo(), AnyInt(), AnyString()).GetCapturedArguments() + + exp, err := ioutil.ReadFile(filepath.Join(testRepoDir, "exp-output-atlantis-plan.txt")) + fmt.Println((string(exp))) + Ok(t, err) + + Equals(t, string(exp), comment) +} + +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 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"), + }, + } +} diff --git a/server/testfixtures/githubIssueCommentEvent.json b/server/testfixtures/githubIssueCommentEvent.json new file mode 100644 index 000000000..a15f67ed4 --- /dev/null +++ b/server/testfixtures/githubIssueCommentEvent.json @@ -0,0 +1,207 @@ +{ + "action": "created", + "issue": { + "url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1", + "repository_url": "https://api.github.com/repos/runatlantis/atlantis-tests", + "labels_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1/comments", + "events_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1/events", + "html_url": "https://github.com/runatlantis/atlantis-tests/pull/1", + "id": 330256251, + "node_id": "MDExOlB1bGxSZXF1ZXN0MTkzMzA4NzA3", + "number": 1, + "title": "Add new project layouts", + "user": { + "login": "runatlantis", + "id": 1034429, + "node_id": "MDQ6VXNlcjEwMzQ0Mjk=", + "avatar_url": "https://avatars1.githubusercontent.com/u/1034429?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/runatlantis", + "html_url": "https://github.com/runatlantis", + "followers_url": "https://api.github.com/users/runatlantis/followers", + "following_url": "https://api.github.com/users/runatlantis/following{/other_user}", + "gists_url": "https://api.github.com/users/runatlantis/gists{/gist_id}", + "starred_url": "https://api.github.com/users/runatlantis/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/runatlantis/subscriptions", + "organizations_url": "https://api.github.com/users/runatlantis/orgs", + "repos_url": "https://api.github.com/users/runatlantis/repos", + "events_url": "https://api.github.com/users/runatlantis/events{/privacy}", + "received_events_url": "https://api.github.com/users/runatlantis/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 61, + "created_at": "2018-06-07T12:45:41Z", + "updated_at": "2018-06-13T12:53:40Z", + "closed_at": null, + "author_association": "OWNER", + "pull_request": { + "url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/1", + "html_url": "https://github.com/runatlantis/atlantis-tests/pull/1", + "diff_url": "https://github.com/runatlantis/atlantis-tests/pull/1.diff", + "patch_url": "https://github.com/runatlantis/atlantis-tests/pull/1.patch" + }, + "body": "" + }, + "comment": { + "url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/comments/396926483", + "html_url": "https://github.com/runatlantis/atlantis-tests/pull/1#issuecomment-396926483", + "issue_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1", + "id": 396926483, + "node_id": "MDEyOklzc3VlQ29tbWVudDM5NjkyNjQ4Mw==", + "user": { + "login": "runatlantis", + "id": 1034429, + "node_id": "MDQ6VXNlcjEwMzQ0Mjk=", + "avatar_url": "https://avatars1.githubusercontent.com/u/1034429?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/runatlantis", + "html_url": "https://github.com/runatlantis", + "followers_url": "https://api.github.com/users/runatlantis/followers", + "following_url": "https://api.github.com/users/runatlantis/following{/other_user}", + "gists_url": "https://api.github.com/users/runatlantis/gists{/gist_id}", + "starred_url": "https://api.github.com/users/runatlantis/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/runatlantis/subscriptions", + "organizations_url": "https://api.github.com/users/runatlantis/orgs", + "repos_url": "https://api.github.com/users/runatlantis/repos", + "events_url": "https://api.github.com/users/runatlantis/events{/privacy}", + "received_events_url": "https://api.github.com/users/runatlantis/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2018-06-13T12:53:40Z", + "updated_at": "2018-06-13T12:53:40Z", + "author_association": "OWNER", + "body": "###comment body###" + }, + "repository": { + "id": 136474117, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzY0NzQxMTc=", + "name": "atlantis-tests", + "full_name": "runatlantis/atlantis-tests", + "owner": { + "login": "runatlantis", + "id": 1034429, + "node_id": "MDQ6VXNlcjEwMzQ0Mjk=", + "avatar_url": "https://avatars1.githubusercontent.com/u/1034429?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/runatlantis", + "html_url": "https://github.com/runatlantis", + "followers_url": "https://api.github.com/users/runatlantis/followers", + "following_url": "https://api.github.com/users/runatlantis/following{/other_user}", + "gists_url": "https://api.github.com/users/runatlantis/gists{/gist_id}", + "starred_url": "https://api.github.com/users/runatlantis/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/runatlantis/subscriptions", + "organizations_url": "https://api.github.com/users/runatlantis/orgs", + "repos_url": "https://api.github.com/users/runatlantis/repos", + "events_url": "https://api.github.com/users/runatlantis/events{/privacy}", + "received_events_url": "https://api.github.com/users/runatlantis/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/runatlantis/atlantis-tests", + "description": "A set of terraform projects that atlantis e2e tests run on.", + "fork": true, + "url": "https://api.github.com/repos/runatlantis/atlantis-tests", + "forks_url": "https://api.github.com/repos/runatlantis/atlantis-tests/forks", + "keys_url": "https://api.github.com/repos/runatlantis/atlantis-tests/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/runatlantis/atlantis-tests/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/runatlantis/atlantis-tests/teams", + "hooks_url": "https://api.github.com/repos/runatlantis/atlantis-tests/hooks", + "issue_events_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/events{/number}", + "events_url": "https://api.github.com/repos/runatlantis/atlantis-tests/events", + "assignees_url": "https://api.github.com/repos/runatlantis/atlantis-tests/assignees{/user}", + "branches_url": "https://api.github.com/repos/runatlantis/atlantis-tests/branches{/branch}", + "tags_url": "https://api.github.com/repos/runatlantis/atlantis-tests/tags", + "blobs_url": "https://api.github.com/repos/runatlantis/atlantis-tests/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/runatlantis/atlantis-tests/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/runatlantis/atlantis-tests/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/runatlantis/atlantis-tests/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/runatlantis/atlantis-tests/statuses/{sha}", + "languages_url": "https://api.github.com/repos/runatlantis/atlantis-tests/languages", + "stargazers_url": "https://api.github.com/repos/runatlantis/atlantis-tests/stargazers", + "contributors_url": "https://api.github.com/repos/runatlantis/atlantis-tests/contributors", + "subscribers_url": "https://api.github.com/repos/runatlantis/atlantis-tests/subscribers", + "subscription_url": "https://api.github.com/repos/runatlantis/atlantis-tests/subscription", + "commits_url": "https://api.github.com/repos/runatlantis/atlantis-tests/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/runatlantis/atlantis-tests/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/runatlantis/atlantis-tests/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/runatlantis/atlantis-tests/contents/{+path}", + "compare_url": "https://api.github.com/repos/runatlantis/atlantis-tests/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/runatlantis/atlantis-tests/merges", + "archive_url": "https://api.github.com/repos/runatlantis/atlantis-tests/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/runatlantis/atlantis-tests/downloads", + "issues_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues{/number}", + "pulls_url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls{/number}", + "milestones_url": "https://api.github.com/repos/runatlantis/atlantis-tests/milestones{/number}", + "notifications_url": "https://api.github.com/repos/runatlantis/atlantis-tests/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/runatlantis/atlantis-tests/labels{/name}", + "releases_url": "https://api.github.com/repos/runatlantis/atlantis-tests/releases{/id}", + "deployments_url": "https://api.github.com/repos/runatlantis/atlantis-tests/deployments", + "created_at": "2018-06-07T12:28:23Z", + "updated_at": "2018-06-07T12:28:27Z", + "pushed_at": "2018-06-11T16:22:17Z", + "git_url": "git://github.com/runatlantis/atlantis-tests.git", + "ssh_url": "git@github.com:runatlantis/atlantis-tests.git", + "clone_url": "https://github.com/runatlantis/atlantis-tests.git", + "svn_url": "https://github.com/runatlantis/atlantis-tests", + "homepage": null, + "size": 8, + "stargazers_count": 0, + "watchers_count": 0, + "language": "HCL", + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 2, + "license": { + "key": "other", + "name": "Other", + "spdx_id": null, + "url": null, + "node_id": "MDc6TGljZW5zZTA=" + }, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "runatlantis", + "id": 1034429, + "node_id": "MDQ6VXNlcjEwMzQ0Mjk=", + "avatar_url": "https://avatars1.githubusercontent.com/u/1034429?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/runatlantis", + "html_url": "https://github.com/runatlantis", + "followers_url": "https://api.github.com/users/runatlantis/followers", + "following_url": "https://api.github.com/users/runatlantis/following{/other_user}", + "gists_url": "https://api.github.com/users/runatlantis/gists{/gist_id}", + "starred_url": "https://api.github.com/users/runatlantis/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/runatlantis/subscriptions", + "organizations_url": "https://api.github.com/users/runatlantis/orgs", + "repos_url": "https://api.github.com/users/runatlantis/repos", + "events_url": "https://api.github.com/users/runatlantis/events{/privacy}", + "received_events_url": "https://api.github.com/users/runatlantis/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/server/testfixtures/test-repos/simple/exp-output-atlantis-plan.txt b/server/testfixtures/test-repos/simple/exp-output-atlantis-plan.txt new file mode 100644 index 000000000..15a712c42 --- /dev/null +++ b/server/testfixtures/test-repos/simple/exp-output-atlantis-plan.txt @@ -0,0 +1,23 @@ +Ran Plan in dir: `.` workspace: `default` +```diff +Refreshing Terraform state in-memory prior to plan... +The refreshed state will be used to calculate this plan, but will not be +persisted to local or remote state storage. + + +------------------------------------------------------------------------ + +An execution plan has been generated and is shown below. +Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + ++ null_resource.simple + id: +Plan: 1 to add, 0 to change, 0 to destroy. + +``` + +* To **discard** this plan click [here](lock-url). + diff --git a/server/testfixtures/test-repos/simple/main.tf b/server/testfixtures/test-repos/simple/main.tf new file mode 100644 index 000000000..648c412e1 --- /dev/null +++ b/server/testfixtures/test-repos/simple/main.tf @@ -0,0 +1,3 @@ +resource "null_resource" "simple" { + count = 1 +} \ No newline at end of file