Files
atlantis/server/events_controller_test.go
Luke Kysow ab1d36d2c8 Comment back on pull request sooner on error.
- Refactor EventParser so it returns a comment we can send to the pull
request when a bad command or help command is commented.
- Remove now unneeded HelpExecutor because we comment right from the
EventsController now
- Use pflag package to parse commands instead of doing it manually
- Comment back when user types terraform instead of atlantis
2018-02-27 17:45:05 -08:00

346 lines
15 KiB
Go

package server_test
import (
"bytes"
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/lkysow/go-gitlab"
. "github.com/petergtz/pegomock"
"github.com/runatlantis/atlantis/server"
"github.com/runatlantis/atlantis/server/events"
emocks "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/vcs"
vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks"
"github.com/runatlantis/atlantis/server/logging"
"github.com/runatlantis/atlantis/server/mocks"
)
const githubHeader = "X-Github-Event"
const gitlabHeader = "X-Gitlab-Event"
var secret = []byte("secret")
var eventsReq *http.Request
func TestPost_NotGithubOrGitlab(t *testing.T) {
t.Log("when the request is not for gitlab or github a 400 is returned")
e, _, _, _, _, _, _ := setup(t)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Ignoring request")
}
func TestPost_UnsupportedVCSGithub(t *testing.T) {
t.Log("when the request is for an unsupported vcs a 400 is returned")
e, _, _, _, _, _, _ := setup(t)
e.SupportedVCSHosts = nil
eventsReq.Header.Set(githubHeader, "value")
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Ignoring request since not configured to support GitHub")
}
func TestPost_UnsupportedVCSGitlab(t *testing.T) {
t.Log("when the request is for an unsupported vcs a 400 is returned")
e, _, _, _, _, _, _ := setup(t)
e.SupportedVCSHosts = nil
eventsReq.Header.Set(gitlabHeader, "value")
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Ignoring request since not configured to support GitLab")
}
func TestPost_InvalidGithubSecret(t *testing.T) {
t.Log("when the github payload can't be validated a 400 is returned")
e, v, _, _, _, _, _ := setup(t)
w := httptest.NewRecorder()
eventsReq.Header.Set(githubHeader, "value")
When(v.Validate(eventsReq, secret)).ThenReturn(nil, errors.New("err"))
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "err")
}
func TestPost_InvalidGitlabSecret(t *testing.T) {
t.Log("when the gitlab payload can't be validated a 400 is returned")
e, _, gl, _, _, _, _ := setup(t)
w := httptest.NewRecorder()
eventsReq.Header.Set(gitlabHeader, "value")
When(gl.Validate(eventsReq, secret)).ThenReturn(nil, errors.New("err"))
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "err")
}
func TestPost_UnsupportedGithubEvent(t *testing.T) {
t.Log("when the event type is an unsupported github event we ignore it")
e, v, _, _, _, _, _ := setup(t)
w := httptest.NewRecorder()
eventsReq.Header.Set(githubHeader, "value")
When(v.Validate(eventsReq, nil)).ThenReturn([]byte(`{"not an event": ""}`), nil)
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring unsupported event")
}
func TestPost_UnsupportedGitlabEvent(t *testing.T) {
t.Log("when the event type is an unsupported gitlab event we ignore it")
e, _, gl, _, _, _, _ := setup(t)
w := httptest.NewRecorder()
eventsReq.Header.Set(gitlabHeader, "value")
When(gl.Validate(eventsReq, secret)).ThenReturn([]byte(`{"not an event": ""}`), nil)
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring unsupported event")
}
func TestPost_GithubCommentNotCreated(t *testing.T) {
t.Log("when the event is a github comment but it's not a created event we ignore it")
e, v, _, _, _, _, _ := setup(t)
eventsReq.Header.Set(githubHeader, "issue_comment")
// comment action is deleted, not created
event := `{"action": "deleted"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring comment event since action was not created")
}
func TestPost_GithubInvalidComment(t *testing.T) {
t.Log("when the event is a github comment without all expected data we return a 400")
e, v, _, p, _, _, _ := setup(t)
eventsReq.Header.Set(githubHeader, "issue_comment")
event := `{"action": "created"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
When(p.ParseGithubIssueCommentEvent(matchers.AnyPtrToGithubIssueCommentEvent())).ThenReturn(models.Repo{}, models.User{}, 1, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Failed parsing event")
}
func TestPost_GitlabCommentInvalidCommand(t *testing.T) {
t.Log("when the event is a gitlab comment with an invalid command we ignore it")
e, _, gl, p, _, _, _ := setup(t)
eventsReq.Header.Set(gitlabHeader, "value")
When(gl.Validate(eventsReq, secret)).ThenReturn(gitlab.MergeCommentEvent{}, nil)
When(p.DetermineCommand("", vcs.Gitlab)).ThenReturn(events.CommandParseResult{Ignore: true})
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring non-command comment: \"\"")
}
func TestPost_GithubCommentInvalidCommand(t *testing.T) {
t.Log("when the event is a github comment with an invalid command we ignore it")
e, v, _, p, _, _, _ := setup(t)
eventsReq.Header.Set(githubHeader, "issue_comment")
event := `{"action": "created"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
When(p.ParseGithubIssueCommentEvent(matchers.AnyPtrToGithubIssueCommentEvent())).ThenReturn(models.Repo{}, models.User{}, 1, nil)
When(p.DetermineCommand("", vcs.Github)).ThenReturn(events.CommandParseResult{Ignore: true})
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring non-command comment: \"\"")
}
func TestPost_GitlabCommentResponse(t *testing.T) {
t.Log("when the event is a gitlab comment that warrants a comment response we comment back")
e, _, gl, p, _, _, vcsClient := setup(t)
eventsReq.Header.Set(gitlabHeader, "value")
When(gl.Validate(eventsReq, secret)).ThenReturn(gitlab.MergeCommentEvent{}, nil)
When(p.DetermineCommand("", vcs.Gitlab)).ThenReturn(events.CommandParseResult{CommentResponse: "a comment"})
w := httptest.NewRecorder()
e.Post(w, eventsReq)
vcsClient.VerifyWasCalledOnce().CreateComment(models.Repo{}, 0, "a comment", vcs.Gitlab)
responseContains(t, w, http.StatusOK, "Commenting back on pull request")
}
func TestPost_GithubCommentResponse(t *testing.T) {
t.Log("when the event is a github comment that warrants a comment response we comment back")
e, v, _, p, _, _, vcsClient := setup(t)
eventsReq.Header.Set(githubHeader, "issue_comment")
event := `{"action": "created"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
baseRepo := models.Repo{}
user := models.User{}
When(p.ParseGithubIssueCommentEvent(matchers.AnyPtrToGithubIssueCommentEvent())).ThenReturn(baseRepo, user, 1, nil)
When(p.DetermineCommand("", vcs.Github)).ThenReturn(events.CommandParseResult{CommentResponse: "a comment"})
w := httptest.NewRecorder()
e.Post(w, eventsReq)
vcsClient.VerifyWasCalledOnce().CreateComment(baseRepo, 1, "a comment", vcs.Github)
responseContains(t, w, http.StatusOK, "Commenting back on pull request")
}
func TestPost_GitlabCommentSuccess(t *testing.T) {
t.Log("when the event is a gitlab comment with a valid command we call the command handler")
e, _, gl, _, cr, _, _ := setup(t)
eventsReq.Header.Set(gitlabHeader, "value")
When(gl.Validate(eventsReq, secret)).ThenReturn(gitlab.MergeCommentEvent{}, nil)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Processing...")
// wait for 200ms so goroutine is called
time.Sleep(200 * time.Millisecond)
cr.VerifyWasCalledOnce().ExecuteCommand(models.Repo{}, models.Repo{}, models.User{}, 0, nil, vcs.Gitlab)
}
func TestPost_GithubCommentSuccess(t *testing.T) {
t.Log("when the event is a github comment with a valid command we call the command handler")
e, v, _, p, cr, _, _ := setup(t)
eventsReq.Header.Set(githubHeader, "issue_comment")
event := `{"action": "created"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
baseRepo := models.Repo{}
user := models.User{}
cmd := events.Command{}
When(p.ParseGithubIssueCommentEvent(matchers.AnyPtrToGithubIssueCommentEvent())).ThenReturn(baseRepo, user, 1, nil)
When(p.DetermineCommand("", vcs.Github)).ThenReturn(events.CommandParseResult{Command: &cmd})
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Processing...")
// wait for 200ms so goroutine is called
time.Sleep(200 * time.Millisecond)
cr.VerifyWasCalledOnce().ExecuteCommand(baseRepo, baseRepo, user, 1, &cmd, vcs.Github)
}
func TestPost_GithubPullRequestNotClosed(t *testing.T) {
t.Log("when the event is a github pull reuqest but it's not a closed event we ignore it")
e, v, _, _, _, _, _ := setup(t)
eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "opened"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring pull request event since action was not closed")
}
func TestPost_GitlabMergeRequestNotClosed(t *testing.T) {
t.Log("when the event is a gitlab merge request but it's not a closed event we ignore it")
e, _, gl, p, _, _, _ := setup(t)
eventsReq.Header.Set(gitlabHeader, "value")
event := gitlab.MergeEvent{}
When(gl.Validate(eventsReq, secret)).ThenReturn(event, nil)
When(p.ParseGitlabMergeEvent(event)).ThenReturn(models.PullRequest{State: models.Open}, models.Repo{})
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Ignoring opened merge request event")
}
func TestPost_GithubPullRequestInvalid(t *testing.T) {
t.Log("when the event is a github pull request with invalid data we return a 400")
e, v, _, p, _, _, _ := setup(t)
eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(models.PullRequest{}, models.Repo{}, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Error parsing pull data: err")
}
func TestPost_GithubPullRequestInvalidRepo(t *testing.T) {
t.Log("when the event is a github pull request with invalid repo data we return a 400")
e, v, _, p, _, _, _ := setup(t)
eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(models.PullRequest{}, models.Repo{}, nil)
When(p.ParseGithubRepo(matchers.AnyPtrToGithubRepository())).ThenReturn(models.Repo{}, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusBadRequest, "Error parsing repo data: err")
}
func TestPost_GithubPullRequestErrCleaningPull(t *testing.T) {
t.Log("when the event is a pull request and we have an error calling CleanUpPull we return a 503")
RegisterMockTestingT(t)
e, v, _, p, _, c, _ := setup(t)
eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{}
When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(pull, repo, nil)
When(p.ParseGithubRepo(matchers.AnyPtrToGithubRepository())).ThenReturn(repo, nil)
When(c.CleanUpPull(repo, pull, vcs.Github)).ThenReturn(errors.New("cleanup err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusInternalServerError, "Error cleaning pull request: cleanup err")
}
func TestPost_GitlabMergeRequestErrCleaningPull(t *testing.T) {
t.Log("when the event is a gitlab merge request and an error occurs calling CleanUpPull we return a 503")
e, _, gl, p, _, c, _ := setup(t)
eventsReq.Header.Set(gitlabHeader, "value")
event := gitlab.MergeEvent{}
When(gl.Validate(eventsReq, secret)).ThenReturn(event, nil)
repo := models.Repo{}
pullRequest := models.PullRequest{State: models.Closed}
When(p.ParseGitlabMergeEvent(event)).ThenReturn(pullRequest, repo)
When(c.CleanUpPull(repo, pullRequest, vcs.Gitlab)).ThenReturn(errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusInternalServerError, "Error cleaning pull request: err")
}
func TestPost_GithubPullRequestSuccess(t *testing.T) {
t.Log("when the event is a pull request and everything works we return a 200")
e, v, _, p, _, c, _ := setup(t)
eventsReq.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
When(v.Validate(eventsReq, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{}
When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(pull, repo, nil)
When(p.ParseGithubRepo(matchers.AnyPtrToGithubRepository())).ThenReturn(repo, nil)
When(c.CleanUpPull(repo, pull, vcs.Github)).ThenReturn(nil)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Pull request cleaned successfully")
}
func TestPost_GitlabMergeRequestSuccess(t *testing.T) {
t.Log("when the event is a gitlab merge request and the cleanup works we return a 200")
e, _, gl, p, _, _, _ := setup(t)
eventsReq.Header.Set(gitlabHeader, "value")
event := gitlab.MergeEvent{}
When(gl.Validate(eventsReq, secret)).ThenReturn(event, nil)
repo := models.Repo{}
pullRequest := models.PullRequest{State: models.Closed}
When(p.ParseGitlabMergeEvent(event)).ThenReturn(pullRequest, repo)
w := httptest.NewRecorder()
e.Post(w, eventsReq)
responseContains(t, w, http.StatusOK, "Merge request cleaned successfully")
}
func setup(t *testing.T) (server.EventsController, *mocks.MockGithubRequestValidator, *mocks.MockGitlabRequestParser, *emocks.MockEventParsing, *emocks.MockCommandRunner, *emocks.MockPullCleaner, *vcsmocks.MockClientProxy) {
RegisterMockTestingT(t)
eventsReq, _ = http.NewRequest("GET", "", bytes.NewBuffer(nil))
v := mocks.NewMockGithubRequestValidator()
gl := mocks.NewMockGitlabRequestParser()
p := emocks.NewMockEventParsing()
cr := emocks.NewMockCommandRunner()
c := emocks.NewMockPullCleaner()
vcsmock := vcsmocks.NewMockClientProxy()
e := server.EventsController{
Logger: logging.NewNoopLogger(),
GithubRequestValidator: v,
Parser: p,
CommandRunner: cr,
PullCleaner: c,
GithubWebHookSecret: secret,
SupportedVCSHosts: []vcs.Host{vcs.Github, vcs.Gitlab},
GitlabWebHookSecret: secret,
GitlabRequestParser: gl,
VCSClient: vcsmock,
}
return e, v, gl, p, cr, c, vcsmock
}