From 930e4eb0263ac8c6f5df011d2e97129ac4f814b5 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 21 Sep 2026 18:22:22 +0000 Subject: [PATCH] test: assert http.Server carries ReadHeaderTimeout and IdleTimeout Failing test first: the server-hardening policy requires a slowloris defense (ReadHeaderTimeout) and a keep-alive bound (IdleTimeout) on the http.Server, neither of which is set today. The test asserts every timeout field is wired onto the constructed server. Model: opus-4-8 --- internal/server/http_internal_test.go | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 internal/server/http_internal_test.go diff --git a/internal/server/http_internal_test.go b/internal/server/http_internal_test.go new file mode 100644 index 0000000..d94a380 --- /dev/null +++ b/internal/server/http_internal_test.go @@ -0,0 +1,65 @@ +package server + +import ( + "testing" + "time" + + "sneak.berlin/go/pixa/internal/config" +) + +// TestNewHTTPServerTimeouts verifies that the constructed http.Server +// carries every hardening timeout wired onto it, including the slowloris +// defense (ReadHeaderTimeout) and the keep-alive bound (IdleTimeout). This +// guards against a field being defined but never set on the server, so +// each assertion compares the server field to its constant. +func TestNewHTTPServerTimeouts(t *testing.T) { + t.Parallel() + + s := &Server{config: &config.Config{Port: 8080}} + + srv := s.newHTTPServer() + + fields := []struct { + name string + got time.Duration + want time.Duration + }{ + {"ReadTimeout", srv.ReadTimeout, HTTPReadTimeout}, + {"ReadHeaderTimeout", srv.ReadHeaderTimeout, HTTPReadHeaderTimeout}, + {"WriteTimeout", srv.WriteTimeout, HTTPWriteTimeout}, + {"IdleTimeout", srv.IdleTimeout, HTTPIdleTimeout}, + } + + for _, f := range fields { + if f.got != f.want { + t.Errorf("%s = %v, want %v", f.name, f.got, f.want) + } + } + + if srv.MaxHeaderBytes != HTTPMaxHeaderBytes { + t.Errorf("MaxHeaderBytes = %d, want %d", + srv.MaxHeaderBytes, HTTPMaxHeaderBytes) + } + + if srv.Handler != s { + t.Error("Handler is not the server") + } +} + +// TestHardeningTimeoutValues pins the intent behind the two new timeouts +// without hard-coding brittle exact durations: the header-read phase is +// bounded strictly shorter than the whole-request read (the slowloris +// dribble), and idle keep-alive connections are bounded rather than held +// open forever. +func TestHardeningTimeoutValues(t *testing.T) { + t.Parallel() + + if HTTPReadHeaderTimeout <= 0 || HTTPReadHeaderTimeout > HTTPReadTimeout { + t.Errorf("ReadHeaderTimeout = %v, want positive and <= ReadTimeout %v", + HTTPReadHeaderTimeout, HTTPReadTimeout) + } + + if HTTPIdleTimeout <= 0 { + t.Errorf("IdleTimeout = %v, want positive bound", HTTPIdleTimeout) + } +}