diff --git a/server/events/mocks/mock_pull_cleaner.go b/server/events/mocks/mock_pull_cleaner.go new file mode 100644 index 000000000..604c910d2 --- /dev/null +++ b/server/events/mocks/mock_pull_cleaner.go @@ -0,0 +1,79 @@ +// Automatically generated by pegomock. DO NOT EDIT! +// Source: github.com/hootsuite/atlantis/server/events (interfaces: PullCleaner) + +package mocks + +import ( + models "github.com/hootsuite/atlantis/server/events/models" + pegomock "github.com/petergtz/pegomock" + "reflect" +) + +type MockPullCleaner struct { + fail func(message string, callerSkip ...int) +} + +func NewMockPullCleaner() *MockPullCleaner { + return &MockPullCleaner{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockPullCleaner) CleanUpPull(repo models.Repo, pull models.PullRequest) error { + params := []pegomock.Param{repo, pull} + result := pegomock.GetGenericMockFrom(mock).Invoke("CleanUpPull", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(error) + } + } + return ret0 +} + +func (mock *MockPullCleaner) VerifyWasCalledOnce() *VerifierPullCleaner { + return &VerifierPullCleaner{mock, pegomock.Times(1), nil} +} + +func (mock *MockPullCleaner) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierPullCleaner { + return &VerifierPullCleaner{mock, invocationCountMatcher, nil} +} + +func (mock *MockPullCleaner) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierPullCleaner { + return &VerifierPullCleaner{mock, invocationCountMatcher, inOrderContext} +} + +type VerifierPullCleaner struct { + mock *MockPullCleaner + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext +} + +func (verifier *VerifierPullCleaner) CleanUpPull(repo models.Repo, pull models.PullRequest) *PullCleaner_CleanUpPull_OngoingVerification { + params := []pegomock.Param{repo, pull} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CleanUpPull", params) + return &PullCleaner_CleanUpPull_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type PullCleaner_CleanUpPull_OngoingVerification struct { + mock *MockPullCleaner + methodInvocations []pegomock.MethodInvocation +} + +func (c *PullCleaner_CleanUpPull_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest) { + repo, pull := c.GetAllCapturedArguments() + return repo[len(repo)-1], pull[len(pull)-1] +} + +func (c *PullCleaner_CleanUpPull_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]models.Repo, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(models.Repo) + } + _param1 = make([]models.PullRequest, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.(models.PullRequest) + } + } + return +} diff --git a/server/events/pull_closed_executor.go b/server/events/pull_closed_executor.go index ada4b3254..a98f39882 100644 --- a/server/events/pull_closed_executor.go +++ b/server/events/pull_closed_executor.go @@ -6,13 +6,19 @@ import ( "strings" "text/template" + "sort" + "github.com/hootsuite/atlantis/server/events/github" "github.com/hootsuite/atlantis/server/events/locking" "github.com/hootsuite/atlantis/server/events/models" "github.com/pkg/errors" - "sort" ) +//go:generate pegomock generate --use-experimental-model-gen --package mocks -o mocks/mock_pull_cleaner.go PullCleaner +type PullCleaner interface { + CleanUpPull(repo models.Repo, pull models.PullRequest) error +} + type PullClosedExecutor struct { Locker locking.Locker Github github.Client diff --git a/server/events_controller.go b/server/events_controller.go index f0ade99db..326563bef 100644 --- a/server/events_controller.go +++ b/server/events_controller.go @@ -10,10 +10,10 @@ import ( ) type EventsController struct { - CommandRunner events.CommandRunner - PullClosedExecutor *events.PullClosedExecutor - Logger *logging.SimpleLogger - Parser events.EventParsing + CommandRunner events.CommandRunner + PullCleaner events.PullCleaner + 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. @@ -88,12 +88,12 @@ func (e *EventsController) HandlePullRequestEvent(w http.ResponseWriter, pullEve return } - if err := e.PullClosedExecutor.CleanUpPull(repo, pull); err != nil { - e.respond(w, logging.Error, http.StatusServiceUnavailable, "Error cleaning pull request: %s", err) + if err := e.PullCleaner.CleanUpPull(repo, pull); err != nil { + e.respond(w, logging.Error, http.StatusInternalServerError, "Error cleaning pull request: %s", err) return } e.Logger.Info("deleted locks and workspace for repo %s, pull %d", repo.FullName, pull.Num) - fmt.Fprint(w, "Pull request cleaned successfully") + fmt.Fprintln(w, "Pull request cleaned successfully") } func (e *EventsController) respond(w http.ResponseWriter, lvl logging.LogLevel, code int, format string, args ...interface{}) { diff --git a/server/events_controller_test.go b/server/events_controller_test.go index 96e9a37d3..3b3565c24 100644 --- a/server/events_controller_test.go +++ b/server/events_controller_test.go @@ -1,24 +1,25 @@ package server_test import ( + "bytes" + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "strings" "testing" + "time" + + "github.com/google/go-github/github" + "github.com/hootsuite/atlantis/server" + "github.com/hootsuite/atlantis/server/events" + emocks "github.com/hootsuite/atlantis/server/events/mocks" + "github.com/hootsuite/atlantis/server/events/models" + "github.com/hootsuite/atlantis/server/logging" + "github.com/hootsuite/atlantis/server/mocks" . "github.com/hootsuite/atlantis/testing_util" . "github.com/petergtz/pegomock" - "github.com/hootsuite/atlantis/server" - "github.com/hootsuite/atlantis/server/logging" - "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) { @@ -90,7 +91,7 @@ func TestPost_CommentInvalidComment(t *testing.T) { e := server.EventsController{ Logger: logging.NewNoopLogger(), Validator: v, - Parser: p, + Parser: p, } req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) req.Header.Set("X-Github-Event", "issue_comment") @@ -115,7 +116,7 @@ func TestPost_CommentInvalidCommand(t *testing.T) { e := server.EventsController{ Logger: logging.NewNoopLogger(), Validator: v, - Parser: p, + Parser: p, } req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) req.Header.Set("X-Github-Event", "issue_comment") @@ -176,26 +177,138 @@ func TestPost_CommentSuccess(t *testing.T) { func TestPost_PullRequestNotClosed(t *testing.T) { t.Log("when the event is pull reuqest but it's not a closed event 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", "pull_request") + Ok(t, err) + + event := `{"action": "opened"}` + 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 pull request event since action was not closed"), "was: %s", string(body)) } func TestPost_PullRequestInvalid(t *testing.T) { - t.Log("when the event is pull reuqest with invalid data we return a 400") + t.Log("when the event is pull request with invalid 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", "pull_request") + Ok(t, err) + + event := `{"action": "closed"}` + When(v.Validate(req, nil)).ThenReturn([]byte(event), nil) + When(p.ExtractPullData(AnyPull())).ThenReturn(models.PullRequest{}, models.Repo{}, errors.New("err")) + w := httptest.NewRecorder() + e.Post(w, req) + + Equals(t, http.StatusBadRequest, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Equals(t, "Error parsing pull data: err\n", string(body)) } func TestPost_PullRequestInvalidRepo(t *testing.T) { t.Log("when the event is pull reuqest with invalid repo 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", "pull_request") + Ok(t, err) + + event := `{"action": "closed"}` + When(v.Validate(req, nil)).ThenReturn([]byte(event), nil) + When(p.ExtractPullData(AnyPull())).ThenReturn(models.PullRequest{}, models.Repo{}, nil) + When(p.ExtractRepoData(AnyRepo())).ThenReturn(models.Repo{}, errors.New("err")) + w := httptest.NewRecorder() + e.Post(w, req) + + Equals(t, http.StatusBadRequest, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Equals(t, "Error parsing repo data: err\n", string(body)) } func TestPost_PullRequestErrCleaningPull(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) + v := mocks.NewMockGHRequestValidator() + p := emocks.NewMockEventParsing() + c := emocks.NewMockPullCleaner() + e := server.EventsController{ + Logger: logging.NewNoopLogger(), + Validator: v, + Parser: p, + PullCleaner: c, + } + req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) + req.Header.Set("X-Github-Event", "pull_request") + Ok(t, err) + + event := `{"action": "closed"}` + When(v.Validate(req, nil)).ThenReturn([]byte(event), nil) + repo := models.Repo{} + pull := models.PullRequest{} + When(p.ExtractPullData(AnyPull())).ThenReturn(pull, repo, nil) + When(p.ExtractRepoData(AnyRepo())).ThenReturn(repo, nil) + When(c.CleanUpPull(repo, pull)).ThenReturn(errors.New("cleanup err")) + w := httptest.NewRecorder() + e.Post(w, req) + + Equals(t, http.StatusInternalServerError, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Equals(t, "Error cleaning pull request: cleanup err\n", string(body)) } func TestPost_PullRequestSuccess(t *testing.T) { t.Log("when the event is a pull request and everything works we return a 200") RegisterMockTestingT(t) + v := mocks.NewMockGHRequestValidator() + p := emocks.NewMockEventParsing() + c := emocks.NewMockPullCleaner() + e := server.EventsController{ + Logger: logging.NewNoopLogger(), + Validator: v, + Parser: p, + PullCleaner: c, + } + req, err := http.NewRequest("GET", "http://localhost/event", bytes.NewBuffer(nil)) + req.Header.Set("X-Github-Event", "pull_request") + Ok(t, err) + + event := `{"action": "closed"}` + When(v.Validate(req, nil)).ThenReturn([]byte(event), nil) + repo := models.Repo{} + pull := models.PullRequest{} + When(p.ExtractPullData(AnyPull())).ThenReturn(pull, repo, nil) + When(p.ExtractRepoData(AnyRepo())).ThenReturn(repo, nil) + When(c.CleanUpPull(repo, pull)).ThenReturn(nil) + w := httptest.NewRecorder() + e.Post(w, req) + + Equals(t, http.StatusOK, w.Result().StatusCode) + body, _ := ioutil.ReadAll(w.Result().Body) + Equals(t, "Pull request cleaned successfully\n", string(body)) } func AnyComment() *github.IssueCommentEvent { @@ -203,6 +316,16 @@ func AnyComment() *github.IssueCommentEvent { return &github.IssueCommentEvent{} } +func AnyPull() *github.PullRequest { + RegisterMatcher(NewAnyMatcher(reflect.TypeOf(&github.PullRequest{}))) + return &github.PullRequest{} +} + +func AnyRepo() *github.Repository { + RegisterMatcher(NewAnyMatcher(reflect.TypeOf(&github.Repository{}))) + return &github.Repository{} +} + func AnyCommandContext() *events.CommandContext { RegisterMatcher(NewAnyMatcher(reflect.TypeOf(&events.CommandContext{}))) return &events.CommandContext{} diff --git a/server/server.go b/server/server.go index 6025cadce..5e1f14afb 100644 --- a/server/server.go +++ b/server/server.go @@ -129,7 +129,7 @@ func NewServer(config ServerConfig) (*Server, error) { } eventsController := &EventsController{ CommandRunner: commandHandler, - PullClosedExecutor: pullClosedExecutor, + PullCleaner: pullClosedExecutor, Parser: eventParser, Logger: logger, GithubWebHookSecret: []byte(config.GithubWebHookSecret),