From c2769a233e3dd653b19418dc86a91b589296b7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Sun, 7 May 2023 07:01:56 +0200 Subject: [PATCH] add tests --- cmd/server/main.go | 96 +------------------------ go.mod | 6 +- go.sum | 11 ++- internal/greeter/server.go | 107 +++++++++++++++++++++++++++ internal/greeter/server_test.go | 123 ++++++++++++++++++++++++++++++++ 5 files changed, 247 insertions(+), 96 deletions(-) create mode 100644 internal/greeter/server.go create mode 100644 internal/greeter/server_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 0d6bfff..8c1df21 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "errors" "fmt" - "io" "log" "net" "os" @@ -14,17 +13,16 @@ import ( "syscall" "gitea.dwysokinski.me/Kichiyaki/grpc-g2a/cmd/internal" + "gitea.dwysokinski.me/Kichiyaki/grpc-g2a/internal/greeter" "gitea.dwysokinski.me/Kichiyaki/grpc-g2a/proto" grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" "go.uber.org/zap" - "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" _ "google.golang.org/grpc/encoding/gzip" - "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) @@ -101,9 +99,7 @@ func newServer(address string, logger *zap.Logger) (*server, error) { ), ) - proto.RegisterGreeterServer(srv, &greeterServer{ - logger: logger, - }) + proto.RegisterGreeterServer(srv, greeter.NewServer(logger)) return &server{ lis: lis, @@ -150,91 +146,3 @@ func createPanicHandler(logger *zap.Logger) grpc_recovery.RecoveryHandlerFunc { return status.Errorf(codes.Internal, "internal server error") } } - -type greeterServer struct { - proto.UnimplementedGreeterServer - logger *zap.Logger -} - -func (g *greeterServer) SayHello(ctx context.Context, r *proto.HelloRequest) (*proto.HelloReply, error) { - md, ok := metadata.FromIncomingContext(ctx) - if ok { - g.logger.Debug("received metadata", zap.Any("metadata", md)) - } - return &proto.HelloReply{ - Message: buildHelloMsg(r.GetName()), - }, nil -} - -func (g *greeterServer) SayHelloToJames(_ context.Context, r *proto.HelloRequest) (*proto.HelloReply, error) { - name := r.GetName() - - if name != "James" { - st, err := status. - New(codes.InvalidArgument, "invalid name"). - WithDetails(&errdetails.BadRequest{ - FieldViolations: []*errdetails.BadRequest_FieldViolation{ - { - Field: "name", - Description: "must be James", - }, - }, - }) - if err != nil { - return nil, status.New(codes.Internal, "internal server error").Err() - } - return nil, st.Err() - } - - return &proto.HelloReply{ - Message: buildHelloMsg(name), - }, nil -} - -func (g *greeterServer) SayHelloServerStream(r *proto.MultiHelloRequest, stream proto.Greeter_SayHelloServerStreamServer) error { - for _, n := range r.GetNames() { - if err := stream.Send(&proto.HelloReply{ - Message: buildHelloMsg(n), - }); err != nil { - return err - } - } - return nil -} - -func (g *greeterServer) SayHelloClientStream(stream proto.Greeter_SayHelloClientStreamServer) error { - var messages []string - for { - r, err := stream.Recv() - if errors.Is(err, io.EOF) { - return stream.SendAndClose(&proto.MultiHelloReply{ - Messages: messages, - }) - } - if err != nil { - return fmt.Errorf("stream returned error: %w", err) - } - - messages = append(messages, buildHelloMsg(r.GetName())) - } -} - -func (g *greeterServer) SayHelloBidirectionalStream(stream proto.Greeter_SayHelloBidirectionalStreamServer) error { - for { - r, err := stream.Recv() - if errors.Is(err, io.EOF) { - return nil - } - if err != nil { - return fmt.Errorf("stream returned error: %w", err) - } - - if err = stream.Send(&proto.HelloReply{Message: buildHelloMsg(r.GetName())}); err != nil { - return fmt.Errorf("couldn't send reply: %w", err) - } - } -} - -func buildHelloMsg(name string) string { - return "Hello " + name + "!" -} diff --git a/go.mod b/go.mod index 58bee69..fb6bad3 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,21 @@ go 1.20 require ( github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 + github.com/stretchr/testify v1.8.2 go.uber.org/zap v1.24.0 + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f google.golang.org/grpc v1.54.0 google.golang.org/protobuf v1.30.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3b44454..231490e 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,10 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vb github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -47,11 +49,16 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= @@ -133,11 +140,13 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/greeter/server.go b/internal/greeter/server.go new file mode 100644 index 0000000..dfe4341 --- /dev/null +++ b/internal/greeter/server.go @@ -0,0 +1,107 @@ +package greeter + +import ( + "context" + "errors" + "fmt" + "io" + + "gitea.dwysokinski.me/Kichiyaki/grpc-g2a/proto" + "go.uber.org/zap" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +type Server struct { + proto.UnimplementedGreeterServer + logger *zap.Logger +} + +func NewServer(logger *zap.Logger) *Server { + return &Server{logger: logger} +} + +func (s *Server) SayHello(ctx context.Context, r *proto.HelloRequest) (*proto.HelloReply, error) { + md, ok := metadata.FromIncomingContext(ctx) + if ok { + s.logger.Debug("received metadata", zap.Any("metadata", md)) + } + return &proto.HelloReply{ + Message: buildHelloMsg(r.GetName()), + }, nil +} + +func (s *Server) SayHelloToJames(_ context.Context, r *proto.HelloRequest) (*proto.HelloReply, error) { + name := r.GetName() + + if name != "James" { + st, err := status. + New(codes.InvalidArgument, "invalid name"). + WithDetails(&errdetails.BadRequest{ + FieldViolations: []*errdetails.BadRequest_FieldViolation{ + { + Field: "name", + Description: "must be James", + }, + }, + }) + if err != nil { + return nil, status.New(codes.Internal, "internal server error").Err() + } + return nil, st.Err() + } + + return &proto.HelloReply{ + Message: buildHelloMsg(name), + }, nil +} + +func (s *Server) SayHelloServerStream(r *proto.MultiHelloRequest, stream proto.Greeter_SayHelloServerStreamServer) error { + for _, n := range r.GetNames() { + if err := stream.Send(&proto.HelloReply{ + Message: buildHelloMsg(n), + }); err != nil { + return err + } + } + return nil +} + +func (s *Server) SayHelloClientStream(stream proto.Greeter_SayHelloClientStreamServer) error { + var messages []string + for { + r, err := stream.Recv() + if errors.Is(err, io.EOF) { + return stream.SendAndClose(&proto.MultiHelloReply{ + Messages: messages, + }) + } + if err != nil { + return fmt.Errorf("stream returned error: %w", err) + } + + messages = append(messages, buildHelloMsg(r.GetName())) + } +} + +func (s *Server) SayHelloBidirectionalStream(stream proto.Greeter_SayHelloBidirectionalStreamServer) error { + for { + r, err := stream.Recv() + if errors.Is(err, io.EOF) { + return nil + } + if err != nil { + return fmt.Errorf("stream returned error: %w", err) + } + + if err = stream.Send(&proto.HelloReply{Message: buildHelloMsg(r.GetName())}); err != nil { + return fmt.Errorf("couldn't send reply: %w", err) + } + } +} + +func buildHelloMsg(name string) string { + return "Hello " + name + "!" +} diff --git a/internal/greeter/server_test.go b/internal/greeter/server_test.go new file mode 100644 index 0000000..58f829e --- /dev/null +++ b/internal/greeter/server_test.go @@ -0,0 +1,123 @@ +package greeter_test + +import ( + "context" + "errors" + "io" + "net" + "testing" + + "gitea.dwysokinski.me/Kichiyaki/grpc-g2a/internal/greeter" + "gitea.dwysokinski.me/Kichiyaki/grpc-g2a/proto" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" +) + +func TestServer_SayHello(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + expected string + }{ + { + name: "James", + expected: "Hello James!", + }, + { + name: "Robert", + expected: "Hello Robert!", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + conn := newServer(t) + + reply, err := conn.SayHello(context.Background(), &proto.HelloRequest{Name: tt.name}) + require.NoError(t, err) + assert.Equal(t, tt.expected, reply.GetMessage()) + }) + } +} + +func TestServer_SayHelloBidirectionalStream(t *testing.T) { + t.Parallel() + + names := make([]string, 20) + for i := range names { + names[i] = uuid.NewString() + } + + client := newServer(t) + + streamClient, err := client.SayHelloBidirectionalStream(context.Background()) + require.NoError(t, err) + + for _, n := range names { + err = streamClient.Send(&proto.HelloRequest{ + Name: n, + }) + assert.NoError(t, err) + } + + assert.NoError(t, streamClient.CloseSend()) + + replies := make([]*proto.HelloReply, 0, len(names)) + for { + reply, err := streamClient.Recv() + if errors.Is(err, io.EOF) { + break + } + assert.NoError(t, err) + replies = append(replies, reply) + } + + require.Len(t, replies, len(names)) + for _, n := range names { + found := false + for _, r := range replies { + if r.GetMessage() == "Hello "+n+"!" { + found = true + break + } + } + assert.True(t, found, "name="+n) + } +} + +func newServer(t *testing.T) proto.GreeterClient { + buffer := 101024 * 1024 + lis := bufconn.Listen(buffer) + + server := grpc.NewServer() + proto.RegisterGreeterServer(server, greeter.NewServer(zap.NewNop())) + go func() { + require.NoError(t, server.Serve(lis)) + }() + t.Cleanup(func() { + server.GracefulStop() + }) + + conn, err := grpc.Dial( + "", + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return lis.Dial() + }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, conn.Close()) + }) + + return proto.NewGreeterClient(conn) +}