package server_test import ( "net/http" "testing" "sneak.berlin/go/dnswatcher/internal/server" ) // noopHandler stands in for the router; newHTTPServer only stores it. func noopHandler() http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }, ) } // TestHTTPServerTimeoutsAreSet asserts that every socket-level // timeout is configured. A zero value in net/http means "no limit", // so a refactor that silently drops one of these reintroduces the // slowloris / unreaped-keep-alive exposure this guards against. // // The assertions are on the configured field values only; nothing // here measures elapsed time, so the test cannot flake on timing. func TestHTTPServerTimeoutsAreSet(t *testing.T) { t.Parallel() srv := server.NewHTTPServer(":8080", noopHandler()) if srv.ReadTimeout <= 0 { t.Errorf( "ReadTimeout must be non-zero, got %v", srv.ReadTimeout, ) } if srv.ReadHeaderTimeout <= 0 { t.Errorf( "ReadHeaderTimeout must be non-zero, got %v", srv.ReadHeaderTimeout, ) } if srv.WriteTimeout <= 0 { t.Errorf( "WriteTimeout must be non-zero, got %v", srv.WriteTimeout, ) } if srv.IdleTimeout <= 0 { t.Errorf( "IdleTimeout must be non-zero, got %v", srv.IdleTimeout, ) } } // TestWriteTimeoutExceedsHandlerBudget pins the one relationship the // values must satisfy. net/http arms the write deadline once request // headers are read, so it covers handler execution plus the response // flush. If WriteTimeout were not greater than the chimw.Timeout // handler budget, the connection would be severed before a handler // that used its full budget could respond, making that budget // unreachable. func TestWriteTimeoutExceedsHandlerBudget(t *testing.T) { t.Parallel() srv := server.NewHTTPServer(":8080", noopHandler()) if srv.WriteTimeout <= server.RequestTimeout { t.Errorf( "WriteTimeout (%v) must exceed handler budget (%v)", srv.WriteTimeout, server.RequestTimeout, ) } } // TestReadTimeoutCoversHeaderTimeout asserts the read deadline for // the whole request is at least as long as the header-only deadline; // a smaller ReadTimeout would make ReadHeaderTimeout unreachable. func TestReadTimeoutCoversHeaderTimeout(t *testing.T) { t.Parallel() srv := server.NewHTTPServer(":8080", noopHandler()) if srv.ReadTimeout < srv.ReadHeaderTimeout { t.Errorf( "ReadTimeout (%v) must be >= ReadHeaderTimeout (%v)", srv.ReadTimeout, srv.ReadHeaderTimeout, ) } } // TestHTTPServerAddrAndHandler covers the rest of the constructor so // a future edit cannot drop the listen address or the handler. func TestHTTPServerAddrAndHandler(t *testing.T) { t.Parallel() srv := server.NewHTTPServer(":9999", noopHandler()) if srv.Addr != ":9999" { t.Errorf("Addr = %q, want %q", srv.Addr, ":9999") } if srv.Handler == nil { t.Error("Handler must not be nil") } }