From e0ba6c6eada6c5c4f225ba500be49b4ec7d42d27 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:22:26 +0100 Subject: [PATCH] fix: enforce ranked match promotion size --- server/store/match_sql.go | 10 ++++++++++ server/store/match_sql_test.go | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/server/store/match_sql.go b/server/store/match_sql.go index 0b520e9d..2e91c2f0 100644 --- a/server/store/match_sql.go +++ b/server/store/match_sql.go @@ -78,6 +78,9 @@ func CreateMatchFromAcceptedProposal(ctx context.Context, db *sql.DB, plan Accep if proposalState != string(domain.Accepted) { return fmt.Errorf("proposal is not accepted") } + if !validAcceptedPlaylistCount(domain.Playlist(playlist), len(plan.Players)) { + return fmt.Errorf("accepted proposal playlist does not match player count") + } participants, err := acceptedProposalParticipants(ctx, tx, plan) if err != nil { return err @@ -110,6 +113,13 @@ func CreateMatchFromAcceptedProposal(ctx context.Context, db *sql.DB, plan Accep }) } +func validAcceptedPlaylistCount(playlist domain.Playlist, count int) bool { + if playlist == domain.Ranked { + return count == 6 + } + return playlist == domain.Casual && count >= 2 && count <= 6 +} + func validAcceptedMatchPlan(plan AcceptedMatchPlan) bool { if plan.MatchID == "" || plan.ProposalID == "" || (plan.Region != "EU" && plan.Region != "NA") || plan.Protocol < 1 || len(plan.Players) < 2 || len(plan.Players) > 6 { return false diff --git a/server/store/match_sql_test.go b/server/store/match_sql_test.go index a0cf10d4..d673ee93 100644 --- a/server/store/match_sql_test.go +++ b/server/store/match_sql_test.go @@ -50,6 +50,15 @@ func TestAcceptedMatchPlanRejectsInvalidPlansBeforeDatabaseUse(t *testing.T) { } } +func TestAcceptedMatchPromotionHonoursPlaylistSizeInvariant(t *testing.T) { + if validAcceptedPlaylistCount(domain.Ranked, 5) || !validAcceptedPlaylistCount(domain.Ranked, 6) { + t.Fatal("ranked accepted-match count invariant is wrong") + } + if validAcceptedPlaylistCount(domain.Casual, 1) || !validAcceptedPlaylistCount(domain.Casual, 2) || validAcceptedPlaylistCount("other", 6) { + t.Fatal("casual accepted-match count invariant is wrong") + } +} + func TestMatchPlayersFromTeamsUsesDeterministicTeamSlots(t *testing.T) { teams := domain.Teams{ Team0: []domain.Candidate{{PlayerID: "bravo"}, {PlayerID: "alpha"}},