package redisstate_test import ( "context" "sort" "testing" "galaxy/lobby/internal/adapters/redisstate" "galaxy/lobby/internal/domain/common" "galaxy/lobby/internal/ports" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func newGameTurnStatsStore(t *testing.T) (*redisstate.GameTurnStatsStore, *miniredis.Miniredis) { t.Helper() server := miniredis.RunT(t) client := redis.NewClient(&redis.Options{Addr: server.Addr()}) t.Cleanup(func() { _ = client.Close() }) store, err := redisstate.NewGameTurnStatsStore(client) require.NoError(t, err) return store, server } func TestGameTurnStatsStoreSaveInitialFreezesValues(t *testing.T) { store, _ := newGameTurnStatsStore(t) ctx := context.Background() gameID := common.GameID("game-stats-1") require.NoError(t, store.SaveInitial(ctx, gameID, []ports.PlayerInitialStats{ {UserID: "user-a", Planets: 3, Population: 100, ShipsBuilt: 7}, })) require.NoError(t, store.SaveInitial(ctx, gameID, []ports.PlayerInitialStats{ {UserID: "user-a", Planets: 99, Population: 9999, ShipsBuilt: 999}, })) aggregate, err := store.Load(ctx, gameID) require.NoError(t, err) require.Len(t, aggregate.Players, 1) assert.Equal(t, int64(3), aggregate.Players[0].InitialPlanets) assert.Equal(t, int64(100), aggregate.Players[0].InitialPopulation) assert.Equal(t, int64(7), aggregate.Players[0].InitialShipsBuilt) assert.Equal(t, int64(3), aggregate.Players[0].MaxPlanets) assert.Equal(t, int64(100), aggregate.Players[0].MaxPopulation) assert.Equal(t, int64(7), aggregate.Players[0].MaxShipsBuilt) } func TestGameTurnStatsStoreUpdateMaxRaisesOnly(t *testing.T) { store, _ := newGameTurnStatsStore(t) ctx := context.Background() gameID := common.GameID("game-stats-2") require.NoError(t, store.SaveInitial(ctx, gameID, []ports.PlayerInitialStats{ {UserID: "user-a", Planets: 3, Population: 100, ShipsBuilt: 7}, })) require.NoError(t, store.UpdateMax(ctx, gameID, []ports.PlayerObservedStats{ {UserID: "user-a", Planets: 5, Population: 80, ShipsBuilt: 9}, })) require.NoError(t, store.UpdateMax(ctx, gameID, []ports.PlayerObservedStats{ {UserID: "user-a", Planets: 4, Population: 60, ShipsBuilt: 8}, })) aggregate, err := store.Load(ctx, gameID) require.NoError(t, err) require.Len(t, aggregate.Players, 1) assert.Equal(t, int64(3), aggregate.Players[0].InitialPlanets) assert.Equal(t, int64(100), aggregate.Players[0].InitialPopulation) assert.Equal(t, int64(7), aggregate.Players[0].InitialShipsBuilt) assert.Equal(t, int64(5), aggregate.Players[0].MaxPlanets) assert.Equal(t, int64(100), aggregate.Players[0].MaxPopulation) assert.Equal(t, int64(9), aggregate.Players[0].MaxShipsBuilt) } func TestGameTurnStatsStoreUpdateMaxBeforeSaveInitial(t *testing.T) { store, _ := newGameTurnStatsStore(t) ctx := context.Background() gameID := common.GameID("game-stats-3") require.NoError(t, store.UpdateMax(ctx, gameID, []ports.PlayerObservedStats{ {UserID: "user-a", Planets: 4, Population: 50, ShipsBuilt: 1}, })) require.NoError(t, store.SaveInitial(ctx, gameID, []ports.PlayerInitialStats{ {UserID: "user-a", Planets: 99, Population: 99, ShipsBuilt: 99}, })) aggregate, err := store.Load(ctx, gameID) require.NoError(t, err) require.Len(t, aggregate.Players, 1) assert.Equal(t, int64(4), aggregate.Players[0].InitialPlanets) assert.Equal(t, int64(50), aggregate.Players[0].InitialPopulation) assert.Equal(t, int64(1), aggregate.Players[0].InitialShipsBuilt) assert.Equal(t, int64(4), aggregate.Players[0].MaxPlanets) assert.Equal(t, int64(50), aggregate.Players[0].MaxPopulation) assert.Equal(t, int64(1), aggregate.Players[0].MaxShipsBuilt) } func TestGameTurnStatsStoreLoadEmpty(t *testing.T) { store, _ := newGameTurnStatsStore(t) gameID := common.GameID("game-stats-empty") aggregate, err := store.Load(context.Background(), gameID) require.NoError(t, err) assert.Equal(t, gameID, aggregate.GameID) assert.Empty(t, aggregate.Players) } func TestGameTurnStatsStoreLoadSortsByUserID(t *testing.T) { store, _ := newGameTurnStatsStore(t) ctx := context.Background() gameID := common.GameID("game-stats-sorted") require.NoError(t, store.SaveInitial(ctx, gameID, []ports.PlayerInitialStats{ {UserID: "user-c", Planets: 1, Population: 1, ShipsBuilt: 1}, {UserID: "user-a", Planets: 2, Population: 2, ShipsBuilt: 2}, {UserID: "user-b", Planets: 3, Population: 3, ShipsBuilt: 3}, })) aggregate, err := store.Load(ctx, gameID) require.NoError(t, err) require.Len(t, aggregate.Players, 3) got := []string{aggregate.Players[0].UserID, aggregate.Players[1].UserID, aggregate.Players[2].UserID} expected := []string{"user-a", "user-b", "user-c"} require.True(t, sort.StringsAreSorted(got)) assert.Equal(t, expected, got) } func TestGameTurnStatsStoreDeleteRemovesEverything(t *testing.T) { store, server := newGameTurnStatsStore(t) ctx := context.Background() gameID := common.GameID("game-stats-del") require.NoError(t, store.SaveInitial(ctx, gameID, []ports.PlayerInitialStats{ {UserID: "user-a", Planets: 1, Population: 1, ShipsBuilt: 1}, {UserID: "user-b", Planets: 2, Population: 2, ShipsBuilt: 2}, })) require.NoError(t, store.Delete(ctx, gameID)) aggregate, err := store.Load(ctx, gameID) require.NoError(t, err) assert.Empty(t, aggregate.Players) keyspace := redisstate.Keyspace{} assert.False(t, server.Exists(keyspace.GameTurnStatsByGame(gameID))) assert.False(t, server.Exists(keyspace.GameTurnStat(gameID, "user-a"))) assert.False(t, server.Exists(keyspace.GameTurnStat(gameID, "user-b"))) } func TestGameTurnStatsStoreDeleteIsIdempotent(t *testing.T) { store, _ := newGameTurnStatsStore(t) ctx := context.Background() gameID := common.GameID("game-stats-del-noop") require.NoError(t, store.Delete(ctx, gameID)) require.NoError(t, store.Delete(ctx, gameID)) } func TestGameTurnStatsStoreRejectsInvalidInputs(t *testing.T) { store, _ := newGameTurnStatsStore(t) ctx := context.Background() gameID := common.GameID("game-stats-bad") err := store.SaveInitial(ctx, gameID, []ports.PlayerInitialStats{ {UserID: "", Planets: 1, Population: 1, ShipsBuilt: 1}, }) assert.Error(t, err) err = store.UpdateMax(ctx, gameID, []ports.PlayerObservedStats{ {UserID: "user-a", Planets: -1, Population: 1, ShipsBuilt: 1}, }) assert.Error(t, err) _, err = store.Load(ctx, common.GameID("")) assert.Error(t, err) }