208 lines
6.2 KiB
Go
208 lines
6.2 KiB
Go
package operationlogstore_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/rtmanager/internal/adapters/postgres/internal/pgtest"
|
|
"galaxy/rtmanager/internal/adapters/postgres/operationlogstore"
|
|
"galaxy/rtmanager/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) *operationlogstore.Store {
|
|
t.Helper()
|
|
pgtest.TruncateAll(t)
|
|
store, err := operationlogstore.New(operationlogstore.Config{
|
|
DB: pgtest.Ensure(t).Pool(),
|
|
OperationTimeout: pgtest.OperationTimeout,
|
|
})
|
|
require.NoError(t, err)
|
|
return store
|
|
}
|
|
|
|
func successStartEntry(gameID string, startedAt time.Time, sourceRef string) operation.OperationEntry {
|
|
finishedAt := startedAt.Add(time.Second)
|
|
return operation.OperationEntry{
|
|
GameID: gameID,
|
|
OpKind: operation.OpKindStart,
|
|
OpSource: operation.OpSourceLobbyStream,
|
|
SourceRef: sourceRef,
|
|
ImageRef: "galaxy/game:v1.2.3",
|
|
ContainerID: "container-1",
|
|
Outcome: operation.OutcomeSuccess,
|
|
StartedAt: startedAt,
|
|
FinishedAt: &finishedAt,
|
|
}
|
|
}
|
|
|
|
func TestAppendReturnsPositiveIDs(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
startedAt := time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC)
|
|
id1, err := store.Append(ctx, successStartEntry("game-001", startedAt, "1700000000000-0"))
|
|
require.NoError(t, err)
|
|
assert.Greater(t, id1, int64(0))
|
|
|
|
id2, err := store.Append(ctx, successStartEntry("game-001", startedAt.Add(time.Minute), "1700000000001-0"))
|
|
require.NoError(t, err)
|
|
assert.Greater(t, id2, id1)
|
|
}
|
|
|
|
func TestAppendValidatesEntry(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
mutate func(*operation.OperationEntry)
|
|
}{
|
|
{"empty game id", func(e *operation.OperationEntry) { e.GameID = "" }},
|
|
{"unknown op kind", func(e *operation.OperationEntry) { e.OpKind = "exotic" }},
|
|
{"unknown op source", func(e *operation.OperationEntry) { e.OpSource = "exotic" }},
|
|
{"unknown outcome", func(e *operation.OperationEntry) { e.Outcome = "exotic" }},
|
|
{"zero started at", func(e *operation.OperationEntry) { e.StartedAt = time.Time{} }},
|
|
{"failure without error code", func(e *operation.OperationEntry) {
|
|
e.Outcome = operation.OutcomeFailure
|
|
e.ErrorCode = ""
|
|
}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
entry := successStartEntry("game-001",
|
|
time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC), "ref")
|
|
tt.mutate(&entry)
|
|
_, err := store.Append(ctx, entry)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListByGameReturnsEntriesNewestFirst(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
base := time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC)
|
|
for index := range 3 {
|
|
_, err := store.Append(ctx, successStartEntry("game-001",
|
|
base.Add(time.Duration(index)*time.Minute),
|
|
"ref-game-001-"))
|
|
require.NoError(t, err)
|
|
}
|
|
// Foreign-game entry must not appear in the list.
|
|
_, err := store.Append(ctx, successStartEntry("game-other", base, "ref-other"))
|
|
require.NoError(t, err)
|
|
|
|
entries, err := store.ListByGame(ctx, "game-001", 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, entries, 3)
|
|
for index := range 2 {
|
|
assert.True(t,
|
|
!entries[index].StartedAt.Before(entries[index+1].StartedAt),
|
|
"entries must be ordered started_at DESC; got %s before %s",
|
|
entries[index].StartedAt, entries[index+1].StartedAt,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestListByGameRespectsLimit(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
base := time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC)
|
|
for index := range 5 {
|
|
_, err := store.Append(ctx, successStartEntry("game-001",
|
|
base.Add(time.Duration(index)*time.Minute), "ref"))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
entries, err := store.ListByGame(ctx, "game-001", 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, entries, 2)
|
|
}
|
|
|
|
func TestListByGameReturnsEmptyForUnknownGame(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
entries, err := store.ListByGame(ctx, "game-missing", 10)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, entries)
|
|
}
|
|
|
|
func TestListByGameRejectsInvalidArgs(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", -3)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestAppendRoundTripsAllFields(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
startedAt := time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC)
|
|
finishedAt := startedAt.Add(2 * time.Second)
|
|
original := operation.OperationEntry{
|
|
GameID: "game-001",
|
|
OpKind: operation.OpKindStop,
|
|
OpSource: operation.OpSourceGMRest,
|
|
SourceRef: "request-7",
|
|
ImageRef: "galaxy/game:v2.0.0",
|
|
ContainerID: "container-X",
|
|
Outcome: operation.OutcomeFailure,
|
|
ErrorCode: "container_start_failed",
|
|
ErrorMessage: "stop deadline exceeded",
|
|
StartedAt: startedAt,
|
|
FinishedAt: &finishedAt,
|
|
}
|
|
id, err := store.Append(ctx, original)
|
|
require.NoError(t, err)
|
|
|
|
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, original.GameID, got.GameID)
|
|
assert.Equal(t, original.OpKind, got.OpKind)
|
|
assert.Equal(t, original.OpSource, got.OpSource)
|
|
assert.Equal(t, original.SourceRef, got.SourceRef)
|
|
assert.Equal(t, original.ImageRef, got.ImageRef)
|
|
assert.Equal(t, original.ContainerID, got.ContainerID)
|
|
assert.Equal(t, original.Outcome, got.Outcome)
|
|
assert.Equal(t, original.ErrorCode, got.ErrorCode)
|
|
assert.Equal(t, original.ErrorMessage, got.ErrorMessage)
|
|
assert.True(t, original.StartedAt.Equal(got.StartedAt))
|
|
require.NotNil(t, got.FinishedAt)
|
|
assert.True(t, original.FinishedAt.Equal(*got.FinishedAt))
|
|
assert.Equal(t, time.UTC, got.StartedAt.Location())
|
|
assert.Equal(t, time.UTC, got.FinishedAt.Location())
|
|
}
|
|
|
|
func TestNewRejectsNilDB(t *testing.T) {
|
|
_, err := operationlogstore.New(operationlogstore.Config{OperationTimeout: time.Second})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestNewRejectsNonPositiveTimeout(t *testing.T) {
|
|
_, err := operationlogstore.New(operationlogstore.Config{
|
|
DB: pgtest.Ensure(t).Pool(),
|
|
})
|
|
require.Error(t, err)
|
|
}
|