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  }