github.com/renbou/grpcbridge@v0.0.2-0.20240416012907-bcbd8b12648a/internal/bridgetest/grpcbridge_test.go (about) 1 package bridgetest 2 3 import ( 4 "context" 5 "io" 6 "math" 7 "net" 8 "net/http" 9 "net/http/httptest" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/google/go-cmp/cmp" 15 "github.com/renbou/grpcbridge" 16 "github.com/renbou/grpcbridge/internal/bridgetest/testpb" 17 "github.com/renbou/grpcbridge/transcoding" 18 "google.golang.org/grpc" 19 "google.golang.org/grpc/credentials/insecure" 20 "google.golang.org/grpc/reflection" 21 "google.golang.org/protobuf/testing/protocmp" 22 "google.golang.org/protobuf/types/known/durationpb" 23 ) 24 25 var testEchoMessage = &testpb.Combined{ 26 Scalars: &testpb.Scalars{ 27 BoolValue: true, 28 Int32Value: -12379, 29 Uint32Value: 378, 30 Uint64Value: math.MaxUint64, 31 Int64Value: math.MinInt64, 32 Sfixed64Value: 1, 33 DoubleValue: math.Inf(-1), 34 StringValue: "sttrrr", 35 }, 36 NonScalars: &testpb.NonScalars{ 37 Str2StrMap: map[string]string{ 38 "key1": "value1", 39 "key2": "value2", 40 }, 41 Str2Int32Map: map[string]int32{ 42 "one": 1, 43 "two": 2, 44 }, 45 Duration: durationpb.New(time.Millisecond * 123), 46 Child: &testpb.NonScalars_Child{ 47 Nested: &testpb.NonScalars_Child_Digits{Digits: testpb.Digits_TWO}, 48 }, 49 }, 50 } 51 52 // Test_GRPCBridge performs a basic integration test of all the [grpcbridge] functionality. 53 func Test_GRPCBridge(t *testing.T) { 54 t.Parallel() 55 56 // Arrange 57 // Set up a single complete grpcbridge instance for all tests to test parallelism. 58 targetListener, targetServer := mustRawServer(t, nil, []func(*grpc.Server){func(s *grpc.Server) { 59 testpb.RegisterTestServiceServer(s, new(testpb.TestService)) 60 reflection.Register(s) 61 }}) 62 63 defer targetServer.Stop() 64 65 router := grpcbridge.NewReflectionRouter( 66 grpcbridge.WithLogger(Logger(t)), 67 grpcbridge.WithDialOpts( 68 grpc.WithTransportCredentials(insecure.NewCredentials()), 69 grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { 70 return targetListener.Dial() 71 }), 72 ), 73 grpcbridge.WithConnFunc(grpc.NewClient), 74 grpcbridge.WithDisabledReflectionPolling(), 75 grpcbridge.WithReflectionPollInterval(time.Second), 76 ) 77 78 if ok, err := router.Add(TestTargetName, TestDialTarget); err != nil { 79 t.Fatalf("ReflectionRouter.Add(%q) returned non-nil error = %q", TestTargetName, err) 80 } else if !ok { 81 t.Fatalf("ReflectionRouter.Add(%q) returned false, expected it to successfully add test target", TestTargetName) 82 } 83 84 t.Cleanup(func() { 85 if ok := router.Remove(TestTargetName); !ok { 86 t.Fatalf("ReflectionRouter.Remove(%q) returned false, expected it to successfully remove test target", TestTargetName) 87 } 88 }) 89 90 // Wait for the router to update. 91 // TODO(renbou): perhaps introduce a blocking mode to the reflection resolver, 92 // which would allow waiting for the first resolution to succeed, or return an error? 93 time.Sleep(time.Millisecond * 50) 94 95 proxy := grpcbridge.NewGRPCProxy(router, grpcbridge.WithLogger(Logger(t))) 96 bridge := grpcbridge.NewWebBridge(router, 97 grpcbridge.WithLogger(Logger(t)), 98 grpcbridge.WithDefaultMarshaler(transcoding.DefaultJSONMarshaler), 99 grpcbridge.WithMarshalers([]transcoding.Marshaler{transcoding.DefaultJSONMarshaler}), 100 ) 101 102 // Set up servers which simulate grpcbridge itself. 103 bridgeListener, grpcServer := mustRawServer(t, []grpc.ServerOption{proxy.AsServerOption()}, nil) 104 defer grpcServer.Stop() 105 106 httpServer := httptest.NewServer(bridge) 107 defer httpServer.Close() 108 109 grpcClient, err := grpc.NewClient(TestDialTarget, 110 grpc.WithTransportCredentials(insecure.NewCredentials()), 111 grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { 112 return bridgeListener.Dial() 113 }), 114 ) 115 if err != nil { 116 t.Fatalf("grpc.NewClient(grpcbridge) returned non-nil error = %q", err) 117 } 118 119 httpClient := httpServer.Client() 120 121 // Run in non-parallel subtest so that server.Stop() runs AFTER all the subtests. 122 t.Run("cases", func(t *testing.T) { 123 t.Run("proxy", func(t *testing.T) { 124 test_GRPCBridge_Proxy(t, grpcClient) 125 }) 126 127 t.Run("bridge", func(t *testing.T) { 128 test_GRPCBridge_Bridge(t, httpClient, httpServer) 129 }) 130 }) 131 } 132 133 func test_GRPCBridge_Proxy(t *testing.T, client *grpc.ClientConn) { 134 t.Parallel() 135 136 // Arrange 137 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 138 defer cancel() 139 140 testClient := testpb.NewTestServiceClient(client) 141 142 // Act 143 resp, gotErr := testClient.Echo(ctx, testEchoMessage) 144 145 // Assert 146 if gotErr != nil { 147 t.Errorf("TestService.Echo() returned non-nil error = %q", gotErr) 148 } 149 150 if diff := cmp.Diff(testEchoMessage, resp, protocmp.Transform()); diff != "" { 151 t.Fatalf("TestService.Echo() returned response differing from request (-want+got):\n%s", diff) 152 } 153 } 154 155 func test_GRPCBridge_Bridge(t *testing.T, client *http.Client, server *httptest.Server) { 156 t.Parallel() 157 158 // Arrange 159 testBody := `{ 160 "scalars": { 161 "boolValue": true, 162 "int32Value": -12379, 163 "uint32Value": 378, 164 "uint64Value": "18446744073709551615", 165 "int64Value": "-9223372036854775808", 166 "sfixed64Value": 1, 167 "doubleValue": "-Infinity", 168 "stringValue": "sttrrr" 169 }, 170 "nonScalars": { 171 "str2strMap": { 172 "key1": "value1", 173 "key2": "value2" 174 }, 175 "str2int32Map": { 176 "one": 1, 177 "two": 2 178 }, 179 "duration": "0.123s", 180 "child": { 181 "digits": "TWO" 182 } 183 } 184 }` 185 186 // Act 187 resp, gotErr := client.Post(server.URL+"/service/echo", "application/json", strings.NewReader(testBody)) 188 189 // Assert 190 if gotErr != nil { 191 t.Fatalf("POST(/service/echo) returned non-nil error = %q", gotErr) 192 } 193 194 if resp.StatusCode != http.StatusOK { 195 body, _ := io.ReadAll(resp.Body) 196 t.Fatalf("POST(/service/echo) returned non-OK response = %s (%s)", resp.Status, string(body)) 197 } 198 199 respMsg := new(testpb.Combined) 200 if err := transcoding.DefaultJSONMarshaler.Unmarshal(testpb.TestServiceTypesResolver, []byte(testBody), respMsg.ProtoReflect(), nil); err != nil { 201 t.Fatalf("POST(/service/echo) returned invalid body, failed to unmarshal with error = %q", err) 202 } 203 204 if diff := cmp.Diff(testEchoMessage, respMsg, protocmp.Transform()); diff != "" { 205 t.Fatalf("POST(/service/echo) returned response differing from request (-want+got):\n%s", diff) 206 } 207 }