generated from VLADIMIR/template
add registration
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package repos
|
||||
|
||||
type Team struct {
|
||||
ID int
|
||||
Name string
|
||||
Status string
|
||||
Password string
|
||||
GameId int
|
||||
CaptainEmail string
|
||||
ParticipantsCount int32
|
||||
ID int
|
||||
Name string
|
||||
Status string
|
||||
Password string
|
||||
GameId int
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package teams_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func TestRegistrationPostgres(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()
|
||||
admin, err := pgxpool.New(ctx, dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer admin.Close()
|
||||
schema := fmt.Sprintf("registration_test_%d", time.Now().UnixNano())
|
||||
quoted := pgx.Identifier{schema}.Sanitize()
|
||||
if _, err := admin.Exec(ctx, "CREATE SCHEMA "+quoted); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if _, err := admin.Exec(ctx, "DROP SCHEMA "+quoted+" CASCADE"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
config, err := pgxpool.ParseConfig(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config.ConnConfig.RuntimeParams["search_path"] = schema
|
||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer pool.Close()
|
||||
_, err = pool.Exec(ctx, `CREATE TABLE games (id SERIAL PRIMARY KEY, status TEXT DEFAULT 'draft');
|
||||
CREATE TABLE teams (id SERIAL PRIMARY KEY, name VARCHAR(255), creator_id INTEGER,
|
||||
game_id INTEGER REFERENCES games(id), password TEXT, status TEXT DEFAULT 'register', created_at TIMESTAMP DEFAULT NOW());
|
||||
INSERT INTO games DEFAULT VALUES; INSERT INTO games DEFAULT VALUES;`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
migration, err := os.ReadFile("../../../migrations/20260913130000_add_team_registration.sql")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, strings.Split(string(migration), "-- +goose Down")[0]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repo := NewTeamsRepo(pool)
|
||||
type result struct {
|
||||
id int
|
||||
err error
|
||||
}
|
||||
results := make(chan result, 8)
|
||||
for range 8 {
|
||||
go func() {
|
||||
id, err := repo.RegisterTeam(ctx, 1, "delivery", "Team", "captain@example.org", 5, "abcdefgh")
|
||||
results <- result{id, err}
|
||||
}()
|
||||
}
|
||||
var id int
|
||||
for range 8 {
|
||||
got := <-results
|
||||
if got.err != nil {
|
||||
t.Fatal(got.err)
|
||||
}
|
||||
if id != 0 && got.id != id {
|
||||
t.Fatal("concurrent delivery created a duplicate")
|
||||
}
|
||||
id = got.id
|
||||
}
|
||||
team, err := repo.GetTeamByID(ctx, id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if team.Name != "Team" || team.CaptainEmail != "captain@example.org" || team.ParticipantsCount != 5 || team.Password != "abcdefgh" {
|
||||
t.Fatalf("incorrect team: %+v", team)
|
||||
}
|
||||
var noUser bool
|
||||
if err := pool.QueryRow(ctx, "SELECT creator_id IS NULL FROM teams WHERE id=$1", id).Scan(&noUser); err != nil || !noUser {
|
||||
t.Fatalf("captain has a user: %v", err)
|
||||
}
|
||||
teams, err := repo.GetTeamsByGameID(ctx, 1)
|
||||
if err != nil || len(teams) != 1 || teams[0].CaptainEmail != team.CaptainEmail {
|
||||
t.Fatalf("teams=%v err=%v", teams, err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, "UPDATE games SET status='run' WHERE id=1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, err := repo.RegisterTeam(ctx, 1, "delivery", "Changed", "other@example.org", 9, "different"); err != nil || got != id {
|
||||
t.Fatalf("retry after start: %d %v", got, err)
|
||||
}
|
||||
if _, err := repo.RegisterTeam(ctx, 1, "new", "Team", "captain@example.org", 5, "abcdefgh"); !errors.Is(err, ErrRegistrationClosed) {
|
||||
t.Fatalf("registration after start: %v", err)
|
||||
}
|
||||
if _, err := repo.RegisterTeam(ctx, 999, "new", "Team", "captain@example.org", 5, "abcdefgh"); !errors.Is(err, ErrRegistrationClosed) {
|
||||
t.Fatalf("missing game: %v", err)
|
||||
}
|
||||
if otherID, err := repo.RegisterTeam(ctx, 2, "delivery", "Team", "captain@example.org", 5, "abcdefgh"); err != nil || otherID == id {
|
||||
t.Fatalf("other game: %d %v", otherID, err)
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ func (r *TeamsRepo) GetTeamByID(ctx context.Context, id int) (*repos.Team, error
|
||||
`SELECT
|
||||
id,
|
||||
name,
|
||||
captain_email,
|
||||
participants_count,
|
||||
status,
|
||||
password,
|
||||
game_id
|
||||
@@ -40,6 +42,8 @@ func (r *TeamsRepo) GetTeamByID(ctx context.Context, id int) (*repos.Team, error
|
||||
err := row.Scan(
|
||||
&team.ID,
|
||||
&team.Name,
|
||||
&team.CaptainEmail,
|
||||
&team.ParticipantsCount,
|
||||
&team.Status,
|
||||
&team.Password,
|
||||
&team.GameId,
|
||||
@@ -63,6 +67,8 @@ func (r *TeamsRepo) GetTeamsByGameID(
|
||||
`SELECT
|
||||
id,
|
||||
name,
|
||||
captain_email,
|
||||
participants_count,
|
||||
status,
|
||||
password
|
||||
FROM teams
|
||||
@@ -81,6 +87,8 @@ func (r *TeamsRepo) GetTeamsByGameID(
|
||||
err := rows.Scan(
|
||||
&team.ID,
|
||||
&team.Name,
|
||||
&team.CaptainEmail,
|
||||
&team.ParticipantsCount,
|
||||
&team.Status,
|
||||
&team.Password,
|
||||
)
|
||||
@@ -155,3 +163,28 @@ func (r *TeamsRepo) DeleteTeam(ctx context.Context, teamId int) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrRegistrationClosed = errors.New("Игра не найдена или регистрация уже закрыта")
|
||||
|
||||
// RegisterTeam сохраняет одну команду на доставку, включая одновременные повторы.
|
||||
func (r *TeamsRepo) RegisterTeam(ctx context.Context, gameID int, deliveryID, name, email string, participantsCount int32, password string) (int, error) {
|
||||
var id int
|
||||
// Повтор уже принятой доставки успешен и после старта игры.
|
||||
err := r.pool.QueryRow(ctx, `SELECT id FROM teams WHERE game_id = $1 AND registration_delivery_id = $2`, gameID, deliveryID).Scan(&id)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
if !errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, err
|
||||
}
|
||||
err = r.pool.QueryRow(ctx, `
|
||||
INSERT INTO teams (game_id, registration_delivery_id, name, captain_email, participants_count, password)
|
||||
SELECT id, $2, $3, $4, $5, $6 FROM games WHERE id = $1 AND status = 'draft'
|
||||
ON CONFLICT (game_id, registration_delivery_id)
|
||||
DO UPDATE SET registration_delivery_id = EXCLUDED.registration_delivery_id
|
||||
RETURNING id`, gameID, deliveryID, name, email, participantsCount, password).Scan(&id)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return 0, ErrRegistrationClosed
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user