Files
browser/platform/dto/game_client_bridge_test.go
T

114 lines
8.8 KiB
Go

package dto
import (
"encoding/json"
"strings"
"testing"
"time"
"browser.local/platform/domain"
)
func TestGameClientBridgeRequestConversionsInjectScopeAndCopyPayloads(t *testing.T) {
payload := map[string]any{"players": []any{map[string]any{"name": "Alice"}}}
queue := GameClientBridgeQueueRequest{ProfileKey: "scum-client", CommandType: "player.lookup", Payload: payload, IdempotencyKey: "lookup-1", Priority: 5, ExpiresAt: time.Now().UTC().Add(time.Minute)}.ToDomain("server-1", "game.scum")
claim := GameClientBridgeClaimRequest{SessionToken: "session", Limit: 5}.ToDomain()
ack := GameClientBridgeAckRequest{SessionToken: "session", FencingToken: 7}.ToDomain("command-1")
result := GameClientBridgeResultRequest{SessionToken: "session", FencingToken: 7, Status: "succeeded", Payload: payload}.ToDomain("command-1")
cancel := GameClientBridgeCancelRequest{Reason: "operator request"}.ToDomain("command-1")
snapshot := GameClientBridgeSnapshotIngestRequest{SessionToken: "session", Type: "players", SchemaVersion: "1", StreamKey: "current", Sequence: 2, Payload: payload, KeepForSeconds: 60, MaxRecords: 4}.ToDomain()
query := GameClientBridgeSnapshotQuery{ProfileKey: "scum-client", Type: "players", StreamKey: "current", Limit: 10}.ToDomain("server-1", "game.scum")
if queue.ServerInstanceID != "server-1" || queue.PluginID != "game.scum" || claim.Limit != 5 || ack.CommandID != "command-1" || result.CommandID != "command-1" || cancel.CommandID != "command-1" || snapshot.Retention.MaxRecords != 4 || query.ServerInstanceID != "server-1" || query.PluginID != "game.scum" {
t.Fatalf("request conversion lost route or body fields: queue=%#v claim=%#v ack=%#v result=%#v cancel=%#v snapshot=%#v query=%#v", queue, claim, ack, result, cancel, snapshot, query)
}
queue.Payload["players"].([]any)[0].(map[string]any)["name"] = "queue"
result.Payload["players"].([]any)[0].(map[string]any)["name"] = "result"
snapshot.Payload["players"].([]any)[0].(map[string]any)["name"] = "snapshot"
if got := payload["players"].([]any)[0].(map[string]any)["name"]; got != "Alice" {
t.Fatalf("request conversion aliases source payload: %v", got)
}
}
func TestGameClientBridgeBrowserProjectionsAreCompleteAndOmitInternalData(t *testing.T) {
now := time.Date(2026, 7, 20, 10, 0, 0, 0, time.UTC)
command := domain.GameClientBridgeCommand{
ID: "command-1", ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", CommandType: "diagnostic.ping", Payload: map[string]any{"message": "internal command payload"}, IdempotencyKey: "internal-idempotency", Priority: 9,
State: domain.GameClientBridgeCommandCancelled, ApprovalState: domain.GameClientBridgeApprovalApproved, RequesterID: "operator-1",
Claim: domain.GameClientBridgeClaim{SessionID: "internal-session-secret", InstallationID: "internal-installation", DeploymentGeneration: 9, FencingToken: 42, LeaseExpiresAt: now.Add(time.Minute)},
Result: domain.GameClientBridgeResult{Status: domain.GameClientBridgeResultCancelled, Summary: "cancelled safely", Payload: map[string]any{"code": "cancelled"}, CompletedBy: "internal-completing-session", CompletedAt: now.Add(3 * time.Minute)},
Cancellation: domain.GameClientBridgeCancellation{RequestedBy: "operator-2", Reason: "operator request", CancelledAt: now.Add(2 * time.Minute)},
ExpiresAt: now.Add(10 * time.Minute), CreatedAt: now, UpdatedAt: now.Add(3 * time.Minute), CompletedAt: now.Add(3 * time.Minute),
}
snapshot := domain.GameClientBridgeSnapshot{ID: "snapshot-1", ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", Type: "players", SchemaVersion: "1", StreamKey: "current", Sequence: 2, SourceSessionID: "internal-source-session", ObservedAt: now, Payload: map[string]any{"players": []any{map[string]any{"name": "Alice"}}}, Retention: domain.GameClientBridgeRetention{KeepForSeconds: 3600, MaxRecords: 10}, CreatedAt: now.Add(time.Second), ExpiresAt: now.Add(time.Hour)}
commandProjection := GameClientBridgeCommandFromDomain(command)
snapshotProjection := GameClientBridgeSnapshotFromDomain(snapshot)
if commandProjection.Result == nil || commandProjection.Result.Status != "cancelled" || commandProjection.Cancellation == nil || commandProjection.Cancellation.Reason != "operator request" || commandProjection.Priority != 9 || !commandProjection.CompletedAt.Equal(command.CompletedAt) {
t.Fatalf("incomplete command projection: %#v", commandProjection)
}
if snapshotProjection.ProfileKey != "scum-client" || snapshotProjection.Retention.KeepForSeconds != 3600 || !snapshotProjection.CreatedAt.Equal(snapshot.CreatedAt) {
t.Fatalf("incomplete snapshot projection: %#v", snapshotProjection)
}
commandProjection.Result.Payload["code"] = "changed"
snapshotProjection.Payload["players"].([]any)[0].(map[string]any)["name"] = "changed"
if command.Result.Payload["code"] != "cancelled" || snapshot.Payload["players"].([]any)[0].(map[string]any)["name"] != "Alice" {
t.Fatal("browser projection aliases domain data")
}
encoded, err := json.Marshal(struct {
Command GameClientBridgeCommandResponse `json:"command"`
Snapshot GameClientBridgeSnapshotResponse `json:"snapshot"`
}{commandProjection, snapshotProjection})
if err != nil {
t.Fatalf("marshal projections: %v", err)
}
text := string(encoded)
for _, forbidden := range []string{"internal-session-secret", "internal-installation", "internal-completing-session", "internal-source-session", "internal-idempotency", "internal command payload", "fencingToken", "deploymentGeneration", "sourceSessionId", "completedBy", "idempotencyKey", "leaseExpiresAt"} {
if strings.Contains(text, forbidden) {
t.Fatalf("browser projection leaked %q: %s", forbidden, text)
}
}
}
func TestGameClientBridgeListAndCompanionResponseConversions(t *testing.T) {
now := time.Date(2026, 7, 20, 10, 0, 0, 0, time.UTC)
command := domain.GameClientBridgeCommand{
ID: "command-1", ProfileKey: "scum-client", CommandType: "diagnostic.safe", Payload: map[string]any{"scope": "health"}, Priority: 3, State: domain.GameClientBridgeCommandSucceeded,
Claim: domain.GameClientBridgeClaim{SessionID: "private-session", FencingToken: 7, ClaimedAt: now, AcknowledgedAt: now.Add(time.Second), LeaseExpiresAt: now.Add(time.Minute)},
Result: domain.GameClientBridgeResult{Status: domain.GameClientBridgeResultSucceeded, Summary: "ok", Payload: map[string]any{"healthy": true}, CompletedBy: "private-session", CompletedAt: now.Add(2 * time.Second)},
Cancellation: domain.GameClientBridgeCancellation{RequestedBy: "operator", Reason: "superseded", CancelledAt: now.Add(3 * time.Second)}, ExpiresAt: now.Add(5 * time.Minute), UpdatedAt: now.Add(2 * time.Second), CompletedAt: now.Add(2 * time.Second),
}
snapshot := domain.GameClientBridgeSnapshot{ID: "snapshot-1", ProfileKey: "scum-client", Type: "players", SchemaVersion: "1", StreamKey: "current", Sequence: 4, CreatedAt: now, ExpiresAt: now.Add(time.Hour)}
claim := GameClientBridgeClaimBatchFromDomain([]domain.GameClientBridgeCommand{command})
ack := GameClientBridgeAckFromDomain(command)
result := GameClientBridgeResultFromDomain(command)
cancel := GameClientBridgeCancelFromDomain(command)
ingest := GameClientBridgeSnapshotIngestFromDomain(snapshot)
commands := GameClientBridgeCommandsFromDomain([]domain.GameClientBridgeCommand{command})
snapshots := GameClientBridgeSnapshotsFromDomain([]domain.GameClientBridgeSnapshot{snapshot})
if claim.Count != 1 || claim.Items[0].FencingToken != 7 || claim.Items[0].ProfileKey != "scum-client" || ack.CommandID != "command-1" || !ack.AcknowledgedAt.Equal(now.Add(time.Second)) || result.Result.Summary != "ok" || cancel.Cancellation.Reason != "superseded" || ingest.SnapshotID != "snapshot-1" || commands.Count != 1 || snapshots.Count != 1 {
t.Fatalf("unexpected responses: claim=%#v ack=%#v result=%#v cancel=%#v ingest=%#v", claim, ack, result, cancel, ingest)
}
claim.Items[0].Payload["scope"] = "changed"
result.Result.Payload["healthy"] = false
if command.Payload["scope"] != "health" || command.Result.Payload["healthy"] != true {
t.Fatal("companion response aliases domain payload")
}
for name, value := range map[string]any{"claim": claim, "ack": ack, "result": result, "cancel": cancel, "ingest": ingest, "commands": commands, "snapshots": snapshots} {
encoded, err := json.Marshal(value)
if err != nil {
t.Fatalf("marshal %s response: %v", name, err)
}
if strings.Contains(string(encoded), "private-session") || strings.Contains(string(encoded), "sessionId") || strings.Contains(string(encoded), "completedBy") {
t.Fatalf("%s response exposes a private session: %s", name, encoded)
}
}
if GameClientBridgeCommandsFromDomain(nil).Items == nil || GameClientBridgeSnapshotsFromDomain(nil).Items == nil || GameClientBridgeClaimBatchFromDomain(nil).Items == nil {
t.Fatal("list converters must serialize empty items as [] instead of null")
}
}