Files
galaxy-game/gamemaster/internal/app/app_test.go
T
2026-05-03 07:59:03 +02:00

126 lines
2.6 KiB
Go

package app
import (
"context"
"errors"
"strings"
"sync/atomic"
"testing"
"time"
"galaxy/gamemaster/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type fakeComponent struct {
runErr error
shutdownErr error
runHook func(context.Context) error
shutdownHook func(context.Context) error
runCount atomic.Int32
downCount atomic.Int32
blockForCtx bool
}
func (component *fakeComponent) Run(ctx context.Context) error {
component.runCount.Add(1)
if component.runHook != nil {
return component.runHook(ctx)
}
if component.blockForCtx {
<-ctx.Done()
return ctx.Err()
}
return component.runErr
}
func (component *fakeComponent) Shutdown(ctx context.Context) error {
component.downCount.Add(1)
if component.shutdownHook != nil {
return component.shutdownHook(ctx)
}
return component.shutdownErr
}
func newCfg() config.Config {
return config.Config{ShutdownTimeout: time.Second}
}
func TestAppRunWithoutComponentsBlocksUntilContextDone(t *testing.T) {
t.Parallel()
app := New(newCfg())
ctx, cancel := context.WithCancel(context.Background())
cancel()
require.NoError(t, app.Run(ctx))
}
func TestAppRunReturnsOnContextCancel(t *testing.T) {
t.Parallel()
component := &fakeComponent{blockForCtx: true}
app := New(newCfg(), component)
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(10 * time.Millisecond)
cancel()
}()
require.NoError(t, app.Run(ctx))
assert.EqualValues(t, 1, component.runCount.Load())
assert.EqualValues(t, 1, component.downCount.Load())
}
func TestAppRunPropagatesComponentFailure(t *testing.T) {
t.Parallel()
failure := errors.New("boom")
component := &fakeComponent{runErr: failure}
app := New(newCfg(), component)
err := app.Run(context.Background())
require.Error(t, err)
require.ErrorIs(t, err, failure)
assert.EqualValues(t, 1, component.downCount.Load())
}
func TestAppRunFailsOnNilContext(t *testing.T) {
t.Parallel()
app := New(newCfg())
var ctx context.Context
require.Error(t, app.Run(ctx))
}
func TestAppRunFailsOnNonPositiveShutdownTimeout(t *testing.T) {
t.Parallel()
app := New(config.Config{}, &fakeComponent{})
require.Error(t, app.Run(context.Background()))
}
func TestAppRunFailsOnNilComponent(t *testing.T) {
t.Parallel()
app := New(newCfg(), nil)
require.Error(t, app.Run(context.Background()))
}
func TestAppRunFlagsCleanExitBeforeShutdown(t *testing.T) {
t.Parallel()
component := &fakeComponent{}
app := New(newCfg(), component)
err := app.Run(context.Background())
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "exited without error"))
}