package internalhttp import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/stretchr/testify/require" ) func newTestConfig() Config { return Config{ Addr: ":0", ReadHeaderTimeout: time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, IdleTimeout: time.Second, } } type stubReadiness struct { err error } func (probe stubReadiness) Check(_ context.Context) error { return probe.err } func newTestServer(t *testing.T, deps Dependencies) http.Handler { t.Helper() server, err := NewServer(newTestConfig(), deps) require.NoError(t, err) return server.handler } func TestHealthzReturnsOK(t *testing.T) { t.Parallel() handler := newTestServer(t, Dependencies{}) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, HealthzPath, nil) handler.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) require.Equal(t, jsonContentType, rec.Header().Get("Content-Type")) var body statusResponse require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) require.Equal(t, "ok", body.Status) } func TestReadyzReturnsReadyWhenProbeIsNil(t *testing.T) { t.Parallel() handler := newTestServer(t, Dependencies{}) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, ReadyzPath, nil) handler.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) var body statusResponse require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) require.Equal(t, "ready", body.Status) } func TestReadyzReturnsReadyWhenProbeSucceeds(t *testing.T) { t.Parallel() handler := newTestServer(t, Dependencies{Readiness: stubReadiness{}}) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, ReadyzPath, nil) handler.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) var body statusResponse require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) require.Equal(t, "ready", body.Status) } func TestReadyzReturnsServiceUnavailableWhenProbeFails(t *testing.T) { t.Parallel() handler := newTestServer(t, Dependencies{ Readiness: stubReadiness{err: errors.New("postgres ping: connection refused")}, }) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, ReadyzPath, nil) handler.ServeHTTP(rec, req) require.Equal(t, http.StatusServiceUnavailable, rec.Code) require.Equal(t, jsonContentType, rec.Header().Get("Content-Type")) var body errorResponse require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) require.Equal(t, errorCodeServiceUnavailable, body.Error.Code) require.True(t, strings.Contains(body.Error.Message, "postgres")) } func TestNewServerRejectsInvalidConfig(t *testing.T) { t.Parallel() _, err := NewServer(Config{}, Dependencies{}) require.Error(t, err) } func TestRunBindsListenerAndShutsDown(t *testing.T) { t.Parallel() server, err := NewServer(newTestConfig(), Dependencies{}) require.NoError(t, err) runErr := make(chan error, 1) go func() { runErr <- server.Run(t.Context()) }() require.Eventually(t, func() bool { return server.Addr() != "" }, time.Second, 10*time.Millisecond, "listener should bind quickly") shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), time.Second) defer shutdownCancel() require.NoError(t, server.Shutdown(shutdownCtx)) select { case err := <-runErr: require.NoError(t, err) case <-time.After(time.Second): t.Fatal("server did not return after shutdown") } }