fix(netxlite): ensure HTTP errors are always wrapped (#584)
1. introduce implementations of HTTPTransport and HTTPClient that apply an error wrapping policy using the constructor for a generic top-level error wrapper 2. make sure we use the implementations in point 1 when we are constructing HTTPTransport and HTTPClient 3. make sure we apply error wrapping using the constructor for a generic top-level error wrapper when reading bodies 4. acknowledge that error wrapping would be broken if we do not return the same classification _and_ operation when we wrap an already wrapped error, so fix the to code to do that 5. acknowledge that the classifiers already deal with preserving the error string and explain why this is a quirk and why we cannot remove it right now and what needs to happen to safely remove this quirk from the codebase Closes https://github.com/ooni/probe/issues/1860
This commit is contained in:
parent
da34cfe6c9
commit
6a935d5407
9 changed files with 217 additions and 28 deletions
|
|
@ -16,6 +16,49 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
|
||||
)
|
||||
|
||||
func TestHTTPTransportErrWrapper(t *testing.T) {
|
||||
t.Run("RoundTrip", func(t *testing.T) {
|
||||
t.Run("with failure", func(t *testing.T) {
|
||||
txp := &httpTransportErrWrapper{
|
||||
HTTPTransport: &mocks.HTTPTransport{
|
||||
MockRoundTrip: func(req *http.Request) (*http.Response, error) {
|
||||
return nil, io.EOF
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := txp.RoundTrip(&http.Request{})
|
||||
var errWrapper *ErrWrapper
|
||||
if !errors.As(err, &errWrapper) {
|
||||
t.Fatal("the returned error is not an ErrWrapper")
|
||||
}
|
||||
if errWrapper.Failure != FailureEOFError {
|
||||
t.Fatal("unexpected failure", errWrapper.Failure)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatal("expected nil response")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with success", func(t *testing.T) {
|
||||
expect := &http.Response{}
|
||||
txp := &httpTransportErrWrapper{
|
||||
HTTPTransport: &mocks.HTTPTransport{
|
||||
MockRoundTrip: func(req *http.Request) (*http.Response, error) {
|
||||
return expect, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := txp.RoundTrip(&http.Request{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp != expect {
|
||||
t.Fatal("not the expected response")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTPTransportLogger(t *testing.T) {
|
||||
t.Run("RoundTrip", func(t *testing.T) {
|
||||
t.Run("with failure", func(t *testing.T) {
|
||||
|
|
@ -198,7 +241,8 @@ func TestNewHTTPTransport(t *testing.T) {
|
|||
if logger.Logger != log.Log {
|
||||
t.Fatal("invalid logger")
|
||||
}
|
||||
connectionsCloser := logger.HTTPTransport.(*httpTransportConnectionsCloser)
|
||||
errWrapper := logger.HTTPTransport.(*httpTransportErrWrapper)
|
||||
connectionsCloser := errWrapper.HTTPTransport.(*httpTransportConnectionsCloser)
|
||||
withReadTimeout := connectionsCloser.Dialer.(*httpDialerWithReadTimeout)
|
||||
if withReadTimeout.Dialer != d {
|
||||
t.Fatal("invalid dialer")
|
||||
|
|
@ -412,3 +456,56 @@ func TestNewHTTPTransportStdlib(t *testing.T) {
|
|||
}
|
||||
txp.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func TestHTTPClientErrWrapper(t *testing.T) {
|
||||
t.Run("Do", func(t *testing.T) {
|
||||
t.Run("with failure", func(t *testing.T) {
|
||||
clnt := &httpClientErrWrapper{
|
||||
HTTPClient: &mocks.HTTPClient{
|
||||
MockDo: func(req *http.Request) (*http.Response, error) {
|
||||
return nil, io.EOF
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := clnt.Do(&http.Request{})
|
||||
var errWrapper *ErrWrapper
|
||||
if !errors.As(err, &errWrapper) {
|
||||
t.Fatal("the returned error is not an ErrWrapper")
|
||||
}
|
||||
if errWrapper.Failure != FailureEOFError {
|
||||
t.Fatal("unexpected failure", errWrapper.Failure)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatal("expected nil response")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with success", func(t *testing.T) {
|
||||
expect := &http.Response{}
|
||||
clnt := &httpClientErrWrapper{
|
||||
HTTPClient: &mocks.HTTPClient{
|
||||
MockDo: func(req *http.Request) (*http.Response, error) {
|
||||
return expect, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := clnt.Do(&http.Request{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp != expect {
|
||||
t.Fatal("not the expected response")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestWrapHTTPClient(t *testing.T) {
|
||||
origClient := &http.Client{}
|
||||
wrapped := WrapHTTPClient(origClient)
|
||||
errWrapper := wrapped.(*httpClientErrWrapper)
|
||||
innerClient := errWrapper.HTTPClient.(*http.Client)
|
||||
if innerClient != origClient {
|
||||
t.Fatal("not the inner client we expected")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue