175 lines
5.7 KiB
Go
175 lines
5.7 KiB
Go
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 TestModeratorIsStaffButNotAdministrator(t *testing.T) {
|
|
account := Account{Role: RoleModerator}
|
|
if !account.IsStaff() {
|
|
t.Fatal("moderator should have operational staff permissions")
|
|
}
|
|
if account.IsAdmin() {
|
|
t.Fatal("moderator must not have administrator role-management permission")
|
|
}
|
|
}
|
|
|
|
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 TestRosterDragOperationsPreserveEmptyRoleSlots(t *testing.T) {
|
|
roster := testRoster()
|
|
roster.Teams[0].CaptainPlayerID = "a-t"
|
|
if err := roster.MoveToReserve("a", "a-t"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if roster.Teams[0].Slots[0].PlayerID != "" || roster.Teams[0].Slots[0].Role != Tank {
|
|
t.Fatal("removed tank did not leave an identifiable empty tank slot")
|
|
}
|
|
if roster.Teams[0].CaptainPlayerID != "" {
|
|
t.Fatal("removed captain was retained")
|
|
}
|
|
if err := roster.Validate(true); !errors.Is(err, ErrInvalid) {
|
|
t.Fatalf("incomplete roster was confirmable: %v", err)
|
|
}
|
|
if err := roster.PlaceReserve("a", Tank, "reserve", 31); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := roster.MoveToReserve("b", "b-t"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := roster.MoveToEmpty("a", "reserve", "b", Tank, 31); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if roster.Teams[1].Slots[0].PlayerID != "reserve" {
|
|
t.Fatal("dragging into an empty role slot did not move the player")
|
|
}
|
|
roster.Teams[0].CaptainPlayerID = "a-d1"
|
|
roster.Teams[1].CaptainPlayerID = "reserve"
|
|
if err := roster.SwapAcrossRoles("a", "a-d1", "b", "reserve", 25, 33); err != nil {
|
|
t.Fatalf("cross-role drag swap failed: %v", err)
|
|
}
|
|
if roster.Teams[0].CaptainPlayerID != "" || roster.Teams[1].CaptainPlayerID != "" {
|
|
t.Fatal("captains that moved to another team were not cleared")
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|