generated from VLADIMIR/template
updates
This commit is contained in:
@@ -131,9 +131,6 @@ func TestUploadFileSuccess(t *testing.T) {
|
||||
if rsp.FileType != "pdf" {
|
||||
t.Errorf("FileType = %q, want pdf", rsp.FileType)
|
||||
}
|
||||
if rsp.Error != "" {
|
||||
t.Errorf("Error = %q, want пусто", rsp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// mockServerTransportStream — заглушка для grpc.SetHeader в юнит-тестах.
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"evening_detective_server/docs"
|
||||
proto "evening_detective_server/proto"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// GetTerms возвращает текст Пользовательского соглашения (постоянный URL
|
||||
@@ -12,7 +14,7 @@ import (
|
||||
func (s *server) GetTerms(_ context.Context, _ *proto.GetTermsReq) (*proto.GetTermsRsp, error) {
|
||||
text, err := docs.FS.ReadFile("TERMS.md")
|
||||
if err != nil {
|
||||
return &proto.GetTermsRsp{Error: "Не удалось получить документ"}, nil
|
||||
return nil, status.Error(codes.Internal, "не удалось получить документ")
|
||||
}
|
||||
return &proto.GetTermsRsp{Text: string(text)}, nil
|
||||
}
|
||||
@@ -22,7 +24,7 @@ func (s *server) GetTerms(_ context.Context, _ *proto.GetTermsReq) (*proto.GetTe
|
||||
func (s *server) GetPrivacy(_ context.Context, _ *proto.GetPrivacyReq) (*proto.GetPrivacyRsp, error) {
|
||||
text, err := docs.FS.ReadFile("PRIVACY.md")
|
||||
if err != nil {
|
||||
return &proto.GetPrivacyRsp{Error: "Не удалось получить документ"}, nil
|
||||
return nil, status.Error(codes.Internal, "не удалось получить документ")
|
||||
}
|
||||
return &proto.GetPrivacyRsp{Text: string(text)}, nil
|
||||
}
|
||||
@@ -32,7 +34,7 @@ func (s *server) GetPrivacy(_ context.Context, _ *proto.GetPrivacyReq) (*proto.G
|
||||
func (s *server) GetConsent(_ context.Context, _ *proto.GetConsentReq) (*proto.GetConsentRsp, error) {
|
||||
text, err := docs.FS.ReadFile("CONSENT.md")
|
||||
if err != nil {
|
||||
return &proto.GetConsentRsp{Error: "Не удалось получить документ"}, nil
|
||||
return nil, status.Error(codes.Internal, "не удалось получить документ")
|
||||
}
|
||||
return &proto.GetConsentRsp{Text: string(text)}, nil
|
||||
}
|
||||
|
||||
@@ -13,39 +13,39 @@ func TestGetLegalDocs(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
call func() (text, errText string)
|
||||
call func() string
|
||||
wantPart string
|
||||
}{
|
||||
{
|
||||
name: "GetTerms отдаёт Пользовательское соглашение",
|
||||
call: func() (string, string) {
|
||||
call: func() string {
|
||||
rsp, err := s.GetTerms(context.Background(), &proto.GetTermsReq{})
|
||||
if err != nil {
|
||||
t.Fatalf("GetTerms error: %v", err)
|
||||
}
|
||||
return rsp.Text, rsp.Error
|
||||
return rsp.Text
|
||||
},
|
||||
wantPart: "СОГЛАШЕНИЕ",
|
||||
},
|
||||
{
|
||||
name: "GetPrivacy отдаёт Политику конфиденциальности",
|
||||
call: func() (string, string) {
|
||||
call: func() string {
|
||||
rsp, err := s.GetPrivacy(context.Background(), &proto.GetPrivacyReq{})
|
||||
if err != nil {
|
||||
t.Fatalf("GetPrivacy error: %v", err)
|
||||
}
|
||||
return rsp.Text, rsp.Error
|
||||
return rsp.Text
|
||||
},
|
||||
wantPart: "КОНФИДЕНЦИАЛЬНОСТИ",
|
||||
},
|
||||
{
|
||||
name: "GetConsent отдаёт текст согласия на обработку ПДн",
|
||||
call: func() (string, string) {
|
||||
call: func() string {
|
||||
rsp, err := s.GetConsent(context.Background(), &proto.GetConsentReq{})
|
||||
if err != nil {
|
||||
t.Fatalf("GetConsent error: %v", err)
|
||||
}
|
||||
return rsp.Text, rsp.Error
|
||||
return rsp.Text
|
||||
},
|
||||
wantPart: "СОГЛАСИЕ",
|
||||
},
|
||||
@@ -53,10 +53,7 @@ func TestGetLegalDocs(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
text, errText := tt.call()
|
||||
if errText != "" {
|
||||
t.Fatalf("ожидался успех, получена ошибка: %s", errText)
|
||||
}
|
||||
text := tt.call()
|
||||
if !strings.Contains(text, tt.wantPart) {
|
||||
t.Errorf("текст документа не содержит %q", tt.wantPart)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"evening_detective_server/internal/services/participation_service"
|
||||
"evening_detective_server/proto"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
@@ -23,36 +25,40 @@ func mapParticipationResponse(p *participation_service.Participation) *proto.Par
|
||||
return result
|
||||
}
|
||||
|
||||
func participationError(err error) string {
|
||||
if errors.Is(err, participation_service.ErrParticipationLink) || errors.Is(err, participation_service.ErrParticipationFinal) || errors.Is(err, participation_service.ErrParticipationClosed) {
|
||||
return err.Error()
|
||||
func participationErrorStatus(err error, internalMessage string) error {
|
||||
switch {
|
||||
case errors.Is(err, participation_service.ErrParticipationLink):
|
||||
return status.Error(codes.NotFound, err.Error())
|
||||
case errors.Is(err, participation_service.ErrInvalidAnswer):
|
||||
return status.Error(codes.InvalidArgument, err.Error())
|
||||
case errors.Is(err, participation_service.ErrParticipationFinal), errors.Is(err, participation_service.ErrParticipationClosed):
|
||||
return status.Error(codes.FailedPrecondition, err.Error())
|
||||
default:
|
||||
return status.Error(codes.Internal, internalMessage)
|
||||
}
|
||||
return "Не удалось обработать ответ. Попробуйте позже."
|
||||
}
|
||||
func (s *server) GetParticipation(ctx context.Context, req *proto.GetParticipationReq) (*proto.ParticipationRsp, error) {
|
||||
_ = grpc.SetHeader(ctx, metadata.Pairs("Cache-Control", "no-store"))
|
||||
if req == nil {
|
||||
return &proto.ParticipationRsp{Error: participationError(participation_service.ErrParticipationLink)}, nil
|
||||
return nil, participationErrorStatus(participation_service.ErrParticipationLink, "не удалось получить данные об участии")
|
||||
}
|
||||
p, err := s.participationService.GetParticipation(ctx, req.Token)
|
||||
result := mapParticipationResponse(p)
|
||||
if err != nil {
|
||||
result.Error = participationError(err)
|
||||
return nil, participationErrorStatus(err, "не удалось получить данные об участии")
|
||||
}
|
||||
return result, nil
|
||||
return mapParticipationResponse(p), nil
|
||||
}
|
||||
func (s *server) AnswerParticipation(ctx context.Context, req *proto.AnswerParticipationReq) (*proto.ParticipationRsp, error) {
|
||||
_ = grpc.SetHeader(ctx, metadata.Pairs("Cache-Control", "no-store"))
|
||||
if req == nil {
|
||||
return &proto.ParticipationRsp{Error: participationError(participation_service.ErrParticipationLink)}, nil
|
||||
return nil, participationErrorStatus(participation_service.ErrParticipationLink, "не удалось сохранить ответ")
|
||||
}
|
||||
if req.Answer != "confirmed" && req.Answer != "cancelled" {
|
||||
return &proto.ParticipationRsp{Error: participation_service.ErrInvalidAnswer.Error()}, nil
|
||||
return nil, participationErrorStatus(participation_service.ErrInvalidAnswer, "не удалось сохранить ответ")
|
||||
}
|
||||
p, err := s.participationService.AnswerParticipation(ctx, req.Token, req.Answer)
|
||||
result := mapParticipationResponse(p)
|
||||
if err != nil {
|
||||
result.Error = participationError(err)
|
||||
return nil, participationErrorStatus(err, "не удалось сохранить ответ")
|
||||
}
|
||||
return result, nil
|
||||
return mapParticipationResponse(p), nil
|
||||
}
|
||||
|
||||
@@ -6,24 +6,29 @@ import (
|
||||
"testing"
|
||||
|
||||
"evening_detective_server/internal/services/participation_service"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestParticipationError(t *testing.T) {
|
||||
func TestParticipationErrorStatus(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
err error
|
||||
code codes.Code
|
||||
want string
|
||||
}{
|
||||
{"invalid link", participation_service.ErrParticipationLink, "Ссылка недействительна"},
|
||||
{"closed", participation_service.ErrParticipationClosed, "Приём ответов закрыт"},
|
||||
{"final", participation_service.ErrParticipationFinal, "Решение уже принято и не может быть изменено"},
|
||||
{"internal", errors.New("private database detail"), "Не удалось обработать ответ. Попробуйте позже."},
|
||||
{"invalid link", participation_service.ErrParticipationLink, codes.NotFound, "Ссылка недействительна"},
|
||||
{"invalid answer", participation_service.ErrInvalidAnswer, codes.InvalidArgument, "Выберите подтверждение или отмену участия"},
|
||||
{"closed", participation_service.ErrParticipationClosed, codes.FailedPrecondition, "Приём ответов закрыт"},
|
||||
{"final", participation_service.ErrParticipationFinal, codes.FailedPrecondition, "Решение уже принято и не может быть изменено"},
|
||||
{"internal", errors.New("private database detail"), codes.Internal, "participation failed"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := participationError(tc.err); got != tc.want {
|
||||
t.Fatalf("message=%q, want %q", got, tc.want)
|
||||
got := participationErrorStatus(tc.err, "participation failed")
|
||||
if status.Code(got) != tc.code || status.Convert(got).Message() != tc.want {
|
||||
t.Fatalf("status=%v, want %s %q", got, tc.code, tc.want)
|
||||
}
|
||||
if strings.Contains(participationError(tc.err), "private database detail") {
|
||||
if tc.code == codes.Internal && strings.Contains(status.Convert(got).Message(), "private database detail") {
|
||||
t.Fatal("private error exposed")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"evening_detective_server/internal/services/participation_service"
|
||||
"evening_detective_server/internal/services/ticket_service"
|
||||
"evening_detective_server/proto"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func assertGRPCStatus(t *testing.T, err error, wantCode codes.Code, wantMessage string) {
|
||||
t.Helper()
|
||||
if status.Code(err) != wantCode {
|
||||
t.Fatalf("status code = %v, want %v", status.Code(err), wantCode)
|
||||
}
|
||||
if status.Convert(err).Message() != wantMessage {
|
||||
t.Fatalf("status message = %q, want %q", status.Convert(err).Message(), wantMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTicketNotFoundUsesGRPCStatus(t *testing.T) {
|
||||
rsp, err := (&server{}).GetTicket(context.Background(), nil)
|
||||
if rsp != nil {
|
||||
t.Fatalf("GetTicket response = %v, want nil", rsp)
|
||||
}
|
||||
assertGRPCStatus(t, err, codes.NotFound, ticket_service.ErrTicketNotFound.Error())
|
||||
}
|
||||
|
||||
func TestAcceptTicketNilRequestUsesGRPCStatus(t *testing.T) {
|
||||
rsp, err := (&server{}).AcceptTicket(ctxWithClaims("organizer"), nil)
|
||||
if rsp != nil {
|
||||
t.Fatalf("AcceptTicket response = %v, want nil", rsp)
|
||||
}
|
||||
assertGRPCStatus(t, err, codes.NotFound, ticket_service.ErrTicketNotFound.Error())
|
||||
}
|
||||
|
||||
func TestGetParticipationInvalidRequestUsesGRPCStatus(t *testing.T) {
|
||||
rsp, err := (&server{}).GetParticipation(context.Background(), nil)
|
||||
if rsp != nil {
|
||||
t.Fatalf("GetParticipation response = %v, want nil", rsp)
|
||||
}
|
||||
assertGRPCStatus(t, err, codes.NotFound, participation_service.ErrParticipationLink.Error())
|
||||
}
|
||||
|
||||
func TestAnswerParticipationInvalidAnswerUsesGRPCStatus(t *testing.T) {
|
||||
rsp, err := (&server{}).AnswerParticipation(context.Background(), &proto.AnswerParticipationReq{Answer: "unknown"})
|
||||
if rsp != nil {
|
||||
t.Fatalf("AnswerParticipation response = %v, want nil", rsp)
|
||||
}
|
||||
assertGRPCStatus(t, err, codes.InvalidArgument, participation_service.ErrInvalidAnswer.Error())
|
||||
}
|
||||
|
||||
func TestResponseMessagesDoNotExposePayloadError(t *testing.T) {
|
||||
for i := 0; i < proto.File_main_proto.Messages().Len(); i++ {
|
||||
message := proto.File_main_proto.Messages().Get(i)
|
||||
if !strings.HasSuffix(string(message.Name()), "Rsp") {
|
||||
continue
|
||||
}
|
||||
for j := 0; j < message.Fields().Len(); j++ {
|
||||
if message.Fields().Get(j).Name() == "error" {
|
||||
t.Errorf("%s still exposes payload error field", message.FullName())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,11 @@ import (
|
||||
func (s *server) GetTicket(ctx context.Context, req *proto.GetTicketReq) (*proto.GetTicketRsp, error) {
|
||||
_ = grpc.SetHeader(ctx, metadata.Pairs("Cache-Control", "no-store"))
|
||||
if req == nil {
|
||||
return &proto.GetTicketRsp{Error: ticket_service.ErrTicketNotFound.Error()}, nil
|
||||
return nil, ticketErrorStatus(ticket_service.ErrTicketNotFound, "не удалось получить билет")
|
||||
}
|
||||
page, err := s.ticketService.Get(ctx, req.Token)
|
||||
if err != nil {
|
||||
return &proto.GetTicketRsp{Error: ticket_service.ErrTicketNotFound.Error()}, nil
|
||||
return nil, ticketErrorStatus(err, "не удалось получить билет")
|
||||
}
|
||||
|
||||
result := &proto.TicketPage{
|
||||
@@ -49,7 +49,7 @@ func (s *server) AcceptTicket(ctx context.Context, req *proto.AcceptTicketReq) (
|
||||
return nil, status.Error(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
if req == nil {
|
||||
return &proto.AcceptTicketRsp{Error: ticket_service.ErrTicketNotFound.Error()}, nil
|
||||
return nil, ticketErrorStatus(ticket_service.ErrTicketNotFound, "не удалось принять билет")
|
||||
}
|
||||
page, err := s.ticketService.Accept(ctx, req.Token, claims.UserID)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user