diff --git a/Makefile b/Makefile index 155ef29..1353523 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,23 @@ SQL_DATABASE?=./ratchet.db +# TODO maybe only use -race in an ARG for CI +FLAGS := -race -v + +# `make ARGS=no-cache` will avoid using cached results in go test. +ifeq ($(ARGS), no-cache) + FLAGS += -count=1 +endif + +# make test ARGS=no-cache test: - go test -v ./... + go test $(FLAGS) ./... PHONEY: test +# make test-int ARGS=no-cache +test-int: + go test $(FLAGS) ./cmd/... +.PHONY: test-int + serve: go run ./cmd/ratchetd/main.go PHONEY: serve diff --git a/cmd/ratchetd/main_test.go b/cmd/ratchetd/main_test.go index 06ab7d0..2210a80 100644 --- a/cmd/ratchetd/main_test.go +++ b/cmd/ratchetd/main_test.go @@ -1 +1,58 @@ package main + +import ( + "net/http" + "testing" + + "git.runcible.io/learning/ratchet/internal/assert" +) + +// func testingLogger() *slog.Logger { +// return slog.New(slog.NewTextHandler(io.Discard, nil)) +// } + +// WITHOUT testutils_test.go helpers +// func TestPingIntegration(t *testing.T) { + +// rs := server.NewRatchetApp(testingLogger(), nil, nil, nil) + +// // We then use the httptest.NewTLSServer() function to create a new test +// // server, passing in the value returned by our app.routes() method as the +// // handler for the server. This starts up a HTTPS server which listens on a +// // randomly-chosen port of your local machine for the duration of the test. +// // Notice that we defer a call to ts.Close() so that the server is shutdown +// // when the test finishes. +// ts := httptest.NewTLSServer(rs) +// defer ts.Close() + +// resp, err := ts.Client().Get(ts.URL + "/ping") +// if err != nil { +// t.Fatal(err) +// } + +// assert.Equal(t, resp.StatusCode, http.StatusOK) + +// defer resp.Body.Close() +// body, err := io.ReadAll(resp.Body) +// if err != nil { +// t.Fatal(err) +// } + +// body = bytes.TrimSpace(body) +// assert.Equal(t, string(body), "OK") +// } + +func TestPingIntegration(t *testing.T) { + // Tests marked using t.Parallel() will be run in parallel with — and only with — other parallel tests. + // By default, the maximum number of tests that will be run simultaneously is the current value of + // GOMAXPROCS. You can override this by setting a specific value via the -parallel flag. + t.Parallel() + app := newTestApplication(t) + + ts := newTestServer(t, app) + defer ts.Close() + + code, _, body := ts.get(t, "/ping") + assert.Equal(t, code, http.StatusOK) + assert.Equal(t, body, "OK") +} diff --git a/cmd/ratchetd/testutils_test.go b/cmd/ratchetd/testutils_test.go new file mode 100644 index 0000000..fafee70 --- /dev/null +++ b/cmd/ratchetd/testutils_test.go @@ -0,0 +1,62 @@ +package main + +import ( + "io" + "log/slog" + "net/http" + "net/http/cookiejar" + "net/http/httptest" + "testing" + + "git.runcible.io/learning/ratchet/internal/server" +) + +// Create a newTestApplication helper which returns an instance of our +// application struct containing mocked dependencies. +func newTestApplication(t *testing.T) *server.RatchetApp { + + rs := server.NewRatchetApp(slog.New(slog.NewTextHandler(io.Discard, nil)), nil, nil, nil) + + return rs +} + +// create out own test server with additional receiver functions for ease of testing +type testServer struct { + *httptest.Server +} + +func newTestServer(t *testing.T, h http.Handler) *testServer { + ts := httptest.NewTLSServer(h) + + jar, err := cookiejar.New(nil) + if err != nil { + t.Fatal(err) + } + + // Any response cookies will be stored and sent on subsequent requests + ts.Client().Jar = jar + + // Disable redirect-following for test server client by setting custom + // CheckRedirect function. Called whenever 3xx response. By returning a + // http.ErrUseLastResponse error it forces the client to immediately return + // the received response + ts.Client().CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + + return &testServer{ts} +} + +func (ts *testServer) get(t *testing.T, urlPath string) (int, http.Header, string) { + resp, err := ts.Client().Get(ts.URL + urlPath) + if err != nil { + t.Fatal(err) + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + return resp.StatusCode, resp.Header, string(body) +} diff --git a/internal/server/routes.go b/internal/server/routes.go index 9b011ab..50a0680 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -19,6 +19,8 @@ func addRoutes(mux *http.ServeMux, userService *model.UserService, snippetService *model.SnippetService) http.Handler { + mux.Handle("GET /ping", PingHandler()) + // /{$} is used to prevent subtree path patterns from acting like a wildcard // resulting in this route requiring an exact match on "/" only // You can only include one HTTP method in a route pattern if you choose