Skip to content

Commit

Permalink
client: Add tests for error handling.
Browse files Browse the repository at this point in the history
The tests are to ensure that vspd errors are correctly wrapped as types.ErrorResponse, and non-vspd errors contain adequate information for debugging.

A small change in the client code itself was required to satisfy these conditions.
  • Loading branch information
jholdstock committed Feb 17, 2023
1 parent 36505b5 commit 31a2726
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 1 deletion.
5 changes: 4 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,11 @@ func (c *Client) do(ctx context.Context, method, path string, addr stdaddr.Addre
}

// Try to unmarshal the response body to a known vspd error.
d := json.NewDecoder(bytes.NewReader(respBody))
d.DisallowUnknownFields()

var apiError types.ErrorResponse
err = json.Unmarshal(respBody, &apiError)
err = d.Decode(&apiError)
if err == nil {
return apiError
}
Expand Down
102 changes: 102 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package client

import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"

"github.com/decred/dcrd/txscript/v4/stdaddr"
"github.com/decred/slog"
"github.com/decred/vspd/types"
)

func NoopSign(ctx context.Context, message string, address stdaddr.Address) ([]byte, error) {
return nil, nil
}

func NoopValidate(resp *http.Response, body []byte, serverPubkey []byte) error {
return nil
}

// TestErrorDetails ensures errors returned by client.do contain adequate
// information for debugging (HTTP status and response body).
func TestErrorDetails(t *testing.T) {

tests := map[string]struct {
httpStatus int
responseBodyBytes []byte
expectedErr string
vspdError bool
}{
"500, vspd error": {
httpStatus: 500,
responseBodyBytes: []byte(`{"code": 1, "message": "bad request"}`),
expectedErr: `bad request`,
vspdError: true,
},
"500, no body": {
httpStatus: 500,
responseBodyBytes: nil,
expectedErr: `http status 500 (Internal Server Error) with no body`,
vspdError: false,
},
"500, non vspd error": {
httpStatus: 500,
responseBodyBytes: []byte(`an error occurred`),
expectedErr: `http status 500 (Internal Server Error) with body "an error occurred"`,
vspdError: false,
},
"500, non vspd error (json)": {
httpStatus: 500,
responseBodyBytes: []byte(`{"some": "json"}`),
expectedErr: `http status 500 (Internal Server Error) with body "{\"some\": \"json\"}"`,
vspdError: false,
},
}

for testName, testData := range tests {
t.Run(testName, func(t *testing.T) {

testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(testData.httpStatus)
_, err := res.Write(testData.responseBodyBytes)
if err != nil {
t.Fatalf("writing response body failed: %v", err)
}
}))

client := Client{
URL: testServer.URL,
PubKey: []byte("fake pubkey"),
Sign: NoopSign,
Validate: NoopValidate,
Log: slog.Disabled,
}

var resp interface{}
err := client.do(context.TODO(), http.MethodGet, "", nil, &resp, nil)

testServer.Close()

if err == nil {
t.Fatalf("client.do did not return an error")
}

if err.Error() != testData.expectedErr {
t.Fatalf("client.do returned incorrect error, expected %q, got %q",
testData.expectedErr, err.Error())
}

if testData.vspdError {
// Error should be unwrappable as a vspd error response.
var e types.ErrorResponse
if !errors.As(err, &e) {
t.Fatal("unable to unwrap vspd error")
}
}

})
}
}

0 comments on commit 31a2726

Please sign in to comment.