go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/grpc/prpc/decoding_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 "bytes" 19 "context" 20 "encoding/base64" 21 "io" 22 "net/http" 23 "testing" 24 "time" 25 26 "github.com/golang/protobuf/proto" 27 "google.golang.org/genproto/protobuf/field_mask" 28 29 "google.golang.org/grpc/metadata" 30 31 "go.chromium.org/luci/common/clock/testclock" 32 33 . "github.com/smartystreets/goconvey/convey" 34 . "go.chromium.org/luci/common/testing/assertions" 35 ) 36 37 func TestDecoding(t *testing.T) { 38 t.Parallel() 39 40 Convey("readMessage", t, func() { 41 var msg HelloRequest 42 read := func(contentType string, body []byte) *protocolError { 43 req := &http.Request{ 44 Body: io.NopCloser(bytes.NewBuffer(body)), 45 Header: http.Header{}, 46 } 47 req.Header.Set("Content-Type", contentType) 48 return readMessage(req, &msg, true) 49 } 50 51 testLucy := func(contentType string, body []byte) { 52 err := read(contentType, body) 53 So(err, ShouldBeNil) 54 So(&msg, ShouldResembleProto, &HelloRequest{ 55 Name: "Lucy", 56 Fields: &field_mask.FieldMask{ 57 Paths: []string{ 58 "name", 59 }, 60 }, 61 }) 62 } 63 64 Convey("binary", func() { 65 testMsg := &HelloRequest{ 66 Name: "Lucy", 67 Fields: &field_mask.FieldMask{ 68 Paths: []string{ 69 "name", 70 }, 71 }, 72 } 73 body, err := proto.Marshal(testMsg) 74 So(err, ShouldBeNil) 75 76 Convey(ContentTypePRPC, func() { 77 testLucy(ContentTypePRPC, body) 78 }) 79 Convey(mtPRPCBinary, func() { 80 testLucy(mtPRPCBinary, body) 81 }) 82 Convey("malformed body", func() { 83 err := read(mtPRPCBinary, []byte{0}) 84 So(err, ShouldNotBeNil) 85 So(err.status, ShouldEqual, http.StatusBadRequest) 86 }) 87 Convey("empty body", func() { 88 err := read(mtPRPCBinary, nil) 89 So(err, ShouldBeNil) 90 }) 91 }) 92 93 Convey("json", func() { 94 body := []byte(`{"name": "Lucy", "fields": "name"}`) 95 Convey(ContentTypeJSON, func() { 96 testLucy(ContentTypeJSON, body) 97 }) 98 Convey(mtPRPCJSONPBLegacy, func() { 99 testLucy(mtPRPCJSONPB, body) 100 }) 101 Convey("malformed body", func() { 102 err := read(mtPRPCJSONPB, []byte{0}) 103 So(err, ShouldNotBeNil) 104 So(err.status, ShouldEqual, http.StatusBadRequest) 105 }) 106 Convey("empty body", func() { 107 err := read(mtPRPCJSONPB, nil) 108 So(err, ShouldNotBeNil) 109 So(err.status, ShouldEqual, http.StatusBadRequest) 110 }) 111 }) 112 113 Convey("text", func() { 114 Convey(mtPRPCText, func() { 115 body := []byte(`name: "Lucy" fields < paths: "name" >`) 116 testLucy(mtPRPCText, body) 117 }) 118 Convey("malformed body", func() { 119 err := read(mtPRPCText, []byte{0}) 120 So(err, ShouldNotBeNil) 121 So(err.status, ShouldEqual, http.StatusBadRequest) 122 }) 123 Convey("empty body", func() { 124 err := read(mtPRPCText, nil) 125 So(err, ShouldBeNil) 126 }) 127 }) 128 129 Convey("unsupported media type", func() { 130 err := read("blah", nil) 131 So(err, ShouldNotBeNil) 132 So(err.status, ShouldEqual, http.StatusUnsupportedMediaType) 133 }) 134 }) 135 136 Convey("parseHeader", t, func() { 137 c := context.Background() 138 139 Convey("host", func() { 140 c, _, err := parseHeader(c, http.Header{}, "example.com") 141 So(err, ShouldBeNil) 142 md, ok := metadata.FromIncomingContext(c) 143 So(ok, ShouldBeTrue) 144 So(md.Get("host"), ShouldResemble, []string{"example.com"}) 145 }) 146 147 header := func(name, value string) http.Header { 148 return http.Header{name: []string{value}} 149 } 150 parse := func(c context.Context, name, value string) (context.Context, error) { 151 ctx, _, err := parseHeader(c, header(name, value), "") 152 return ctx, err 153 } 154 155 Convey(HeaderTimeout, func() { 156 Convey("Works", func() { 157 now := time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC) 158 c, _ = testclock.UseTime(c, now) 159 160 var err error 161 c, err = parse(c, HeaderTimeout, "1M") 162 So(err, ShouldBeNil) 163 164 deadline, ok := c.Deadline() 165 So(ok, ShouldBeTrue) 166 So(deadline, ShouldHappenWithin, time.Second, now.Add(time.Minute)) 167 }) 168 169 Convey("Fails", func() { 170 c, err := parse(c, HeaderTimeout, "blah") 171 So(c, ShouldEqual, c) 172 So(err, ShouldErrLike, `"`+HeaderTimeout+`" header: unit is not recognized: "blah"`) 173 }) 174 }) 175 176 Convey("Content-Type", func() { 177 c, err := parse(c, "Content-Type", "blah") 178 So(err, ShouldBeNil) 179 _, ok := metadata.FromIncomingContext(c) 180 So(ok, ShouldBeFalse) 181 }) 182 183 Convey("Accept", func() { 184 c, err := parse(c, "Accept", "blah") 185 So(err, ShouldBeNil) 186 _, ok := metadata.FromIncomingContext(c) 187 So(ok, ShouldBeFalse) 188 }) 189 190 Convey("Unrecognized headers", func() { 191 test := func(ctx context.Context, header http.Header, expectedMetadata metadata.MD) { 192 ctx, _, err := parseHeader(ctx, header, "") 193 So(err, ShouldBeNil) 194 md, ok := metadata.FromIncomingContext(ctx) 195 So(ok, ShouldBeTrue) 196 So(md, ShouldResemble, expectedMetadata) 197 } 198 199 headers := http.Header{ 200 "X": []string{"1"}, 201 "Y": []string{"1", "2"}, 202 } 203 204 Convey("without metadata in context", func() { 205 test(c, headers, metadata.MD{ 206 "x": []string{"1"}, 207 "y": []string{"1", "2"}, 208 }) 209 }) 210 211 Convey("with metadata in context", func() { 212 c = metadata.NewIncomingContext(c, metadata.MD{ 213 "x": []string{"0"}, 214 "z": []string{"1"}, 215 }) 216 test(c, headers, metadata.MD{ 217 "x": []string{"0", "1"}, 218 "y": []string{"1", "2"}, 219 "z": []string{"1"}, 220 }) 221 }) 222 223 Convey("binary", func() { 224 Convey("Works", func() { 225 const name = "Lucy" 226 b64 := base64.StdEncoding.EncodeToString([]byte(name)) 227 test(c, header("Name-Bin", b64), metadata.MD{ 228 "name-bin": []string{name}, 229 }) 230 }) 231 Convey("Fails", func() { 232 c, err := parse(c, "Name-Bin", "zzz") 233 So(c, ShouldEqual, c) 234 So(err, ShouldErrLike, `header "Name-Bin": illegal base64 data at input byte 0`) 235 }) 236 }) 237 }) 238 }) 239 }