package app import ( "context" "net" "net/http" "testing" "time" "galaxy/lobby/internal/api/internalhttp" "galaxy/lobby/internal/api/publichttp" "galaxy/lobby/internal/config" "github.com/alicebob/miniredis/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // newTestConfig builds a valid Config that listens on ephemeral ports and a // miniredis instance provided by redisServer. func newTestConfig(t *testing.T, redisAddr string) config.Config { t.Helper() reserve := func() string { listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) addr := listener.Addr().String() require.NoError(t, listener.Close()) return addr } cfg := config.DefaultConfig() cfg.Redis.Addr = redisAddr cfg.UserService.BaseURL = "http://127.0.0.1:1" cfg.GM.BaseURL = "http://127.0.0.1:1" cfg.PublicHTTP.Addr = reserve() cfg.InternalHTTP.Addr = reserve() return cfg } func TestNewRuntimeValidatesContext(t *testing.T) { t.Parallel() _, err := NewRuntime(nil, config.Config{}, nil) //nolint:staticcheck // test exercises the nil-context guard. require.Error(t, err) require.Contains(t, err.Error(), "nil context") } func TestNewRuntimeRejectsInvalidConfig(t *testing.T) { t.Parallel() _, err := NewRuntime(context.Background(), config.Config{}, nil) require.Error(t, err) require.Contains(t, err.Error(), "new lobby runtime") } func TestNewRuntimeSucceedsWithMiniredis(t *testing.T) { redisServer := miniredis.RunT(t) runtime, err := NewRuntime(context.Background(), newTestConfig(t, redisServer.Addr()), nil) require.NoError(t, err) require.NotNil(t, runtime) t.Cleanup(func() { _ = runtime.Close() }) assert.NotNil(t, runtime.PublicServer()) assert.NotNil(t, runtime.InternalServer()) } func TestNewRuntimeWiresRaceNameDirectory(t *testing.T) { redisServer := miniredis.RunT(t) runtime, err := NewRuntime(context.Background(), newTestConfig(t, redisServer.Addr()), nil) require.NoError(t, err) t.Cleanup(func() { _ = runtime.Close() }) require.NotNil(t, runtime.wiring) assert.NotNil(t, runtime.wiring.raceNameDirectory) } func TestNewRuntimeFailsWhenRedisUnreachable(t *testing.T) { t.Parallel() cfg := newTestConfig(t, "127.0.0.1:1") // guaranteed unreachable cfg.Redis.OperationTimeout = 100 * time.Millisecond _, err := NewRuntime(context.Background(), cfg, nil) require.Error(t, err) require.Contains(t, err.Error(), "ping redis") } func TestRuntimeCloseIsIdempotent(t *testing.T) { redisServer := miniredis.RunT(t) runtime, err := NewRuntime(context.Background(), newTestConfig(t, redisServer.Addr()), nil) require.NoError(t, err) require.NoError(t, runtime.Close()) require.NoError(t, runtime.Close()) } func TestRuntimeRunServesProbesAndStopsOnCancel(t *testing.T) { redisServer := miniredis.RunT(t) cfg := newTestConfig(t, redisServer.Addr()) runtime, err := NewRuntime(context.Background(), cfg, nil) require.NoError(t, err) t.Cleanup(func() { _ = runtime.Close() }) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) runErr := make(chan error, 1) go func() { runErr <- runtime.Run(ctx) }() require.Eventually(t, func() bool { return runtime.PublicServer().Addr() != "" && runtime.InternalServer().Addr() != "" }, 2*time.Second, 10*time.Millisecond) for _, probe := range []struct { label string url string }{ {"public healthz", "http://" + runtime.PublicServer().Addr() + publichttp.HealthzPath}, {"public readyz", "http://" + runtime.PublicServer().Addr() + publichttp.ReadyzPath}, {"internal healthz", "http://" + runtime.InternalServer().Addr() + internalhttp.HealthzPath}, {"internal readyz", "http://" + runtime.InternalServer().Addr() + internalhttp.ReadyzPath}, } { resp, err := http.Get(probe.url) require.NoError(t, err, probe.label) _ = resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, probe.label) } cancel() select { case err := <-runErr: require.NoError(t, err) case <-time.After(3 * time.Second): t.Fatal("runtime did not stop after cancel") } } func TestRuntimeRunNilContext(t *testing.T) { t.Parallel() var runtime *Runtime require.Error(t, runtime.Run(context.Background())) }