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"}, AcceptedMatchSelectSQL: {"playlist", "region", "protocol_version", "arena_path", "FOR UPDATE"}, 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 TestAcceptedMatchReplayDoesNotDependOnMutableLifecycleFields(t *testing.T) { if contains(AcceptedMatchSelectSQL, "state") || contains(AcceptedMatchSelectSQL, "server_id") { t.Fatalf("accepted promotion replay is coupled to mutable lifecycle fields: %s", AcceptedMatchSelectSQL) } } 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]) } } }