github.com/renbou/grpcbridge@v0.0.2-0.20240416012907-bcbd8b12648a/webbridge/http_test.go (about) 1 package webbridge 2 3 import ( 4 "context" 5 "encoding/base64" 6 "errors" 7 "net/http" 8 "net/http/httptest" 9 "strings" 10 "testing" 11 12 "github.com/google/go-cmp/cmp" 13 "github.com/renbou/grpcbridge/internal/bridgetest/testpb" 14 spb "google.golang.org/genproto/googleapis/rpc/status" 15 "google.golang.org/grpc/codes" 16 "google.golang.org/grpc/status" 17 "google.golang.org/protobuf/testing/protocmp" 18 ) 19 20 func mustTranscodedHTTPBridge(t *testing.T) (*testpb.TestService, *TranscodedHTTPBridge) { 21 testsvc, router, transcoder := mustTranscodedTestSvc(t) 22 bridge := NewTranscodedHTTPBridge(router, transcoder, TranscodedHTTPBridgeOpts{}) 23 24 return testsvc, bridge 25 } 26 27 type brokenReader struct{} 28 29 func (br *brokenReader) Read([]byte) (int, error) { 30 return 0, errors.New("broken reader") 31 } 32 33 // Test_TranscodedHTTPBridge_Unary tests the TranscodedHTTPBridge.ServeHTTP method for a basic unary RPC. 34 func Test_TranscodedHTTPBridge_Unary(t *testing.T) { 35 t.Parallel() 36 37 wantRequest := &testpb.Scalars{ 38 StringValue: "stttrrr", 39 Fixed64Value: 4242, 40 BytesValue: []byte("byteesssss"), 41 } 42 43 wantResponse := &testpb.Combined{ 44 Scalars: nil, 45 NonScalars: &testpb.NonScalars{ 46 Str2StrMap: map[string]string{ 47 "key1": "value1", 48 "key2": "value2", 49 }, 50 }, 51 } 52 53 // Arrange 54 testsvc, bridge := mustTranscodedHTTPBridge(t) 55 testsvc.UnaryBoundResponse = testpb.PrepareResponse(wantResponse, nil) 56 57 recorder := httptest.NewRecorder() 58 request := httptest.NewRequest("POST", "/service/unary/stttrrr/4242", strings.NewReader(`"`+base64.StdEncoding.EncodeToString([]byte("byteesssss"))+`"`)) 59 60 // Act 61 bridge.ServeHTTP(recorder, request) 62 63 // Assert 64 if diff := cmp.Diff(wantRequest, testsvc.UnaryBoundRequest, protocmp.Transform()); diff != "" { 65 t.Errorf("TestService.UnaryBound() received request differing from expected (-want+got):\n%s", diff) 66 } 67 68 if recorder.Code != http.StatusOK { 69 t.Errorf("TranscodedHTTPBridge.ServeHTTP() returned unexpected non-ok response code = %d, body = %s", recorder.Code, recorder.Body.String()) 70 } 71 72 if contentType := recorder.Result().Header.Get(contentTypeHeader); contentType != ctJson { 73 t.Errorf("TranscodedHTTPBridge.ServeHTTP() returned unexpected content type = %q, want %q", contentType, ctJson) 74 } 75 76 gotResponse := new(testpb.Combined) 77 if err := unmarshalJSON(recorder.Body.Bytes(), gotResponse); err != nil { 78 t.Fatalf("TranscodedHTTPBridge.ServeHTTP() returned invalid response, failed to unmarshal: %s", err) 79 } 80 81 if diff := cmp.Diff(wantResponse, gotResponse, protocmp.Transform()); diff != "" { 82 t.Errorf("TranscodedHTTPBridge.ServeHTTP() returned response differing from expected (-want+got):\n%s", diff) 83 } 84 } 85 86 // Test_TranscodedHTTPBridge_Errors tests how TranscodedHTTPBridge.ServeHTTP handles and returns errors with various codes and formats. 87 func Test_TranscodedHTTPBridge_Errors(t *testing.T) { 88 t.Parallel() 89 90 _, bridge := mustTranscodedHTTPBridge(t) 91 92 tests := []struct { 93 name string 94 request *http.Request 95 httpCode int 96 isMarshaled bool 97 statusCode codes.Code 98 }{ 99 { 100 name: "404", 101 request: httptest.NewRequest("GET", "/notaroute", nil), 102 httpCode: http.StatusNotFound, 103 isMarshaled: false, 104 }, 105 { 106 name: "unsupported content-type", 107 request: func() *http.Request { 108 req := httptest.NewRequest("POST", "/grpcbridge.internal.bridgetest.testpb.TestService/UnaryUnbound", nil) 109 req.Header.Set(contentTypeHeader, "img/png") 110 return req 111 }(), 112 httpCode: http.StatusUnsupportedMediaType, 113 isMarshaled: false, 114 }, 115 { 116 name: "canceled request", 117 request: func() *http.Request { 118 req := httptest.NewRequest("POST", "/grpcbridge.internal.bridgetest.testpb.TestService/UnaryUnbound", nil) 119 ctx, cancel := context.WithCancel(req.Context()) 120 cancel() 121 return req.WithContext(ctx) 122 }(), 123 httpCode: httpStatusCanceled, 124 isMarshaled: false, 125 }, 126 { 127 name: "unreadable request", 128 request: httptest.NewRequest("POST", "/grpcbridge.internal.bridgetest.testpb.TestService/UnaryUnbound", new(brokenReader)), 129 httpCode: http.StatusBadRequest, 130 isMarshaled: true, 131 statusCode: codes.InvalidArgument, 132 }, 133 { 134 name: "invalid request", 135 request: httptest.NewRequest("POST", "/service/unary/stttrrr/4242", strings.NewReader(`bad`)), 136 httpCode: http.StatusBadRequest, 137 isMarshaled: true, 138 statusCode: codes.InvalidArgument, 139 }, 140 { 141 name: "failed response marshal", 142 request: httptest.NewRequest("POST", "/service/bad-response-path", nil), 143 httpCode: http.StatusInternalServerError, 144 isMarshaled: true, 145 statusCode: codes.Internal, 146 }, 147 } 148 149 for _, tt := range tests { 150 t.Run(tt.name, func(t *testing.T) { 151 t.Parallel() 152 153 // Arrange 154 recorder := httptest.NewRecorder() 155 156 // Act 157 bridge.ServeHTTP(recorder, tt.request) 158 159 // Assert 160 if recorder.Code != tt.httpCode { 161 t.Errorf("TranscodedHTTPBridge.ServeHTTP() returned response code = %d, want %d", recorder.Code, tt.httpCode) 162 } 163 164 if !tt.isMarshaled { 165 return 166 } 167 168 gotStatusPB := new(spb.Status) 169 if err := unmarshalJSON(recorder.Body.Bytes(), gotStatusPB); err != nil { 170 t.Fatalf("TranscodedHTTPBridge.ServeHTTP() returned invalid status response, failed to unmarshal: %s", err) 171 } 172 173 if gotStatus := status.FromProto(gotStatusPB); gotStatus.Code() != tt.statusCode { 174 t.Errorf("TranscodedHTTPBridge.ServeHTTP() returned status code = %s, want %s", gotStatus.Code(), tt.statusCode) 175 } 176 }) 177 } 178 }