This commit is contained in:
2026-10-11 16:00:18 +07:00
parent 504b49f7cc
commit 030ca56900
77 changed files with 793 additions and 638 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ type GameResults struct {
func buildTeamResults(
questions []*storytelling.Question,
teams []*game_model.Team,
answerByTeamAndCode map[int]map[string]AnswerScore,
answerByTeamAndCode map[string]map[string]AnswerScore,
) []TeamResult {
res := make([]TeamResult, 0, len(teams))
for _, team := range teams {
@@ -2,6 +2,7 @@ package answer_service
import (
"evening_detective_server/internal/services/game_model"
"fmt"
"testing"
"evening_detective_server/internal/modules/storytelling"
@@ -11,7 +12,7 @@ func intPtr(v int) *int { return &v }
func teamResult(id, actions, total int) TeamResult {
return TeamResult{
Team: &game_model.Team{ID: id, ActionsCount: actions},
Team: &game_model.Team{ID: fmt.Sprintf("019a1234-5678-7abc-8def-%012x", id), ActionsCount: actions},
TotalScore: total,
}
}
@@ -25,11 +26,11 @@ func TestRankTeamsSortsByTotalThenActions(t *testing.T) {
}
got := rankTeams(rows)
wantIDs := []int{2, 1, 3, 4}
wantIDs := []string{"019a1234-5678-7abc-8def-000000000002", "019a1234-5678-7abc-8def-000000000001", "019a1234-5678-7abc-8def-000000000003", "019a1234-5678-7abc-8def-000000000004"}
wantPlaces := []int{1, 2, 3, 4}
for i, id := range wantIDs {
if got[i].Team.ID != id {
t.Fatalf("rankTeams[%d].Team.ID = %d, want %d (порядок: total desc, actions asc)", i, got[i].Team.ID, id)
t.Fatalf("rankTeams[%d].Team.ID = %s, want %s (порядок: total desc, actions asc)", i, got[i].Team.ID, id)
}
if got[i].Place != wantPlaces[i] {
t.Fatalf("rankTeams[%d].Place = %d, want %d", i, got[i].Place, wantPlaces[i])
@@ -48,10 +49,10 @@ func TestRankTeamsSharesPlaceOnEqualTotalAndActions(t *testing.T) {
}
got := rankTeams(rows)
wantIDs := []int{2, 5, 7}
wantIDs := []string{"019a1234-5678-7abc-8def-000000000002", "019a1234-5678-7abc-8def-000000000005", "019a1234-5678-7abc-8def-000000000007"}
for i, id := range wantIDs {
if got[i].Team.ID != id {
t.Fatalf("rankTeams[%d].Team.ID = %d, want %d (порядок показа внутри группы — id asc)", i, got[i].Team.ID, id)
t.Fatalf("rankTeams[%d].Team.ID = %s, want %s (порядок показа внутри группы — id asc)", i, got[i].Team.ID, id)
}
if got[i].Place != 1 {
t.Fatalf("rankTeams[%d].Place = %d, want 1 (одно место у равных по сумме и ходам)", i, got[i].Place)
@@ -68,11 +69,11 @@ func TestRankTeamsSkipsPlaceAfterSharedTie(t *testing.T) {
}
got := rankTeams(rows)
wantIDs := []int{1, 2, 3, 4}
wantIDs := []string{"019a1234-5678-7abc-8def-000000000001", "019a1234-5678-7abc-8def-000000000002", "019a1234-5678-7abc-8def-000000000003", "019a1234-5678-7abc-8def-000000000004"}
wantPlaces := []int{1, 1, 3, 4}
for i := range wantIDs {
if got[i].Team.ID != wantIDs[i] {
t.Fatalf("rankTeams[%d].Team.ID = %d, want %d", i, got[i].Team.ID, wantIDs[i])
t.Fatalf("rankTeams[%d].Team.ID = %s, want %s", i, got[i].Team.ID, wantIDs[i])
}
if got[i].Place != wantPlaces[i] {
t.Fatalf("rankTeams[%d].Place = %d, want %d (места 1,1,3,4)", i, got[i].Place, wantPlaces[i])
@@ -90,8 +91,8 @@ func TestRankTeamsDoesNotMutateInput(t *testing.T) {
if rows[0].Place != 0 || rows[1].Place != 0 {
t.Fatalf("rankTeams изменил входной срез: places = %d, %d; want 0, 0", rows[0].Place, rows[1].Place)
}
if rows[0].Team.ID != 1 || rows[1].Team.ID != 2 {
t.Fatalf("rankTeams изменил порядок входного среза: ids = %d, %d; want 1, 2", rows[0].Team.ID, rows[1].Team.ID)
if rows[0].Team.ID != "019a1234-5678-7abc-8def-000000000001" || rows[1].Team.ID != "019a1234-5678-7abc-8def-000000000002" {
t.Fatalf("rankTeams изменил порядок входного среза: ids = %s, %s; want 1, 2", rows[0].Team.ID, rows[1].Team.ID)
}
}
@@ -102,15 +103,15 @@ func TestBuildTeamResultsScenarioOrderAndNilScores(t *testing.T) {
{Code: "q3", Text: "Вопрос 3"},
}
teams := []*game_model.Team{
{ID: 1, Name: "А", ActionsCount: 4},
{ID: 2, Name: "Б", ActionsCount: 2},
{ID: "019a1234-5678-7abc-8def-000000000001", Name: "А", ActionsCount: 4},
{ID: "019a1234-5678-7abc-8def-000000000002", Name: "Б", ActionsCount: 2},
}
answerByTeamAndCode := map[int]map[string]AnswerScore{
1: {
answerByTeamAndCode := map[string]map[string]AnswerScore{
"019a1234-5678-7abc-8def-000000000001": {
"q1": {QuestionCode: "q1", Text: "ответ 1", Score: intPtr(5)},
"q3": {QuestionCode: "q3", Text: "ответ 3", Score: intPtr(-1)},
},
2: {
"019a1234-5678-7abc-8def-000000000002": {
"q2": {QuestionCode: "q2", Text: "ответ 2"},
},
}
@@ -122,8 +123,8 @@ func TestBuildTeamResultsScenarioOrderAndNilScores(t *testing.T) {
}
first := got[0]
if first.Team.ID != 1 || first.TotalScore != 4 {
t.Fatalf("команда 1: Team.ID = %d, TotalScore = %d; want ID 1, Total 4", first.Team.ID, first.TotalScore)
if first.Team.ID != "019a1234-5678-7abc-8def-000000000001" || first.TotalScore != 4 {
t.Fatalf("команда 1: Team.ID = %s, TotalScore = %d; want ID 1, Total 4", first.Team.ID, first.TotalScore)
}
if len(first.Answers) != 3 {
t.Fatalf("команда 1: len(Answers) = %d, want 3 (порядок сценария)", len(first.Answers))
@@ -138,8 +139,8 @@ func TestBuildTeamResultsScenarioOrderAndNilScores(t *testing.T) {
}
second := got[1]
if second.Team.ID != 2 || second.TotalScore != 0 {
t.Fatalf("команда 2: Team.ID = %d, TotalScore = %d; want ID 2, Total 0", second.Team.ID, second.TotalScore)
if second.Team.ID != "019a1234-5678-7abc-8def-000000000002" || second.TotalScore != 0 {
t.Fatalf("команда 2: Team.ID = %s, TotalScore = %d; want ID 2, Total 0", second.Team.ID, second.TotalScore)
}
if second.Answers[1].Score != nil {
t.Fatalf("команда 2: неоценённый q2: Score = %v, want nil", second.Answers[1].Score)
+8 -8
View File
@@ -49,7 +49,7 @@ var (
ErrOfflineAnswers = errors.New("в offline-игре ответы через сайт недоступны")
)
func (s *AnswerService) getTeamAndGame(ctx context.Context, teamID int, password string) (*repos.Team, *repos.Game, error) {
func (s *AnswerService) getTeamAndGame(ctx context.Context, teamID string, password string) (*repos.Team, *repos.Game, error) {
team, err := s.teamsRepo.GetTeamByID(ctx, teamID)
if err != nil {
if errors.Is(err, teams_repo.ErrTeamNotFound) {
@@ -82,7 +82,7 @@ func (s *AnswerService) getScenarioQuestionsByCode(ctx context.Context, scenario
return scenario.Story.Questions, byCode, nil
}
func (s *AnswerService) GetTeamAnswers(ctx context.Context, teamID int, password string) ([]Answer, error) {
func (s *AnswerService) GetTeamAnswers(ctx context.Context, teamID string, password string) ([]Answer, error) {
_, game, err := s.getTeamAndGame(ctx, teamID, password)
if err != nil {
return nil, err
@@ -111,7 +111,7 @@ func (s *AnswerService) GetTeamAnswers(ctx context.Context, teamID int, password
return answers, nil
}
func (s *AnswerService) SubmitTeamAnswers(ctx context.Context, teamID int, password string, answers []Answer) error {
func (s *AnswerService) SubmitTeamAnswers(ctx context.Context, teamID string, password string, answers []Answer) error {
_, game, err := s.getTeamAndGame(ctx, teamID, password)
if err != nil {
return err
@@ -157,7 +157,7 @@ func (s *AnswerService) GetGameAnswers(ctx context.Context, gameID int) (*GameAn
if err != nil {
return nil, err
}
teamIDs := make([]int, 0, len(teams))
teamIDs := make([]string, 0, len(teams))
for _, team := range teams {
teamIDs = append(teamIDs, team.ID)
}
@@ -169,7 +169,7 @@ func (s *AnswerService) GetGameAnswers(ctx context.Context, gameID int) (*GameAn
if err != nil {
return nil, err
}
answerByTeamAndCode := make(map[int]map[string]AnswerScore, len(teams))
answerByTeamAndCode := make(map[string]map[string]AnswerScore, len(teams))
for _, row := range rows {
if _, ok := byCode[row.QuestionCode]; !ok {
continue
@@ -208,7 +208,7 @@ func (s *AnswerService) GetGameAnswers(ctx context.Context, gameID int) (*GameAn
}, nil
}
func (s *AnswerService) SetTeamAnswerScore(ctx context.Context, teamID int, questionCode string, score *int) error {
func (s *AnswerService) SetTeamAnswerScore(ctx context.Context, teamID string, questionCode string, score *int) error {
team, err := s.teamsRepo.GetTeamByID(ctx, teamID)
if err != nil {
return mapAnswerError(err)
@@ -243,7 +243,7 @@ func (s *AnswerService) GetGameResults(ctx context.Context, gameID int) (*GameRe
if err != nil {
return nil, err
}
teamIDs := make([]int, 0, len(teams))
teamIDs := make([]string, 0, len(teams))
for _, team := range teams {
teamIDs = append(teamIDs, team.ID)
}
@@ -255,7 +255,7 @@ func (s *AnswerService) GetGameResults(ctx context.Context, gameID int) (*GameRe
if err != nil {
return nil, err
}
answerByTeamAndCode := make(map[int]map[string]AnswerScore, len(teams))
answerByTeamAndCode := make(map[string]map[string]AnswerScore, len(teams))
for _, row := range rows {
if _, ok := byCode[row.QuestionCode]; !ok {
continue
@@ -32,10 +32,10 @@ func TestAnswerAccessPostgres(t *testing.T) {
CREATE TEMP TABLE game_exports(id TEXT, game_id INT, state TEXT, package_id TEXT, size BIGINT, sha256 TEXT, failure TEXT, created_at TIMESTAMPTZ);
CREATE TEMP TABLE games (id INT, name TEXT, description TEXT, location TEXT, start_at TIMESTAMP,
format TEXT, status TEXT, scenario_id INT, started_at TIMESTAMP, ended_at TIMESTAMP);
CREATE TEMP TABLE teams (id INT, name TEXT, captain_email TEXT, participants_count INT,
CREATE TEMP TABLE teams (id UUID, name TEXT, captain_email TEXT, participants_count INT,
status TEXT, password TEXT, game_id INT);
INSERT INTO games VALUES (1, 'Game', '', NULL, NOW(), 'online', 'finish', 1, NULL, NULL);
INSERT INTO teams VALUES (1, 'Team', '', 1, 'register', 'secret', 1);
INSERT INTO teams VALUES ('019a1234-5678-7abc-8def-000000000001', 'Team', '', 1, 'register', 'secret', 1);
ALTER TABLE teams ADD COLUMN participation_status TEXT DEFAULT 'pending', ADD COLUMN participation_source TEXT DEFAULT '',
ADD COLUMN participation_email_error TEXT DEFAULT '', ADD COLUMN participation_sent_at TIMESTAMPTZ;`)
if err != nil {
@@ -51,17 +51,17 @@ func TestAnswerAccessPostgres(t *testing.T) {
if _, err := pool.Exec(ctx, "UPDATE games SET status=$1", tc.status); err != nil {
t.Fatal(err)
}
if err := service.SubmitTeamAnswers(ctx, 1, "secret", nil); !errors.Is(err, tc.want) {
if err := service.SubmitTeamAnswers(ctx, "019a1234-5678-7abc-8def-000000000001", "secret", nil); !errors.Is(err, tc.want) {
t.Fatalf("status %s: %v", tc.status, err)
}
}
if _, err := pool.Exec(ctx, "UPDATE games SET status='run',format='offline'"); err != nil {
t.Fatal(err)
}
if answers, err := service.GetTeamAnswers(ctx, 1, "secret"); err != nil || len(answers) != 0 {
if answers, err := service.GetTeamAnswers(ctx, "019a1234-5678-7abc-8def-000000000001", "secret"); err != nil || len(answers) != 0 {
t.Fatalf("offline answers: %v %v", answers, err)
}
if err := service.SubmitTeamAnswers(ctx, 1, "secret", nil); err == nil {
if err := service.SubmitTeamAnswers(ctx, "019a1234-5678-7abc-8def-000000000001", "secret", nil); err == nil {
t.Fatal("offline accepted answers")
}
if _, err := service.GetGameResults(ctx, 1); !errors.Is(err, ErrGameNotFinished) {
@@ -73,10 +73,10 @@ func TestAnswerAccessPostgres(t *testing.T) {
if _, err := pool.Exec(ctx, "UPDATE teams SET participation_status=$1", tc.participation); err != nil {
t.Fatal(err)
}
if _, err := service.GetTeamAnswers(ctx, 1, tc.password); !errors.Is(err, ErrTeamNotFound) {
if _, err := service.GetTeamAnswers(ctx, "019a1234-5678-7abc-8def-000000000001", tc.password); !errors.Is(err, ErrTeamNotFound) {
t.Fatalf("read access: %v", err)
}
if err := service.SubmitTeamAnswers(ctx, 1, tc.password, nil); !errors.Is(err, ErrTeamNotFound) {
if err := service.SubmitTeamAnswers(ctx, "019a1234-5678-7abc-8def-000000000001", tc.password, nil); !errors.Is(err, ErrTeamNotFound) {
t.Fatalf("write access: %v", err)
}
}