package job import ( "errors" "io" "net" "testing" "time" "github.com/valyala/fasthttp" ) // stallingListener accepts connections, reads whatever is sent and then never // answers and never closes — the receiver shape that used to hold the traffic // job open indefinitely (#6115). func stallingListener(t *testing.T) (addr string, release func()) { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } done := make(chan struct{}) go func() { defer close(done) for { conn, err := ln.Accept() if err != nil { return } go func(c net.Conn) { <-done _ = c.Close() }(conn) go func(c net.Conn) { _, _ = io.Copy(io.Discard, c) }(conn) } }() return ln.Addr().String(), func() { _ = ln.Close() <-done } } func TestExternalInformClient_StalledReceiverTimesOut(t *testing.T) { addr, release := stallingListener(t) defer release() request := fasthttp.AcquireRequest() defer fasthttp.ReleaseRequest(request) request.Header.SetMethod("POST") request.Header.SetContentType("application/json; charset=UTF-8") request.SetBody([]byte(`{"clientTraffics":[],"inboundTraffics":[]}`)) request.SetRequestURI("http://" + addr + "/inform") request.Header.SetConnectionClose() response := fasthttp.AcquireResponse() defer fasthttp.ReleaseResponse(response) start := time.Now() err := externalInformClient.DoTimeout(request, response, externalInformTimeout) elapsed := time.Since(start) if err == nil { t.Fatal("a receiver that never answers must produce an error, not a completed request") } if !errors.Is(err, fasthttp.ErrTimeout) { t.Errorf("want a timeout error, got %v", err) } // The whole point is that the call cannot outlast the 5s poll cadence. if elapsed > externalInformTimeout+2*time.Second { t.Errorf("call took %v, must be bounded by %v", elapsed, externalInformTimeout) } } func TestExternalInformClient_HasDeadlines(t *testing.T) { if externalInformClient.ReadTimeout <= 0 || externalInformClient.WriteTimeout <= 0 { t.Fatal("the traffic-notify client must carry read and write deadlines") } if externalInformTimeout >= 5*time.Second { t.Errorf("the call budget (%v) must stay under the 5s traffic poll cadence", externalInformTimeout) } } // An idle panel posts nothing, which keeps a misbehaving receiver out of the // job's path entirely for most ticks. func TestInformSkippedWhenNothingToReport(t *testing.T) { j := &XrayTrafficJob{} done := make(chan struct{}) go func() { defer close(done) j.informTrafficToExternalAPI(nil, nil) }() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("an empty payload must return before touching settings or the network") } }