Files
CosmicClash/server/store/match_sql_test.go
T
2026-09-01 21:04:55 +01:00

97 lines
3.7 KiB
Go

package store
import (
"testing"
"time"
"github.com/cosmic-clash/cosmic-clash/server/domain"
)
func TestAcceptedMatchSQLPreservesAtomicProposalToMatchBoundary(t *testing.T) {
checks := map[string][]string{
AcceptedProposalLockSQL: {"FOR UPDATE", "proposal_id = $1"},
AcceptedProposalParticipantsSQL: {"response", "ORDER BY player_id", "FOR UPDATE"},
AcceptedMatchInsertSQL: {"'ALLOCATING'", "ON CONFLICT (match_id) DO NOTHING"},
AcceptedTicketSQL: {"state = 'ACCEPTED'", "state = 'PROPOSED'", "revision = revision + 1"},
AcceptedMatchParticipantInsertSQL: {"match_participants", "slot", "team"},
}
for query, fragments := range checks {
for _, fragment := range fragments {
if !contains(query, fragment) {
t.Fatalf("query %q missing %q", query, fragment)
}
}
}
}
func TestAcceptedMatchPlanRejectsInvalidPlansBeforeDatabaseUse(t *testing.T) {
valid := AcceptedMatchPlan{
MatchID: "match-1", ProposalID: "proposal-1", Region: "EU", Protocol: 1,
Players: []MatchPlayer{{PlayerID: "player-a", Team: 0, Slot: 0}, {PlayerID: "player-b", Team: 1, Slot: 3}},
}
if !validAcceptedMatchPlan(valid) {
t.Fatal("valid accepted match plan rejected")
}
for name, mutate := range map[string]func(*AcceptedMatchPlan){
"no second team": func(p *AcceptedMatchPlan) { p.Players[1].Team = 0 },
"duplicate slot": func(p *AcceptedMatchPlan) { p.Players[1].Slot = 0 },
"duplicate player": func(p *AcceptedMatchPlan) { p.Players[1].PlayerID = "player-a" },
"bad region": func(p *AcceptedMatchPlan) { p.Region = "AP" },
} {
plan := valid
plan.Players = append([]MatchPlayer(nil), valid.Players...)
mutate(&plan)
if validAcceptedMatchPlan(plan) {
t.Fatalf("%s plan accepted", name)
}
}
if err := CreateMatchFromAcceptedProposal(nil, nil, valid, time.Now()); err == nil {
t.Fatal("nil database accepted")
}
}
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 TestRankedAcceptedMatchPlanRequiresArenaAfterPlaylistResolution(t *testing.T) {
plan := AcceptedMatchPlan{MatchID: "match-1", ProposalID: "proposal-1", Region: "EU", Protocol: 1, Players: []MatchPlayer{{PlayerID: "player-a", Team: 0, Slot: 0}, {PlayerID: "player-b", Team: 1, Slot: 3}}}
if !validAcceptedMatchPlan(plan) {
t.Fatal("test plan should reach the database playlist guard")
}
// CreateMatchFromAcceptedProposal owns the playlist lookup, so a nil DB is
// the only no-database check available here; the integration suite exercises
// the resolved ranked branch against PostgreSQL.
if err := CreateMatchFromAcceptedProposal(nil, nil, plan, time.Now()); err == nil {
t.Fatal("nil database accepted")
}
}
func TestMatchPlayersFromTeamsUsesDeterministicTeamSlots(t *testing.T) {
teams := domain.Teams{
Team0: []domain.Candidate{{PlayerID: "bravo"}, {PlayerID: "alpha"}},
Team1: []domain.Candidate{{PlayerID: "delta"}, {PlayerID: "charlie"}},
}
players, err := MatchPlayersFromTeams(teams)
if err != nil {
t.Fatal(err)
}
want := []MatchPlayer{
{PlayerID: "alpha", Team: 0, Slot: 0}, {PlayerID: "bravo", Team: 0, Slot: 1},
{PlayerID: "charlie", Team: 1, Slot: 3}, {PlayerID: "delta", Team: 1, Slot: 4},
}
if len(players) != len(want) {
t.Fatalf("players = %+v", players)
}
for index := range want {
if players[index] != want[index] {
t.Fatalf("player %d = %+v, want %+v", index, players[index], want[index])
}
}
}