This commit is contained in:
2026-10-03 01:09:06 +07:00
parent fc2586f72a
commit 3f7731411b
7 changed files with 169 additions and 64 deletions
+23
View File
@@ -0,0 +1,23 @@
package app
import (
"errors"
"evening_detective_server/internal/services/answer_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func answerErrorStatus(err error, internalMessage string) error {
switch {
case errors.Is(err, answer_service.ErrTeamNotFound), errors.Is(err, answer_service.ErrGameNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, answer_service.ErrQuestionNotFound), errors.Is(err, answer_service.ErrOfflineAnswers):
return status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, answer_service.ErrGameFinished), errors.Is(err, answer_service.ErrGameNotStarted), errors.Is(err, answer_service.ErrGameNotFinished):
return status.Error(codes.FailedPrecondition, err.Error())
default:
return status.Error(codes.Internal, internalMessage)
}
}
+3 -9
View File
@@ -17,9 +17,7 @@ func (s *server) GetTeamAnswers(ctx context.Context, req *proto.GetTeamAnswersRe
req.Password, req.Password,
) )
if err != nil { if err != nil {
return &proto.GetTeamAnswersRsp{ return nil, answerErrorStatus(err, "не удалось получить ответы команды")
Error: err.Error(),
}, nil
} }
return &proto.GetTeamAnswersRsp{ return &proto.GetTeamAnswersRsp{
@@ -35,9 +33,7 @@ func (s *server) SubmitTeamAnswers(ctx context.Context, req *proto.SubmitTeamAns
convertAnswers(req.Answers), convertAnswers(req.Answers),
) )
if err != nil { if err != nil {
return &proto.SubmitTeamAnswersRsp{ return nil, answerErrorStatus(err, "не удалось сохранить ответы команды")
Error: err.Error(),
}, nil
} }
return &proto.SubmitTeamAnswersRsp{}, nil return &proto.SubmitTeamAnswersRsp{}, nil
@@ -65,9 +61,7 @@ func (s *server) SetTeamAnswerScore(ctx context.Context, req *proto.SetTeamAnswe
score, score,
) )
if err != nil { if err != nil {
return &proto.SetTeamAnswerScoreRsp{ return nil, answerErrorStatus(err, "не удалось сохранить оценку ответа")
Error: err.Error(),
}, nil
} }
return &proto.SetTeamAnswerScoreRsp{}, nil return &proto.SetTeamAnswerScoreRsp{}, nil
+51
View File
@@ -0,0 +1,51 @@
package app
import (
"errors"
"testing"
"evening_detective_server/internal/services/answer_service"
"evening_detective_server/internal/services/game_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestGameErrorStatus(t *testing.T) {
for _, tc := range []struct {
name, message string
err error
code codes.Code
}{
{"not found", "Игра не найдена", game_service.ErrGameNotFound, codes.NotFound},
{"invalid format", "Формат игры должен быть online или offline", game_service.ErrInvalidGameFormat, codes.InvalidArgument},
{"internal", "не удалось получить игру", errors.New("private database detail"), codes.Internal},
} {
t.Run(tc.name, func(t *testing.T) {
err := gameErrorStatus(tc.err, "не удалось получить игру")
if status.Code(err) != tc.code || status.Convert(err).Message() != tc.message {
t.Fatalf("status=%v, want %v %q", err, tc.code, tc.message)
}
})
}
}
func TestAnswerErrorStatus(t *testing.T) {
for _, tc := range []struct {
name, message string
err error
code codes.Code
}{
{"team not found", "Команда не найдена", answer_service.ErrTeamNotFound, codes.NotFound},
{"question not found", "Вопрос не найден", answer_service.ErrQuestionNotFound, codes.InvalidArgument},
{"game not started", "Игра не начата, отвечать на вопросы нельзя", answer_service.ErrGameNotStarted, codes.FailedPrecondition},
{"internal", "не удалось получить ответы игры", errors.New("private database detail"), codes.Internal},
} {
t.Run(tc.name, func(t *testing.T) {
err := answerErrorStatus(tc.err, "не удалось получить ответы игры")
if status.Code(err) != tc.code || status.Convert(err).Message() != tc.message {
t.Fatalf("status=%v, want %v %q", err, tc.code, tc.message)
}
})
}
}
+22
View File
@@ -0,0 +1,22 @@
package app
import (
"errors"
"evening_detective_server/internal/services/game_service"
"evening_detective_server/internal/services/scenarios_service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func gameErrorStatus(err error, internalMessage string) error {
switch {
case errors.Is(err, game_service.ErrGameNotFound), errors.Is(err, scenarios_service.ErrScenarioNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, game_service.ErrInvalidGameFormat):
return status.Error(codes.InvalidArgument, err.Error())
default:
return status.Error(codes.Internal, internalMessage)
}
}
+12 -36
View File
@@ -40,9 +40,7 @@ func (s *server) AddGame(ctx context.Context, req *proto.AddGameReq) (*proto.Add
req.Format, req.Format,
) )
if err != nil { if err != nil {
return &proto.AddGameRsp{ return nil, gameErrorStatus(err, "не удалось создать игру")
Error: err.Error(),
}, nil
} }
return &proto.AddGameRsp{ return &proto.AddGameRsp{
@@ -64,9 +62,7 @@ func (s *server) DeleteGame(ctx context.Context, req *proto.DeleteGameReq) (*pro
int(req.ID), int(req.ID),
) )
if err != nil { if err != nil {
return &proto.DeleteGameRsp{ return nil, gameErrorStatus(err, "не удалось удалить игру")
Error: err.Error(),
}, nil
} }
return &proto.DeleteGameRsp{}, nil return &proto.DeleteGameRsp{}, nil
@@ -83,9 +79,7 @@ func (s *server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.Get
game, err := s.gameService.GetFullGame(ctx, int(req.ID)) game, err := s.gameService.GetFullGame(ctx, int(req.ID))
if err != nil { if err != nil {
return &proto.GetGameRsp{ return nil, gameErrorStatus(err, "не удалось получить игру")
Error: err.Error(),
}, nil
} }
return &proto.GetGameRsp{ return &proto.GetGameRsp{
@@ -104,9 +98,7 @@ func (s *server) GetGameAnswers(ctx context.Context, req *proto.GetGameAnswersRe
gameAnswers, err := s.answerService.GetGameAnswers(ctx, int(req.GameID)) gameAnswers, err := s.answerService.GetGameAnswers(ctx, int(req.GameID))
if err != nil { if err != nil {
return &proto.GetGameAnswersRsp{ return nil, answerErrorStatus(err, "не удалось получить ответы игры")
Error: err.Error(),
}, nil
} }
return &proto.GetGameAnswersRsp{ return &proto.GetGameAnswersRsp{
@@ -126,9 +118,7 @@ func (s *server) GetGameResults(ctx context.Context, req *proto.GetGameResultsRe
results, err := s.answerService.GetGameResults(ctx, int(req.GameID)) results, err := s.answerService.GetGameResults(ctx, int(req.GameID))
if err != nil { if err != nil {
return &proto.GetGameResultsRsp{ return nil, answerErrorStatus(err, "не удалось получить результаты игры")
Error: err.Error(),
}, nil
} }
return &proto.GetGameResultsRsp{ return &proto.GetGameResultsRsp{
@@ -148,9 +138,7 @@ func (s *server) GetGames(ctx context.Context, req *proto.GetGamesReq) (*proto.G
games, err := s.gameService.GetGames(ctx) games, err := s.gameService.GetGames(ctx)
if err != nil { if err != nil {
return &proto.GetGamesRsp{ return nil, gameErrorStatus(err, "не удалось получить игры")
Error: err.Error(),
}, nil
} }
return &proto.GetGamesRsp{ return &proto.GetGamesRsp{
@@ -182,9 +170,7 @@ func (s *server) UpdateGame(ctx context.Context, req *proto.UpdateGameReq) (*pro
req.Format, req.Format,
) )
if err != nil { if err != nil {
return &proto.UpdateGameRsp{ return nil, gameErrorStatus(err, "не удалось изменить игру")
Error: err.Error(),
}, nil
} }
return &proto.UpdateGameRsp{}, nil return &proto.UpdateGameRsp{}, nil
@@ -201,9 +187,7 @@ func (s *server) StartGame(ctx context.Context, req *proto.StartGameReq) (*proto
err := s.gameService.StartGame(ctx, int(req.ID)) err := s.gameService.StartGame(ctx, int(req.ID))
if err != nil { if err != nil {
return &proto.StartGameRsp{ return nil, gameErrorStatus(err, "не удалось запустить игру")
Error: err.Error(),
}, nil
} }
return &proto.StartGameRsp{}, nil return &proto.StartGameRsp{}, nil
@@ -220,9 +204,7 @@ func (s *server) PauseGame(ctx context.Context, req *proto.PauseGameReq) (*proto
err := s.gameService.PauseGame(ctx, int(req.ID)) err := s.gameService.PauseGame(ctx, int(req.ID))
if err != nil { if err != nil {
return &proto.PauseGameRsp{ return nil, gameErrorStatus(err, "не удалось приостановить игру")
Error: err.Error(),
}, nil
} }
return &proto.PauseGameRsp{}, nil return &proto.PauseGameRsp{}, nil
@@ -239,9 +221,7 @@ func (s *server) PlayGame(ctx context.Context, req *proto.PlayGameReq) (*proto.P
err := s.gameService.PlayGame(ctx, int(req.ID)) err := s.gameService.PlayGame(ctx, int(req.ID))
if err != nil { if err != nil {
return &proto.PlayGameRsp{ return nil, gameErrorStatus(err, "не удалось продолжить игру")
Error: err.Error(),
}, nil
} }
return &proto.PlayGameRsp{}, nil return &proto.PlayGameRsp{}, nil
@@ -258,9 +238,7 @@ func (s *server) FinishGame(ctx context.Context, req *proto.FinishGameReq) (*pro
err := s.gameService.FinishGame(ctx, int(req.ID)) err := s.gameService.FinishGame(ctx, int(req.ID))
if err != nil { if err != nil {
return &proto.FinishGameRsp{ return nil, gameErrorStatus(err, "не удалось завершить игру")
Error: err.Error(),
}, nil
} }
return &proto.FinishGameRsp{}, nil return &proto.FinishGameRsp{}, nil
@@ -277,9 +255,7 @@ func (s *server) ResetGame(ctx context.Context, req *proto.ResetGameReq) (*proto
err := s.gameService.ResetGame(ctx, int(req.ID)) err := s.gameService.ResetGame(ctx, int(req.ID))
if err != nil { if err != nil {
return &proto.ResetGameRsp{ return nil, gameErrorStatus(err, "не удалось сбросить игру")
Error: err.Error(),
}, nil
} }
return &proto.ResetGameRsp{}, nil return &proto.ResetGameRsp{}, nil
+28 -6
View File
@@ -38,6 +38,10 @@ func NewAnswerService(
} }
var ( var (
// ErrTeamNotFound — команда не найдена или пароль неверен.
ErrTeamNotFound = errors.New("Команда не найдена")
// ErrGameNotFound — игра не найдена.
ErrGameNotFound = errors.New("Игра не найдена")
// ErrGameFinished — после завершения игры ответы команд больше нельзя // ErrGameFinished — после завершения игры ответы команд больше нельзя
// изменять: организатор оценивает их в панели игры. // изменять: организатор оценивает их в панели игры.
ErrGameFinished = errors.New("Игра завершена, ответы больше нельзя изменять") ErrGameFinished = errors.New("Игра завершена, ответы больше нельзя изменять")
@@ -47,19 +51,27 @@ var (
ErrQuestionNotFound = errors.New("Вопрос не найден") ErrQuestionNotFound = errors.New("Вопрос не найден")
// ErrGameNotFinished — итоги игры доступны только после её завершения. // ErrGameNotFinished — итоги игры доступны только после её завершения.
ErrGameNotFinished = errors.New("Итоги доступны после завершения игры") ErrGameNotFinished = errors.New("Итоги доступны после завершения игры")
// ErrOfflineAnswers — ответы для offline-игры недоступны через сайт.
ErrOfflineAnswers = errors.New("В offline-игре ответы через сайт недоступны")
) )
// getTeamAndGame проверяет команду по паролю и возвращает её вместе с игрой. // getTeamAndGame проверяет команду по паролю и возвращает её вместе с игрой.
func (s *AnswerService) getTeamAndGame(ctx context.Context, teamID int, password string) (*repos.Team, *repos.Game, error) { func (s *AnswerService) getTeamAndGame(ctx context.Context, teamID int, password string) (*repos.Team, *repos.Game, error) {
team, err := s.teamsRepo.GetTeamByID(ctx, teamID) team, err := s.teamsRepo.GetTeamByID(ctx, teamID)
if err != nil { if err != nil {
if errors.Is(err, teams_repo.ErrTeamNotFound) {
return nil, nil, ErrTeamNotFound
}
return nil, nil, err return nil, nil, err
} }
if team.Password != password || team.ParticipationStatus == "cancelled" { if team.Password != password || team.ParticipationStatus == "cancelled" {
return nil, nil, teams_repo.ErrTeamNotFound return nil, nil, ErrTeamNotFound
} }
game, err := s.gamesRepo.GetGameByID(ctx, team.GameID) game, err := s.gamesRepo.GetGameByID(ctx, team.GameID)
if err != nil { if err != nil {
if errors.Is(err, games_repo.ErrGameNotFound) {
return nil, nil, ErrGameNotFound
}
return nil, nil, err return nil, nil, err
} }
return team, game, nil return team, game, nil
@@ -120,7 +132,7 @@ func (s *AnswerService) SubmitTeamAnswers(ctx context.Context, teamID int, passw
return err return err
} }
if game.Format == "offline" { if game.Format == "offline" {
return errors.New("В offline-игре ответы через сайт недоступны") return ErrOfflineAnswers
} }
switch game.Status { switch game.Status {
case "run", "pause": case "run", "pause":
@@ -157,7 +169,7 @@ func (s *AnswerService) SubmitTeamAnswers(ctx context.Context, teamID int, passw
func (s *AnswerService) GetGameAnswers(ctx context.Context, gameID int) (*GameAnswers, error) { func (s *AnswerService) GetGameAnswers(ctx context.Context, gameID int) (*GameAnswers, error) {
game, err := s.gamesRepo.GetGameByID(ctx, gameID) game, err := s.gamesRepo.GetGameByID(ctx, gameID)
if err != nil { if err != nil {
return nil, err return nil, mapAnswerError(err)
} }
questions, byCode, err := s.getScenarioQuestionsByCode(ctx, game.ScenarioID) questions, byCode, err := s.getScenarioQuestionsByCode(ctx, game.ScenarioID)
if err != nil { if err != nil {
@@ -227,11 +239,11 @@ func (s *AnswerService) GetGameAnswers(ctx context.Context, gameID int) (*GameAn
func (s *AnswerService) SetTeamAnswerScore(ctx context.Context, teamID int, questionCode string, score *int) error { func (s *AnswerService) SetTeamAnswerScore(ctx context.Context, teamID int, questionCode string, score *int) error {
team, err := s.teamsRepo.GetTeamByID(ctx, teamID) team, err := s.teamsRepo.GetTeamByID(ctx, teamID)
if err != nil { if err != nil {
return err return mapAnswerError(err)
} }
game, err := s.gamesRepo.GetGameByID(ctx, team.GameID) game, err := s.gamesRepo.GetGameByID(ctx, team.GameID)
if err != nil { if err != nil {
return err return mapAnswerError(err)
} }
_, byCode, err := s.getScenarioQuestionsByCode(ctx, game.ScenarioID) _, byCode, err := s.getScenarioQuestionsByCode(ctx, game.ScenarioID)
if err != nil { if err != nil {
@@ -251,7 +263,7 @@ func (s *AnswerService) SetTeamAnswerScore(ctx context.Context, teamID int, ques
func (s *AnswerService) GetGameResults(ctx context.Context, gameID int) (*GameResults, error) { func (s *AnswerService) GetGameResults(ctx context.Context, gameID int) (*GameResults, error) {
game, err := s.gamesRepo.GetGameByID(ctx, gameID) game, err := s.gamesRepo.GetGameByID(ctx, gameID)
if err != nil { if err != nil {
return nil, err return nil, mapAnswerError(err)
} }
if game.Status != "finish" { if game.Status != "finish" {
return nil, ErrGameNotFinished return nil, ErrGameNotFinished
@@ -301,3 +313,13 @@ func (s *AnswerService) GetGameResults(ctx context.Context, gameID int) (*GameRe
Teams: rankTeams(buildTeamResults(questions, mappedTeams, answerByTeamAndCode)), Teams: rankTeams(buildTeamResults(questions, mappedTeams, answerByTeamAndCode)),
}, nil }, nil
} }
func mapAnswerError(err error) error {
if errors.Is(err, teams_repo.ErrTeamNotFound) {
return ErrTeamNotFound
}
if errors.Is(err, games_repo.ErrGameNotFound) {
return ErrGameNotFound
}
return err
}
+30 -13
View File
@@ -23,6 +23,13 @@ type GameService struct {
applicationsRepo *applications_repo.ApplicationsRepo applicationsRepo *applications_repo.ApplicationsRepo
} }
var (
// ErrGameNotFound — игра не найдена.
ErrGameNotFound = errors.New("Игра не найдена")
// ErrInvalidGameFormat — формат игры не поддерживается.
ErrInvalidGameFormat = errors.New("Формат игры должен быть online или offline")
)
func NewGameService( func NewGameService(
gamesRepo *games_repo.GamesRepo, gamesRepo *games_repo.GamesRepo,
teamsRepo *teams_repo.TeamsRepo, teamsRepo *teams_repo.TeamsRepo,
@@ -56,7 +63,7 @@ func (s *GameService) AddGame(
format = "online" format = "online"
} }
if format != "online" && format != "offline" { if format != "online" && format != "offline" {
return 0, errors.New("Формат игры должен быть online или offline") return 0, ErrInvalidGameFormat
} }
id, err := s.gamesRepo.AddGame(ctx, name, description, location, startAt, scenarioID, format) id, err := s.gamesRepo.AddGame(ctx, name, description, location, startAt, scenarioID, format)
if err != nil { if err != nil {
@@ -68,7 +75,7 @@ func (s *GameService) AddGame(
func (s *GameService) GetGame(ctx context.Context, id int) (*game_model.Game, error) { func (s *GameService) GetGame(ctx context.Context, id int) (*game_model.Game, error) {
game, err := s.gamesRepo.GetGameByID(ctx, id) game, err := s.gamesRepo.GetGameByID(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, mapGameError(err)
} }
res := game_model.MapGame(game, s.domain) res := game_model.MapGame(game, s.domain)
return res, nil return res, nil
@@ -77,7 +84,7 @@ func (s *GameService) GetGame(ctx context.Context, id int) (*game_model.Game, er
func (s *GameService) GetGameMedia(ctx context.Context, id int) (*game_model.Game, error) { func (s *GameService) GetGameMedia(ctx context.Context, id int) (*game_model.Game, error) {
game, err := s.gamesRepo.GetGameByID(ctx, id) game, err := s.gamesRepo.GetGameByID(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, mapGameError(err)
} }
gameResult := game_model.MapGame(game, s.domain) gameResult := game_model.MapGame(game, s.domain)
gameResult.Scenario, err = s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioID) gameResult.Scenario, err = s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioID)
@@ -93,10 +100,13 @@ func (s *GameService) GetFullGame(
) (*game_model.Game, error) { ) (*game_model.Game, error) {
game, err := s.gamesRepo.GetGameByID(ctx, id) game, err := s.gamesRepo.GetGameByID(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, mapGameError(err)
} }
scenario, err := s.scenariosRepo.GetScenarioByID(ctx, game.ScenarioID) scenario, err := s.scenariosRepo.GetScenarioByID(ctx, game.ScenarioID)
if err != nil { if err != nil {
if errors.Is(err, scenarios_repo.ErrScenarioNotFound) {
return nil, scenarios_service.ErrScenarioNotFound
}
return nil, err return nil, err
} }
game.Scenario = scenario game.Scenario = scenario
@@ -135,7 +145,7 @@ func (s *GameService) DeleteGame(
ctx context.Context, ctx context.Context,
id int, id int,
) error { ) error {
return s.gamesRepo.DeleteGameByID(ctx, id) return mapGameError(s.gamesRepo.DeleteGameByID(ctx, id))
} }
func (s *GameService) GetGames( func (s *GameService) GetGames(
@@ -161,32 +171,39 @@ func (s *GameService) UpdateGame(
if format == "" { if format == "" {
game, err := s.gamesRepo.GetGameByID(ctx, id) game, err := s.gamesRepo.GetGameByID(ctx, id)
if err != nil { if err != nil {
return err return mapGameError(err)
} }
format = game.Format format = game.Format
} }
if format != "online" && format != "offline" { if format != "online" && format != "offline" {
return errors.New("Формат игры должен быть online или offline") return ErrInvalidGameFormat
} }
return s.gamesRepo.UpdateGame(ctx, id, name, description, location, startAt, scenarioID, format) return mapGameError(s.gamesRepo.UpdateGame(ctx, id, name, description, location, startAt, scenarioID, format))
} }
func (s *GameService) StartGame(ctx context.Context, id int) error { func (s *GameService) StartGame(ctx context.Context, id int) error {
return s.gamesRepo.StartGame(ctx, id) return mapGameError(s.gamesRepo.StartGame(ctx, id))
} }
func (s *GameService) PauseGame(ctx context.Context, id int) error { func (s *GameService) PauseGame(ctx context.Context, id int) error {
return s.gamesRepo.PauseGame(ctx, id) return mapGameError(s.gamesRepo.PauseGame(ctx, id))
} }
func (s *GameService) PlayGame(ctx context.Context, id int) error { func (s *GameService) PlayGame(ctx context.Context, id int) error {
return s.gamesRepo.PlayGame(ctx, id) return mapGameError(s.gamesRepo.PlayGame(ctx, id))
} }
func (s *GameService) FinishGame(ctx context.Context, id int) error { func (s *GameService) FinishGame(ctx context.Context, id int) error {
return s.gamesRepo.FinishGame(ctx, id) return mapGameError(s.gamesRepo.FinishGame(ctx, id))
} }
func (s *GameService) ResetGame(ctx context.Context, id int) error { func (s *GameService) ResetGame(ctx context.Context, id int) error {
return s.gamesRepo.ResetGame(ctx, id) return mapGameError(s.gamesRepo.ResetGame(ctx, id))
}
func mapGameError(err error) error {
if errors.Is(err, games_repo.ErrGameNotFound) {
return ErrGameNotFound
}
return err
} }