From 3cd1d4743527d46165427f45da63bca029c30ccd Mon Sep 17 00:00:00 2001 From: zoncoen Date: Sat, 15 Apr 2023 00:35:12 +0900 Subject: [PATCH] feat(grpc): contain response status in log --- go.mod | 2 +- protocol/grpc/expect.go | 65 +++------- protocol/grpc/expect_test.go | 229 ++++++++++++++++++++++++---------- protocol/grpc/request.go | 55 +++++++- protocol/grpc/request_test.go | 162 ++++++++++++++++-------- protocol/grpc/type.go | 2 +- 6 files changed, 350 insertions(+), 165 deletions(-) diff --git a/go.mod b/go.mod index 06e520b5..3d4a8477 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/fatih/color v1.15.0 github.com/goccy/go-yaml v1.11.0 github.com/golang/mock v1.6.0 - github.com/golang/protobuf v1.5.3 github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-multierror v1.1.1 github.com/mattn/go-encoding v0.0.2 @@ -28,6 +27,7 @@ require ( require ( github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/protocol/grpc/expect.go b/protocol/grpc/expect.go index ba5670d6..c1e3b08b 100644 --- a/protocol/grpc/expect.go +++ b/protocol/grpc/expect.go @@ -4,11 +4,11 @@ import ( "fmt" "reflect" "strconv" - "strings" "github.com/goccy/go-yaml" - "github.com/golang/protobuf/proto" //nolint:staticcheck "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" // Register proto messages to unmarshal com.google.protobuf.Any. _ "google.golang.org/genproto/googleapis/rpc/errdetails" @@ -128,14 +128,26 @@ func (e *Expect) buildStatusDetailAssertions(ctx *context.Context) ([]assert.Ass if err != nil { return nil, errors.WrapPath(err, fmt.Sprintf("status.details[%d].'%s'", i, k), "failed to execute template") } - fullName := assert.Build(executed) + var fullName assert.Assertion + if s, ok := executed.(string); ok { + fullName = assert.Build(protoreflect.FullName(s)) + } else { + fullName = assert.Build(executed) + } executed, err = ctx.ExecuteTemplate(v) if err != nil { return nil, errors.WrapPath(err, fmt.Sprintf("status.details[%d].'%s'", i, k), "failed to execute template") } fields := assert.Build(executed) statusDetailAssertions[i] = assert.AssertionFunc(func(v interface{}) error { - if err := fullName.Assert(proto.MessageV2(v).ProtoReflect().Descriptor().FullName()); err != nil { + m, ok := v.(proto.Message) + if !ok { + return fmt.Errorf("expect proto.Message but got %T", v) + } + if m == nil { + return errors.New("got nil proto.Message") + } + if err := fullName.Assert(proto.MessageName(m)); err != nil { return err } if err := fields.Assert(v); err != nil { @@ -155,16 +167,7 @@ func assertStatusCode(assertion assert.Assertion, sts *status.Status) error { if err == nil { return nil } - err = assertion.Assert(strconv.Itoa(int(sts.Code()))) - if err == nil { - return nil - } - return errors.Errorf( - `%s: message="%s"%s`, - err, - sts.Message(), - appendDetailsString(sts), - ) + return assertion.Assert(strconv.Itoa(int(sts.Code()))) } func (e *Expect) assertStatusMessage(assertion assert.Assertion, sts *status.Status) error { @@ -175,11 +178,7 @@ func (e *Expect) assertStatusMessage(assertion assert.Assertion, sts *status.Sta if err == nil { return nil } - return errors.Errorf( - `%s%s`, - err, - appendDetailsString(sts), - ) + return err } func (e *Expect) assertStatusDetails(assertions []assert.Assertion, sts *status.Status) error { @@ -191,41 +190,17 @@ func (e *Expect) assertStatusDetails(assertions []assert.Assertion, sts *status. for i, assertion := range assertions { if i >= len(actualDetails) { - return errors.ErrorPathf(fmt.Sprintf("details[%d]", i), `not found%s`, appendDetailsString(sts)) + return errors.ErrorPath(fmt.Sprintf("details[%d]", i), `not found`) } if err := assertion.Assert(actualDetails[i]); err != nil { - return errors.WrapPath(err, fmt.Sprintf("details[%d]", i), appendDetailsString(sts)) + return errors.WithPath(err, fmt.Sprintf("details[%d]", i)) } } return nil } -func appendDetailsString(sts *status.Status) string { - format := "%s: {%s}" - var details []string - - for _, i := range sts.Details() { - if pb, ok := i.(proto.Message); ok { - details = append(details, fmt.Sprintf(format, proto.MessageV2(pb).ProtoReflect().Descriptor().FullName(), pb.String())) - continue - } - - if e, ok := i.(interface{ Error() string }); ok { - details = append(details, fmt.Sprintf(format, "", e.Error())) - continue - } - - details = append(details, fmt.Sprintf(format, "", fmt.Sprintf("{%#v}", i))) - } - - if len(details) == 0 { - return "" - } - return fmt.Sprintf(": details=[ %s ]", strings.Join(details, ", ")) -} - func extract(v response) (proto.Message, *status.Status, error) { vs := v.rvalues if len(vs) != 2 { diff --git a/protocol/grpc/expect_test.go b/protocol/grpc/expect_test.go index c2f71f82..e5251bca 100644 --- a/protocol/grpc/expect_test.go +++ b/protocol/grpc/expect_test.go @@ -6,11 +6,13 @@ import ( "testing" "github.com/goccy/go-yaml" - "github.com/golang/protobuf/proto" //nolint:staticcheck "google.golang.org/genproto/googleapis/rpc/errdetails" + spb "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" "github.com/zoncoen/scenarigo/context" "github.com/zoncoen/scenarigo/internal/reflectutil" @@ -162,16 +164,23 @@ func TestExpect_Build(t *testing.T) { v: response{ rvalues: []reflect.Value{ reflect.Zero(reflect.TypeOf(&test.EchoResponse{})), - reflect.ValueOf(mustWithDetails( - status.New(codes.InvalidArgument, "invalid argument"), - &errdetails.LocalizedMessage{ - Locale: "ja-JP", - Message: "エラー", - }, - &errdetails.DebugInfo{ - Detail: "debug", + reflect.ValueOf(status.FromProto(&spb.Status{ + Code: int32(codes.InvalidArgument), + Message: "invalid argument", + Details: []*anypb.Any{ + mustAny(t, + &errdetails.LocalizedMessage{ + Locale: "ja-JP", + Message: "エラー", + }, + ), + mustAny(t, + &errdetails.DebugInfo{ + Detail: "debug", + }, + ), }, - ).Err()), + }).Err()), }, }, }, @@ -185,9 +194,8 @@ func TestExpect_Build(t *testing.T) { v: response{ rvalues: []reflect.Value{ reflect.Zero(reflect.TypeOf(&test.EchoResponse{})), - reflect.ValueOf(mustWithDetails( - status.New(codes.InvalidArgument, "invalid argument"), - ).Err()), + reflect.ValueOf( + status.New(codes.InvalidArgument, "invalid argument").Err()), }, }, }, @@ -240,6 +248,7 @@ func TestExpect_Build(t *testing.T) { v response expectBuildError bool expectAssertError bool + expectError string }{ "failed to execute template": { expect: &Expect{ @@ -505,7 +514,9 @@ func TestExpect_Build(t *testing.T) { }, expectAssertError: true, }, - "wrong status details: name is an invalid template": { + + // status details + "wrong status details: type name is an invalid template": { expect: &Expect{ Status: ExpectStatus{ Details: []map[string]yaml.MapSlice{ @@ -523,23 +534,31 @@ func TestExpect_Build(t *testing.T) { v: response{ rvalues: []reflect.Value{ reflect.Zero(reflect.TypeOf(&test.EchoResponse{})), - reflect.ValueOf(mustWithDetails( - status.New(codes.InvalidArgument, "invalid argument"), - &errdetails.LocalizedMessage{ - Locale: "ja-JP", - Message: "エラー", - }, - &errdetails.DebugInfo{ - Detail: "debug", + reflect.ValueOf(status.FromProto(&spb.Status{ + Code: int32(codes.InvalidArgument), + Message: "invalid argument", + Details: []*anypb.Any{ + mustAny(t, + &errdetails.LocalizedMessage{ + Locale: "ja-JP", + Message: "エラー", + }, + ), + mustAny(t, + &errdetails.DebugInfo{ + Detail: "debug", + }, + ), }, - ).Err()), + }).Err()), }, }, expectBuildError: true, }, - "wrong status details: name is wrong": { + "wrong status details: type name is wrong": { expect: &Expect{ Status: ExpectStatus{ + Code: "InvalidArgument", Details: []map[string]yaml.MapSlice{ { "google.rpc.Invalid": yaml.MapSlice{ @@ -555,23 +574,32 @@ func TestExpect_Build(t *testing.T) { v: response{ rvalues: []reflect.Value{ reflect.Zero(reflect.TypeOf(&test.EchoResponse{})), - reflect.ValueOf(mustWithDetails( - status.New(codes.InvalidArgument, "invalid argument"), - &errdetails.LocalizedMessage{ - Locale: "ja-JP", - Message: "エラー", - }, - &errdetails.DebugInfo{ - Detail: "debug", + reflect.ValueOf(status.FromProto(&spb.Status{ + Code: int32(codes.InvalidArgument), + Message: "invalid argument", + Details: []*anypb.Any{ + mustAny(t, + &errdetails.LocalizedMessage{ + Locale: "ja-JP", + Message: "エラー", + }, + ), + mustAny(t, + &errdetails.DebugInfo{ + Detail: "debug", + }, + ), }, - ).Err()), + }).Err()), }, }, expectAssertError: true, + expectError: `.status.details[0]: expected google.rpc.Invalid but got google.rpc.LocalizedMessage`, }, - "wrong status details: value is an invalid template": { + "wrong status details: key is an invalid template": { expect: &Expect{ Status: ExpectStatus{ + Code: "InvalidArgument", Details: []map[string]yaml.MapSlice{ { "google.rpc.LocalizedMessage": yaml.MapSlice{ @@ -587,29 +615,38 @@ func TestExpect_Build(t *testing.T) { v: response{ rvalues: []reflect.Value{ reflect.Zero(reflect.TypeOf(&test.EchoResponse{})), - reflect.ValueOf(mustWithDetails( - status.New(codes.InvalidArgument, "invalid argument"), - &errdetails.LocalizedMessage{ - Locale: "ja-JP", - Message: "エラー", - }, - &errdetails.DebugInfo{ - Detail: "debug", + reflect.ValueOf(status.FromProto(&spb.Status{ + Code: int32(codes.InvalidArgument), + Message: "invalid argument", + Details: []*anypb.Any{ + mustAny(t, + &errdetails.LocalizedMessage{ + Locale: "ja-JP", + Message: "エラー", + }, + ), + mustAny(t, + &errdetails.DebugInfo{ + Detail: "debug", + }, + ), }, - ).Err()), + }).Err()), }, }, expectBuildError: true, + expectError: `.status.details[0].'google.rpc.LocalizedMessage'.{{locale: failed to execute template: failed to parse "{{locale": col 9: expected '}}', found 'EOF'`, }, - "wrong status details: value is wrong": { + "wrong status details: key not found": { expect: &Expect{ Status: ExpectStatus{ + Code: "InvalidArgument", Details: []map[string]yaml.MapSlice{ { - "google.rpc.DebugInfo": yaml.MapSlice{ + "google.rpc.LocalizedMessage": yaml.MapSlice{ yaml.MapItem{ - Key: "detail", - Value: "unknown", + Key: "Loc", + Value: "ja-JP", }, }, }, @@ -619,19 +656,68 @@ func TestExpect_Build(t *testing.T) { v: response{ rvalues: []reflect.Value{ reflect.Zero(reflect.TypeOf(&test.EchoResponse{})), - reflect.ValueOf(mustWithDetails( - status.New(codes.InvalidArgument, "invalid argument"), - &errdetails.LocalizedMessage{ - Locale: "ja-JP", - Message: "エラー", + reflect.ValueOf(status.FromProto(&spb.Status{ + Code: int32(codes.InvalidArgument), + Message: "invalid argument", + Details: []*anypb.Any{ + mustAny(t, + &errdetails.LocalizedMessage{ + Locale: "ja-JP", + Message: "エラー", + }, + ), + mustAny(t, + &errdetails.DebugInfo{ + Detail: "debug", + }, + ), }, - &errdetails.DebugInfo{ - Detail: "debug", + }).Err()), + }, + }, + expectAssertError: true, + expectError: `.status.details[0].'google.rpc.LocalizedMessage': ".Loc" not found`, + }, + "wrong status details: value is wrong": { + expect: &Expect{ + Status: ExpectStatus{ + Code: "InvalidArgument", + Details: []map[string]yaml.MapSlice{ + { + "google.rpc.LocalizedMessage": yaml.MapSlice{ + yaml.MapItem{ + Key: "Locale", + Value: "en-US", + }, + }, }, - ).Err()), + }, + }, + }, + v: response{ + rvalues: []reflect.Value{ + reflect.Zero(reflect.TypeOf(&test.EchoResponse{})), + reflect.ValueOf(status.FromProto(&spb.Status{ + Code: int32(codes.InvalidArgument), + Message: "invalid argument", + Details: []*anypb.Any{ + mustAny(t, + &errdetails.LocalizedMessage{ + Locale: "ja-JP", + Message: "エラー", + }, + ), + mustAny(t, + &errdetails.DebugInfo{ + Detail: "debug", + }, + ), + }, + }).Err()), }, }, expectAssertError: true, + expectError: `.status.details[0].'google.rpc.LocalizedMessage'.Locale: expected en-US but got ja-JP`, }, } for name, test := range tests { @@ -639,8 +725,15 @@ func TestExpect_Build(t *testing.T) { t.Run(name, func(t *testing.T) { ctx := context.FromT(t) assertion, err := test.expect.Build(ctx) - if test.expectBuildError && err == nil { - t.Fatal("succeeded building assertion") + if test.expectBuildError { + if err == nil { + t.Fatal("succeeded building assertion") + } + if test.expectError != "" { + if got, expect := err.Error(), test.expectError; got != expect { + t.Fatalf("expect %q but got %q", expect, got) + } + } } if !test.expectBuildError && err != nil { t.Fatalf("failed to build assertion: %s", err) @@ -650,11 +743,18 @@ func TestExpect_Build(t *testing.T) { } err = assertion.Assert(test.v) - if test.expectAssertError && err == nil { - t.Errorf("no assertion error") + if test.expectAssertError { + if err == nil { + t.Fatal("no assertion error") + } + if test.expectError != "" { + if got, expect := err.Error(), test.expectError; got != expect { + t.Fatalf("\nexpect: %s\ngot: %s", expect, got) + } + } } if !test.expectAssertError && err != nil { - t.Errorf("got assertion error: %s", err) + t.Fatalf("got assertion error: %s", err) } }) } @@ -691,10 +791,11 @@ func TestExpect_Build(t *testing.T) { }) } -func mustWithDetails(s *status.Status, details ...proto.Message) *status.Status { - ss, err := s.WithDetails(details...) +func mustAny(t *testing.T, m proto.Message) *anypb.Any { + t.Helper() + a, err := anypb.New(m) if err != nil { - panic(err) + t.Fatal(err) } - return ss + return a } diff --git a/protocol/grpc/request.go b/protocol/grpc/request.go index 3643645a..4c1ddc2c 100644 --- a/protocol/grpc/request.go +++ b/protocol/grpc/request.go @@ -7,11 +7,12 @@ import ( "strings" "github.com/goccy/go-yaml" - "github.com/golang/protobuf/jsonpb" //nolint:staticcheck - "github.com/golang/protobuf/proto" //nolint:staticcheck - "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" "github.com/zoncoen/scenarigo/context" "github.com/zoncoen/scenarigo/errors" @@ -30,12 +31,19 @@ type Request struct { } type response struct { + Status responseStatus `yaml:"status,omitempty"` Header metadata.MD `yaml:"header,omitempty"` Trailer metadata.MD `yaml:"trailer,omitempty"` Message interface{} `yaml:"message,omitempty"` rvalues []reflect.Value `yaml:"-"` } +type responseStatus struct { + Code string `yaml:"code,omitempty"` + Message string `yaml:"message,omitempty"` + Details yaml.MapSlice `yaml:"details,omitempty"` +} + const ( indentNum = 2 ) @@ -185,12 +193,47 @@ func invoke(ctx *context.Context, method reflect.Value, r *Request) (*context.Co rvalues := method.Call(in) message := rvalues[0].Interface() + var err error + if rvalues[1].IsValid() && rvalues[1].CanInterface() { + e, ok := rvalues[1].Interface().(error) + if ok { + err = e + } + } resp := response{ + Status: responseStatus{ + Code: codes.OK.String(), + Message: "", + Details: nil, + }, Header: header, Trailer: trailer, Message: message, rvalues: rvalues, } + if err != nil { + if sts, ok := status.FromError(err); ok { + resp.Status.Code = sts.Code().String() + resp.Status.Message = sts.Message() + details := sts.Details() + if l := len(details); l > 0 { + m := make(yaml.MapSlice, l) + for i, d := range details { + item := yaml.MapItem{ + Key: "", + Value: d, + } + if msg, ok := d.(proto.Message); ok { + item.Key = string(proto.MessageName(msg)) + } else { + item.Key = fmt.Sprintf("%T (not proto.Message)", d) + } + m[i] = item + } + resp.Status.Details = m + } + } + } ctx = ctx.WithResponse(message) if b, err := yaml.Marshal(resp); err == nil { ctx.Reporter().Logf("response:\n%s", r.addIndent(string(b), indentNum)) @@ -206,14 +249,16 @@ func buildRequestMsg(ctx *context.Context, req interface{}, src interface{}) err if err != nil { return err } + if x == nil { + return nil + } var buf bytes.Buffer if err := yaml.NewEncoder(&buf, yaml.JSON()).Encode(x); err != nil { return err } message, ok := req.(proto.Message) if ok { - r := bytes.NewReader(buf.Bytes()) - if err := jsonpb.Unmarshal(r, message); err != nil { + if err := protojson.Unmarshal(buf.Bytes(), message); err != nil { return err } } diff --git a/protocol/grpc/request_test.go b/protocol/grpc/request_test.go index f7ba39b6..860be87f 100644 --- a/protocol/grpc/request_test.go +++ b/protocol/grpc/request_test.go @@ -9,28 +9,31 @@ import ( "github.com/goccy/go-yaml" "github.com/golang/mock/gomock" - "github.com/golang/protobuf/proto" //nolint:staticcheck "github.com/google/go-cmp/cmp" "github.com/zoncoen/scenarigo/context" "github.com/zoncoen/scenarigo/internal/mockutil" "github.com/zoncoen/scenarigo/internal/testutil" "github.com/zoncoen/scenarigo/reporter" - "github.com/zoncoen/scenarigo/testdata/gen/pb/test" + testpb "github.com/zoncoen/scenarigo/testdata/gen/pb/test" + "google.golang.org/genproto/googleapis/rpc/errdetails" + spb "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/anypb" ) func TestRequest_Invoke(t *testing.T) { t.Run("success", func(t *testing.T) { t.Run("Echo returns no error", func(t *testing.T) { - req := &test.EchoRequest{MessageId: "1", MessageBody: "hello"} - resp := &test.EchoResponse{MessageId: "1", MessageBody: "hello"} + req := &testpb.EchoRequest{MessageId: "1", MessageBody: "hello"} + resp := &testpb.EchoResponse{MessageId: "1", MessageBody: "hello"} ctrl := gomock.NewController(t) defer ctrl.Finish() - client := test.NewMockTestClient(ctrl) + client := testpb.NewMockTestClient(ctrl) client.EXPECT().Echo(gomock.Any(), mockutil.ProtoMessage(req), gomock.Any()).Return(resp, nil) r := &Request{ @@ -72,11 +75,11 @@ func TestRequest_Invoke(t *testing.T) { } }) t.Run("Echo returns error", func(t *testing.T) { - req := &test.EchoRequest{MessageId: "1", MessageBody: "hello"} + req := &testpb.EchoRequest{MessageId: "1", MessageBody: "hello"} ctrl := gomock.NewController(t) defer ctrl.Finish() - client := test.NewMockTestClient(ctrl) + client := testpb.NewMockTestClient(ctrl) client.EXPECT().Echo(gomock.Any(), mockutil.ProtoMessage(req), gomock.Any()).Return(nil, status.New(codes.Unauthenticated, "unauthenticated").Err()) r := &Request{ @@ -138,7 +141,7 @@ func TestRequest_Invoke(t *testing.T) { }, "method not found": { vars: map[string]interface{}{ - "client": test.NewTestClient(nil), + "client": testpb.NewTestClient(nil), }, client: "{{vars.client}}", method: "NotFound", @@ -146,7 +149,7 @@ func TestRequest_Invoke(t *testing.T) { }, "invalid metadata": { vars: map[string]interface{}{ - "client": test.NewTestClient(nil), + "client": testpb.NewTestClient(nil), }, method: "Echo", client: "{{vars.client}}", @@ -180,39 +183,54 @@ func TestRequest_Invoke(t *testing.T) { } func TestRequest_Invoke_Log(t *testing.T) { - req := &test.EchoRequest{MessageId: "1", MessageBody: "hello"} - resp := &test.EchoResponse{MessageId: "1", MessageBody: "hello"} + req := &testpb.EchoRequest{MessageId: "1", MessageBody: "hello"} + resp := &testpb.EchoResponse{MessageId: "1", MessageBody: "hello"} - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := test.NewMockTestClient(ctrl) - client.EXPECT().Echo(gomock.Any(), mockutil.ProtoMessage(req), gomock.Any()).Return(resp, nil) - - r := &Request{ - Client: "{{vars.client}}", - Method: "Echo", - Metadata: map[string]string{ - "version": "1.0.0", - }, - Message: yaml.MapSlice{ - yaml.MapItem{Key: "messageId", Value: "1"}, - yaml.MapItem{Key: "messageBody", Value: "hello"}, + tests := map[string]struct { + err error + expect string + }{ + "success": { + expect: ` +=== RUN test.yaml +--- PASS: test.yaml (0.00s) + request: + method: Echo + metadata: + version: + - 1.0.0 + message: + messageId: "1" + messageBody: hello + response: + status: + code: OK + message: + messageId: "1" + messageBody: hello +PASS +ok test.yaml 0.000s +`, }, - } - - var b bytes.Buffer - reporter.Run(func(rptr reporter.Reporter) { - rptr.Run("test.yaml", func(rptr reporter.Reporter) { - ctx := context.New(rptr).WithVars(map[string]interface{}{ - "client": client, - }) - if _, _, err := r.Invoke(ctx); err != nil { - t.Fatalf("unexpected error: %s", err) - } - }) - }, reporter.WithWriter(&b), reporter.WithVerboseLog()) - - expect := strings.TrimPrefix(` + "failure": { + err: status.FromProto(&spb.Status{ + Code: int32(codes.InvalidArgument), + Message: "invalid argument", + Details: []*anypb.Any{ + mustAny(t, + &errdetails.LocalizedMessage{ + Locale: "ja-JP", + Message: "エラー", + }, + ), + mustAny(t, + &errdetails.DebugInfo{ + Detail: "debug", + }, + ), + }, + }).Err(), + expect: ` === RUN test.yaml --- PASS: test.yaml (0.00s) request: @@ -224,20 +242,66 @@ func TestRequest_Invoke_Log(t *testing.T) { messageId: "1" messageBody: hello response: + status: + code: InvalidArgument + message: invalid argument + details: + google.rpc.LocalizedMessage: + locale: ja-JP + message: エラー + google.rpc.DebugInfo: + detail: debug message: messageId: "1" messageBody: hello PASS ok test.yaml 0.000s -`, "\n") - if diff := cmp.Diff(expect, testutil.ResetDuration(b.String())); diff != "" { - t.Errorf("differs (-want +got):\n%s", diff) +`, + }, + } + for name, test := range tests { + test := test + t.Run(name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := testpb.NewMockTestClient(ctrl) + client.EXPECT().Echo(gomock.Any(), mockutil.ProtoMessage(req), gomock.Any()).Return(resp, test.err) + + r := &Request{ + Client: "{{vars.client}}", + Method: "Echo", + Metadata: map[string]string{ + "version": "1.0.0", + }, + Message: yaml.MapSlice{ + yaml.MapItem{Key: "messageId", Value: "1"}, + yaml.MapItem{Key: "messageBody", Value: "hello"}, + }, + } + + var b bytes.Buffer + reporter.Run(func(rptr reporter.Reporter) { + rptr.Run("test.yaml", func(rptr reporter.Reporter) { + ctx := context.New(rptr).WithVars(map[string]interface{}{ + "client": client, + }) + if _, _, err := r.Invoke(ctx); err != nil { + t.Fatalf("unexpected error: %s", err) + } + }) + }, reporter.WithWriter(&b), reporter.WithVerboseLog()) + + expect := strings.TrimPrefix(test.expect, "\n") + if diff := cmp.Diff(expect, testutil.ResetDuration(b.String())); diff != "" { + t.Errorf("differs (-want +got):\n%s", diff) + } + }) } } func TestValidateMethod(t *testing.T) { t.Run("valid", func(t *testing.T) { - method := reflect.ValueOf(test.NewTestClient(nil)).MethodByName("Echo") + method := reflect.ValueOf(testpb.NewTestClient(nil)).MethodByName("Echo") if err := validateMethod(method); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -305,11 +369,11 @@ func TestBuildRequestBody(t *testing.T) { tests := map[string]struct { vars interface{} src interface{} - expect *test.EchoRequest + expect *testpb.EchoRequest error bool }{ "empty": { - expect: &test.EchoRequest{}, + expect: &testpb.EchoRequest{}, }, "set fields": { src: yaml.MapSlice{ @@ -322,7 +386,7 @@ func TestBuildRequestBody(t *testing.T) { Value: "hello", }, }, - expect: &test.EchoRequest{ + expect: &testpb.EchoRequest{ MessageId: "1", MessageBody: "hello", }, @@ -337,7 +401,7 @@ func TestBuildRequestBody(t *testing.T) { Value: "{{vars.body}}", }, }, - expect: &test.EchoRequest{ + expect: &testpb.EchoRequest{ MessageBody: "hello", }, }, @@ -357,7 +421,7 @@ func TestBuildRequestBody(t *testing.T) { if tc.vars != nil { ctx = ctx.WithVars(tc.vars) } - var req test.EchoRequest + var req testpb.EchoRequest err := buildRequestMsg(ctx, &req, tc.src) if err != nil { if !tc.error { diff --git a/protocol/grpc/type.go b/protocol/grpc/type.go index e5be2069..1e728650 100644 --- a/protocol/grpc/type.go +++ b/protocol/grpc/type.go @@ -4,8 +4,8 @@ import ( "context" "reflect" - "github.com/golang/protobuf/proto" //nolint:staticcheck "google.golang.org/grpc" + "google.golang.org/protobuf/proto" ) var (