generated from VLADIMIR/template
up
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
package game_service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"evening_detective_server/internal/modules/cleaner"
|
||||
"evening_detective_server/internal/modules/password_generator"
|
||||
"evening_detective_server/internal/modules/storytelling"
|
||||
"evening_detective_server/internal/modules/team_qr_pdf"
|
||||
"evening_detective_server/internal/repos/actions_repo"
|
||||
"evening_detective_server/internal/repos/applications_repo"
|
||||
"evening_detective_server/internal/repos/games_repo"
|
||||
"evening_detective_server/internal/repos/scenarios_repo"
|
||||
"evening_detective_server/internal/repos/team_answers_repo"
|
||||
"evening_detective_server/internal/repos/teams_repo"
|
||||
"evening_detective_server/internal/services/answer_service"
|
||||
"evening_detective_server/internal/services/game_service"
|
||||
"evening_detective_server/internal/services/gameplay_service"
|
||||
"evening_detective_server/internal/services/participation_service"
|
||||
"evening_detective_server/internal/services/scenarios_service"
|
||||
"evening_detective_server/internal/services/team_service"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func TestServicesLifecyclePostgres(t *testing.T) {
|
||||
dsn := os.Getenv("TEST_DATABASE_URL")
|
||||
if dsn == "" {
|
||||
t.Skip("set TEST_DATABASE_URL to run PostgreSQL integration check")
|
||||
}
|
||||
ctx := context.Background()
|
||||
check := func(err error) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
config, err := pgxpool.ParseConfig(dsn)
|
||||
check(err)
|
||||
config.MaxConns = 1 // Temporary tables stay on the same connection.
|
||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
||||
check(err)
|
||||
defer pool.Close()
|
||||
_, err = pool.Exec(ctx, `
|
||||
CREATE TEMP TABLE users(id INT, username TEXT);
|
||||
CREATE TEMP TABLE scenarios(id INT PRIMARY KEY, name TEXT, description TEXT, image TEXT, scenario TEXT,
|
||||
author_id INT, status TEXT DEFAULT 'draft', updated_at TIMESTAMP DEFAULT NOW(), created_at TIMESTAMP DEFAULT NOW(), published_at TIMESTAMP, is_deleted BOOLEAN DEFAULT FALSE);
|
||||
CREATE TEMP TABLE games(id SERIAL PRIMARY KEY, name TEXT, description TEXT, start_at TIMESTAMP, scenario_id INT,
|
||||
format TEXT, status TEXT DEFAULT 'draft', started_at TIMESTAMP, ended_at TIMESTAMP, updated_at TIMESTAMP DEFAULT NOW(), created_at TIMESTAMP DEFAULT NOW());
|
||||
CREATE TEMP TABLE teams(id SERIAL PRIMARY KEY, name TEXT, creator_id INT, game_id INT, password TEXT, status TEXT DEFAULT 'register', created_at TIMESTAMP DEFAULT NOW());
|
||||
CREATE TEMP TABLE actions(id SERIAL PRIMARY KEY, team_id INT, code TEXT, via_mcp BOOLEAN, created_at TIMESTAMP DEFAULT NOW());
|
||||
CREATE TEMP TABLE applications(id SERIAL PRIMARY KEY, team_id INT, name TEXT, image TEXT, file_type TEXT, status TEXT DEFAULT 'need', created_at TIMESTAMP DEFAULT NOW());
|
||||
CREATE TEMP TABLE team_answers(team_id INT, question_code TEXT, answer TEXT, score INT, updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(team_id, question_code));
|
||||
INSERT INTO scenarios(id,name,scenario) VALUES(1,'Case','{"introduction":{"text":"Intro"},"places":[{"code":"p","name":"Place","applications":[{"name":"Clue","image":"clue.pdf"}]}],"questions":[{"code":"q","text":"Who?","answer":"Butler"}]}');`)
|
||||
check(err)
|
||||
for _, name := range []string{"20260913130000_add_team_registration.sql", "20260915120000_add_team_participation.sql"} {
|
||||
data, err := os.ReadFile("../../../migrations/" + name)
|
||||
check(err)
|
||||
_, err = pool.Exec(ctx, strings.Split(string(data), "-- +goose Down")[0])
|
||||
check(err)
|
||||
}
|
||||
games, teams := games_repo.NewGamesRepo(pool), teams_repo.NewTeamsRepo(pool)
|
||||
scenarios := scenarios_repo.NewScenariosRepo(pool)
|
||||
actions, applications := actions_repo.NewActionsRepo(pool), applications_repo.NewApplicationsRepo(pool)
|
||||
textCleaner := cleaner.NewCleaner()
|
||||
scenarioService := scenarios_service.NewScenarioService(scenarios, textCleaner, "https://files.test/", nil, nil)
|
||||
game := game_service.NewGameService(games, teams, scenarios, scenarioService, "https://files.test/", actions, applications)
|
||||
team := team_service.NewTeamService(games, teams, password_generator.NewGenerator(12), team_qr_pdf.NewGenerator(), "https://app.test")
|
||||
play := gameplay_service.NewGameplayService(games, teams, scenarioService, storytelling.NewStory(textCleaner), actions, applications, textCleaner, "https://files.test/")
|
||||
answers := answer_service.NewAnswerService(games, teams, scenarioService, team_answers_repo.NewTeamAnswersRepo(pool), actions)
|
||||
participation := participation_service.NewParticipationService(teams, "https://app.test")
|
||||
start := time.Now().Add(48 * time.Hour)
|
||||
id, err := game.AddGame(ctx, "Game", "", start, 1, "online")
|
||||
check(err)
|
||||
check(game.UpdateGame(ctx, id, "Game", "Updated", start, 1, ""))
|
||||
registration := team_service.Registration{Name: "Team", Email: "captain@example.org", ParticipantsCount: 3}
|
||||
teamID, err := team.RegisterTeam(ctx, id, "delivery", registration)
|
||||
check(err)
|
||||
retryID, err := team.RegisterTeam(ctx, id, "delivery", registration)
|
||||
check(err)
|
||||
if retryID != teamID {
|
||||
t.Fatal("duplicate registration")
|
||||
}
|
||||
registered, err := teams.GetTeamByID(ctx, teamID)
|
||||
check(err)
|
||||
if registered.ParticipationStatus != "pending" {
|
||||
t.Fatal("early registration must be pending")
|
||||
}
|
||||
token := strings.Repeat("a", 64)
|
||||
check(teams.SetParticipationToken(ctx, teamID, token))
|
||||
p, err := participation.GetParticipation(ctx, token)
|
||||
check(err)
|
||||
if p.Status != "pending" || p.TeamName != "Team" {
|
||||
t.Fatalf("participation: %+v", p)
|
||||
}
|
||||
media, err := game.GetGameMedia(ctx, id)
|
||||
check(err)
|
||||
if media.Scenario.Story.Introduction.Text != "Intro" {
|
||||
t.Fatal("game media missing")
|
||||
}
|
||||
pdf, _, err := team.DownloadTeamQR(ctx, id)
|
||||
check(err)
|
||||
if !strings.HasPrefix(string(pdf), "%PDF-") {
|
||||
t.Fatal("invalid QR PDF")
|
||||
}
|
||||
check(game.StartGame(ctx, id))
|
||||
check(play.AddTeamAction(ctx, teamID, registered.Password, "p", true))
|
||||
registered, err = teams.GetTeamByID(ctx, teamID)
|
||||
check(err)
|
||||
if registered.ParticipationStatus != "confirmed" || registered.ParticipationSource != "first_action" {
|
||||
t.Fatal("first action did not confirm participation")
|
||||
}
|
||||
story, _, moves, err := play.GetTeamActions(ctx, teamID, registered.Password)
|
||||
check(err)
|
||||
if len(moves) != 1 || !moves[0].ViaMCP || len(story.Places) != 1 || story.Questions[0].Answer != "" {
|
||||
t.Fatal("incorrect team story")
|
||||
}
|
||||
full, err := game.GetFullGame(ctx, id)
|
||||
check(err)
|
||||
if len(full.Teams) != 1 || full.Teams[0].ActionsCount != 1 || len(full.Teams[0].Applications) != 1 || full.Teams[0].Applications[0].Image != "https://files.test/clue.pdf" {
|
||||
t.Fatalf("incorrect game details: %+v", full)
|
||||
}
|
||||
check(play.GiveTeamApplications(ctx, teamID, "Clue"))
|
||||
full, err = game.GetFullGame(ctx, id)
|
||||
check(err)
|
||||
if len(full.Teams[0].Applications) != 0 {
|
||||
t.Fatal("given clue still pending")
|
||||
}
|
||||
check(play.DeleteLastTeamAction(ctx, teamID))
|
||||
full, err = game.GetFullGame(ctx, id)
|
||||
check(err)
|
||||
if full.Teams[0].ActionsCount != 0 {
|
||||
t.Fatal("move not rolled back")
|
||||
}
|
||||
check(play.AddTeamAction(ctx, teamID, registered.Password, "p", false))
|
||||
if err := answers.SubmitTeamAnswers(ctx, teamID, registered.Password, []answer_service.Answer{{QuestionCode: "missing"}}); !errors.Is(err, answer_service.ErrQuestionNotFound) {
|
||||
t.Fatalf("unknown question: %v", err)
|
||||
}
|
||||
check(answers.SubmitTeamAnswers(ctx, teamID, registered.Password, []answer_service.Answer{{QuestionCode: "q", Text: "Butler"}}))
|
||||
check(game.PauseGame(ctx, id))
|
||||
check(answers.SubmitTeamAnswers(ctx, teamID, registered.Password, []answer_service.Answer{{QuestionCode: "q", Text: "Butler"}}))
|
||||
check(game.PlayGame(ctx, id))
|
||||
check(game.FinishGame(ctx, id))
|
||||
if err := answers.SubmitTeamAnswers(ctx, teamID, registered.Password, nil); !errors.Is(err, answer_service.ErrGameFinished) {
|
||||
t.Fatalf("finished answers: %v", err)
|
||||
}
|
||||
score := 5
|
||||
check(answers.SetTeamAnswerScore(ctx, teamID, "q", &score))
|
||||
results, err := answers.GetGameResults(ctx, id)
|
||||
check(err)
|
||||
if len(results.Teams) != 1 || results.Teams[0].TotalScore != 5 || results.Teams[0].Place != 1 || results.Teams[0].Team.ActionsCount != 1 {
|
||||
t.Fatalf("incorrect results: %+v", results)
|
||||
}
|
||||
check(game.ResetGame(ctx, id))
|
||||
full, err = game.GetFullGame(ctx, id)
|
||||
check(err)
|
||||
if full.Status != "draft" || full.StartedAt != nil || full.EndedAt != nil || full.Teams[0].ActionsCount != 1 {
|
||||
t.Fatal("reset changed history or retained timestamps")
|
||||
}
|
||||
grading, err := answers.GetGameAnswers(ctx, id)
|
||||
check(err)
|
||||
if len(grading.Teams) != 1 || grading.Teams[0].Answers[0].Score == nil || *grading.Teams[0].Answers[0].Score != 5 {
|
||||
t.Fatal("reset removed answers or scores")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user