diff options
author | Mikhail Mazurskiy <mmazurskiy@gitlab.com> | 2023-05-17 13:51:18 +0300 |
---|---|---|
committer | Mikhail Mazurskiy <mmazurskiy@gitlab.com> | 2023-05-18 03:25:13 +0300 |
commit | 64dbd379058e89264802d03a9621ec98b987b58a (patch) | |
tree | b24a1f76ce2e37f43800c50fa84883ae369424db /internal/testhelper/testserver/structerr_interceptors_test.go | |
parent | 624fc0095e283c990a627b702b6eaf5a99c2da7b (diff) |
Decouple production code from test proto
WithInterceptedMetadata() uses testproto.ErrorMetadata, which is in a test-only package. This function is only used in tests so move it to testhelper and interceptors into testserver
Diffstat (limited to 'internal/testhelper/testserver/structerr_interceptors_test.go')
-rw-r--r-- | internal/testhelper/testserver/structerr_interceptors_test.go | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/internal/testhelper/testserver/structerr_interceptors_test.go b/internal/testhelper/testserver/structerr_interceptors_test.go new file mode 100644 index 000000000..330c823b5 --- /dev/null +++ b/internal/testhelper/testserver/structerr_interceptors_test.go @@ -0,0 +1,184 @@ +package testserver + +import ( + "context" + "errors" + "fmt" + "net" + "testing" + + grpcmwlogrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v16/internal/log" + "gitlab.com/gitlab-org/gitaly/v16/internal/structerr" + "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb/testproto" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/interop/grpc_testing" + "google.golang.org/grpc/status" +) + +func TestInterceptedError(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + desc string + err error + expectedErr error + }{ + { + desc: "normal error", + err: fmt.Errorf("test"), + expectedErr: fmt.Errorf("test"), + }, + { + desc: "structured error", + err: structerr.NewNotFound("not found"), + expectedErr: structerr.NewNotFound("not found"), + }, + { + desc: "wrapped structured error", + err: fmt.Errorf("wrapped: %w", structerr.NewNotFound("not found")), + expectedErr: fmt.Errorf("wrapped: %w", structerr.NewNotFound("not found")), + }, + { + desc: "metadata", + err: structerr.NewNotFound("not found").WithMetadata("key", "value"), + expectedErr: structerr.NewNotFound("not found").WithDetail( + &testproto.ErrorMetadata{ + Key: []byte("key"), + Value: []byte("value"), + }, + ), + }, + { + desc: "wrapped error with metadata", + err: fmt.Errorf("wrapped: %w", structerr.NewNotFound("not found").WithMetadata("key", "value")), + expectedErr: structerr.NewNotFound("wrapped: not found").WithDetail( + &testproto.ErrorMetadata{ + Key: []byte("key"), + Value: []byte("value"), + }, + ), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + err := interceptedError(tc.err) + testhelper.RequireGrpcError(t, tc.expectedErr, err) + }) + } +} + +type mockService struct { + grpc_testing.UnimplementedTestServiceServer + err error +} + +func (m *mockService) UnaryCall( + context.Context, *grpc_testing.SimpleRequest, +) (*grpc_testing.SimpleResponse, error) { + return &grpc_testing.SimpleResponse{}, m.err +} + +func TestFieldsProducer(t *testing.T) { + t.Parallel() + + ctx := testhelper.Context(t) + logger, loggerHook := test.NewNullLogger() + logger.SetLevel(logrus.ErrorLevel) + + listener, err := net.Listen("tcp", "localhost:0") + require.NoError(t, err) + + service := &mockService{} + server := grpc.NewServer( + grpc.ChainUnaryInterceptor( + grpcmwlogrus.UnaryServerInterceptor( + logrus.NewEntry(logger), + grpcmwlogrus.WithMessageProducer( + log.MessageProducer( + grpcmwlogrus.DefaultMessageProducer, + structerr.FieldsProducer, + ), + ), + ), + ), + ) + grpc_testing.RegisterTestServiceServer(server, service) + + go testhelper.MustServe(t, server, listener) + defer server.Stop() + + conn, err := grpc.Dial(listener.Addr().String(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + defer testhelper.MustClose(t, conn) + + client := grpc_testing.NewTestServiceClient(conn) + + for _, tc := range []struct { + desc string + returnedErr error + expectedErr error + expectedMetadata []map[string]any + }{ + { + desc: "no error", + returnedErr: nil, + }, + { + desc: "plain error", + returnedErr: errors.New("message"), + expectedErr: status.Error(codes.Unknown, "message"), + }, + { + desc: "structured error", + returnedErr: structerr.New("message"), + expectedErr: status.Error(codes.Unknown, "message"), + }, + { + desc: "structured error with metadata", + returnedErr: structerr.New("message").WithMetadata("key", "value"), + expectedErr: status.Error(codes.Unknown, "message"), + expectedMetadata: []map[string]any{ + { + "key": "value", + }, + }, + }, + { + desc: "structured error with nested metadata", + returnedErr: structerr.New("message: %w", structerr.New("nested").WithMetadata("nested", "value")).WithMetadata("key", "value"), + expectedErr: status.Error(codes.Unknown, "message: nested"), + expectedMetadata: []map[string]any{ + { + "key": "value", + "nested": "value", + }, + }, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + loggerHook.Reset() + + service.err = tc.returnedErr + + _, err := client.UnaryCall(ctx, &grpc_testing.SimpleRequest{}) + testhelper.ProtoEqual(t, tc.expectedErr, err) + + var metadata []map[string]any + for _, entry := range loggerHook.AllEntries() { + if untypedMetadata := entry.Data["error_metadata"]; untypedMetadata != nil { + require.IsType(t, untypedMetadata, map[string]any{}) + metadata = append(metadata, untypedMetadata.(map[string]any)) + } + } + require.Equal(t, tc.expectedMetadata, metadata) + }) + } +} |