diff --git a/go.mod b/go.mod index e5ebc89..9c1ca57 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + go.uber.org/mock v0.4.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect golang.org/x/net v0.26.0 // indirect diff --git a/go.sum b/go.sum index c59d29c..a4930ba 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= diff --git a/server_mock_test.go b/server_mock_test.go new file mode 100644 index 0000000..aafc36a --- /dev/null +++ b/server_mock_test.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/kazhuravlev/healthcheck (interfaces: IHealthcheck) +// +// Generated by this command: +// +// mockgen -destination server_mock_test.go -package healthcheck_test . IHealthcheck +// + +// Package healthcheck_test is a generated GoMock package. +package healthcheck_test + +import ( + context "context" + reflect "reflect" + + healthcheck "github.com/kazhuravlev/healthcheck" + gomock "go.uber.org/mock/gomock" +) + +// MockIHealthcheck is a mock of IHealthcheck interface. +type MockIHealthcheck struct { + ctrl *gomock.Controller + recorder *MockIHealthcheckMockRecorder +} + +// MockIHealthcheckMockRecorder is the mock recorder for MockIHealthcheck. +type MockIHealthcheckMockRecorder struct { + mock *MockIHealthcheck +} + +// NewMockIHealthcheck creates a new mock instance. +func NewMockIHealthcheck(ctrl *gomock.Controller) *MockIHealthcheck { + mock := &MockIHealthcheck{ctrl: ctrl} + mock.recorder = &MockIHealthcheckMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIHealthcheck) EXPECT() *MockIHealthcheckMockRecorder { + return m.recorder +} + +// RunAllChecks mocks base method. +func (m *MockIHealthcheck) RunAllChecks(arg0 context.Context) healthcheck.Report { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RunAllChecks", arg0) + ret0, _ := ret[0].(healthcheck.Report) + return ret0 +} + +// RunAllChecks indicates an expected call of RunAllChecks. +func (mr *MockIHealthcheckMockRecorder) RunAllChecks(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunAllChecks", reflect.TypeOf((*MockIHealthcheck)(nil).RunAllChecks), arg0) +} diff --git a/server_test.go b/server_test.go new file mode 100644 index 0000000..73c70c2 --- /dev/null +++ b/server_test.go @@ -0,0 +1,103 @@ +package healthcheck_test + +import ( + "context" + "github.com/kazhuravlev/healthcheck" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "io" + "math/rand" + "net/http" + "net/http/httptest" + "strconv" + "testing" + "time" +) + +//go:generate mockgen -destination server_mock_test.go -package healthcheck_test . IHealthcheck + +func TestReadyHandler(t *testing.T) { + ctrl := gomock.NewController(t) + hc := NewMockIHealthcheck(ctrl) + + f := func(status healthcheck.Status, expStatus int, expBody string) { + t.Run("", func(t *testing.T) { + ctx := context.Background() + hc. + EXPECT(). + RunAllChecks(gomock.Eq(ctx)). + Return(healthcheck.Report{ + Status: status, + Checks: []healthcheck.Check{}, + }) + + req := httptest.NewRequest(http.MethodGet, "/ready", nil) + w := httptest.NewRecorder() + + handler := healthcheck.ReadyHandler(hc) + handler(w, req) + + res := w.Result() + defer res.Body.Close() + + require.Equal(t, expStatus, res.StatusCode) + require.Equal(t, "application/json", res.Header.Get("Content-Type")) + + bb, err := io.ReadAll(res.Body) + require.NoError(t, err) + + require.Equal(t, expBody, string(bb)) + }) + } + + f(healthcheck.StatusUp, http.StatusOK, `{"status":"up","checks":[]}`) + + f(healthcheck.StatusDown, http.StatusInternalServerError, `{"status":"down","checks":[]}`) + + f("i_do_not_know", http.StatusInternalServerError, `{"status":"unknown","checks":[]}`) +} + +func TestServer(t *testing.T) { + ctrl := gomock.NewController(t) + hc := NewMockIHealthcheck(ctrl) + + port := rand.Intn(1000) + 8000 + srv, err := healthcheck.NewServer(hc, healthcheck.WithPort(port)) + require.NoError(t, err) + + ctx := context.Background() + require.NoError(t, srv.Run(ctx)) + + // FIXME: fix crunch + time.Sleep(time.Second) + + t.Run("live_returns_200", func(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:"+strconv.Itoa(port)+"/live", nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + }) + + t.Run("ready_always_call_healthcheck", func(t *testing.T) { + hc. + EXPECT(). + RunAllChecks(gomock.Any()). + Return(healthcheck.Report{ + Status: healthcheck.StatusDown, + Checks: []healthcheck.Check{}, + }) + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:"+strconv.Itoa(port)+"/ready", nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusInternalServerError, resp.StatusCode) + }) +}