go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/grpc/prpc/encoding_test.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package prpc 16 17 import ( 18 "context" 19 "net/http" 20 "net/http/httptest" 21 "strings" 22 "testing" 23 24 "github.com/golang/protobuf/proto" 25 "google.golang.org/genproto/googleapis/rpc/errdetails" 26 27 "google.golang.org/grpc/codes" 28 "google.golang.org/grpc/status" 29 30 "go.chromium.org/luci/common/logging" 31 "go.chromium.org/luci/common/logging/memlogger" 32 33 . "github.com/smartystreets/goconvey/convey" 34 . "go.chromium.org/luci/common/testing/assertions" 35 ) 36 37 func TestEncoding(t *testing.T) { 38 t.Parallel() 39 40 Convey("responseFormat", t, func() { 41 test := func(acceptHeader string, expectedFormat Format, expectedErr any) { 42 acceptHeader = strings.Replace(acceptHeader, "{json}", mtPRPCJSONPB, -1) 43 acceptHeader = strings.Replace(acceptHeader, "{binary}", mtPRPCBinary, -1) 44 acceptHeader = strings.Replace(acceptHeader, "{text}", mtPRPCText, -1) 45 46 Convey("Accept: "+acceptHeader, func() { 47 actualFormat, err := responseFormat(acceptHeader) 48 So(err, ShouldErrLike, expectedErr) 49 if err == nil { 50 So(actualFormat, ShouldEqual, expectedFormat) 51 } 52 }) 53 } 54 55 test("", FormatBinary, nil) 56 test(ContentTypePRPC, FormatBinary, nil) 57 test(mtPRPCBinary, FormatBinary, nil) 58 test(mtPRPCJSONPBLegacy, FormatJSONPB, nil) 59 test(mtPRPCText, FormatText, nil) 60 test(ContentTypeJSON, FormatJSONPB, nil) 61 62 test("application/*", FormatBinary, nil) 63 test("*/*", FormatBinary, nil) 64 65 // test cases with multiple types 66 test("{json},{binary}", FormatBinary, nil) 67 test("{json},{binary};q=0.9", FormatJSONPB, nil) 68 test("{json};q=1,{binary};q=0.9", FormatJSONPB, nil) 69 test("{json},{text}", FormatJSONPB, nil) 70 test("{json};q=0.9,{text}", FormatText, nil) 71 test("{binary},{json},{text}", FormatBinary, nil) 72 73 test("{json},{binary},*/*", FormatBinary, nil) 74 test("{json},{binary},*/*;q=0.9", FormatBinary, nil) 75 test("{json},{binary},*/*;x=y", FormatBinary, nil) 76 test("{json},{binary};q=0.9,*/*", FormatBinary, nil) 77 test("{json},{binary};q=0.9,*/*;q=0.8", FormatJSONPB, nil) 78 79 // supported and unsupported mix 80 test("{json},foo/bar", FormatJSONPB, nil) 81 test("{json};q=0.1,foo/bar", FormatJSONPB, nil) 82 test("foo/bar;q=0.1,{json}", FormatJSONPB, nil) 83 84 // only unsupported types 85 const err406 = "pRPC: bad Accept header: specified media types are not not supported" 86 test(ContentTypePRPC+"; boo=true", 0, err406) 87 test(ContentTypePRPC+"; encoding=blah", 0, err406) 88 test("x", 0, err406) 89 test("x,y", 0, err406) 90 91 test("x//y", 0, "pRPC: bad Accept header: specified media types are not not supported") 92 }) 93 94 Convey("writeMessage", t, func() { 95 msg := &HelloReply{Message: "Hi"} 96 c := context.Background() 97 98 test := func(f Format, body []byte, contentType string) { 99 Convey(contentType, func() { 100 rec := httptest.NewRecorder() 101 writeMessage(c, rec, msg, f, false) 102 So(rec.Code, ShouldEqual, http.StatusOK) 103 So(rec.Header().Get(HeaderGRPCCode), ShouldEqual, "0") 104 So(rec.Header().Get(headerContentType), ShouldEqual, contentType) 105 So(rec.Body.Bytes(), ShouldResemble, body) 106 }) 107 } 108 109 msgBytes, err := proto.Marshal(msg) 110 So(err, ShouldBeNil) 111 112 test(FormatBinary, msgBytes, mtPRPCBinary) 113 test(FormatJSONPB, []byte(JSONPBPrefix+"{\"message\":\"Hi\"}\n"), mtPRPCJSONPB) 114 test(FormatText, []byte("message: \"Hi\"\n"), mtPRPCText) 115 116 Convey("compression", func() { 117 rec := httptest.NewRecorder() 118 msg := &HelloReply{Message: strings.Repeat("A", 1024)} 119 writeMessage(c, rec, msg, FormatText, true) 120 So(rec.Code, ShouldEqual, http.StatusOK) 121 So(rec.Header().Get("Content-Encoding"), ShouldEqual, "gzip") 122 So(rec.Body.Len(), ShouldBeLessThan, 1024) 123 }) 124 }) 125 126 Convey("writeError", t, func() { 127 c := context.Background() 128 c = memlogger.Use(c) 129 log := logging.Get(c).(*memlogger.MemLogger) 130 131 rec := httptest.NewRecorder() 132 133 Convey("client error", func() { 134 writeError(c, rec, status.Error(codes.NotFound, "not found"), FormatBinary) 135 So(rec.Code, ShouldEqual, http.StatusNotFound) 136 So(rec.Header().Get(HeaderGRPCCode), ShouldEqual, "5") 137 So(rec.Header().Get(headerContentType), ShouldEqual, "text/plain") 138 So(rec.Body.String(), ShouldEqual, "not found\n") 139 So(log, memlogger.ShouldHaveLog, logging.Warning, "prpc: responding with NotFound error (HTTP 404): not found") 140 }) 141 142 Convey("internal error", func() { 143 writeError(c, rec, status.Error(codes.Internal, "errmsg"), FormatBinary) 144 So(rec.Code, ShouldEqual, http.StatusInternalServerError) 145 So(rec.Header().Get(HeaderGRPCCode), ShouldEqual, "13") 146 So(rec.Header().Get(headerContentType), ShouldEqual, "text/plain") 147 So(rec.Body.String(), ShouldEqual, "Internal server error\n") 148 So(log, memlogger.ShouldHaveLog, logging.Error, "prpc: responding with Internal error (HTTP 500): errmsg") 149 }) 150 151 Convey("unknown error", func() { 152 writeError(c, rec, status.Error(codes.Unknown, "errmsg"), FormatBinary) 153 So(rec.Code, ShouldEqual, http.StatusInternalServerError) 154 So(rec.Header().Get(HeaderGRPCCode), ShouldEqual, "2") 155 So(rec.Header().Get(headerContentType), ShouldEqual, "text/plain") 156 So(rec.Body.String(), ShouldEqual, "Unknown server error\n") 157 So(log, memlogger.ShouldHaveLog, logging.Error, "prpc: responding with Unknown error (HTTP 500): errmsg") 158 }) 159 160 Convey("status details", func() { 161 testStatusDetails := func(format Format, expected []string) { 162 st := status.New(codes.InvalidArgument, "invalid argument") 163 164 st, err := st.WithDetails(&errdetails.BadRequest{ 165 FieldViolations: []*errdetails.BadRequest_FieldViolation{ 166 {Field: "a"}, 167 }, 168 }) 169 So(err, ShouldBeNil) 170 171 st, err = st.WithDetails(&errdetails.Help{ 172 Links: []*errdetails.Help_Link{ 173 {Url: "https://example.com"}, 174 }, 175 }) 176 So(err, ShouldBeNil) 177 178 writeError(c, rec, st.Err(), format) 179 So(rec.Header()[HeaderStatusDetail], ShouldResemble, expected) 180 } 181 182 Convey("binary", func() { 183 testStatusDetails(FormatBinary, []string{ 184 "Cil0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5ycGMuQmFkUmVxdWVzdBIFCgMKAWE=", 185 "CiN0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5ycGMuSGVscBIXChUSE2h0dHBzOi8vZXhhbXBsZS5jb20=", 186 }) 187 }) 188 189 Convey("json", func() { 190 testStatusDetails(FormatJSONPB, []string{ 191 "eyJAdHlwZSI6InR5cGUuZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLnJwYy5CYWRSZXF1ZXN0IiwiZmllbGRWaW9sYXRpb25zIjpbeyJmaWVsZCI6ImEifV19", 192 "eyJAdHlwZSI6InR5cGUuZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLnJwYy5IZWxwIiwibGlua3MiOlt7InVybCI6Imh0dHBzOi8vZXhhbXBsZS5jb20ifV19", 193 }) 194 }) 195 196 Convey("text", func() { 197 testStatusDetails(FormatText, []string{ 198 "dHlwZV91cmw6ICJ0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5ycGMuQmFkUmVxdWVzdCIKdmFsdWU6ICJcblwwMDNcblwwMDFhIgo=", 199 "dHlwZV91cmw6ICJ0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5ycGMuSGVscCIKdmFsdWU6ICJcblwwMjVcMDIyXDAyM2h0dHBzOi8vZXhhbXBsZS5jb20iCg==", 200 }) 201 }) 202 }) 203 }) 204 }