test(server): notes service

pull/1/head
sundowndev 2024-02-26 17:57:19 +04:00
parent ad530b28da
commit 0f1ad91770
5 changed files with 111 additions and 46 deletions

1
.gitignore vendored
View File

@ -7,6 +7,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
bin/*
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test

View File

@ -3,6 +3,7 @@ package main
import ( import (
"errors" "errors"
"github.com/sundowndev/grpc-api-example/server" "github.com/sundowndev/grpc-api-example/server"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"io" "io"
"net" "net"
@ -16,8 +17,8 @@ func main() {
log := grpclog.NewLoggerV2(os.Stdout, io.Discard, io.Discard) log := grpclog.NewLoggerV2(os.Stdout, io.Discard, io.Discard)
grpclog.SetLoggerV2(log) grpclog.SetLoggerV2(log)
addr := "0.0.0.0:10000" addr := "0.0.0.0:10000" // TODO: handle this with CLI flags
srv := server.NewServer() srv := server.NewServer(insecure.NewCredentials()) // TODO: Replace with your own certificate!
// Serve gRPC Server // Serve gRPC Server
log.Info("Serving gRPC on https://", addr) log.Info("Serving gRPC on https://", addr)
@ -39,4 +40,6 @@ func main() {
if err := srv.Close(); err != nil { if err := srv.Close(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Info("graceful shut down succeeded")
} }

View File

@ -14,7 +14,10 @@ type NotesService struct {
} }
func NewNotesService() *NotesService { func NewNotesService() *NotesService {
return &NotesService{} return &NotesService{
mu: &sync.RWMutex{},
notes: make([]*notesv1.Note, 0),
}
} }
func (s *NotesService) ListNotes(_ *notesv1.ListNotesRequest, srv notesv1.NotesService_ListNotesServer) error { func (s *NotesService) ListNotes(_ *notesv1.ListNotesRequest, srv notesv1.NotesService_ListNotesServer) error {
@ -32,15 +35,15 @@ func (s *NotesService) ListNotes(_ *notesv1.ListNotesRequest, srv notesv1.NotesS
} }
func (s *NotesService) AddNote(_ context.Context, req *notesv1.AddNoteRequest) (*notesv1.AddNoteResponse, error) { func (s *NotesService) AddNote(_ context.Context, req *notesv1.AddNoteRequest) (*notesv1.AddNoteResponse, error) {
n := &notesv1.Note{ note := &notesv1.Note{
Id: uuid.Must(uuid.NewV4()).String(), Id: uuid.Must(uuid.NewV4()).String(),
Title: req.Title, Title: req.Title,
Archived: false, Archived: false,
} }
s.notes = append(s.notes, n) s.notes = append(s.notes, note)
return &notesv1.AddNoteResponse{ return &notesv1.AddNoteResponse{
Note: n, Note: note,
}, nil }, nil
} }

View File

@ -2,56 +2,112 @@ package server
import ( import (
"context" "context"
"errors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
notesv1 "github.com/sundowndev/grpc-api-example/proto/notes/v1" notesv1 "github.com/sundowndev/grpc-api-example/proto/notes/v1"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
"io"
"net"
"testing" "testing"
) )
func newTestServer() (*Server, *grpc.ClientConn, error) {
srv := NewServer(insecure.NewCredentials())
buffer := 101024 * 1024
lis := bufconn.Listen(buffer)
srv.listener = lis
go srv.grpcSrv.Serve(lis)
conn, err := grpc.DialContext(context.Background(), "",
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
_ = srv.Close()
return nil, nil, err
}
return srv, conn, nil
}
func TestNotesService_ListNotes(t *testing.T) { func TestNotesService_ListNotes(t *testing.T) {
addr := "0.0.0.0:10000" testcases := []struct {
srv := NewServer() name string
go srv.Listen(addr) notes []*notesv1.Note
defer srv.Close() }{
{
conn, err := grpc.DialContext(context.Background(), addr, grpc.WithTransportCredentials(insecure.NewCredentials())) name: "test with no notes",
if err != nil { notes: []*notesv1.Note{},
t.Fatal(err) },
} {
defer conn.Close() name: "test with few notes",
notes: []*notesv1.Note{
client := notesv1.NewNotesServiceClient(conn) {Title: "test note 1", Archived: false},
res, err := client.ListNotes(context.TODO(), &notesv1.ListNotesRequest{}) {Title: "note 2", Archived: true},
if err != nil { {Title: "note 3", Archived: false},
t.Fatal(err) },
},
} }
assert.NotNil(t, res) for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
srv, conn, err := newTestServer()
if err != nil {
t.Fatal(err)
}
client := notesv1.NewNotesServiceClient(conn)
defer srv.Close()
defer conn.Close()
//err = res.RecvMsg() for _, note := range tt.notes {
//if err != nil && !errors.Is(err, io.EOF) { _, err := client.AddNote(context.Background(), &notesv1.AddNoteRequest{Title: note.Title})
// t.Fatal(err) if err != nil {
//} t.Fatal(err)
}
}
res, err := client.ListNotes(context.Background(), &notesv1.ListNotesRequest{})
if err != nil {
t.Fatal(err)
}
notes := make([]*notesv1.Note, 0)
for {
recv, err := res.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
t.Fatal(err)
}
notes = append(notes, recv.Note)
}
assert.Len(t, notes, len(tt.notes))
})
}
} }
func TestNotesService_AddNote(t *testing.T) { func TestNotesService_AddNote(t *testing.T) {
addr := "0.0.0.0:10000" srv, conn, err := newTestServer()
srv := NewServer()
go srv.Listen(addr)
defer srv.Close()
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
client := notesv1.NewNotesServiceClient(conn)
defer srv.Close()
defer conn.Close() defer conn.Close()
client := notesv1.NewNotesServiceClient(conn) res, err := client.AddNote(context.Background(), &notesv1.AddNoteRequest{Title: "test note"})
res, err := client.AddNote(context.TODO(), &notesv1.AddNoteRequest{Title: "test note"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.NotNil(t, res) assert.NotNil(t, res)
assert.Equal(t, "test note", res.Note.Title)
assert.Equal(t, false, res.Note.Archived)
} }

View File

@ -5,25 +5,23 @@ import (
"fmt" "fmt"
notesv1 "github.com/sundowndev/grpc-api-example/proto/notes/v1" notesv1 "github.com/sundowndev/grpc-api-example/proto/notes/v1"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials"
"net" "net"
) )
type Server struct { type Server struct {
listener net.Listener listener net.Listener
srv *grpc.Server grpcSrv *grpc.Server
} }
func NewServer() *Server { func NewServer(c credentials.TransportCredentials) *Server {
s := grpc.NewServer( s := grpc.NewServer(
// TODO: Replace with your own certificate! grpc.Creds(c),
grpc.Creds(insecure.NewCredentials()),
) )
notesv1.RegisterNotesServiceServer(s, NewNotesService())
srv := &Server{ srv := &Server{
srv: s, grpcSrv: s,
} }
srv.registerServices()
return srv return srv
} }
@ -31,16 +29,20 @@ func NewServer() *Server {
func (s *Server) Listen(addr string) error { func (s *Server) Listen(addr string) error {
lis, err := net.Listen("tcp", addr) lis, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return fmt.Errorf("failed to listen: %e", err) return fmt.Errorf("failed to listen: %v", err)
} }
s.listener = lis s.listener = lis
return s.srv.Serve(s.listener) return s.grpcSrv.Serve(s.listener)
} }
func (s *Server) Close() error { func (s *Server) Close() error {
s.srv.GracefulStop() s.grpcSrv.GracefulStop()
if err := s.listener.Close(); !errors.Is(err, net.ErrClosed) { if err := s.listener.Close(); !errors.Is(err, net.ErrClosed) {
return err return fmt.Errorf("error closing server: %v", err)
} }
return nil return nil
} }
func (s *Server) registerServices() {
notesv1.RegisterNotesServiceServer(s.grpcSrv, NewNotesService())
}