package server_test import ( "net/http" "net/http/httptest" "strings" "testing" "sneak.berlin/go/netwatch/internal/config" "sneak.berlin/go/netwatch/internal/globals" "sneak.berlin/go/netwatch/internal/handlers" "sneak.berlin/go/netwatch/internal/healthcheck" "sneak.berlin/go/netwatch/internal/logger" "sneak.berlin/go/netwatch/internal/middleware" "sneak.berlin/go/netwatch/internal/reportbuf" "sneak.berlin/go/netwatch/internal/server" "go.uber.org/fx" "go.uber.org/fx/fxtest" ) // newServer builds the server from the same constructors as main, // never started: SetupRoutes is called directly, so nothing listens. func newServer(t *testing.T) *server.Server { t.Helper() var srv *server.Server app := fxtest.New(t, fx.Provide( config.New, globals.New, handlers.New, healthcheck.New, logger.New, middleware.New, reportbuf.New, server.New, ), fx.Populate(&srv), ) err := app.Err() if err != nil { t.Fatalf("build server: %v", err) } srv.SetupRoutes() return srv } // TestReportsAreRateLimited checks that POST /api/v1/reports is // behind the per-address rate limit, set here to two a minute. func TestReportsAreRateLimited(t *testing.T) { t.Setenv("REPORTS_PER_MINUTE", "2") srv := newServer(t) post := func() int { rec := httptest.NewRecorder() req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/api/v1/reports", strings.NewReader(`{"clientId":"c1","hosts":[]}`), ) srv.ServeHTTP(rec, req) return rec.Code } for i := range 2 { if code := post(); code != http.StatusOK { t.Fatalf("report %d: status = %d, want %d", i+1, code, http.StatusOK) } } if code := post(); code != http.StatusTooManyRequests { t.Fatalf("third report in a minute: status = %d, want %d", code, http.StatusTooManyRequests) } } // TestHealthCheckRejectsOversizeBody sends the health check, which // never reads its body, a body one byte over the limit. Only the // router-wide body limit can reject it. func TestHealthCheckRejectsOversizeBody(t *testing.T) { t.Parallel() srv := newServer(t) rec := httptest.NewRecorder() req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/.well-known/healthcheck", strings.NewReader( strings.Repeat("x", int(server.MaxRequestBodyBytes)+1), ), ) srv.ServeHTTP(rec, req) if rec.Code != http.StatusRequestEntityTooLarge { t.Fatalf("status = %d, want %d", rec.Code, http.StatusRequestEntityTooLarge) } if got := rec.Body.String(); got != "{\"status\":\"error\"}\n" { t.Errorf("body = %q, want %q", got, "{\"status\":\"error\"}\n") } got := rec.Header().Get("Content-Type") if got != "application/json; charset=utf-8" { t.Errorf("Content-Type = %q, want a JSON content type", got) } }