From f206db2ceeec3c24e662f02295f7da86585f8f16 Mon Sep 17 00:00:00 2001 From: Michael Marineau Date: Mon, 10 Feb 2014 22:18:06 -0800 Subject: [PATCH] add(server/v1/tests): Port many of the v2 HTTP handler tests to v1 This should cover most aspects of the v1 API but being new to the etcd code base I cannot promise that or that the tests are even correct. They do pass though :) --- server/v1/tests/delete_handler_test.go | 31 +++++ server/v1/tests/get_handler_test.go | 179 +++++++++++++++++++++++++ server/v1/tests/put_handler_test.go | 130 +++++++++++++++++- 3 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 server/v1/tests/delete_handler_test.go create mode 100644 server/v1/tests/get_handler_test.go diff --git a/server/v1/tests/delete_handler_test.go b/server/v1/tests/delete_handler_test.go new file mode 100644 index 000000000..35a1b410e --- /dev/null +++ b/server/v1/tests/delete_handler_test.go @@ -0,0 +1,31 @@ +package v1 + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + "github.com/coreos/etcd/server" + "github.com/coreos/etcd/tests" + "github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert" +) + +// Ensures that a key is deleted. +// +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX +// $ curl -X DELETE localhost:4001/v1/keys/foo/bar +// +func TestV1DeleteKey(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v) + tests.ReadBody(resp) + resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), url.Values{}) + assert.Equal(t, resp.StatusCode, http.StatusOK) + body := tests.ReadBody(resp) + assert.Nil(t, err, "") + assert.Equal(t, string(body), `{"action":"delete","key":"/foo/bar","prevValue":"XXX","index":3}`, "") + }) +} diff --git a/server/v1/tests/get_handler_test.go b/server/v1/tests/get_handler_test.go new file mode 100644 index 000000000..c1a868ee9 --- /dev/null +++ b/server/v1/tests/get_handler_test.go @@ -0,0 +1,179 @@ +package v1 + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "testing" + "time" + + "github.com/coreos/etcd/server" + "github.com/coreos/etcd/tests" + "github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert" +) + +// Ensures that a value can be retrieve for a given key. +// +// $ curl localhost:4001/v1/keys/foo/bar -> fail +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX +// $ curl localhost:4001/v1/keys/foo/bar +// +func TestV1GetKey(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar") + resp, _ := tests.Get(fullURL) + assert.Equal(t, resp.StatusCode, http.StatusNotFound) + + resp, _ = tests.PutForm(fullURL, v) + tests.ReadBody(resp) + + resp, _ = tests.Get(fullURL) + assert.Equal(t, resp.StatusCode, http.StatusOK) + body := tests.ReadBodyJSON(resp) + assert.Equal(t, body["action"], "get", "") + assert.Equal(t, body["key"], "/foo/bar", "") + assert.Equal(t, body["value"], "XXX", "") + assert.Equal(t, body["index"], 2, "") + }) +} + +// Ensures that a directory of values can be retrieved for a given key. +// +// $ curl -X PUT localhost:4001/v1/keys/foo/x -d value=XXX +// $ curl -X PUT localhost:4001/v1/keys/foo/y/z -d value=YYY +// $ curl localhost:4001/v1/keys/foo +// +func TestV1GetKeyDir(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/x"), v) + tests.ReadBody(resp) + + v.Set("value", "YYY") + resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/y/z"), v) + tests.ReadBody(resp) + + resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo")) + assert.Equal(t, resp.StatusCode, http.StatusOK) + body := tests.ReadBody(resp) + nodes := make([]interface{}, 0) + if err := json.Unmarshal(body, &nodes); err != nil { + panic(fmt.Sprintf("HTTP body JSON parse error: %v", err)) + } + assert.Equal(t, len(nodes), 2, "") + + node0 := nodes[0].(map[string]interface{}) + assert.Equal(t, node0["action"], "get", "") + assert.Equal(t, node0["key"], "/foo/x", "") + assert.Equal(t, node0["value"], "XXX", "") + + node1 := nodes[1].(map[string]interface{}) + assert.Equal(t, node1["action"], "get", "") + assert.Equal(t, node1["key"], "/foo/y", "") + assert.Equal(t, node1["dir"], true, "") + }) +} + +// Ensures that a watcher can wait for a value to be set and return it to the client. +// +// $ curl localhost:4001/v1/watch/foo/bar +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX +// +func TestV1WatchKey(t *testing.T) { + tests.RunServer(func(s *server.Server) { + var body map[string]interface{} + c := make(chan bool) + go func() { + resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v1/watch/foo/bar")) + body = tests.ReadBodyJSON(resp) + c <- true + }() + + // Make sure response didn't fire early. + time.Sleep(1 * time.Millisecond) + assert.Nil(t, body, "") + + // Set a value. + v := url.Values{} + v.Set("value", "XXX") + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v) + tests.ReadBody(resp) + + // A response should follow from the GET above. + time.Sleep(1 * time.Millisecond) + + select { + case <-c: + + default: + t.Fatal("cannot get watch result") + } + + assert.NotNil(t, body, "") + assert.Equal(t, body["action"], "set", "") + + assert.Equal(t, body["key"], "/foo/bar", "") + assert.Equal(t, body["value"], "XXX", "") + assert.Equal(t, body["index"], 2, "") + }) +} + +// Ensures that a watcher can wait for a value to be set after a given index. +// +// $ curl -X POST localhost:4001/v1/watch/foo/bar -d index=4 +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=YYY +// +func TestV1WatchKeyWithIndex(t *testing.T) { + tests.RunServer(func(s *server.Server) { + var body map[string]interface{} + c := make(chan bool) + go func() { + v := url.Values{} + v.Set("index", "3") + resp, _ := tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v1/watch/foo/bar"), v) + body = tests.ReadBodyJSON(resp) + c <- true + }() + + // Make sure response didn't fire early. + time.Sleep(1 * time.Millisecond) + assert.Nil(t, body, "") + + // Set a value (before given index). + v := url.Values{} + v.Set("value", "XXX") + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v) + tests.ReadBody(resp) + + // Make sure response didn't fire early. + time.Sleep(1 * time.Millisecond) + assert.Nil(t, body, "") + + // Set a value (before given index). + v.Set("value", "YYY") + resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v) + tests.ReadBody(resp) + + // A response should follow from the GET above. + time.Sleep(1 * time.Millisecond) + + select { + case <-c: + + default: + t.Fatal("cannot get watch result") + } + + assert.NotNil(t, body, "") + assert.Equal(t, body["action"], "set", "") + + assert.Equal(t, body["key"], "/foo/bar", "") + assert.Equal(t, body["value"], "YYY", "") + assert.Equal(t, body["index"], 3, "") + }) +} diff --git a/server/v1/tests/put_handler_test.go b/server/v1/tests/put_handler_test.go index 87d2f9c8d..5e55a6928 100644 --- a/server/v1/tests/put_handler_test.go +++ b/server/v1/tests/put_handler_test.go @@ -1,10 +1,11 @@ -package v2 +package v1 import ( "fmt" "net/http" "net/url" "testing" + "time" "github.com/coreos/etcd/server" "github.com/coreos/etcd/tests" @@ -27,3 +28,130 @@ func TestV1SetKey(t *testing.T) { assert.Equal(t, string(body), `{"action":"set","key":"/foo/bar","value":"XXX","newKey":true,"index":2}`, "") }) } + +// Ensures that a time-to-live is added to a key. +// +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d ttl=20 +// +func TestV1SetKeyWithTTL(t *testing.T) { + tests.RunServer(func(s *server.Server) { + t0 := time.Now() + v := url.Values{} + v.Set("value", "XXX") + v.Set("ttl", "20") + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v) + assert.Equal(t, resp.StatusCode, http.StatusOK) + body := tests.ReadBodyJSON(resp) + assert.Equal(t, body["ttl"], 20, "") + + // Make sure the expiration date is correct. + expiration, _ := time.Parse(time.RFC3339Nano, body["expiration"].(string)) + assert.Equal(t, expiration.Sub(t0)/time.Second, 20, "") + }) +} + +// Ensures that an invalid time-to-live is returned as an error. +// +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d ttl=bad_ttl +// +func TestV1SetKeyWithBadTTL(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + v.Set("ttl", "bad_ttl") + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v) + assert.Equal(t, resp.StatusCode, http.StatusBadRequest) + body := tests.ReadBodyJSON(resp) + assert.Equal(t, body["errorCode"], 202, "") + assert.Equal(t, body["message"], "The given TTL in POST form is not a number", "") + assert.Equal(t, body["cause"], "Set", "") + }) +} + +// Ensures that a key is conditionally set if it previously did not exist. +// +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d prevValue= +// +func TestV1CreateKeySuccess(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + v.Set("prevValue", "") + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar"), v) + assert.Equal(t, resp.StatusCode, http.StatusOK) + body := tests.ReadBodyJSON(resp) + assert.Equal(t, body["value"], "XXX", "") + }) +} + +// Ensures that a key is not conditionally set because it previously existed. +// +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d prevValue= +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX -d prevValue= -> fail +// +func TestV1CreateKeyFail(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + v.Set("prevValue", "") + fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar") + resp, _ := tests.PutForm(fullURL, v) + assert.Equal(t, resp.StatusCode, http.StatusOK) + tests.ReadBody(resp) + resp, _ = tests.PutForm(fullURL, v) + assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed) + body := tests.ReadBodyJSON(resp) + assert.Equal(t, body["errorCode"], 105, "") + assert.Equal(t, body["message"], "Key already exists", "") + assert.Equal(t, body["cause"], "/foo/bar", "") + }) +} + +// Ensures that a key is set only if the previous value matches. +// +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=YYY -d prevValue=XXX +// +func TestV1SetKeyCASOnValueSuccess(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar") + resp, _ := tests.PutForm(fullURL, v) + assert.Equal(t, resp.StatusCode, http.StatusOK) + tests.ReadBody(resp) + v.Set("value", "YYY") + v.Set("prevValue", "XXX") + resp, _ = tests.PutForm(fullURL, v) + assert.Equal(t, resp.StatusCode, http.StatusOK) + body := tests.ReadBodyJSON(resp) + assert.Equal(t, body["action"], "testAndSet", "") + assert.Equal(t, body["value"], "YYY", "") + assert.Equal(t, body["index"], 3, "") + }) +} + +// Ensures that a key is not set if the previous value does not match. +// +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=YYY -d prevValue=AAA +// +func TestV1SetKeyCASOnValueFail(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar") + resp, _ := tests.PutForm(fullURL, v) + assert.Equal(t, resp.StatusCode, http.StatusOK) + tests.ReadBody(resp) + v.Set("value", "YYY") + v.Set("prevValue", "AAA") + resp, _ = tests.PutForm(fullURL, v) + assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed) + body := tests.ReadBodyJSON(resp) + assert.Equal(t, body["errorCode"], 101, "") + assert.Equal(t, body["message"], "Compare failed", "") + assert.Equal(t, body["cause"], "[AAA != XXX] [0 != 2]", "") + assert.Equal(t, body["index"], 2, "") + }) +}