influxdb/kit/check/check_test.go

356 lines
7.3 KiB
Go

package check
import (
"context"
"encoding/json"
"errors"
"io"
"net"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
)
func TestEmptyCheck(t *testing.T) {
c := NewCheck()
resp := c.CheckReady(context.Background())
if len(resp.Checks) > 0 {
t.Errorf("no checks added but %d returned", len(resp.Checks))
}
if resp.Name != "Ready" {
t.Errorf("expected: \"Ready\", got: %q", resp.Name)
}
if resp.Status != StatusPass {
t.Errorf("expected: %q, got: %q", StatusPass, resp.Status)
}
}
func TestAddHealthCheck(t *testing.T) {
h := NewCheck()
h.AddHealthCheck(Named("awesome", ErrCheck(func() error {
return nil
})))
r := h.CheckHealth(context.Background())
if r.Status != StatusPass {
t.Error("Health should fail because one of the check is unhealthy")
}
if len(r.Checks) != 1 {
t.Fatalf("check not in results: %+v", r.Checks)
}
v := r.Checks[0]
if v.Status != StatusPass {
t.Errorf("the added check should be pass not %q.", v.Status)
}
}
func TestAddUnHealthyCheck(t *testing.T) {
h := NewCheck()
h.AddHealthCheck(Named("failure", ErrCheck(func() error {
return errors.New("Oops! I am sorry")
})))
r := h.CheckHealth(context.Background())
if r.Status != StatusFail {
t.Error("Health should fail because one of the check is unhealthy")
}
if len(r.Checks) != 1 {
t.Fatal("check not in results")
}
v := r.Checks[0]
if v.Status != StatusFail {
t.Errorf("the added check should be fail not %s.", v.Status)
}
if v.Message != "Oops! I am sorry" {
t.Errorf(
"the error should be 'Oops! I am sorry' not %s.",
v.Message,
)
}
}
func buildCheckWithServer() (*Check, *httptest.Server) {
c := NewCheck()
return c, httptest.NewServer(c)
}
type mockCheck struct {
status Status
name string
}
func (m mockCheck) Check(_ context.Context) Response {
return Response{
Name: m.name,
Status: m.status,
}
}
func mockPass(name string) Checker {
return mockCheck{status: StatusPass, name: name}
}
func mockFail(name string) Checker {
return mockCheck{status: StatusFail, name: name}
}
func respBuilder(body io.ReadCloser) (*Response, error) {
defer body.Close()
d := json.NewDecoder(body)
r := &Response{}
return r, d.Decode(r)
}
func TestBasicHTTPHandler(t *testing.T) {
_, ts := buildCheckWithServer()
defer ts.Close()
resp, err := http.Get(ts.URL + "/ready")
if err != nil {
t.Fatal(err)
}
actual, err := respBuilder(resp.Body)
if err != nil {
t.Fatal(err)
}
expected := &Response{
Name: "Ready",
Status: StatusPass,
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected response. expected %v, actual %v", expected, actual)
}
}
func TestHealthSorting(t *testing.T) {
c, ts := buildCheckWithServer()
defer ts.Close()
c.AddHealthCheck(mockPass("a"))
c.AddHealthCheck(mockPass("c"))
c.AddHealthCheck(mockPass("b"))
c.AddHealthCheck(mockFail("k"))
c.AddHealthCheck(mockFail("b"))
resp, err := http.Get(ts.URL + "/health")
if err != nil {
t.Fatal(err)
}
actual, err := respBuilder(resp.Body)
if err != nil {
t.Fatal(err)
}
expected := &Response{
Name: "Health",
Status: "fail",
Checks: Responses{
Response{Name: "b", Status: "fail"},
Response{Name: "k", Status: "fail"},
Response{Name: "a", Status: "pass"},
Response{Name: "b", Status: "pass"},
Response{Name: "c", Status: "pass"},
},
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected response. expected %v, actual %v", expected, actual)
}
}
func TestForceHealth(t *testing.T) {
c, ts := buildCheckWithServer()
defer ts.Close()
c.AddHealthCheck(mockPass("a"))
_, err := http.Get(ts.URL + "/health?force=true&healthy=false")
if err != nil {
t.Fatal(err)
}
resp, err := http.Get(ts.URL + "/health")
if err != nil {
t.Fatal(err)
}
actual, err := respBuilder(resp.Body)
if err != nil {
t.Fatal(err)
}
expected := &Response{
Name: "Health",
Status: "fail",
Checks: Responses{
Response{Name: "manual-override", Message: "health manually overriden"},
Response{Name: "a", Status: "pass"},
},
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected response. expected %v, actual %v", expected, actual)
}
_, err = http.Get(ts.URL + "/health?force=false")
if err != nil {
t.Fatal(err)
}
expected = &Response{
Name: "Health",
Status: "pass",
Checks: Responses{
Response{Name: "a", Status: "pass"},
},
}
resp, err = http.Get(ts.URL + "/health")
if err != nil {
t.Fatal(err)
}
actual, err = respBuilder(resp.Body)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected response. expected %v, actual %v", expected, actual)
}
}
func TestNoCrossOver(t *testing.T) {
c, ts := buildCheckWithServer()
defer ts.Close()
c.AddHealthCheck(mockPass("a"))
c.AddHealthCheck(mockPass("c"))
c.AddReadyCheck(mockPass("b"))
c.AddReadyCheck(mockFail("k"))
c.AddHealthCheck(mockFail("b"))
resp, err := http.Get(ts.URL + "/health")
if err != nil {
t.Fatal(err)
}
actual, err := respBuilder(resp.Body)
if err != nil {
t.Fatal(err)
}
expected := &Response{
Name: "Health",
Status: "fail",
Checks: Responses{
Response{Name: "b", Status: "fail"},
Response{Name: "a", Status: "pass"},
Response{Name: "c", Status: "pass"},
},
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected response. expected %v, actual %v", expected, actual)
}
resp, err = http.Get(ts.URL + "/ready")
if err != nil {
t.Fatal(err)
}
actual, err = respBuilder(resp.Body)
if err != nil {
t.Fatal(err)
}
expected = &Response{
Name: "Ready",
Status: "fail",
Checks: Responses{
Response{Name: "k", Status: "fail"},
Response{Name: "b", Status: "pass"},
},
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected response. expected %v, actual %v", expected, actual)
}
}
func TestPassthrough(t *testing.T) {
c, ts := buildCheckWithServer()
defer ts.Close()
resp, err := http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 404 {
t.Fatalf("failed to error when no passthrough is present, status: %d", resp.StatusCode)
}
used := false
s := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
used = true
w.Write([]byte("hi"))
})
c.SetPassthrough(s)
resp, err = http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatalf("bad response code from passthrough, status: %d", resp.StatusCode)
}
if !used {
t.Fatal("passthrough server not used")
}
}
func ExampleNewCheck() {
// Run the default healthcheck. it always return 200. It is good if you
// have a service without any dependency
h := NewCheck()
h.CheckHealth(context.Background())
}
func ExampleCheck_CheckHealth() {
h := NewCheck()
h.AddHealthCheck(Named("google", CheckerFunc(func(ctx context.Context) Response {
var r net.Resolver
_, err := r.LookupHost(ctx, "google.com")
if err != nil {
return Error(err)
}
return Pass()
})))
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
h.CheckHealth(ctx)
}
func ExampleCheck_ServeHTTP() {
c := NewCheck()
http.ListenAndServe(":6060", c)
}
func ExampleCheck_SetPassthrough() {
c := NewCheck()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello friends!"))
})
c.SetPassthrough(http.DefaultServeMux)
http.ListenAndServe(":6060", c)
}