package server import ( "encoding/json" "net" "net/http" "net/http/httptest" "strings" "testing" "github.com/gin-gonic/gin" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/health" healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/metadata" "google.golang.org/grpc/reflection" ) func TestDynamicGatewayInvokesUnaryRPC(t *testing.T) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } grpcServer := grpc.NewServer() healthServer := health.NewServer() healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) healthpb.RegisterHealthServer(grpcServer, healthServer) reflection.Register(grpcServer) go func() { _ = grpcServer.Serve(listener) }() t.Cleanup(func() { grpcServer.Stop() _ = listener.Close() }) gateway, err := newDynamicGateway(listener.Addr().String(), []string{"grpc.health.v1.Health.Check"}) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = gateway.Close() }) gin.SetMode(gin.TestMode) engine := gin.New() engine.POST("/rpc/:module/:service/:method", gateway.handle) request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{"service":""}`)) response := httptest.NewRecorder() engine.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("unexpected HTTP status: %d", response.Code) } var payload struct { Code int32 `json:"code"` Data json.RawMessage `json:"data"` } if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil { t.Fatal(err) } if payload.Code != 0 || !strings.Contains(string(payload.Data), `"SERVING"`) { t.Fatalf("unexpected response: %s", response.Body.String()) } } func TestDynamicGatewayDeniesMethodsByDefault(t *testing.T) { gateway := &dynamicGateway{allow: map[string]struct{}{}} gin.SetMode(gin.TestMode) engine := gin.New() engine.POST("/rpc/:module/:service/:method", gateway.handle) request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{}`)) response := httptest.NewRecorder() engine.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("dynamic RPC errors must use HTTP 200, got %d", response.Code) } var payload dynamicRPCResponse if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil { t.Fatal(err) } if payload.Code != int32(codes.PermissionDenied) { t.Fatalf("expected permission denied, got %s", response.Body.String()) } } func TestOutgoingMetadataFiltersHeaders(t *testing.T) { request := httptest.NewRequest(http.MethodPost, "/", nil) request.Header.Set("Authorization", "Bearer token") request.Header.Set("X-Request-ID", "request-id") request.Header.Set("Content-Type", "application/json") ctx := outgoingMetadata(request) forwarded, ok := metadata.FromOutgoingContext(ctx) if !ok || len(forwarded.Get("authorization")) != 1 || len(forwarded.Get("x-request-id")) != 1 { t.Fatalf("expected forwarded metadata: %v", forwarded) } if len(forwarded.Get("content-type")) != 0 { t.Fatalf("content-type must not be forwarded: %v", forwarded) } }