package domain import ( "errors" "fmt" "testing" "time" ) func TestEventWorkflowRejectsStaleVersion(t *testing.T) { event := Event{State: RegistrationOpen, Version: 2} if err := event.Transition([]EventState{RegistrationOpen}, RegistrationClosed, 1); !errors.Is(err, ErrConflict) { t.Fatalf("expected stale version conflict, got %v", err) } if event.State != RegistrationOpen || event.Version != 2 { t.Fatal("stale transition mutated the event") } } func TestRosterSwapAndReserveKeepRoleShape(t *testing.T) { roster := testRoster() if err := roster.Swap("a", "a-d1", "b", "b-s1"); !errors.Is(err, ErrInvalid) { t.Fatalf("expected cross-role swap rejection, got %v", err) } if err := roster.Swap("a", "a-d1", "b", "b-d1"); err != nil { t.Fatalf("same-role swap failed: %v", err) } if err := roster.Substitute("a", "b-d1", "reserve", 30); err != nil { t.Fatalf("reserve substitution failed: %v", err) } if roster.Reserve[0] != "b-d1" { t.Fatalf("displaced player was not returned to reserve: %#v", roster.Reserve) } if err := roster.Validate(false); err != nil { t.Fatalf("valid 1/2/2 roster rejected: %v", err) } } func TestSeriesFSMRunsCoinBansAndResult(t *testing.T) { rules := testRules() series, err := NewSeries("series", "event", "", [2]string{"a", "b"}, rules) if err != nil { t.Fatal(err) } now := time.Unix(100, 0).UTC() if err = series.Toss("seed", "admin", now, rules); err != nil { t.Fatal(err) } for !func() bool { _, selected := series.MapDraft.Selected(); return selected }() { name := firstAvailableMap(series.MapDraft) if err = series.BanMap(series.MapDraft.NextTeam(), name, "admin", now, rules); err != nil { t.Fatal(err) } } if series.Phase != HeroBanPhase { t.Fatalf("expected hero bans, got %s", series.Phase) } for _, hero := range []string{"Tank A", "Tank B", "Damage A", "Damage B"} { if err = series.BanHero(series.HeroDraft.NextTeam(), hero, "admin", now); err != nil { t.Fatal(err) } } if series.Phase != PlayingPhase { t.Fatalf("expected playing phase, got %s", series.Phase) } if err = series.RecordCurrentMap(TeamAWin, "admin", now, rules); err != nil { t.Fatal(err) } if series.Phase != MapPickPhase || len(series.PlayedMaps) != 1 || series.NextMapPickerID != "b" { t.Fatalf("next map was not prepared: phase=%s maps=%v", series.Phase, series.PlayedMaps) } if contains(series.AvailableMaps, series.PlayedMaps[0]) { t.Fatal("played map remained available in a later pool") } nextMap := series.AvailableMaps[0] if err = series.PickMap("a", nextMap, "admin", now, rules); !errors.Is(err, ErrConflict) { t.Fatalf("winner was allowed to pick the next map: %v", err) } if err = series.PickMap("b", nextMap, "admin", now, rules); err != nil || series.Phase != HeroBanPhase { t.Fatalf("loser could not pick the next map: phase=%s err=%v", series.Phase, err) } } func testRoster() RosterDraft { teams := make([]Team, 2) for teamIndex, id := range []string{"a", "b"} { slots := []Slot{{PlayerID: id + "-t", Role: Tank, Rating: 20}} for i := 1; i <= 2; i++ { slots = append(slots, Slot{PlayerID: fmt.Sprintf("%s-d%d", id, i), Role: Damage, Rating: 20}, Slot{PlayerID: fmt.Sprintf("%s-s%d", id, i), Role: Support, Rating: 20}, ) } teams[teamIndex] = Team{ID: id, EventID: "event", Name: id, Slots: slots} } return RosterDraft{EventID: "event", Teams: teams, Reserve: []string{"reserve"}} } func testRules() Ruleset { return Ruleset{ ID: "rules", BestOf: 3, HeroBansPerTeam: 2, MapPools: [][]string{{"Map 1", "Map 2", "Map 3"}, {"Map 1", "Map 4", "Map 5"}, {"Map 1", "Map 2", "Map 6"}}, Heroes: []Hero{ {Name: "Tank A", Role: Tank}, {Name: "Tank B", Role: Tank}, {Name: "Damage A", Role: Damage}, {Name: "Damage B", Role: Damage}, {Name: "Support A", Role: Support}, {Name: "Support B", Role: Support}, }, } } func firstAvailableMap(draft *MapDraft) string { for _, name := range draft.Pool { if !contains(draft.Banned, name) { return name } } return "" } func contains(values []string, value string) bool { for _, item := range values { if item == value { return true } } return false }