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) } }