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.Fail(t, "expecting a successful ioutil.ReadAll", "#%d", i) continue } recv := new(common.ErrorResponse) if err := json.Unmarshal(blob, recv); err != nil { assert.Fail(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) } }