Files
mixmaker/backend/internal/domain/workflow_test.go

128 lines
4.1 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 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
}