package webhook import ( "context" "fmt" "net/http" "net/http/httptest" "strings" "testing" "time" ) func TestHungTargetDoesNotStarveOtherTargets(t *testing.T) { database := newTestDB(t) const healthy = 5 arrived := make(chan string, healthy) fast := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { arrived <- r.URL.Path })) t.Cleanup(fast.Close) stop := make(chan struct{}) hung := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { select { case <-stop: case <-r.Context().Done(): } })) t.Cleanup(hung.Close) s := New(database, true) // cleanups run in reverse, and the service has to drain before hung.Close cuts it off t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() s.Close(ctx) }) t.Cleanup(func() { close(stop) }) // more than the queue holds, all to a host that never answers for i := range 300 { s.dispatch(hookJob(context.Background(), fmt.Sprintf("%s/hung%d", hung.URL, i))) } // webhook queues are per hostname, and localhost gets a different one from 127.0.0.1 otherHost := strings.Replace(fast.URL, "127.0.0.1", "localhost", 1) for i := range healthy { s.dispatch(hookJob(context.Background(), fmt.Sprintf("%s/ok%d", otherHost, i))) } timeout := time.After(5 * time.Second) for got := range healthy { select { case <-arrived: case <-timeout: t.Fatalf("%d of %d healthy deliveries arrived within 5s while another target hung", got, healthy) } } }