From 1c541f78af321bb88cbfee3fe1de828ed23a79e9 Mon Sep 17 00:00:00 2001 From: Vitaly Karpenko Date: Thu, 12 Apr 2018 18:54:54 +0300 Subject: [PATCH] Added tests for health checks (#3) --- health/checkers.go | 28 --------- health/dnsprobecheck_test.go | 13 +++++ health/handler.go | 24 ++++---- health/handler_test.go | 107 +++++++++++++++++++++++++++++++++++ health/httpgetcheck_test.go | 14 +++++ 5 files changed, 148 insertions(+), 38 deletions(-) delete mode 100644 health/checkers.go create mode 100644 health/dnsprobecheck_test.go create mode 100644 health/handler_test.go create mode 100644 health/httpgetcheck_test.go diff --git a/health/checkers.go b/health/checkers.go deleted file mode 100644 index 959627c6..00000000 --- a/health/checkers.go +++ /dev/null @@ -1,28 +0,0 @@ -package health - -import ( - "fmt" - "net/http" - "net/url" - "time" -) - -func HttpGetCheck(url *url.URL, timeout time.Duration) Check { - client := http.Client{ - Timeout: timeout, - CheckRedirect: func(*http.Request, []*http.Request) error { - return http.ErrUseLastResponse - }, - } - return func() error { - resp, err := client.Get(url.String()) - if err != nil { - return err - } - resp.Body.Close() - if resp.StatusCode != 200 { - return fmt.Errorf("%d: %s", resp.StatusCode, resp.Status) - } - return nil - } -} diff --git a/health/dnsprobecheck_test.go b/health/dnsprobecheck_test.go new file mode 100644 index 00000000..3d22ac9b --- /dev/null +++ b/health/dnsprobecheck_test.go @@ -0,0 +1,13 @@ +package health + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestDNSProbeCheck(t *testing.T) { + assert.NoError(t, DNSProbeCheck("google.com", 5*time.Second)()) + assert.Error(t, DNSProbeCheck("never.ever.where.com", 5*time.Second)()) +} diff --git a/health/handler.go b/health/handler.go index a3e47293..5e8ceee5 100644 --- a/health/handler.go +++ b/health/handler.go @@ -61,10 +61,10 @@ func (ch *checksHandler) healthEndpoint(rw http.ResponseWriter, r *http.Request) } func (ch *checksHandler) readyEndpoint(rw http.ResponseWriter, r *http.Request) { - ch.handle(rw, r, ch.readinessChecks) + ch.handle(rw, r, ch.readinessChecks, ch.livenessChecks) } -func (ch *checksHandler) handle(rw http.ResponseWriter, r *http.Request, checks map[string]Check) { +func (ch *checksHandler) handle(rw http.ResponseWriter, r *http.Request, checksSets ...map[string]Check) { if r.Method != http.MethodGet { http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return @@ -74,18 +74,22 @@ func (ch *checksHandler) handle(rw http.ResponseWriter, r *http.Request, checks status := http.StatusOK ch.lock.RLock() defer ch.lock.RUnlock() - for name, check := range checks { - if check == nil { - continue - } - if err := check(); err != nil { - status = http.StatusServiceUnavailable - errors[name] = err + + for _, checks := range checksSets { + for name, check := range checks { + if check == nil { + continue + } + if err := check(); err != nil { + status = http.StatusServiceUnavailable + errors[name] = err + } } } - rw.WriteHeader(status) + return + // Uncomment to write errors and get non-empty response // rw.Header().Set("Content-Type", "application/json; charset=utf-8") // if status == http.StatusOK { diff --git a/health/handler_test.go b/health/handler_test.go new file mode 100644 index 00000000..5e0d67df --- /dev/null +++ b/health/handler_test.go @@ -0,0 +1,107 @@ +package health + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewHandler(t *testing.T) { + var tests = []struct { + name string + method string + path string + failHealth bool + failReady bool + expectedCode int + }{ + { + name: "Non-existent URL", + method: http.MethodPost, + path: "/neverwhere", + expectedCode: http.StatusNotFound, + }, + { + name: "POST Method not allowed health", + method: http.MethodPost, + path: "/healthz", + expectedCode: http.StatusMethodNotAllowed, + }, + { + name: "POST Method not allowed ready", + method: http.MethodPost, + path: "/ready", + expectedCode: http.StatusMethodNotAllowed, + }, + { + name: "No checks health", + method: http.MethodGet, + path: "/healthz", + expectedCode: http.StatusOK, + }, + { + name: "No checks ready", + method: http.MethodGet, + path: "/ready", + expectedCode: http.StatusOK, + }, + { + name: "Health succeed Ready fail health", + method: http.MethodGet, + path: "/healthz", + expectedCode: http.StatusOK, + failReady: true, + }, + { + name: "Health succeed Ready fail ready", + method: http.MethodGet, + path: "/ready", + expectedCode: http.StatusServiceUnavailable, + failReady: true, + }, + { + name: "Health fail Ready succeed health", + method: http.MethodGet, + path: "/healthz", + expectedCode: http.StatusServiceUnavailable, + failHealth: true, + }, + { + name: "Health fail Ready succeed ready", + method: http.MethodGet, + path: "/ready", + expectedCode: http.StatusServiceUnavailable, + failHealth: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + h := NewChecksHandler("/healthz", "/ready") + + if test.failHealth { + h.AddLiveness("Liveness check test", func() error { + return errors.New("Liveness check failed") + }) + } + + if test.failReady { + h.AddReadiness("Readiness check test", func() error { + return errors.New("Readiness check failed") + }) + } + + req, err := http.NewRequest(test.method, test.path, nil) + assert.NoError(t, err) + + reqStr := test.method + " " + test.path + httpRecorder := httptest.NewRecorder() + h.Handler().ServeHTTP(httpRecorder, req) + assert.Equal(t, test.expectedCode, httpRecorder.Code, + "Result codes don't match %q. [%s]", reqStr, test.name) + }) + } +} diff --git a/health/httpgetcheck_test.go b/health/httpgetcheck_test.go new file mode 100644 index 00000000..f4f38156 --- /dev/null +++ b/health/httpgetcheck_test.go @@ -0,0 +1,14 @@ +package health + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestHTTPGetCheck(t *testing.T) { + assert.NoError(t, HTTPGetCheck("http://httpbin.org/get", 5*time.Second)(), "Simple HTTP GET request shouldn't fail") + assert.Error(t, HTTPGetCheck("http://httpbin.org/relative-redirect/:1", 5*time.Second)(), "Redirrect is not HTTP 200: OK") + assert.Error(t, HTTPGetCheck("http://httpbin.org/nonexistent", 5*time.Second)(), "Non-existing site exists") +}