133 lines
7.0 KiB
Go
133 lines
7.0 KiB
Go
package repo
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"browser.local/platform/domain"
|
|
)
|
|
|
|
func TestGameClientBridgeCommandRepositoryIdempotencyAndRetention(t *testing.T) {
|
|
store := NewMemoryStore()
|
|
now := time.Now().UTC()
|
|
command := domain.GameClientBridgeCommand{ID: "command-1", ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", CommandType: "announcement.send", IdempotencyKey: "same-key", RequesterID: "user-1", State: domain.GameClientBridgeCommandSucceeded, Payload: map[string]any{"nested": map[string]any{"value": "original"}}, ExpiresAt: now.Add(-time.Minute), CompletedAt: now.Add(-time.Minute)}
|
|
if err := store.GameClientBridgeCommands().Create(command); err != nil {
|
|
t.Fatalf("create command: %v", err)
|
|
}
|
|
command.Payload["nested"].(map[string]any)["value"] = "mutated"
|
|
loaded, err := store.GameClientBridgeCommands().GetByIdempotency("server-1", "user-1", "announcement.send", "same-key")
|
|
if err != nil {
|
|
t.Fatalf("get by idempotency: %v", err)
|
|
}
|
|
if loaded.Payload["nested"].(map[string]any)["value"] != "original" {
|
|
t.Fatal("repository did not isolate nested payload")
|
|
}
|
|
for _, lookup := range [][4]string{{"server-2", "user-1", "announcement.send", "same-key"}, {"server-1", "user-2", "announcement.send", "same-key"}, {"server-1", "user-1", "diagnostic.safe", "same-key"}} {
|
|
if _, err := store.GameClientBridgeCommands().GetByIdempotency(lookup[0], lookup[1], lookup[2], lookup[3]); !errors.Is(err, ErrNotFound) {
|
|
t.Fatalf("idempotency scope leaked for %v: %v", lookup, err)
|
|
}
|
|
}
|
|
retained, err := store.GameClientBridgeCommands().List(domain.GameClientBridgeCommandFilter{CompletedBefore: now})
|
|
if err != nil || len(retained) != 1 {
|
|
t.Fatalf("completed retention query: len=%d err=%v", len(retained), err)
|
|
}
|
|
}
|
|
|
|
func TestGameClientBridgeSnapshotRepositoryOrderingLimitAndStreams(t *testing.T) {
|
|
store := NewMemoryStore()
|
|
now := time.Now().UTC()
|
|
for _, snapshot := range []domain.GameClientBridgeSnapshot{
|
|
{ID: "snapshot-1", ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", Type: "players", StreamKey: "current", Sequence: 1, ObservedAt: now.Add(-time.Minute), ExpiresAt: now.Add(time.Hour)},
|
|
{ID: "snapshot-2", ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", Type: "players", StreamKey: "current", Sequence: 2, ObservedAt: now, ExpiresAt: now.Add(time.Hour)},
|
|
{ID: "snapshot-expired", ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", Type: "vehicles", StreamKey: "current", Sequence: 1, ObservedAt: now, ExpiresAt: now.Add(-time.Minute)},
|
|
} {
|
|
if err := store.GameClientBridgeSnapshots().Create(snapshot); err != nil {
|
|
t.Fatalf("create snapshot: %v", err)
|
|
}
|
|
}
|
|
items, err := store.GameClientBridgeSnapshots().List(domain.GameClientBridgeSnapshotFilter{ServerInstanceID: "server-1", Type: "players", Limit: 1})
|
|
if err != nil || len(items) != 1 || items[0].Sequence != 2 {
|
|
t.Fatalf("newest snapshot query: %#v err=%v", items, err)
|
|
}
|
|
expired, err := store.GameClientBridgeSnapshots().List(domain.GameClientBridgeSnapshotFilter{ExpiresBefore: now})
|
|
if err != nil || len(expired) != 1 || expired[0].ID != "snapshot-expired" {
|
|
t.Fatalf("snapshot retention query: %#v err=%v", expired, err)
|
|
}
|
|
for _, stream := range []domain.GameClientBridgeSnapshotStream{
|
|
{ID: "stream-players", ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", Type: "players", StreamKey: "current", LatestSequence: 2},
|
|
{ID: "stream-vehicles", ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", Type: "vehicles", StreamKey: "current", LatestSequence: 7},
|
|
} {
|
|
if err := store.GameClientBridgeSnapshotStreams().Create(stream); err != nil {
|
|
t.Fatalf("create stream: %v", err)
|
|
}
|
|
}
|
|
streams, err := store.GameClientBridgeSnapshotStreams().List(domain.GameClientBridgeSnapshotStreamFilter{ServerInstanceID: "server-1"})
|
|
if err != nil || len(streams) != 2 || streams[0].LatestSequence == streams[1].LatestSequence {
|
|
t.Fatalf("independent stream sequences: %#v err=%v", streams, err)
|
|
}
|
|
}
|
|
|
|
func TestGameClientBridgeFileStorePersistsAndDeletes(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "metadata.json")
|
|
store, err := NewFileStore(path)
|
|
if err != nil {
|
|
t.Fatalf("new file store: %v", err)
|
|
}
|
|
command := domain.GameClientBridgeCommand{ID: "command-1", ServerInstanceID: "server-1", CommandType: "diagnostic.safe", RequesterID: "user-1", IdempotencyKey: "diag-1", State: domain.GameClientBridgeCommandClaimed, Claim: domain.GameClientBridgeClaim{FencingToken: 3}}
|
|
snapshot := domain.GameClientBridgeSnapshot{ID: "snapshot-1", ServerInstanceID: "server-1", Type: "health", StreamKey: "current", Sequence: 4}
|
|
stream := domain.GameClientBridgeSnapshotStream{ID: "stream-1", ServerInstanceID: "server-1", Type: "health", StreamKey: "current", LatestSequence: 4}
|
|
if err := store.GameClientBridgeCommands().Create(command); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := store.GameClientBridgeSnapshots().Create(snapshot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := store.GameClientBridgeSnapshotStreams().Create(stream); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
restarted, err := NewFileStore(path)
|
|
if err != nil {
|
|
t.Fatalf("restart file store: %v", err)
|
|
}
|
|
if got, err := restarted.GameClientBridgeCommands().Get(command.ID); err != nil || got.Claim.FencingToken != 3 {
|
|
t.Fatalf("persisted command: %#v err=%v", got, err)
|
|
}
|
|
if got, err := restarted.GameClientBridgeSnapshots().Get(snapshot.ID); err != nil || got.Sequence != 4 {
|
|
t.Fatalf("persisted snapshot: %#v err=%v", got, err)
|
|
}
|
|
if got, err := restarted.GameClientBridgeSnapshotStreams().Get(stream.ID); err != nil || got.LatestSequence != 4 {
|
|
t.Fatalf("persisted stream: %#v err=%v", got, err)
|
|
}
|
|
if err := restarted.GameClientBridgeSnapshots().Delete(snapshot.ID); err != nil {
|
|
t.Fatalf("delete snapshot: %v", err)
|
|
}
|
|
reloaded, err := NewFileStore(path)
|
|
if err != nil {
|
|
t.Fatalf("reload file store: %v", err)
|
|
}
|
|
if _, err := reloaded.GameClientBridgeSnapshots().Get(snapshot.ID); !errors.Is(err, ErrNotFound) {
|
|
t.Fatalf("deleted snapshot revived: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMySQLSnapshotRoundTripIncludesGameClientBridge(t *testing.T) {
|
|
source := &MySQLStore{MemoryStore: NewMemoryStore()}
|
|
_ = source.MemoryStore.GameClientBridgeCommands().Create(domain.GameClientBridgeCommand{ID: "command-1"})
|
|
_ = source.MemoryStore.GameClientBridgeSnapshots().Create(domain.GameClientBridgeSnapshot{ID: "snapshot-1"})
|
|
_ = source.MemoryStore.GameClientBridgeSnapshotStreams().Create(domain.GameClientBridgeSnapshotStream{ID: "stream-1", LatestSequence: 8})
|
|
target := &MySQLStore{MemoryStore: NewMemoryStore()}
|
|
target.loadSnapshot(source.snapshot())
|
|
if _, err := target.MemoryStore.GameClientBridgeCommands().Get("command-1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := target.MemoryStore.GameClientBridgeSnapshots().Get("snapshot-1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stream, err := target.MemoryStore.GameClientBridgeSnapshotStreams().Get("stream-1"); err != nil || stream.LatestSequence != 8 {
|
|
t.Fatalf("stream round trip: %#v err=%v", stream, err)
|
|
}
|
|
}
|