etcdserver/etcdhttp: add tests for serveKeys

This commit is contained in:
Jonathan Boulle 2014-09-15 18:33:10 -07:00
parent 43acdef660
commit 27cf7747ea
2 changed files with 253 additions and 9 deletions

View File

@ -246,10 +246,10 @@ func parseRequest(r *http.Request, id int64) (etcdserverpb.Request, error) {
rr.PrevExists = pe rr.PrevExists = pe
} }
// TODO(jonboulle): use fake clock instead of time module
// https://github.com/coreos/etcd/issues/1021
if ttl > 0 { if ttl > 0 {
expr := time.Duration(ttl) * time.Second expr := time.Duration(ttl) * time.Second
// TODO(jonboulle): use fake clock instead of time module
// https://github.com/coreos/etcd/issues/1021
rr.Expiration = time.Now().Add(expr).UnixNano() rr.Expiration = time.Now().Add(expr).UnixNano()
} }

View File

@ -2,6 +2,7 @@ package etcdhttp
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"io" "io"
"net/http" "net/http"
@ -33,10 +34,11 @@ func mustNewURL(t *testing.T, s string) *url.URL {
} }
// mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs // mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
// an *http.Request referencing the resulting URL // a GET *http.Request referencing the resulting URL
func mustNewRequest(t *testing.T, p string) *http.Request { func mustNewRequest(t *testing.T, p string) *http.Request {
return &http.Request{ return &http.Request{
URL: mustNewURL(t, path.Join(keysPrefix, p)), Method: "GET",
URL: mustNewURL(t, path.Join(keysPrefix, p)),
} }
} }
@ -191,8 +193,9 @@ func TestGoodParseRequest(t *testing.T) {
// good prefix, all other values default // good prefix, all other values default
mustNewRequest(t, "foo"), mustNewRequest(t, "foo"),
etcdserverpb.Request{ etcdserverpb.Request{
Id: 1234, Id: 1234,
Path: "/foo", Method: "GET",
Path: "/foo",
}, },
}, },
{ {
@ -782,21 +785,65 @@ func mustMarshalMsg(t *testing.T, m raftpb.Message) []byte {
func TestServeRaft(t *testing.T) { func TestServeRaft(t *testing.T) {
testCases := []struct { testCases := []struct {
reqBody io.Reader method string
body io.Reader
serverErr error serverErr error
wcode int
wcode int
}{ }{
{ {
// bad method
"GET",
bytes.NewReader(
mustMarshalMsg(
t,
raftpb.Message{},
),
),
nil,
http.StatusMethodNotAllowed,
},
{
// bad method
"PUT",
bytes.NewReader(
mustMarshalMsg(
t,
raftpb.Message{},
),
),
nil,
http.StatusMethodNotAllowed,
},
{
// bad method
"DELETE",
bytes.NewReader(
mustMarshalMsg(
t,
raftpb.Message{},
),
),
nil,
http.StatusMethodNotAllowed,
},
{
// bad request body
"POST",
&errReader{}, &errReader{},
nil, nil,
http.StatusBadRequest, http.StatusBadRequest,
}, },
{ {
// bad request JSON
"POST",
strings.NewReader("malformed garbage"), strings.NewReader("malformed garbage"),
nil, nil,
http.StatusBadRequest, http.StatusBadRequest,
}, },
{ {
// good request, etcdserver.Server error
"POST",
bytes.NewReader( bytes.NewReader(
mustMarshalMsg( mustMarshalMsg(
t, t,
@ -807,6 +854,8 @@ func TestServeRaft(t *testing.T) {
http.StatusInternalServerError, http.StatusInternalServerError,
}, },
{ {
// good request
"POST",
bytes.NewReader( bytes.NewReader(
mustMarshalMsg( mustMarshalMsg(
t, t,
@ -818,7 +867,7 @@ func TestServeRaft(t *testing.T) {
}, },
} }
for i, tt := range testCases { for i, tt := range testCases {
req, err := http.NewRequest("POST", "foo", tt.reqBody) req, err := http.NewRequest(tt.method, "foo", tt.body)
if err != nil { if err != nil {
t.Fatalf("#%d: could not create request: %#v", i, err) t.Fatalf("#%d: could not create request: %#v", i, err)
} }
@ -834,3 +883,198 @@ func TestServeRaft(t *testing.T) {
} }
} }
} }
// resServer implements the etcd.Server interface for testing.
// It returns the given responsefrom any Do calls, and nil error
type resServer struct {
res etcdserver.Response
}
func (rs *resServer) Do(ctx context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
return rs.res, nil
}
func (rs *resServer) Process(ctx context.Context, m raftpb.Message) error {
return nil
}
func (rs *resServer) Start() {}
func (rs *resServer) Stop() {}
func mustMarshalEvent(t *testing.T, ev *store.Event) string {
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(ev); err != nil {
t.Fatalf("error marshalling event %#v: #v", ev, err)
}
return b.String()
}
func TestBadServeKeys(t *testing.T) {
testBadCases := []struct {
req *http.Request
server etcdserver.Server
wcode int
}{
{
// bad method
&http.Request{
Method: "CONNECT",
},
&resServer{},
http.StatusMethodNotAllowed,
},
{
// bad method
&http.Request{
Method: "TRACE",
},
&resServer{},
http.StatusMethodNotAllowed,
},
{
// parseRequest error
&http.Request{
Body: nil,
Method: "PUT",
},
&resServer{},
http.StatusBadRequest,
},
{
// etcdserver.Server error
mustNewRequest(t, "foo"),
&errServer{
errors.New("blah"),
},
http.StatusInternalServerError,
},
{
// timeout waiting for event (watcher never returns)
mustNewRequest(t, "foo"),
&resServer{
etcdserver.Response{
Watcher: &dummyWatcher{},
},
},
http.StatusGatewayTimeout,
},
{
// non-event/watcher response from etcdserver.Server
mustNewRequest(t, "foo"),
&resServer{
etcdserver.Response{},
},
http.StatusInternalServerError,
},
}
for i, tt := range testBadCases {
h := &serverHandler{
timeout: 0, // context times out immediately
server: tt.server,
peers: nil,
}
rw := httptest.NewRecorder()
h.serveKeys(rw, tt.req)
if rw.Code != tt.wcode {
t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
}
}
}
func TestServeKeysEvent(t *testing.T) {
req := mustNewRequest(t, "foo")
server := &resServer{
etcdserver.Response{
Event: &store.Event{
Action: store.Get,
Node: &store.NodeExtern{
Key: "foo",
ModifiedIndex: 2,
},
},
},
}
h := &serverHandler{
timeout: time.Hour,
server: server,
peers: nil,
}
rw := httptest.NewRecorder()
h.serveKeys(rw, req)
wcode := http.StatusOK
wbody := mustMarshalEvent(
t,
&store.Event{
Action: store.Get,
Node: &store.NodeExtern{
Key: "foo",
ModifiedIndex: 2,
},
},
)
if rw.Code != wcode {
t.Errorf("got code=%d, want %d", rw.Code, wcode)
}
g := rw.Body.String()
if g != wbody {
t.Errorf("got body=%#v, want %#v", g, wbody)
}
}
func TestServeKeysWatch(t *testing.T) {
req := mustNewRequest(t, "/foo/bar")
ec := make(chan *store.Event)
dw := &dummyWatcher{
echan: ec,
}
server := &resServer{
etcdserver.Response{
Watcher: dw,
},
}
h := &serverHandler{
timeout: time.Hour,
server: server,
peers: nil,
}
go func() {
ec <- &store.Event{
Action: store.Get,
Node: &store.NodeExtern{
Key: "/foo/bar",
ModifiedIndex: 12345,
},
}
}()
rw := httptest.NewRecorder()
h.serveKeys(rw, req)
wcode := http.StatusOK
wbody := mustMarshalEvent(
t,
&store.Event{
Action: store.Get,
Node: &store.NodeExtern{
Key: "/foo/bar",
ModifiedIndex: 12345,
},
},
)
if rw.Code != wcode {
t.Errorf("got code=%d, want %d", rw.Code, wcode)
}
g := rw.Body.String()
if g != wbody {
t.Errorf("got body=%#v, want %#v", g, wbody)
}
}