Add Client with response wrapper, request helpers, and full middleware assembly
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
This commit is contained in:
90
error_test.go
Normal file
90
error_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user