feat: game lobby service

This commit is contained in:
Ilia Denisov
2026-04-25 23:20:55 +02:00
committed by GitHub
parent 32dc29359a
commit 48b0056b49
336 changed files with 57074 additions and 1418 deletions
+177
View File
@@ -0,0 +1,177 @@
package game
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStatusIsKnown(t *testing.T) {
for _, status := range []Status{
StatusDraft,
StatusEnrollmentOpen,
StatusReadyToStart,
StatusStarting,
StatusStartFailed,
StatusRunning,
StatusPaused,
StatusFinished,
StatusCancelled,
} {
assert.Truef(t, status.IsKnown(), "expected %q known", status)
}
assert.False(t, Status("").IsKnown())
assert.False(t, Status("unknown").IsKnown())
}
func TestStatusIsTerminal(t *testing.T) {
assert.True(t, StatusFinished.IsTerminal())
assert.True(t, StatusCancelled.IsTerminal())
for _, status := range []Status{
StatusDraft,
StatusEnrollmentOpen,
StatusReadyToStart,
StatusStarting,
StatusStartFailed,
StatusRunning,
StatusPaused,
} {
assert.Falsef(t, status.IsTerminal(), "expected %q non-terminal", status)
}
}
func TestTriggerIsKnown(t *testing.T) {
for _, trigger := range []Trigger{
TriggerCommand,
TriggerManual,
TriggerDeadline,
TriggerGap,
TriggerRuntimeEvent,
TriggerExternalBlock,
} {
assert.Truef(t, trigger.IsKnown(), "expected %q known", trigger)
}
assert.False(t, Trigger("").IsKnown())
assert.False(t, Trigger("bogus").IsKnown())
}
func TestTransitionHappyPathsCoverFrozenTable(t *testing.T) {
cases := []struct {
from Status
to Status
triggers []Trigger
}{
{StatusDraft, StatusEnrollmentOpen, []Trigger{TriggerCommand}},
{StatusEnrollmentOpen, StatusReadyToStart, []Trigger{TriggerManual, TriggerDeadline, TriggerGap}},
{StatusReadyToStart, StatusStarting, []Trigger{TriggerCommand}},
{StatusStarting, StatusRunning, []Trigger{TriggerRuntimeEvent}},
{StatusStarting, StatusPaused, []Trigger{TriggerRuntimeEvent}},
{StatusStarting, StatusStartFailed, []Trigger{TriggerRuntimeEvent}},
{StatusStartFailed, StatusReadyToStart, []Trigger{TriggerCommand}},
{StatusRunning, StatusPaused, []Trigger{TriggerCommand}},
{StatusRunning, StatusFinished, []Trigger{TriggerRuntimeEvent}},
{StatusPaused, StatusRunning, []Trigger{TriggerCommand}},
{StatusPaused, StatusFinished, []Trigger{TriggerRuntimeEvent}},
{StatusDraft, StatusCancelled, []Trigger{TriggerCommand, TriggerExternalBlock}},
{StatusEnrollmentOpen, StatusCancelled, []Trigger{TriggerCommand, TriggerExternalBlock}},
{StatusReadyToStart, StatusCancelled, []Trigger{TriggerCommand, TriggerExternalBlock}},
{StatusStartFailed, StatusCancelled, []Trigger{TriggerCommand, TriggerExternalBlock}},
{StatusStarting, StatusCancelled, []Trigger{TriggerExternalBlock}},
{StatusRunning, StatusCancelled, []Trigger{TriggerExternalBlock}},
{StatusPaused, StatusCancelled, []Trigger{TriggerExternalBlock}},
}
for _, tc := range cases {
for _, trigger := range tc.triggers {
t.Run(string(tc.from)+"->"+string(tc.to)+"/"+string(trigger), func(t *testing.T) {
require.NoError(t, Transition(tc.from, tc.to, trigger))
})
}
}
}
func TestTransitionRejectsUnknownPair(t *testing.T) {
err := Transition(StatusDraft, StatusRunning, TriggerCommand)
require.Error(t, err)
assert.True(t, errors.Is(err, ErrInvalidTransition))
var typed *InvalidTransitionError
require.True(t, errors.As(err, &typed))
assert.Equal(t, StatusDraft, typed.From)
assert.Equal(t, StatusRunning, typed.To)
assert.Equal(t, TriggerCommand, typed.Trigger)
}
func TestTransitionRejectsWrongTrigger(t *testing.T) {
err := Transition(StatusDraft, StatusEnrollmentOpen, TriggerDeadline)
require.Error(t, err)
assert.True(t, errors.Is(err, ErrInvalidTransition))
}
func TestTransitionRejectsUnknownStatusOrTrigger(t *testing.T) {
require.Error(t, Transition(Status("bogus"), StatusEnrollmentOpen, TriggerCommand))
require.Error(t, Transition(StatusDraft, Status("bogus"), TriggerCommand))
require.Error(t, Transition(StatusDraft, StatusEnrollmentOpen, Trigger("bogus")))
}
func TestTransitionsOutOfTerminalStatusAllRejected(t *testing.T) {
triggers := []Trigger{
TriggerCommand,
TriggerManual,
TriggerDeadline,
TriggerGap,
TriggerRuntimeEvent,
TriggerExternalBlock,
}
for _, from := range []Status{StatusFinished, StatusCancelled} {
for _, to := range []Status{
StatusDraft,
StatusEnrollmentOpen,
StatusReadyToStart,
StatusStarting,
StatusStartFailed,
StatusRunning,
StatusPaused,
StatusFinished,
StatusCancelled,
} {
for _, trigger := range triggers {
if from == to {
continue
}
err := Transition(from, to, trigger)
require.Errorf(t, err, "%s->%s via %s should be rejected", from, to, trigger)
}
}
}
}
func TestAllowedTransitionsSnapshotMatchesTable(t *testing.T) {
snapshot := AllowedTransitions()
count := 0
for _, inner := range snapshot {
count += len(inner)
}
assert.Equal(t, len(allowedTransitions), count)
for key, triggers := range allowedTransitions {
inner, ok := snapshot[key.from]
require.Truef(t, ok, "expected from=%s in snapshot", key.from)
list, ok := inner[key.to]
require.Truef(t, ok, "expected to=%s under from=%s", key.to, key.from)
for trigger := range triggers {
assert.Containsf(t, list, trigger, "missing trigger %q for %s->%s", trigger, key.from, key.to)
}
}
}