package hws_test import ( "bytes" "context" "io" "net/http" "net/http/httptest" "testing" "git.haelnorr.com/h/golib/hws" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type ( goodPage struct{} badPage struct{} ) func goodRender(error hws.HWSError) (hws.ErrorPage, error) { return goodPage{}, nil } func badRender1(error hws.HWSError) (hws.ErrorPage, error) { return badPage{}, nil } func badRender2(error hws.HWSError) (hws.ErrorPage, error) { return nil, errors.New("I'm an error") } func (g goodPage) Render(ctx context.Context, w io.Writer) error { _, err := w.Write([]byte("Test write to ResponseWriter")) return err } func (b badPage) Render(ctx context.Context, w io.Writer) error { return nil } func Test_AddErrorPage(t *testing.T) { var buf bytes.Buffer server := createTestServer(t, &buf) goodRender := goodRender badRender1 := badRender1 badRender2 := badRender2 tests := []struct { name string renderer hws.ErrorPageFunc valid bool }{ { name: "Valid Renderer", renderer: goodRender, valid: true, }, { name: "Invalid Renderer 1", renderer: badRender1, valid: false, }, { name: "Invalid Renderer 2", renderer: badRender2, valid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := server.AddErrorPage(tt.renderer) if tt.valid { assert.NoError(t, err) } else { assert.Error(t, err) } }) } } func Test_ThrowError(t *testing.T) { var buf bytes.Buffer server := createTestServer(t, &buf) rr := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) t.Run("Server not started", func(t *testing.T) { buf.Reset() server.ThrowError(rr, req, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "Error", Error: errors.New("Error"), }) // ThrowError logs errors internally when validation fails output := buf.String() assert.Contains(t, output, "ThrowError called before server started") }) startTestServer(t, server) tests := []struct { name string request *http.Request error hws.HWSError expectLogItem string }{ { name: "No HWSError.Status code", request: nil, error: hws.HWSError{}, expectLogItem: "HWSError.StatusCode cannot be 0", }, { name: "Negative HWSError.Status code", request: nil, error: hws.HWSError{StatusCode: -1}, expectLogItem: "HWSError.StatusCode cannot be 0", }, { name: "No HWSError.Message", request: nil, error: hws.HWSError{StatusCode: http.StatusInternalServerError}, expectLogItem: "HWSError.Message cannot be empty", }, { name: "No HWSError.Error", request: nil, error: hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", }, expectLogItem: "HWSError.Error cannot be nil", }, { name: "No request provided", request: nil, error: hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", Error: errors.New("Error"), }, expectLogItem: "Request cannot be nil", }, { name: "Valid", request: httptest.NewRequest("GET", "/", nil), error: hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", Error: errors.New("Error"), }, expectLogItem: "An error occured", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { buf.Reset() rr := httptest.NewRecorder() server.ThrowError(rr, tt.request, tt.error) // ThrowError no longer returns errors; check logs instead output := buf.String() assert.Contains(t, output, tt.expectLogItem) }) } t.Run("Log level set correctly", func(t *testing.T) { buf.Reset() rr := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) server.ThrowError(rr, req, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", Error: errors.New("Error"), Level: hws.ErrorWARN, }) _, err := buf.ReadString([]byte(" ")[0]) require.NoError(t, err) loglvl, err := buf.ReadString([]byte(" ")[0]) require.NoError(t, err) assert.Equal(t, "\x1b[33mWRN\x1b[0m ", loglvl, "Log level should be WRN for ErrorWARN") buf.Reset() server.ThrowError(rr, req, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", Error: errors.New("Error"), }) _, err = buf.ReadString([]byte(" ")[0]) require.NoError(t, err) loglvl, err = buf.ReadString([]byte(" ")[0]) require.NoError(t, err) assert.Equal(t, "\x1b[31mERR\x1b[0m ", loglvl, "Log level should be ERR when no level specified") }) t.Run("Error page doesnt render if no error page set", func(t *testing.T) { // Must be run before adding the error page to the test server rr := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) server.ThrowError(rr, req, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", Error: errors.New("Error"), RenderErrorPage: true, }) body := rr.Body.String() assert.Empty(t, body, "Error page should not render when no error page is set") }) t.Run("Error page renders", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) // Adding the error page will carry over to all future tests and cant be undone err := server.AddErrorPage(goodRender) require.NoError(t, err) server.ThrowError(rr, req, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", Error: errors.New("Error"), RenderErrorPage: true, }) body := rr.Body.String() assert.NotEmpty(t, body, "Error page should render when RenderErrorPage is true") }) t.Run("Error page doesnt render if not told to render", func(t *testing.T) { // Error page already added to server rr := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) server.ThrowError(rr, req, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", Error: errors.New("Error"), }) body := rr.Body.String() assert.Empty(t, body, "Error page should not render when RenderErrorPage is false") }) err := server.Shutdown(t.Context()) require.NoError(t, err) t.Run("Doesn't panic if no logger added to server", func(t *testing.T) { server, err := hws.NewServer(&hws.Config{ Host: "127.0.0.1", Port: randomPort(), }) require.NoError(t, err) err = server.AddRoutes(hws.Route{ Path: "/", Method: hws.MethodGET, Handler: testHandler, }) require.NoError(t, err) err = server.Start(t.Context()) require.NoError(t, err) <-server.Ready() rr := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) // Should not panic when no logger is present assert.NotPanics(t, func() { server.ThrowError(rr, req, hws.HWSError{ StatusCode: http.StatusInternalServerError, Message: "An error occured", Error: errors.New("Error"), }) }, "ThrowError should not panic when no logger is present") err = server.Shutdown(t.Context()) require.NoError(t, err) }) }