158 lines
4.7 KiB
Go
158 lines
4.7 KiB
Go
package healthsnapshotstore_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/rtmanager/internal/adapters/postgres/healthsnapshotstore"
|
|
"galaxy/rtmanager/internal/adapters/postgres/internal/pgtest"
|
|
"galaxy/rtmanager/internal/domain/health"
|
|
"galaxy/rtmanager/internal/domain/runtime"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMain(m *testing.M) { pgtest.RunMain(m) }
|
|
|
|
func newStore(t *testing.T) *healthsnapshotstore.Store {
|
|
t.Helper()
|
|
pgtest.TruncateAll(t)
|
|
store, err := healthsnapshotstore.New(healthsnapshotstore.Config{
|
|
DB: pgtest.Ensure(t).Pool(),
|
|
OperationTimeout: pgtest.OperationTimeout,
|
|
})
|
|
require.NoError(t, err)
|
|
return store
|
|
}
|
|
|
|
func probeFailedSnapshot(gameID string, observedAt time.Time) health.HealthSnapshot {
|
|
return health.HealthSnapshot{
|
|
GameID: gameID,
|
|
ContainerID: "container-1",
|
|
Status: health.SnapshotStatusProbeFailed,
|
|
Source: health.SnapshotSourceProbe,
|
|
Details: json.RawMessage(`{"consecutive_failures":3,"last_status":503,"last_error":"timeout"}`),
|
|
ObservedAt: observedAt,
|
|
}
|
|
}
|
|
|
|
func TestUpsertAndGetRoundTrip(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
snapshot := probeFailedSnapshot("game-001",
|
|
time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC))
|
|
require.NoError(t, store.Upsert(ctx, snapshot))
|
|
|
|
got, err := store.Get(ctx, "game-001")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, snapshot.GameID, got.GameID)
|
|
assert.Equal(t, snapshot.ContainerID, got.ContainerID)
|
|
assert.Equal(t, snapshot.Status, got.Status)
|
|
assert.Equal(t, snapshot.Source, got.Source)
|
|
assert.JSONEq(t, string(snapshot.Details), string(got.Details))
|
|
assert.True(t, snapshot.ObservedAt.Equal(got.ObservedAt))
|
|
assert.Equal(t, time.UTC, got.ObservedAt.Location())
|
|
}
|
|
|
|
func TestUpsertOverwritesPriorSnapshot(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
first := probeFailedSnapshot("game-001",
|
|
time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC))
|
|
require.NoError(t, store.Upsert(ctx, first))
|
|
|
|
second := health.HealthSnapshot{
|
|
GameID: "game-001",
|
|
ContainerID: "container-2",
|
|
Status: health.SnapshotStatusHealthy,
|
|
Source: health.SnapshotSourceInspect,
|
|
Details: json.RawMessage(`{"restart_count":0,"state":"running"}`),
|
|
ObservedAt: first.ObservedAt.Add(time.Minute),
|
|
}
|
|
require.NoError(t, store.Upsert(ctx, second))
|
|
|
|
got, err := store.Get(ctx, "game-001")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "container-2", got.ContainerID)
|
|
assert.Equal(t, health.SnapshotStatusHealthy, got.Status)
|
|
assert.Equal(t, health.SnapshotSourceInspect, got.Source)
|
|
assert.JSONEq(t, string(second.Details), string(got.Details))
|
|
assert.True(t, second.ObservedAt.Equal(got.ObservedAt))
|
|
}
|
|
|
|
func TestGetReturnsNotFound(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
_, err := store.Get(ctx, "game-missing")
|
|
require.ErrorIs(t, err, runtime.ErrNotFound)
|
|
}
|
|
|
|
func TestUpsertEmptyDetailsRoundTripsAsEmptyObject(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
snapshot := probeFailedSnapshot("game-001",
|
|
time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC))
|
|
snapshot.Details = nil
|
|
require.NoError(t, store.Upsert(ctx, snapshot))
|
|
|
|
got, err := store.Get(ctx, "game-001")
|
|
require.NoError(t, err)
|
|
assert.JSONEq(t, "{}", string(got.Details),
|
|
"empty json.RawMessage must round-trip as the SQL default {}, got %q",
|
|
string(got.Details))
|
|
}
|
|
|
|
func TestUpsertValidatesSnapshot(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
mutate func(*health.HealthSnapshot)
|
|
}{
|
|
{"empty game id", func(s *health.HealthSnapshot) { s.GameID = "" }},
|
|
{"unknown status", func(s *health.HealthSnapshot) { s.Status = "exotic" }},
|
|
{"unknown source", func(s *health.HealthSnapshot) { s.Source = "exotic" }},
|
|
{"zero observed at", func(s *health.HealthSnapshot) { s.ObservedAt = time.Time{} }},
|
|
{"invalid json details", func(s *health.HealthSnapshot) {
|
|
s.Details = json.RawMessage("not json")
|
|
}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
snapshot := probeFailedSnapshot("game-001",
|
|
time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC))
|
|
tt.mutate(&snapshot)
|
|
err := store.Upsert(ctx, snapshot)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRejectsEmptyGameID(t *testing.T) {
|
|
ctx := context.Background()
|
|
store := newStore(t)
|
|
|
|
_, err := store.Get(ctx, "")
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestNewRejectsNilDB(t *testing.T) {
|
|
_, err := healthsnapshotstore.New(healthsnapshotstore.Config{OperationTimeout: time.Second})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestNewRejectsNonPositiveTimeout(t *testing.T) {
|
|
_, err := healthsnapshotstore.New(healthsnapshotstore.Config{
|
|
DB: pgtest.Ensure(t).Pool(),
|
|
})
|
|
require.Error(t, err)
|
|
}
|