Implements the top-level httpx.Client that composes the full chain: Logging → User Middlewares → Retry → Circuit Breaker → Balancer → Transport - Response wrapper with JSON/XML/Bytes decoding and body caching - NewJSONRequest helper with Content-Type and GetBody support - Functional options: WithBaseURL, WithTimeout, WithRetry, WithEndpoints, etc. - Integration tests covering retry, balancing, error mapping, and JSON round-trips
91 lines
2.1 KiB
Go
91 lines
2.1 KiB
Go
package httpx_test
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.codelab.vc/pkg/httpx"
|
|
)
|
|
|
|
func TestError(t *testing.T) {
|
|
t.Run("formats without endpoint", func(t *testing.T) {
|
|
inner := errors.New("connection refused")
|
|
e := &httpx.Error{
|
|
Op: "Get",
|
|
URL: "http://example.com/api",
|
|
Err: inner,
|
|
}
|
|
|
|
want := "httpx: Get http://example.com/api: connection refused"
|
|
if got := e.Error(); got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("formats with endpoint different from url", func(t *testing.T) {
|
|
inner := errors.New("timeout")
|
|
e := &httpx.Error{
|
|
Op: "Do",
|
|
URL: "http://example.com/api",
|
|
Endpoint: "http://node1.example.com/api",
|
|
Err: inner,
|
|
}
|
|
|
|
want := "httpx: Do http://example.com/api (endpoint http://node1.example.com/api): timeout"
|
|
if got := e.Error(); got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("formats with endpoint same as url", func(t *testing.T) {
|
|
inner := errors.New("not found")
|
|
e := &httpx.Error{
|
|
Op: "Get",
|
|
URL: "http://example.com/api",
|
|
Endpoint: "http://example.com/api",
|
|
Err: inner,
|
|
}
|
|
|
|
want := "httpx: Get http://example.com/api: not found"
|
|
if got := e.Error(); got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("unwrap returns inner error", func(t *testing.T) {
|
|
inner := errors.New("underlying")
|
|
e := &httpx.Error{Op: "Get", URL: "http://example.com", Err: inner}
|
|
|
|
if got := e.Unwrap(); got != inner {
|
|
t.Errorf("Unwrap() = %v, want %v", got, inner)
|
|
}
|
|
|
|
if !errors.Is(e, inner) {
|
|
t.Error("errors.Is should find the inner error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSentinelErrors(t *testing.T) {
|
|
t.Run("ErrRetryExhausted", func(t *testing.T) {
|
|
if httpx.ErrRetryExhausted == nil {
|
|
t.Fatal("ErrRetryExhausted is nil")
|
|
}
|
|
if httpx.ErrRetryExhausted.Error() == "" {
|
|
t.Fatal("ErrRetryExhausted has empty message")
|
|
}
|
|
})
|
|
|
|
t.Run("ErrCircuitOpen", func(t *testing.T) {
|
|
if httpx.ErrCircuitOpen == nil {
|
|
t.Fatal("ErrCircuitOpen is nil")
|
|
}
|
|
})
|
|
|
|
t.Run("ErrNoHealthy", func(t *testing.T) {
|
|
if httpx.ErrNoHealthy == nil {
|
|
t.Fatal("ErrNoHealthy is nil")
|
|
}
|
|
})
|
|
}
|