191 lines
5.8 KiB
Go
191 lines
5.8 KiB
Go
package operationlog_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/gamemaster/internal/adapters/postgres/internal/pgtest"
|
|
"galaxy/gamemaster/internal/adapters/postgres/operationlog"
|
|
"galaxy/gamemaster/internal/domain/operation"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMain(m *testing.M) { pgtest.RunMain(m) }
|
|
|
|
func newStore(t *testing.T) *operationlog.Store {
|
|
t.Helper()
|
|
pgtest.TruncateAll(t)
|
|
store, err := operationlog.New(operationlog.Config{
|
|
DB: pgtest.Ensure(t).Pool(),
|
|
OperationTimeout: pgtest.OperationTimeout,
|
|
})
|
|
require.NoError(t, err)
|
|
return store
|
|
}
|
|
|
|
func successEntry(gameID string, kind operation.OpKind, source operation.OpSource, startedAt time.Time) operation.OperationEntry {
|
|
finishedAt := startedAt.Add(50 * time.Millisecond)
|
|
return operation.OperationEntry{
|
|
GameID: gameID,
|
|
OpKind: kind,
|
|
OpSource: source,
|
|
SourceRef: "req-001",
|
|
Outcome: operation.OutcomeSuccess,
|
|
StartedAt: startedAt,
|
|
FinishedAt: &finishedAt,
|
|
}
|
|
}
|
|
|
|
func TestNewRejectsInvalidConfig(t *testing.T) {
|
|
_, err := operationlog.New(operationlog.Config{})
|
|
require.Error(t, err)
|
|
|
|
store, err := operationlog.New(operationlog.Config{
|
|
DB: pgtest.Ensure(t).Pool(),
|
|
OperationTimeout: 0,
|
|
})
|
|
require.Error(t, err)
|
|
require.Nil(t, store)
|
|
}
|
|
|
|
func TestAppendSuccessEntry(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
at := time.Date(2026, time.April, 27, 12, 0, 0, 0, time.UTC)
|
|
entry := successEntry("game-001", operation.OpKindRegisterRuntime, operation.OpSourceLobbyInternal, at)
|
|
|
|
id, err := store.Append(ctx, entry)
|
|
require.NoError(t, err)
|
|
assert.Greater(t, id, int64(0))
|
|
|
|
entries, err := store.ListByGame(ctx, "game-001", 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, entries, 1)
|
|
got := entries[0]
|
|
assert.Equal(t, id, got.ID)
|
|
assert.Equal(t, entry.GameID, got.GameID)
|
|
assert.Equal(t, entry.OpKind, got.OpKind)
|
|
assert.Equal(t, entry.OpSource, got.OpSource)
|
|
assert.Equal(t, entry.SourceRef, got.SourceRef)
|
|
assert.Equal(t, operation.OutcomeSuccess, got.Outcome)
|
|
assert.Empty(t, got.ErrorCode)
|
|
assert.Empty(t, got.ErrorMessage)
|
|
assert.True(t, got.StartedAt.Equal(at))
|
|
require.NotNil(t, got.FinishedAt)
|
|
assert.Equal(t, time.UTC, got.StartedAt.Location())
|
|
assert.Equal(t, time.UTC, got.FinishedAt.Location())
|
|
}
|
|
|
|
func TestAppendFailureEntry(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
at := time.Date(2026, time.April, 27, 12, 0, 0, 0, time.UTC)
|
|
finishedAt := at.Add(time.Second)
|
|
entry := operation.OperationEntry{
|
|
GameID: "game-001",
|
|
OpKind: operation.OpKindTurnGeneration,
|
|
OpSource: operation.OpSourceAdminRest,
|
|
Outcome: operation.OutcomeFailure,
|
|
ErrorCode: "engine_unreachable",
|
|
ErrorMessage: "connection refused",
|
|
StartedAt: at,
|
|
FinishedAt: &finishedAt,
|
|
}
|
|
|
|
_, err := store.Append(ctx, entry)
|
|
require.NoError(t, err)
|
|
|
|
got, err := store.ListByGame(ctx, "game-001", 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, got, 1)
|
|
assert.Equal(t, operation.OutcomeFailure, got[0].Outcome)
|
|
assert.Equal(t, "engine_unreachable", got[0].ErrorCode)
|
|
assert.Equal(t, "connection refused", got[0].ErrorMessage)
|
|
}
|
|
|
|
func TestAppendIDsAreMonotonic(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
at := time.Date(2026, time.April, 27, 12, 0, 0, 0, time.UTC)
|
|
id1, err := store.Append(ctx, successEntry("game-001", operation.OpKindRegisterRuntime, operation.OpSourceLobbyInternal, at))
|
|
require.NoError(t, err)
|
|
|
|
id2, err := store.Append(ctx, successEntry("game-001", operation.OpKindTurnGeneration, operation.OpSourceLobbyInternal, at.Add(time.Second)))
|
|
require.NoError(t, err)
|
|
|
|
assert.Greater(t, id2, id1, "bigserial ids must be monotonic across appends")
|
|
}
|
|
|
|
func TestAppendValidationRejection(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
bad := operation.OperationEntry{}
|
|
_, err := store.Append(ctx, bad)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestListByGameOrderingDesc(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
at := time.Date(2026, time.April, 27, 12, 0, 0, 0, time.UTC)
|
|
_, err := store.Append(ctx, successEntry("game-001", operation.OpKindRegisterRuntime, operation.OpSourceLobbyInternal, at))
|
|
require.NoError(t, err)
|
|
_, err = store.Append(ctx, successEntry("game-001", operation.OpKindTurnGeneration, operation.OpSourceLobbyInternal, at.Add(time.Second)))
|
|
require.NoError(t, err)
|
|
_, err = store.Append(ctx, successEntry("game-001", operation.OpKindStop, operation.OpSourceAdminRest, at.Add(2*time.Second)))
|
|
require.NoError(t, err)
|
|
|
|
got, err := store.ListByGame(ctx, "game-001", 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, got, 3)
|
|
assert.Equal(t, operation.OpKindStop, got[0].OpKind)
|
|
assert.Equal(t, operation.OpKindTurnGeneration, got[1].OpKind)
|
|
assert.Equal(t, operation.OpKindRegisterRuntime, got[2].OpKind)
|
|
}
|
|
|
|
func TestListByGameRespectsLimit(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
at := time.Date(2026, time.April, 27, 12, 0, 0, 0, time.UTC)
|
|
for index := range 5 {
|
|
_, err := store.Append(ctx, successEntry("game-001", operation.OpKindTurnGeneration, operation.OpSourceLobbyInternal, at.Add(time.Duration(index)*time.Second)))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
got, err := store.ListByGame(ctx, "game-001", 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, got, 2)
|
|
}
|
|
|
|
func TestListByGameUnknownGame(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
got, err := store.ListByGame(ctx, "unknown-game", 10)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, got)
|
|
}
|
|
|
|
func TestListByGameRejectsBadArgs(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
_, err := store.ListByGame(ctx, "", 10)
|
|
require.Error(t, err)
|
|
|
|
_, err = store.ListByGame(ctx, "game-001", 0)
|
|
require.Error(t, err)
|
|
|
|
_, err = store.ListByGame(ctx, "game-001", -1)
|
|
require.Error(t, err)
|
|
}
|