114 lines
8.7 KiB
Go
114 lines
8.7 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, 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 TestGameClientBridgeListResponseConversions(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("bridge 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")
|
|
}
|
|
}
|