From 0ff1c6f89ac7c3efefe5bb5f0a8c764ceb54ae76 Mon Sep 17 00:00:00 2001 From: Windfarer Date: Mon, 14 Jun 2021 12:25:15 +0800 Subject: [PATCH] test http (#1045) * test http * fix response codec * benchmark --- go.mod | 1 + go.sum | 1 + transport/http/client.go | 2 +- transport/http/client_test.go | 174 ++++++++++++++++++++++++++++++++++ transport/http/codec_test.go | 108 +++++++++++++++++++++ transport/http/server_test.go | 33 +++++++ 6 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 transport/http/client_test.go create mode 100644 transport/http/codec_test.go diff --git a/go.mod b/go.mod index da8477d3e..d47d9b75b 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 github.com/imdario/mergo v0.3.12 + github.com/stretchr/testify v1.7.0 go.opentelemetry.io/otel v0.20.0 go.opentelemetry.io/otel/trace v0.20.0 golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect diff --git a/go.sum b/go.sum index 0b5b6e442..fa5474ca3 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,7 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= diff --git a/transport/http/client.go b/transport/http/client.go index 5d5a13e4d..526150e5b 100644 --- a/transport/http/client.go +++ b/transport/http/client.go @@ -307,7 +307,7 @@ func DefaultErrorDecoder(ctx context.Context, res *http.Response) error { // CodecForResponse get encoding.Codec via http.Response func CodecForResponse(r *http.Response) encoding.Codec { - codec := encoding.GetCodec(httputil.ContentSubtype("Content-Type")) + codec := encoding.GetCodec(httputil.ContentSubtype(r.Header.Get("Content-Type"))) if codec != nil { return codec } diff --git a/transport/http/client_test.go b/transport/http/client_test.go new file mode 100644 index 000000000..30e4ba3b5 --- /dev/null +++ b/transport/http/client_test.go @@ -0,0 +1,174 @@ +package http + +import ( + "bytes" + "context" + "encoding/json" + "github.com/go-kratos/kratos/v2/errors" + "io/ioutil" + nethttp "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/go-kratos/kratos/v2/registry" +) + +type mockRoundTripper struct { +} + +func (rt *mockRoundTripper) RoundTrip(req *nethttp.Request) (resp *nethttp.Response, err error) { + return +} + +func TestWithTransport(t *testing.T) { + ov := &mockRoundTripper{} + o := WithTransport(ov) + co := &clientOptions{} + o(co) + assert.Equal(t, co.transport, ov) +} + +func TestWithTimeout(t *testing.T) { + ov := 1 * time.Second + o := WithTimeout(ov) + co := &clientOptions{} + o(co) + assert.Equal(t, co.timeout, ov) +} + +func TestWithBalancer(t *testing.T) { + +} + +func TestWithUserAgent(t *testing.T) { + ov := "kratos" + o := WithUserAgent(ov) + co := &clientOptions{} + o(co) + assert.Equal(t, co.userAgent, ov) +} + +func TestWithMiddleware(t *testing.T) { +} + +func TestWithEndpoint(t *testing.T) { + ov := "some-endpoint" + o := WithEndpoint(ov) + co := &clientOptions{} + o(co) + assert.Equal(t, co.endpoint, ov) +} + +func TestWithRequestEncoder(t *testing.T) { + +} + +func TestWithResponseDecoder(t *testing.T) { + +} + +func TestWithErrorDecoder(t *testing.T) { +} + +type mockDiscovery struct { +} + +func (*mockDiscovery) GetService(ctx context.Context, serviceName string) ([]*registry.ServiceInstance, error) { + return nil, nil +} + +func (*mockDiscovery) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { + return nil, nil +} + +func TestWithDiscovery(t *testing.T) { + ov := &mockDiscovery{} + o := WithDiscovery(ov) + co := &clientOptions{} + o(co) + assert.Equal(t, co.discovery, ov) +} + +func TestDefaultRequestEncoder(t *testing.T) { + req1 := &nethttp.Request{ + Header: make(nethttp.Header), + Body: ioutil.NopCloser(bytes.NewBufferString("{\"a\":\"1\", \"b\": 2}")), + } + req1.Header.Set("Content-Type", "application/xml") + + v1 := &struct { + A string `json:"a"` + B int64 `json:"b"` + }{"a", 1} + b, err1 := DefaultRequestEncoder(context.TODO(), "application/json", v1) + assert.Nil(t, err1) + v1b := &struct { + A string `json:"a"` + B int64 `json:"b"` + }{} + err1 = json.Unmarshal(b, v1b) + assert.Nil(t, err1) + assert.Equal(t, v1, v1b) +} + +func TestDefaultResponseDecoder(t *testing.T) { + resp1 := &nethttp.Response{ + Header: make(nethttp.Header), + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewBufferString("{\"a\":\"1\", \"b\": 2}")), + } + v1 := &struct { + A string `json:"a"` + B int64 `json:"b"` + }{} + err1 := DefaultResponseDecoder(context.TODO(), resp1, &v1) + assert.Nil(t, err1) + assert.Equal(t, "1", v1.A) + assert.Equal(t, int64(2), v1.B) + + resp2 := &nethttp.Response{ + Header: make(nethttp.Header), + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewBufferString("{badjson}")), + } + v2 := &struct { + A string `json:"a"` + B int64 `json:"b"` + }{} + err2 := DefaultResponseDecoder(context.TODO(), resp2, &v2) + terr1 := &json.SyntaxError{} + assert.ErrorAs(t, err2, &terr1) +} + +func TestDefaultErrorDecoder(t *testing.T) { + for i := 200; i < 300; i++ { + resp := &nethttp.Response{Header: make(nethttp.Header), StatusCode: i} + assert.Nil(t, DefaultErrorDecoder(context.TODO(), resp)) + } + resp1 := &nethttp.Response{ + Header: make(nethttp.Header), + StatusCode: 300, + Body: ioutil.NopCloser(bytes.NewBufferString("{\"foo\":\"bar\"}")), + } + assert.Error(t, DefaultErrorDecoder(context.TODO(), resp1)) + + resp2 := &nethttp.Response{ + Header: make(nethttp.Header), + StatusCode: 500, + Body: ioutil.NopCloser(bytes.NewBufferString("{\"code\":54321, \"message\": \"hi\", \"reason\": \"FOO\"}")), + } + err2 := DefaultErrorDecoder(context.TODO(), resp2) + assert.Error(t, err2) + assert.Equal(t, int32(54321), err2.(*errors.Error).GetCode()) + assert.Equal(t, "hi", err2.(*errors.Error).GetMessage()) + assert.Equal(t, "FOO", err2.(*errors.Error).GetReason()) +} + +func TestCodecForResponse(t *testing.T) { + resp := &nethttp.Response{Header: make(nethttp.Header)} + resp.Header.Set("Content-Type", "application/xml") + c := CodecForResponse(resp) + assert.Equal(t, "xml", c.Name()) +} diff --git a/transport/http/codec_test.go b/transport/http/codec_test.go new file mode 100644 index 000000000..ca9a1ab5f --- /dev/null +++ b/transport/http/codec_test.go @@ -0,0 +1,108 @@ +package http + +import ( + "bytes" + "github.com/go-kratos/kratos/v2/errors" + "github.com/stretchr/testify/assert" + "io/ioutil" + nethttp "net/http" + "testing" +) + +func TestDefaultRequestDecoder(t *testing.T) { + req1 := &nethttp.Request{ + Header: make(nethttp.Header), + Body: ioutil.NopCloser(bytes.NewBufferString("{\"a\":\"1\", \"b\": 2}")), + } + req1.Header.Set("Content-Type", "application/json") + + v1 := &struct { + A string `json:"a"` + B int64 `json:"b"` + }{} + err1 := DefaultRequestDecoder(req1, &v1) + assert.Nil(t, err1) + assert.Equal(t, "1", v1.A) + assert.Equal(t, int64(2), v1.B) +} + +type mockResponseWriter struct { + StatusCode int + Data []byte + header nethttp.Header +} + +func (w *mockResponseWriter) Header() nethttp.Header { + return w.header +} + +func (w *mockResponseWriter) Write(b []byte) (int, error) { + w.Data = b + return len(b), nil +} + +func (w *mockResponseWriter) WriteHeader(statusCode int) { + w.StatusCode = statusCode +} + +type dataWithStatusCode struct { + statusCode int + A string `json:"a"` + B int64 `json:"b"` +} + +func (d *dataWithStatusCode) StatusCode() int { + return d.statusCode +} + +func TestDefaultResponseEncoder(t *testing.T) { + w := &mockResponseWriter{header: make(nethttp.Header)} + req1 := &nethttp.Request{ + Header: make(nethttp.Header), + } + req1.Header.Set("Content-Type", "application/json") + + v1 := &dataWithStatusCode{statusCode: 201, A: "1", B: 2} + err := DefaultResponseEncoder(w, req1, v1) + assert.Nil(t, err) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + assert.Equal(t, 201, w.StatusCode) + assert.NotNil(t, w.Data) +} + +func TestDefaultResponseEncoderWithError(t *testing.T) { + w := &mockResponseWriter{header: make(nethttp.Header)} + req1 := &nethttp.Request{ + Header: make(nethttp.Header), + } + req1.Header.Set("Content-Type", "application/json") + + v1 := &errors.Error{Code: 511} + err := DefaultResponseEncoder(w, req1, v1) + assert.Nil(t, err) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + assert.Equal(t, 511, w.StatusCode) + assert.NotNil(t, w.Data) +} + +func TestCodecForRequest(t *testing.T) { + req1 := &nethttp.Request{ + Header: make(nethttp.Header), + Body: ioutil.NopCloser(bytes.NewBufferString("")), + } + req1.Header.Set("Content-Type", "application/xml") + + c, ok := CodecForRequest(req1, "Content-Type") + assert.True(t, ok) + assert.Equal(t, "xml", c.Name()) + + req2 := &nethttp.Request{ + Header: make(nethttp.Header), + Body: ioutil.NopCloser(bytes.NewBufferString("{\"a\":\"1\", \"b\": 2}")), + } + req2.Header.Set("Content-Type", "blablablabla") + + c, ok = CodecForRequest(req2, "Content-Type") + assert.False(t, ok) + assert.Equal(t, "json", c.Name()) +} diff --git a/transport/http/server_test.go b/transport/http/server_test.go index 782961609..12783befa 100644 --- a/transport/http/server_test.go +++ b/transport/http/server_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/go-kratos/kratos/v2/internal/host" + "github.com/stretchr/testify/assert" ) type testKey struct{} @@ -103,3 +104,35 @@ func testClient(t *testing.T, srv *Server) { } } + +func BenchmarkServer(b *testing.B) { + fn := func(w http.ResponseWriter, r *http.Request) { + data := &testData{Path: r.RequestURI} + json.NewEncoder(w).Encode(data) + if r.Context().Value(testKey{}) != "test" { + w.WriteHeader(500) + } + } + ctx := context.Background() + ctx = context.WithValue(ctx, testKey{}, "test") + srv := NewServer() + srv.HandleFunc("/index", fn) + go func() { + if err := srv.Start(ctx); err != nil { + panic(err) + } + }() + time.Sleep(time.Second) + port, ok := host.Port(srv.lis) + assert.True(b, ok) + client, err := NewClient(context.Background(), WithEndpoint(fmt.Sprintf("127.0.0.1:%d", port))) + assert.NoError(b, err) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + var res testData + err := client.Invoke(context.Background(), "POST", "/index", nil, &res) + assert.NoError(b, err) + } + srv.Stop(ctx) +}