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  }