package config import ( "strings" "testing" "time" ) // A write timeout below the deepest agent deadline is refused at startup. // // This is the misconfiguration that shipped: HTTP_WRITE_TIMEOUT=30s against a // balanced deadline of 60s. The server aborted the response on any run over // half its allowed time and the proxy in front answered 502, so it read as an // infrastructure fault for months. Refusing it at startup turns a slow, // intermittent, misattributed failure into a message on the first boot. func TestValidateWriteTimeout(t *testing.T) { withTimeout := func(d time.Duration) *Config { c := &Config{} c.HTTP.WriteTimeout = d return c } for _, tc := range []struct { name string timeout time.Duration wantErr bool }{ {"the value that shipped", 30 * time.Second, true}, {"equal to the balanced deadline is still short of deep", 60 * time.Second, true}, {"one second under", DeepestAgentDeadline - time.Second, true}, {"exactly the deepest deadline", DeepestAgentDeadline, false}, {"comfortably above", DeepestAgentDeadline + 30*time.Second, false}, {"no deadline at all cuts nothing off", 0, false}, {"negative is treated as unset", -1, false}, } { t.Run(tc.name, func(t *testing.T) { err := withTimeout(tc.timeout).validateWriteTimeout() if tc.wantErr && err == nil { t.Fatalf("%s was accepted; it would abort a legal run", tc.timeout) } if !tc.wantErr && err != nil { t.Fatalf("%s was refused: %v", tc.timeout, err) } }) } } // The message has to name the fix. An operator reading it at 3am should not // have to find the deep tier's deadline in another package. func TestValidateWriteTimeoutSaysWhatToDo(t *testing.T) { c := &Config{} c.HTTP.WriteTimeout = 30 * time.Second err := c.validateWriteTimeout() if err == nil { t.Fatal("expected a refusal") } for _, want := range []string{"HTTP_WRITE_TIMEOUT", "30s", "2m0s", "502"} { if !strings.Contains(err.Error(), want) { t.Errorf("the message does not mention %q:\n %v", want, err) } } }