generated from VLADIMIR/template
up
This commit is contained in:
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type Participation struct {
|
||||
TeamID int
|
||||
TeamID string
|
||||
TeamName string
|
||||
GameName string
|
||||
StartAt time.Time
|
||||
|
||||
@@ -29,8 +29,8 @@ func (s *participationSender) Send(_ context.Context, m email_sender.Message) er
|
||||
}
|
||||
|
||||
func TestParticipationEmail(t *testing.T) {
|
||||
token := participationToken("test-secret", 42)
|
||||
if len(token) != 64 || token != participationToken("test-secret", 42) || token == participationToken("test-secret", 43) || token == participationToken("other", 42) {
|
||||
token := participationToken("test-secret", "019a1234-5678-7abc-8def-00000000002a")
|
||||
if len(token) != 64 || token != participationToken("test-secret", "019a1234-5678-7abc-8def-00000000002a") || token == participationToken("test-secret", "019a1234-5678-7abc-8def-00000000002b") || token == participationToken("other", "019a1234-5678-7abc-8def-00000000002a") {
|
||||
t.Fatal("token isolation or retry stability broken")
|
||||
}
|
||||
p := &Participation{TeamName: "<script>team</script>", GameName: "Game", Email: "captain@example.org", StartAt: time.Date(2026, 9, 20, 12, 0, 0, 0, time.UTC), ParticipantsCount: 5}
|
||||
@@ -80,9 +80,9 @@ func TestParticipationPostgres(t *testing.T) {
|
||||
}
|
||||
exec(`CREATE TABLE game_exports(game_id INT, state TEXT);
|
||||
CREATE TABLE games(id SERIAL PRIMARY KEY,name TEXT DEFAULT 'Game',start_at TIMESTAMP NOT NULL,status TEXT DEFAULT 'draft');
|
||||
CREATE TABLE teams(id SERIAL PRIMARY KEY,name TEXT DEFAULT 'Team',creator_id INTEGER,game_id INTEGER REFERENCES games(id),password TEXT DEFAULT 'secret',status TEXT DEFAULT 'register',created_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC'));
|
||||
CREATE TABLE teams(id UUID PRIMARY KEY,name TEXT DEFAULT 'Team',creator_id INTEGER,game_id INTEGER REFERENCES games(id),password TEXT DEFAULT 'secret',status TEXT DEFAULT 'register',created_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC'));
|
||||
INSERT INTO games(start_at) VALUES((NOW() AT TIME ZONE 'UTC')+INTERVAL '12 hours'),((NOW() AT TIME ZONE 'UTC')+INTERVAL '2 days');
|
||||
INSERT INTO teams(game_id,created_at) VALUES(1,(NOW() AT TIME ZONE 'UTC')-INTERVAL '2 days'),(1,(NOW() AT TIME ZONE 'UTC')-INTERVAL '1 hour');`)
|
||||
INSERT INTO teams(id,game_id,created_at) VALUES('019a1234-5678-7abc-8def-000000000001',1,(NOW() AT TIME ZONE 'UTC')-INTERVAL '2 days'),('019a1234-5678-7abc-8def-000000000002',1,(NOW() AT TIME ZONE 'UTC')-INTERVAL '1 hour');`)
|
||||
for _, name := range []string{"20260913130000_add_team_registration.sql", "20260915120000_add_team_participation.sql"} {
|
||||
data, err := os.ReadFile("../../../migrations/" + name)
|
||||
if err != nil {
|
||||
@@ -94,7 +94,7 @@ func TestParticipationPostgres(t *testing.T) {
|
||||
service := NewParticipationService(repo, "https://example.org")
|
||||
state := func(id int, want string) {
|
||||
t.Helper()
|
||||
team, err := repo.GetTeamByID(ctx, id)
|
||||
team, err := repo.GetTeamByID(ctx, fmt.Sprintf("019a1234-5678-7abc-8def-%012x", id))
|
||||
if err != nil || team.ParticipationStatus != want {
|
||||
t.Fatalf("team %d want %s got %+v, %v", id, want, team, err)
|
||||
}
|
||||
@@ -102,20 +102,20 @@ func TestParticipationPostgres(t *testing.T) {
|
||||
state(1, "pending")
|
||||
state(2, "pending")
|
||||
// Model registration output explicitly: direct SQL inserts do not auto-confirm teams.
|
||||
exec(`UPDATE teams SET captain_email='captain@example.org',participants_count=5 WHERE id=1;
|
||||
INSERT INTO teams(game_id,created_at,captain_email) SELECT 1,start_at-INTERVAL '24 hours','boundary@example.org' FROM games WHERE id=1;
|
||||
INSERT INTO teams(game_id,created_at,captain_email,participation_status,participation_source,participation_answered_at) SELECT 1,start_at-INTERVAL '24 hours'+INTERVAL '1 microsecond','late@example.org','confirmed','automatic',NOW() FROM games WHERE id=1;
|
||||
INSERT INTO teams(game_id,captain_email) VALUES(2,'future@example.org');
|
||||
INSERT INTO teams(game_id,created_at) SELECT 1,start_at-INTERVAL '25 hours' FROM games WHERE id=1;`)
|
||||
exec(`UPDATE teams SET captain_email='captain@example.org',participants_count=5 WHERE id='019a1234-5678-7abc-8def-000000000001';
|
||||
INSERT INTO teams(id,game_id,created_at,captain_email) SELECT '019a1234-5678-7abc-8def-000000000003',1,start_at-INTERVAL '24 hours','boundary@example.org' FROM games WHERE id=1;
|
||||
INSERT INTO teams(id,game_id,created_at,captain_email,participation_status,participation_source,participation_answered_at) SELECT '019a1234-5678-7abc-8def-000000000004',1,start_at-INTERVAL '24 hours'+INTERVAL '1 microsecond','late@example.org','confirmed','automatic',NOW() FROM games WHERE id=1;
|
||||
INSERT INTO teams(id,game_id,captain_email) VALUES('019a1234-5678-7abc-8def-000000000005',2,'future@example.org');
|
||||
INSERT INTO teams(id,game_id,created_at) SELECT '019a1234-5678-7abc-8def-000000000006',1,start_at-INTERVAL '25 hours' FROM games WHERE id=1;`)
|
||||
state(3, "pending")
|
||||
state(4, "confirmed")
|
||||
state(5, "pending")
|
||||
if err := repo.ConfirmParticipationByFirstAction(ctx, 5); err != nil {
|
||||
if err := repo.ConfirmParticipationByFirstAction(ctx, "019a1234-5678-7abc-8def-000000000005"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
state(5, "confirmed")
|
||||
var wg sync.WaitGroup
|
||||
ids := make(chan int, 8)
|
||||
ids := make(chan string, 8)
|
||||
errs := make(chan error, 8)
|
||||
for range 8 {
|
||||
wg.Add(1)
|
||||
@@ -132,17 +132,17 @@ func TestParticipationPostgres(t *testing.T) {
|
||||
wg.Wait()
|
||||
close(ids)
|
||||
close(errs)
|
||||
seen := map[int]bool{}
|
||||
seen := map[string]bool{}
|
||||
for id := range ids {
|
||||
if seen[id] {
|
||||
t.Fatalf("duplicate claim %d", id)
|
||||
t.Fatalf("duplicate claim %s", id)
|
||||
}
|
||||
seen[id] = true
|
||||
}
|
||||
for err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(seen) != 2 || !seen[1] || !seen[3] {
|
||||
if len(seen) != 2 || !seen["019a1234-5678-7abc-8def-000000000001"] || !seen["019a1234-5678-7abc-8def-000000000003"] {
|
||||
t.Fatalf("wrong eligible teams: %v", seen)
|
||||
}
|
||||
exec(`UPDATE teams SET participation_next_attempt_at=NULL`)
|
||||
@@ -153,26 +153,26 @@ func TestParticipationPostgres(t *testing.T) {
|
||||
if len(sender.messages) != 2 {
|
||||
t.Fatalf("wanted two attempts: %d", len(sender.messages))
|
||||
}
|
||||
team, err := repo.GetTeamByID(ctx, 1)
|
||||
team, err := repo.GetTeamByID(ctx, "019a1234-5678-7abc-8def-000000000001")
|
||||
if err != nil || team.ParticipationEmailError == "" || team.ParticipationEmailSent {
|
||||
t.Fatalf("failed send not recorded: %+v %v", team, err)
|
||||
}
|
||||
sender.err = nil
|
||||
exec(`UPDATE teams SET participation_next_attempt_at=NOW()-INTERVAL '1 minute' WHERE id IN (1,3)`)
|
||||
exec(`UPDATE teams SET participation_next_attempt_at=NOW()-INTERVAL '1 minute' WHERE id IN ('019a1234-5678-7abc-8def-000000000001','019a1234-5678-7abc-8def-000000000003')`)
|
||||
if err := service.sendParticipationEmails(ctx, sender, "secret", time.UTC); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(sender.messages) != 4 {
|
||||
t.Fatal("expired lease not recovered")
|
||||
}
|
||||
exec(`UPDATE teams SET participation_attempts=8, participation_next_attempt_at=NULL, participation_sent_at=NULL WHERE id=1`)
|
||||
exec(`UPDATE teams SET participation_attempts=8, participation_next_attempt_at=NULL, participation_sent_at=NULL WHERE id='019a1234-5678-7abc-8def-000000000001'`)
|
||||
if err := service.sendParticipationEmails(ctx, sender, "secret", time.UTC); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(sender.messages) != 4 {
|
||||
t.Fatal("email retry exceeded the eight-attempt limit")
|
||||
}
|
||||
token := participationToken("secret", 1)
|
||||
token := participationToken("secret", "019a1234-5678-7abc-8def-000000000001")
|
||||
if _, err := service.GetParticipation(ctx, "bad"); !errors.Is(err, ErrParticipationLink) {
|
||||
t.Fatalf("invalid token: %v", err)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func TestParticipationPostgres(t *testing.T) {
|
||||
if _, err := service.AnswerParticipation(ctx, token, p.Status); err != nil {
|
||||
t.Fatalf("same answer not idempotent: %v", err)
|
||||
}
|
||||
token3 := participationToken("secret", 3)
|
||||
token3 := participationToken("secret", "019a1234-5678-7abc-8def-000000000003")
|
||||
if _, err := service.AnswerParticipation(ctx, token3, "cancelled"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -217,23 +217,23 @@ func TestParticipationPostgres(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, team := range active {
|
||||
if team.ID == 3 {
|
||||
if team.ID == "019a1234-5678-7abc-8def-000000000003" {
|
||||
t.Fatal("cancelled team in active list")
|
||||
}
|
||||
}
|
||||
gameplayService := gameplay_service.NewGameplayService(nil, repo, nil, nil, nil, nil, nil, "")
|
||||
if _, _, _, err := gameplayService.GetTeamActions(ctx, 3, "secret"); !errors.Is(err, gameplay_service.ErrTeamNotFound) {
|
||||
if _, _, _, err := gameplayService.GetTeamActions(ctx, "019a1234-5678-7abc-8def-000000000003", "secret"); !errors.Is(err, gameplay_service.ErrTeamNotFound) {
|
||||
t.Fatalf("cancelled team got story: %v", err)
|
||||
}
|
||||
if err := gameplayService.AddTeamAction(ctx, 3, "secret", "anything", false); !errors.Is(err, gameplay_service.ErrTeamNotFound) {
|
||||
if err := gameplayService.AddTeamAction(ctx, "019a1234-5678-7abc-8def-000000000003", "secret", "anything", false); !errors.Is(err, gameplay_service.ErrTeamNotFound) {
|
||||
t.Fatalf("cancelled team added action: %v", err)
|
||||
}
|
||||
answerService := answer_service.NewAnswerService(nil, repo, nil, nil, nil)
|
||||
if _, err := answerService.GetTeamAnswers(ctx, 3, "secret"); !errors.Is(err, answer_service.ErrTeamNotFound) {
|
||||
if _, err := answerService.GetTeamAnswers(ctx, "019a1234-5678-7abc-8def-000000000003", "secret"); !errors.Is(err, answer_service.ErrTeamNotFound) {
|
||||
t.Fatalf("cancelled team got answers: %v", err)
|
||||
}
|
||||
token6 := participationToken("secret", 6)
|
||||
if err := repo.SetParticipationToken(ctx, 6, token6); err != nil {
|
||||
token6 := participationToken("secret", "019a1234-5678-7abc-8def-000000000006")
|
||||
if err := repo.SetParticipationToken(ctx, "019a1234-5678-7abc-8def-000000000006", token6); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exec(`UPDATE games SET status='run' WHERE id=1`)
|
||||
|
||||
@@ -58,9 +58,9 @@ func mapParticipationError(err error) error {
|
||||
}
|
||||
}
|
||||
|
||||
func participationToken(secret string, id int) string {
|
||||
func participationToken(secret string, id string) string {
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
_, _ = fmt.Fprintf(mac, "team-participation:v1:%d", id)
|
||||
_, _ = fmt.Fprintf(mac, "team-participation:v1:%s", id)
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user