From aadd9ce367cae3e516c7c8f2ddeef0af4a6b5b10 Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Wed, 18 Oct 2017 07:36:37 -0700 Subject: [PATCH] Test EventsController Post() --- server/events/command_handler.go | 5 + server/events/mocks/mock_command_runner.go | 68 +++++++++++ server/events_controller.go | 13 ++- server/events_controller_test.go | 126 +++++++++++++++++++++ server/server.go | 2 +- 5 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 server/events/mocks/mock_command_runner.go diff --git a/server/events/command_handler.go b/server/events/command_handler.go index c3e478b50..ac2750b49 100644 --- a/server/events/command_handler.go +++ b/server/events/command_handler.go @@ -8,6 +8,11 @@ import ( "github.com/hootsuite/atlantis/server/recovery" ) +//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_command_runner.go CommandRunner +type CommandRunner interface { + ExecuteCommand(ctx *CommandContext) +} + // CommandHandler is the first step when processing a comment command. type CommandHandler struct { PlanExecutor Executor diff --git a/server/events/mocks/mock_command_runner.go b/server/events/mocks/mock_command_runner.go new file mode 100644 index 000000000..8df1d35c4 --- /dev/null +++ b/server/events/mocks/mock_command_runner.go @@ -0,0 +1,68 @@ +// Automatically generated by pegomock. DO NOT EDIT! +// Source: github.com/hootsuite/atlantis/server/events (interfaces: CommandRunner) + +package mocks + +import ( + events "github.com/hootsuite/atlantis/server/events" + pegomock "github.com/petergtz/pegomock" + "reflect" +) + +type MockCommandRunner struct { + fail func(message string, callerSkip ...int) +} + +func NewMockCommandRunner() *MockCommandRunner { + return &MockCommandRunner{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockCommandRunner) ExecuteCommand(ctx *events.CommandContext) { + params := []pegomock.Param{ctx} + pegomock.GetGenericMockFrom(mock).Invoke("ExecuteCommand", params, []reflect.Type{}) +} + +func (mock *MockCommandRunner) VerifyWasCalledOnce() *VerifierCommandRunner { + return &VerifierCommandRunner{mock, pegomock.Times(1), nil} +} + +func (mock *MockCommandRunner) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierCommandRunner { + return &VerifierCommandRunner{mock, invocationCountMatcher, nil} +} + +func (mock *MockCommandRunner) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierCommandRunner { + return &VerifierCommandRunner{mock, invocationCountMatcher, inOrderContext} +} + +type VerifierCommandRunner struct { + mock *MockCommandRunner + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext +} + +func (verifier *VerifierCommandRunner) ExecuteCommand(ctx *events.CommandContext) *CommandRunner_ExecuteCommand_OngoingVerification { + params := []pegomock.Param{ctx} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ExecuteCommand", params) + return &CommandRunner_ExecuteCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type CommandRunner_ExecuteCommand_OngoingVerification struct { + mock *MockCommandRunner + methodInvocations []pegomock.MethodInvocation +} + +func (c *CommandRunner_ExecuteCommand_OngoingVerification) GetCapturedArguments() *events.CommandContext { + ctx := c.GetAllCapturedArguments() + return ctx[len(ctx)-1] +} + +func (c *CommandRunner_ExecuteCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]*events.CommandContext, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(*events.CommandContext) + } + } + return +} diff --git a/server/events_controller.go b/server/events_controller.go index 44a275092..f0ade99db 100644 --- a/server/events_controller.go +++ b/server/events_controller.go @@ -10,10 +10,13 @@ import ( ) type EventsController struct { - CommandHandler *events.CommandHandler - PullClosedExecutor *events.PullClosedExecutor - Logger *logging.SimpleLogger - Parser events.EventParsing + CommandRunner events.CommandRunner + PullClosedExecutor *events.PullClosedExecutor + Logger *logging.SimpleLogger + Parser events.EventParsing + // GithubWebHookSecret is the secret added to this webhook via the GitHub + // UI that identifies this call as coming from GitHub. If empty, no + // request validation is done. GithubWebHookSecret []byte Validator GHRequestValidator } @@ -65,7 +68,7 @@ func (e *EventsController) HandleCommentEvent(w http.ResponseWriter, event *gith // We use a goroutine so that this function returns and the connection is // closed. fmt.Fprintln(w, "Processing...") - go e.CommandHandler.ExecuteCommand(ctx) + go e.CommandRunner.ExecuteCommand(ctx) } // HandlePullRequestEvent will delete any locks associated with the pull request diff --git a/server/events_controller_test.go b/server/events_controller_test.go index c998e2a3b..96e9a37d3 100644 --- a/server/events_controller_test.go +++ b/server/events_controller_test.go @@ -9,8 +9,16 @@ import ( "net/http" "bytes" "github.com/hootsuite/atlantis/server/mocks" + emocks "github.com/hootsuite/atlantis/server/events/mocks" "net/http/httptest" "errors" + "strings" + "io/ioutil" + "reflect" + "github.com/google/go-github/github" + "github.com/hootsuite/atlantis/server/events/models" + "github.com/hootsuite/atlantis/server/events" + "time" ) func TestPost_InvalidSecret(t *testing.T) { @@ -35,26 +43,134 @@ func TestPost_InvalidSecret(t *testing.T) { func TestPost_UnsupportedEvent(t *testing.T) { t.Log("when the event type is unsupported we ignore it") RegisterMockTestingT(t) + v := mocks.NewMockGHRequestValidator() + e := server.EventsController{ + Logger: logging.NewNoopLogger(), + Validator: v, + } + req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) + Ok(t, err) + w := httptest.NewRecorder() + When(v.Validate(req, nil)).ThenReturn([]byte(`{"not an event": ""}`), nil) + e.Post(w, req) + + Equals(t, http.StatusOK, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Assert(t, strings.Contains(string(body), "Ignoring unsupported event"), "Response body was: %s", string(body)) } func TestPost_CommentNotCreated(t *testing.T) { t.Log("when the event is a comment but it's not a created event we ignore it") RegisterMockTestingT(t) + v := mocks.NewMockGHRequestValidator() + e := server.EventsController{ + Logger: logging.NewNoopLogger(), + Validator: v, + } + req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) + req.Header.Set("X-Github-Event", "issue_comment") + Ok(t, err) + + // comment action is deleted, not created + event := `{"action": "deleted"}` + When(v.Validate(req, nil)).ThenReturn([]byte(event), nil) + w := httptest.NewRecorder() + e.Post(w, req) + + Equals(t, http.StatusOK, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Assert(t, strings.Contains(string(body), "Ignoring comment event since action was not created"), "Response body was: %s", string(body)) } func TestPost_CommentInvalidComment(t *testing.T) { t.Log("when the event is a comment without all expected data we return a 400") RegisterMockTestingT(t) + v := mocks.NewMockGHRequestValidator() + p := emocks.NewMockEventParsing() + e := server.EventsController{ + Logger: logging.NewNoopLogger(), + Validator: v, + Parser: p, + } + req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) + req.Header.Set("X-Github-Event", "issue_comment") + Ok(t, err) + + event := `{"action": "created"}` + When(v.Validate(req, nil)).ThenReturn([]byte(event), nil) + When(p.ExtractCommentData(AnyComment())).ThenReturn(models.Repo{}, models.User{}, models.PullRequest{}, errors.New("err")) + w := httptest.NewRecorder() + e.Post(w, req) + + Equals(t, http.StatusBadRequest, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Assert(t, strings.Contains(string(body), "Failed parsing event"), "Response body was: %s", string(body)) } func TestPost_CommentInvalidCommand(t *testing.T) { t.Log("when the event is a comment with an invalid command we ignore it") RegisterMockTestingT(t) + v := mocks.NewMockGHRequestValidator() + p := emocks.NewMockEventParsing() + e := server.EventsController{ + Logger: logging.NewNoopLogger(), + Validator: v, + Parser: p, + } + req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) + req.Header.Set("X-Github-Event", "issue_comment") + Ok(t, err) + + event := `{"action": "created"}` + When(v.Validate(req, nil)).ThenReturn([]byte(event), nil) + When(p.ExtractCommentData(AnyComment())).ThenReturn(models.Repo{}, models.User{}, models.PullRequest{}, nil) + When(p.DetermineCommand(AnyComment())).ThenReturn(nil, errors.New("err")) + w := httptest.NewRecorder() + e.Post(w, req) + + Equals(t, http.StatusOK, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Assert(t, strings.Contains(string(body), "Ignoring: err"), "Response body was: %s", string(body)) } func TestPost_CommentSuccess(t *testing.T) { t.Log("when the event is comment with a valid command we call the command handler") RegisterMockTestingT(t) + v := mocks.NewMockGHRequestValidator() + p := emocks.NewMockEventParsing() + cr := emocks.NewMockCommandRunner() + e := server.EventsController{ + Logger: logging.NewNoopLogger(), + Validator: v, + Parser: p, + CommandRunner: cr, + } + req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) + req.Header.Set("X-Github-Event", "issue_comment") + Ok(t, err) + + event := `{"action": "created"}` + When(v.Validate(req, nil)).ThenReturn([]byte(event), nil) + baseRepo := models.Repo{} + user := models.User{} + pull := models.PullRequest{} + cmd := events.Command{} + When(p.ExtractCommentData(AnyComment())).ThenReturn(baseRepo, user, pull, nil) + When(p.DetermineCommand(AnyComment())).ThenReturn(&cmd, nil) + w := httptest.NewRecorder() + e.Post(w, req) + + Equals(t, http.StatusOK, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Equals(t, "Processing...\n", string(body)) + + // wait for 200ms so goroutine is called + time.Sleep(200 * time.Millisecond) + ctx := cr.VerifyWasCalledOnce().ExecuteCommand(AnyCommandContext()).GetCapturedArguments() + Equals(t, baseRepo, ctx.BaseRepo) + Equals(t, user, ctx.User) + Equals(t, pull, ctx.Pull) + Equals(t, cmd, *ctx.Command) } func TestPost_PullRequestNotClosed(t *testing.T) { @@ -81,3 +197,13 @@ func TestPost_PullRequestSuccess(t *testing.T) { t.Log("when the event is a pull request and everything works we return a 200") RegisterMockTestingT(t) } + +func AnyComment() *github.IssueCommentEvent { + RegisterMatcher(NewAnyMatcher(reflect.TypeOf(&github.IssueCommentEvent{}))) + return &github.IssueCommentEvent{} +} + +func AnyCommandContext() *events.CommandContext { + RegisterMatcher(NewAnyMatcher(reflect.TypeOf(&events.CommandContext{}))) + return &events.CommandContext{} +} diff --git a/server/server.go b/server/server.go index 9a560fc43..6025cadce 100644 --- a/server/server.go +++ b/server/server.go @@ -128,7 +128,7 @@ func NewServer(config ServerConfig) (*Server, error) { Logger: logger, } eventsController := &EventsController{ - CommandHandler: commandHandler, + CommandRunner: commandHandler, PullClosedExecutor: pullClosedExecutor, Parser: eventParser, Logger: logger,