|
@ -0,0 +1,250 @@ |
|
|
|
|
|
package common_test |
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
|
"bytes" |
|
|
|
|
|
"encoding/json" |
|
|
|
|
|
"errors" |
|
|
|
|
|
"io" |
|
|
|
|
|
"io/ioutil" |
|
|
|
|
|
"net/http" |
|
|
|
|
|
"net/http/httptest" |
|
|
|
|
|
"reflect" |
|
|
|
|
|
"strings" |
|
|
|
|
|
"sync" |
|
|
|
|
|
"testing" |
|
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert" |
|
|
|
|
|
"github.com/stretchr/testify/require" |
|
|
|
|
|
|
|
|
|
|
|
"github.com/tendermint/tmlibs/common" |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
func TestWriteSuccess(t *testing.T) { |
|
|
|
|
|
w := httptest.NewRecorder() |
|
|
|
|
|
common.WriteSuccess(w, "foo") |
|
|
|
|
|
assert.Equal(t, w.Code, 200, "should get a 200") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var blankErrResponse = new(common.ErrorResponse) |
|
|
|
|
|
|
|
|
|
|
|
func TestWriteError(t *testing.T) { |
|
|
|
|
|
tests := [...]struct { |
|
|
|
|
|
msg string |
|
|
|
|
|
code int |
|
|
|
|
|
}{ |
|
|
|
|
|
0: { |
|
|
|
|
|
msg: "this is a message", |
|
|
|
|
|
code: 419, |
|
|
|
|
|
}, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for i, tt := range tests { |
|
|
|
|
|
w := httptest.NewRecorder() |
|
|
|
|
|
msg := tt.msg |
|
|
|
|
|
|
|
|
|
|
|
// First check without a defined code, should send back a 400
|
|
|
|
|
|
common.WriteError(w, errors.New(msg)) |
|
|
|
|
|
assert.Equal(t, w.Code, http.StatusBadRequest, "#%d: should get a 400", i) |
|
|
|
|
|
blob, err := ioutil.ReadAll(w.Body) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
assert.Failf(t, "expecting a successful ioutil.ReadAll", "#%d", i) |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
recv := new(common.ErrorResponse) |
|
|
|
|
|
if err := json.Unmarshal(blob, recv); err != nil { |
|
|
|
|
|
assert.Failf(t, "expecting a successful json.Unmarshal", "#%d", i) |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, reflect.DeepEqual(recv, blankErrResponse), false, "expecting a non-blank error response") |
|
|
|
|
|
|
|
|
|
|
|
// Now test with an error that's .HTTPCode() int conforming
|
|
|
|
|
|
|
|
|
|
|
|
// Reset w
|
|
|
|
|
|
w = httptest.NewRecorder() |
|
|
|
|
|
|
|
|
|
|
|
common.WriteError(w, common.ErrorWithCode(errors.New("foo"), tt.code)) |
|
|
|
|
|
assert.Equal(t, w.Code, tt.code, "case #%d", i) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type marshalFailer struct{} |
|
|
|
|
|
|
|
|
|
|
|
var errFooFailed = errors.New("foo failed here") |
|
|
|
|
|
|
|
|
|
|
|
func (mf *marshalFailer) MarshalJSON() ([]byte, error) { |
|
|
|
|
|
return nil, errFooFailed |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestWriteCode(t *testing.T) { |
|
|
|
|
|
codes := [...]int{ |
|
|
|
|
|
0: http.StatusOK, |
|
|
|
|
|
1: http.StatusBadRequest, |
|
|
|
|
|
2: http.StatusUnauthorized, |
|
|
|
|
|
3: http.StatusInternalServerError, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for i, code := range codes { |
|
|
|
|
|
w := httptest.NewRecorder() |
|
|
|
|
|
common.WriteCode(w, "foo", code) |
|
|
|
|
|
assert.Equal(t, w.Code, code, "#%d", i) |
|
|
|
|
|
|
|
|
|
|
|
// Then for the failed JSON marshaling
|
|
|
|
|
|
w = httptest.NewRecorder() |
|
|
|
|
|
common.WriteCode(w, &marshalFailer{}, code) |
|
|
|
|
|
wantCode := http.StatusBadRequest |
|
|
|
|
|
assert.Equal(t, w.Code, wantCode, "#%d", i) |
|
|
|
|
|
assert.True(t, strings.Contains(string(w.Body.Bytes()), errFooFailed.Error()), |
|
|
|
|
|
"#%d: expected %q in the error message", i, errFooFailed) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type saver struct { |
|
|
|
|
|
Foo int `json:"foo" validate:"min=10"` |
|
|
|
|
|
Bar string `json:"bar"` |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type rcloser struct { |
|
|
|
|
|
closeOnce sync.Once |
|
|
|
|
|
body *bytes.Buffer |
|
|
|
|
|
closeChan chan bool |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var errAlreadyClosed = errors.New("already closed") |
|
|
|
|
|
|
|
|
|
|
|
func (rc *rcloser) Close() error { |
|
|
|
|
|
var err = errAlreadyClosed |
|
|
|
|
|
rc.closeOnce.Do(func() { |
|
|
|
|
|
err = nil |
|
|
|
|
|
rc.closeChan <- true |
|
|
|
|
|
close(rc.closeChan) |
|
|
|
|
|
}) |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (rc *rcloser) Read(b []byte) (int, error) { |
|
|
|
|
|
return rc.body.Read(b) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var _ io.ReadCloser = (*rcloser)(nil) |
|
|
|
|
|
|
|
|
|
|
|
func makeReq(strBody string) (*http.Request, <-chan bool) { |
|
|
|
|
|
closeChan := make(chan bool, 1) |
|
|
|
|
|
buf := new(bytes.Buffer) |
|
|
|
|
|
buf.Write([]byte(strBody)) |
|
|
|
|
|
req := &http.Request{ |
|
|
|
|
|
Header: make(http.Header), |
|
|
|
|
|
Body: &rcloser{body: buf, closeChan: closeChan}, |
|
|
|
|
|
} |
|
|
|
|
|
return req, closeChan |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestParseRequestJSON(t *testing.T) { |
|
|
|
|
|
tests := [...]struct { |
|
|
|
|
|
body string |
|
|
|
|
|
wantErr bool |
|
|
|
|
|
useNil bool |
|
|
|
|
|
}{ |
|
|
|
|
|
0: {wantErr: true, body: ``}, |
|
|
|
|
|
1: {body: `{}`}, |
|
|
|
|
|
2: {body: `{"foo": 2}`}, // Not that the validate tags don't matter here since we are just parsing
|
|
|
|
|
|
3: {body: `{"foo": "abcd"}`, wantErr: true}, |
|
|
|
|
|
4: {useNil: true, wantErr: true}, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for i, tt := range tests { |
|
|
|
|
|
req, closeChan := makeReq(tt.body) |
|
|
|
|
|
if tt.useNil { |
|
|
|
|
|
req.Body = nil |
|
|
|
|
|
} |
|
|
|
|
|
sav := new(saver) |
|
|
|
|
|
err := common.ParseRequestJSON(req, sav) |
|
|
|
|
|
if tt.wantErr { |
|
|
|
|
|
assert.NotEqual(t, err, nil, "#%d: want non-nil error", i) |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
assert.Equal(t, err, nil, "#%d: want nil error", i) |
|
|
|
|
|
wasClosed := <-closeChan |
|
|
|
|
|
assert.Equal(t, wasClosed, true, "#%d: should have invoked close", i) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestFparseJSON(t *testing.T) { |
|
|
|
|
|
r1 := strings.NewReader(`{"foo": 1}`) |
|
|
|
|
|
sav := new(saver) |
|
|
|
|
|
require.Equal(t, common.FparseJSON(r1, sav), nil, "expecting successful parsing") |
|
|
|
|
|
r2 := strings.NewReader(`{"bar": "blockchain"}`) |
|
|
|
|
|
require.Equal(t, common.FparseJSON(r2, sav), nil, "expecting successful parsing") |
|
|
|
|
|
require.Equal(t, reflect.DeepEqual(sav, &saver{Foo: 1, Bar: "blockchain"}), true, "should have parsed both") |
|
|
|
|
|
|
|
|
|
|
|
// Now with a nil body
|
|
|
|
|
|
require.NotEqual(t, nil, common.FparseJSON(nil, sav), "expecting a nil error report") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestFparseAndValidateJSON(t *testing.T) { |
|
|
|
|
|
r1 := strings.NewReader(`{"foo": 1}`) |
|
|
|
|
|
sav := new(saver) |
|
|
|
|
|
require.NotEqual(t, common.FparseAndValidateJSON(r1, sav), nil, "expecting validation to fail") |
|
|
|
|
|
r1 = strings.NewReader(`{"foo": 100}`) |
|
|
|
|
|
require.Equal(t, common.FparseJSON(r1, sav), nil, "expecting successful parsing") |
|
|
|
|
|
r2 := strings.NewReader(`{"bar": "blockchain"}`) |
|
|
|
|
|
require.Equal(t, common.FparseAndValidateJSON(r2, sav), nil, "expecting successful parsing") |
|
|
|
|
|
require.Equal(t, reflect.DeepEqual(sav, &saver{Foo: 100, Bar: "blockchain"}), true, "should have parsed both") |
|
|
|
|
|
|
|
|
|
|
|
// Now with a nil body
|
|
|
|
|
|
require.NotEqual(t, nil, common.FparseJSON(nil, sav), "expecting a nil error report") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var blankSaver = new(saver) |
|
|
|
|
|
|
|
|
|
|
|
func TestParseAndValidateRequestJSON(t *testing.T) { |
|
|
|
|
|
tests := [...]struct { |
|
|
|
|
|
body string |
|
|
|
|
|
wantErr bool |
|
|
|
|
|
useNil bool |
|
|
|
|
|
}{ |
|
|
|
|
|
0: {wantErr: true, body: ``}, |
|
|
|
|
|
1: {body: `{}`, wantErr: true}, // Here it should fail since Foo doesn't meet the minimum value
|
|
|
|
|
|
2: {body: `{"foo": 2}`, wantErr: true}, // Here validation should fail
|
|
|
|
|
|
3: {body: `{"foo": "abcd"}`, wantErr: true}, |
|
|
|
|
|
4: {useNil: true, wantErr: true}, |
|
|
|
|
|
5: {body: `{"foo": 100}`}, // Must succeed
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for i, tt := range tests { |
|
|
|
|
|
req, closeChan := makeReq(tt.body) |
|
|
|
|
|
if tt.useNil { |
|
|
|
|
|
req.Body = nil |
|
|
|
|
|
} |
|
|
|
|
|
sav := new(saver) |
|
|
|
|
|
err := common.ParseRequestAndValidateJSON(req, sav) |
|
|
|
|
|
if tt.wantErr { |
|
|
|
|
|
assert.NotEqual(t, err, nil, "#%d: want non-nil error", i) |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
assert.Equal(t, err, nil, "#%d: want nil error", i) |
|
|
|
|
|
assert.False(t, reflect.DeepEqual(blankSaver, sav), "#%d: expecting a set saver", i) |
|
|
|
|
|
|
|
|
|
|
|
wasClosed := <-closeChan |
|
|
|
|
|
assert.Equal(t, wasClosed, true, "#%d: should have invoked close", i) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestErrorWithCode(t *testing.T) { |
|
|
|
|
|
tests := [...]struct { |
|
|
|
|
|
code int |
|
|
|
|
|
err error |
|
|
|
|
|
}{ |
|
|
|
|
|
0: {code: 500, err: errors.New("funky")}, |
|
|
|
|
|
1: {code: 406, err: errors.New("purist")}, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for i, tt := range tests { |
|
|
|
|
|
errRes := common.ErrorWithCode(tt.err, tt.code) |
|
|
|
|
|
assert.Equal(t, errRes.Error(), tt.err.Error(), "#%d: expecting the error values to be equal", i) |
|
|
|
|
|
assert.Equal(t, errRes.Code, tt.code, "expecting the same status code", i) |
|
|
|
|
|
assert.Equal(t, errRes.HTTPCode(), tt.code, "expecting the same status code", i) |
|
|
|
|
|
} |
|
|
|
|
|
} |