Files
hauler/pkg/getter/https_test.go
T
2026-08-05 16:34:41 -04:00

63 lines
1.8 KiB
Go

package getter_test
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"hauler.dev/go/hauler/v2/pkg/getter"
)
// TestHttp_Open_HonorsContextCancellation proves that Http.Open actually
// wires the ctx argument into the outgoing HTTP request (via
// http.NewRequestWithContext), rather than accepting-but-ignoring it. A
// handler that never writes a response blocks http.Get indefinitely; if Open
// used http.Get instead of a context-aware request, cancelling ctx would
// never unblock the call and this test would hang until killed by the test
// binary's own timeout rather than returning promptly.
func TestHttp_Open_HonorsContextCancellation(t *testing.T) {
blockCh := make(chan struct{})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Never respond until the test cleans up... simulates a hung/slow
// server so the only way Open returns early is via ctx cancellation.
<-blockCh
}))
// Cleanup runs LIFO: unblock the handler goroutine first so srv.Close
// (registered second, run first) doesn't itself hang waiting for the
// in-flight handler to return.
t.Cleanup(srv.Close)
t.Cleanup(func() { close(blockCh) })
u, err := url.Parse(srv.URL)
if err != nil {
t.Fatalf("url.Parse: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(100 * time.Millisecond)
cancel()
}()
h := getter.NewHttp()
done := make(chan error, 1)
go func() {
_, err := h.Open(ctx, u)
done <- err
}()
select {
case err := <-done:
if err == nil {
t.Fatal("expected an error from Open after ctx cancellation, got nil")
}
case <-time.After(5 * time.Second):
t.Fatal("Open did not return within 5s of ctx cancellation... ctx is not wired into the request")
}
}