package domain import ( "testing" "time" ) func candidate(id string, rating float64, wait time.Duration, eu, na float64, now time.Time) Candidate { return Candidate{TicketID: id, PlayerID: "player-" + id, Rating: rating, EnqueuedAt: now.Add(-wait), PredictedRTT: map[string]float64{"EU": eu, "NA": na}} } func TestSelectCandidatesNeverCrossesRTTOrMutualRatingCeilings(t *testing.T) { now := time.Unix(100000, 0) anchor := candidate("a", 1500, 0, 40, 140, now) players := []Candidate{ candidate("b", 1590, 10*time.Second, 45, 40, now), candidate("c", 1590, 70*time.Second, 50, 50, now), candidate("d", 1800, 70*time.Second, 40, 40, now), } selection, err := SelectCandidates(anchor, players, 3, now) if err != nil { t.Fatal(err) } if selection.Region != "EU" || selection.WorstRTT > MaxPlacementRTT { t.Fatalf("bad region/RTT: %+v", selection) } if ticketIDs(selection.Players) != "a\x00b\x00c\x00" { t.Fatalf("selected incompatible or non-optimal set: %q", ticketIDs(selection.Players)) } } func TestSelectCandidatesRequiresAnchorAndUsesDeterministicTieBreak(t *testing.T) { now := time.Unix(100000, 0) anchor := candidate("anchor", 1500, 60*time.Second, 50, 50, now) players := []Candidate{ candidate("z", 1500, 10*time.Second, 50, 50, now), candidate("y", 1500, 10*time.Second, 50, 50, now), candidate("x", 1500, 10*time.Second, 50, 50, now), } selection, err := SelectCandidates(anchor, players, 3, now) if err != nil { t.Fatal(err) } if !containsTicket(selection.Players, "anchor") { t.Fatal("anchor was omitted") } if ticketIDs(selection.Players) != "anchor\x00x\x00y\x00" { t.Fatalf("tie break was not lexical: %q", ticketIDs(selection.Players)) } } func TestRatingToleranceWideningIsCapped(t *testing.T) { if RatingTolerance(29) != 100 || RatingTolerance(30) != 125 { t.Fatal("30-second widening boundary is wrong") } if RatingTolerance(1000) != MaxRatingTolerance { t.Fatal("rating tolerance is not capped") } } func TestSelectCandidatesRejectsNoCommonRegion(t *testing.T) { now := time.Unix(100000, 0) anchor := candidate("a", 1500, 0, 101, 40, now) other := candidate("b", 1500, 0, 40, 101, now) if _, err := SelectCandidates(anchor, []Candidate{other}, 2, now); err == nil { t.Fatal("selected players without a common <=100ms region") } } func TestFormFromQueueUsesServerProjectionAndBalancesTeams(t *testing.T) { now := time.Unix(100000, 0) queue := NewQueue() for _, id := range []string{"c", "a", "b", "d"} { candidate := candidate(id, 1500, time.Second, 40, 45, now) if _, err := queue.Create(candidate.PlayerID, candidate.TicketID, "create-key-"+id+"-123456", candidate, now.Add(-time.Duration(len(id))*time.Millisecond)); err != nil { t.Fatal(err) } } formation, err := FormFromQueue(queue, 4, now) if err != nil { t.Fatal(err) } if formation.Selection.Players[0].TicketID != "a" || formation.Selection.Region != "EU" || len(formation.Teams.Team0) != 2 || len(formation.Teams.Team1) != 2 { t.Fatalf("formation = %+v", formation) } seen := map[string]bool{} for _, player := range append(formation.Teams.Team0, formation.Teams.Team1...) { if seen[player.PlayerID] { t.Fatalf("duplicate player in teams: %s", player.PlayerID) } seen[player.PlayerID] = true } } func TestSelectCandidatesRejectsMalformedCandidateInsteadOfTrustingIt(t *testing.T) { now := time.Unix(100000, 0) anchor := candidate("a", 1500, 0, 40, 40, now) malformed := candidate("b", 1500, 0, 40, 40, now) malformed.PlayerID = anchor.PlayerID if _, err := SelectCandidates(anchor, []Candidate{malformed}, 2, now); err == nil { t.Fatal("duplicate player candidate accepted") } }