package grpc import ( "context" "git.sch9.ru/new_gate/ms-auth/internal/models" "git.sch9.ru/new_gate/ms-auth/internal/users" mock_users "git.sch9.ru/new_gate/ms-auth/internal/users/delivery/mock" userv1 "git.sch9.ru/new_gate/ms-auth/proto/user/v1" "github.com/google/uuid" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "golang.org/x/crypto/bcrypt" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" "net" "testing" "time" ) func startServer(t *testing.T, uc users.UseCase, addr string) { t.Helper() gserver := grpc.NewServer() NewUserHandlers(gserver, uc) ln, err := net.Listen("tcp", addr) if err != nil { panic(err) } go func() { if err = gserver.Serve(ln); err != nil { panic(err) } }() t.Cleanup(func() { gserver.Stop() }) } func buildClient(t *testing.T, addr string) userv1.UserServiceClient { t.Helper() conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) require.NoError(t, err) return userv1.NewUserServiceClient(conn) } func TestUserHandlers_Login(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62999" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid login", func(t *testing.T) { password := "password" hpwd, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) require.NoError(t, err) user := &models.User{ Id: 1, Username: "username", HashedPassword: string(hpwd), Role: models.RoleAdmin, } sid := uuid.NewString() uc.EXPECT().ReadUserByUsername(gomock.Any(), user.Username).Return(user, nil) uc.EXPECT().CreateSession(gomock.Any(), user.Id, user.Role).Return(sid, nil) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) var header metadata.MD _, err = client.Login(ctx, &userv1.LoginRequest{ Username: user.Username, Password: password, }, grpc.Header(&header)) require.NoError(t, err) require.Equal(t, sid, header.Get(SessionHeaderName)[0]) }) t.Run("invalid login (wrong password)", func(t *testing.T) { password := "password" hpwd, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) require.NoError(t, err) user := &models.User{ Id: 1, Username: "username", HashedPassword: string(hpwd), Role: models.RoleAdmin, } uc.EXPECT().ReadUserByUsername(gomock.Any(), user.Username).Return(user, nil) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) _, err = client.Login(ctx, &userv1.LoginRequest{ Username: user.Username, Password: "wrongpassword", }) s, ok := status.FromError(err) require.True(t, ok) require.Equal(t, codes.NotFound, s.Code()) }) } func TestUserHandlers_Refresh(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62998" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid refresh", func(t *testing.T) { sid := uuid.NewString() uc.EXPECT().UpdateSession(gomock.Any(), sid).Return(nil) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) ctx = metadata.AppendToOutgoingContext(ctx, SessionHeaderName, sid) _, err := client.Refresh(ctx, &emptypb.Empty{}) require.NoError(t, err) }) t.Run("invalid refresh (no session id in context)", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) _, err := client.Refresh(ctx, &emptypb.Empty{}) s, ok := status.FromError(err) require.True(t, ok) require.Equal(t, codes.Unauthenticated, s.Code()) }) } func TestUserHandlers_Logout(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62997" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid logout", func(t *testing.T) { sid := uuid.NewString() uc.EXPECT().DeleteSession(gomock.Any(), sid).Return(nil) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) ctx = metadata.AppendToOutgoingContext(ctx, SessionHeaderName, sid) _, err := client.Logout(ctx, &emptypb.Empty{}) require.NoError(t, err) }) t.Run("invalid logout (no session id in context)", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) _, err := client.Logout(ctx, &emptypb.Empty{}) s, ok := status.FromError(err) require.True(t, ok) require.Equal(t, codes.Unauthenticated, s.Code()) }) } func TestUserHandlers_CompleteLogout(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62996" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid complete logout", func(t *testing.T) { sid := uuid.NewString() ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) ctx = metadata.AppendToOutgoingContext(ctx, SessionHeaderName, sid) t.Cleanup(cancel) uc.EXPECT().ReadSession(gomock.Any(), sid).Return(&models.Session{UserId: 1}, nil) uc.EXPECT().DeleteAllSessions(gomock.Any(), int32(1)).Return(nil) _, err := client.CompleteLogout(ctx, &emptypb.Empty{}) require.NoError(t, err) }) t.Run("invalid complete logout (no session id in context)", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) _, err := client.CompleteLogout(ctx, &emptypb.Empty{}) s, ok := status.FromError(err) require.True(t, ok) require.Equal(t, codes.Unauthenticated, s.Code()) }) } func TestUserHandlers_Verify(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62995" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid verify", func(t *testing.T) { sid := uuid.NewString() ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) ctx = metadata.AppendToOutgoingContext(ctx, SessionHeaderName, sid) t.Cleanup(cancel) uc.EXPECT().Verify(gomock.Any(), sid).Return("jwt", nil) var header metadata.MD _, err := client.Verify(ctx, &emptypb.Empty{}, grpc.Header(&header)) require.NoError(t, err) require.Equal(t, header.Get(AuthUserHeaderName)[0], "jwt") }) t.Run("invalid verify (no session id in context)", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) _, err := client.Verify(ctx, &emptypb.Empty{}) s, ok := status.FromError(err) require.True(t, ok) require.Equal(t, codes.Unauthenticated, s.Code()) }) } func TestUserHandlers_CreateUser(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62994" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid create user", func(t *testing.T) { username := "username" password := "password" ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) ctx = metadata.AppendToOutgoingContext(ctx, SessionHeaderName, uuid.NewString()) t.Cleanup(cancel) uc.EXPECT().CreateUser(gomock.Any(), username, password, models.RoleParticipant).Return(int32(2), nil) _, err := client.CreateUser(ctx, &userv1.CreateUserRequest{ Username: username, Password: password, }) require.NoError(t, err) }) t.Run("invalid create user (no session id in context)", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) _, err := client.CreateUser(ctx, &userv1.CreateUserRequest{ Username: "username", Password: "password", }) s, ok := status.FromError(err) require.True(t, ok) require.Equal(t, codes.Unauthenticated, s.Code()) }) } func TestUserHandlers_GetUser(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62993" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid get user", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) uc.EXPECT().ReadUserById(gomock.Any(), int32(1)).Return(&models.User{ Id: 1, Username: "username", CreatedAt: time.Now(), ModifiedAt: time.Now(), Role: models.RoleParticipant, }, nil) _, err := client.GetUser(ctx, &userv1.GetUserRequest{ Id: 1, }) require.NoError(t, err) }) } func TestUserHandlers_UpdateUser(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62992" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid update user", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) ctx = metadata.AppendToOutgoingContext(ctx, SessionHeaderName, uuid.NewString()) t.Cleanup(cancel) uc.EXPECT().UpdateUser(gomock.Any(), int32(1), AsStringP("username"), AsRoleP(models.RoleModerator), ).Return(nil) _, err := client.UpdateUser(ctx, &userv1.UpdateUserRequest{ Id: 1, Username: "username", Role: userv1.Role_ROLE_MODERATOR, }) require.NoError(t, err) }) t.Run("invalid update user (no session id in context)", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) t.Cleanup(cancel) _, err := client.UpdateUser(ctx, &userv1.UpdateUserRequest{ Id: 1, Username: "username", Role: userv1.Role_ROLE_MODERATOR, }) s, ok := status.FromError(err) require.True(t, ok) require.Equal(t, codes.Unauthenticated, s.Code()) }) } func TestUserHandlers_DeleteUser(t *testing.T) { t.Parallel() const addr = "127.0.0.1:62991" ctrl := gomock.NewController(t) defer ctrl.Finish() uc := mock_users.NewMockUseCase(ctrl) startServer(t, uc, addr) client := buildClient(t, addr) t.Run("valid delete user", func(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second)) ctx = metadata.AppendToOutgoingContext(ctx, SessionHeaderName, uuid.NewString()) t.Cleanup(cancel) uc.EXPECT().DeleteUser(gomock.Any(), int32(1)).Return(nil) _, err := client.DeleteUser(ctx, &userv1.DeleteUserRequest{ Id: 1, }) require.NoError(t, err) }) }