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) } }