package validator import ( "encoding/json" "fmt" "math" "strings" "testing" "time" "browser.local/platform/domain" ) func validBridgeQueueRequest() domain.GameClientBridgeQueueRequest { return domain.GameClientBridgeQueueRequest{ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", CommandType: "announcement.send", Payload: map[string]any{"message": "hello"}, IdempotencyKey: "announce-1", ExpiresAt: time.Now().UTC().Add(time.Minute)} } func TestValidateGameClientBridgeRequests(t *testing.T) { now := time.Now().UTC() checks := map[string]error{ "queue": ValidateGameClientBridgeQueueRequest(validBridgeQueueRequest()), "claim": ValidateGameClientBridgeClaimRequest(domain.GameClientBridgeClaimRequest{SessionToken: "session", Limit: 50}), "ack": ValidateGameClientBridgeAckRequest(domain.GameClientBridgeAckRequest{SessionToken: "session", CommandID: "command-1", FencingToken: 1}), "result": ValidateGameClientBridgeResultRequest(domain.GameClientBridgeResultRequest{SessionToken: "session", CommandID: "command-1", FencingToken: 1, Status: domain.GameClientBridgeResultSucceeded, Summary: "completed"}), "cancel": ValidateGameClientBridgeCancelRequest(domain.GameClientBridgeCancelRequest{CommandID: "command-1", Reason: "operator request"}), "snapshot": ValidateGameClientBridgeSnapshotIngestRequest(domain.GameClientBridgeSnapshotIngestRequest{SessionToken: "session", Type: "players", SchemaVersion: "1.2.0", StreamKey: "current", Sequence: 1, ObservedAt: now, Payload: map[string]any{"players": []any{}}, Retention: domain.GameClientBridgeRetention{KeepForSeconds: 3600}}), "query": ValidateGameClientBridgeSnapshotQuery(domain.GameClientBridgeSnapshotQuery{ServerInstanceID: "server-1", PluginID: "game.scum", ProfileKey: "scum-client", Type: "players", StreamKey: "current", Limit: 200}), } for name, err := range checks { if err != nil { t.Fatalf("validate %s request: %v", name, err) } } } func TestValidateGameClientBridgeRequestFieldBounds(t *testing.T) { tests := []struct { name string err error want string }{ {name: "queue identifier", err: func() error { request := validBridgeQueueRequest() request.CommandType = "bad/type" return ValidateGameClientBridgeQueueRequest(request) }(), want: "commandType"}, {name: "queue priority", err: func() error { request := validBridgeQueueRequest() request.Priority = 101 return ValidateGameClientBridgeQueueRequest(request) }(), want: "priority"}, {name: "claim token", err: ValidateGameClientBridgeClaimRequest(domain.GameClientBridgeClaimRequest{SessionToken: " session"}), want: "sessionToken"}, {name: "claim limit", err: ValidateGameClientBridgeClaimRequest(domain.GameClientBridgeClaimRequest{SessionToken: "session", Limit: 51}), want: "limit"}, {name: "ack fence", err: ValidateGameClientBridgeAckRequest(domain.GameClientBridgeAckRequest{SessionToken: "session", CommandID: "command-1"}), want: "fencingToken"}, {name: "result state", err: ValidateGameClientBridgeResultRequest(domain.GameClientBridgeResultRequest{SessionToken: "session", CommandID: "command-1", FencingToken: 1, Status: "unexpected"}), want: "status"}, {name: "result text", err: ValidateGameClientBridgeResultRequest(domain.GameClientBridgeResultRequest{SessionToken: "session", CommandID: "command-1", FencingToken: 1, Status: domain.GameClientBridgeResultFailed, Summary: "read /etc/passwd"}), want: "unsafe"}, {name: "cancel text", err: ValidateGameClientBridgeCancelRequest(domain.GameClientBridgeCancelRequest{CommandID: "command-1", Reason: "Bearer private"}), want: "unsafe"}, {name: "snapshot payload", err: ValidateGameClientBridgeSnapshotIngestRequest(domain.GameClientBridgeSnapshotIngestRequest{SessionToken: "session", Type: "players", SchemaVersion: "1", StreamKey: "current", Sequence: 1, ObservedAt: time.Now(), Retention: domain.GameClientBridgeRetention{KeepForSeconds: 1}}), want: "payload"}, {name: "snapshot sequence", err: ValidateGameClientBridgeSnapshotIngestRequest(domain.GameClientBridgeSnapshotIngestRequest{SessionToken: "session", Type: "players", SchemaVersion: "1", StreamKey: "current", ObservedAt: time.Now(), Payload: map[string]any{}, Retention: domain.GameClientBridgeRetention{KeepForSeconds: 1}}), want: "sequence"}, {name: "snapshot retention", err: ValidateGameClientBridgeSnapshotIngestRequest(domain.GameClientBridgeSnapshotIngestRequest{SessionToken: "session", Type: "players", SchemaVersion: "1", StreamKey: "current", Sequence: 1, ObservedAt: time.Now(), Payload: map[string]any{}, Retention: domain.GameClientBridgeRetention{KeepForSeconds: 31*24*60*60 + 1}}), want: "keepForSeconds"}, {name: "query field", err: ValidateGameClientBridgeSnapshotQuery(domain.GameClientBridgeSnapshotQuery{ServerInstanceID: "server-1", PluginID: "game.scum", StreamKey: "tcp://host"}), want: "streamKey"}, {name: "query limit", err: ValidateGameClientBridgeSnapshotQuery(domain.GameClientBridgeSnapshotQuery{ServerInstanceID: "server-1", PluginID: "game.scum", Limit: 201}), want: "limit"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.err == nil || !strings.Contains(test.err.Error(), test.want) { t.Fatalf("expected %q violation, got %v", test.want, test.err) } }) } } func TestValidateGameClientBridgePayloadAcceptsJSONValuesWithoutKeyFalsePositives(t *testing.T) { request := validBridgeQueueRequest() request.Payload = map[string]any{ "nullValue": nil, "enabled": true, "count": int(2), "ratio": float64(1.5), "sequence": json.Number("9007199254740993"), "nested": []any{map[string]any{"value": "safe"}}, "secretaryName": "Mina", "socketCount": 2, "apiKeyEnabled": false, "sqlQueryTemplateKey": "players.by-id", "shellStatus": "unavailable", "logicalPath": "snapshots.current", "monkey": "ordinary", "chatMessage": "Select your reward from the list", } if err := ValidateGameClientBridgeQueueRequest(request); err != nil { t.Fatalf("valid JSON payload with benign keys rejected: %v", err) } } func TestValidateGameClientBridgePayloadRejectsUnsafeKeyPatterns(t *testing.T) { keys := []string{"sessionToken", "authToken", "accessKey", "privateKey", "component-key", "databasePassword", "clientSecret", "api_key", "databaseDSN", "storageCredential", "hostPath", "absolute_path", "directSocket", "socketAddress", "runEndpoint", "runUrl", "rawSQL", "rawQuery", "sqlText", "sql_statement", "shellCommand", "shell_script", "scriptBody", "terminalCommand", "commandLine"} for _, key := range keys { t.Run(key, func(t *testing.T) { request := validBridgeQueueRequest() request.Payload = map[string]any{key: "value"} err := ValidateGameClientBridgeQueueRequest(request) if err == nil || !strings.Contains(err.Error(), "forbidden key") { t.Fatalf("expected forbidden key rejection, got %v", err) } }) } } func TestValidateGameClientBridgePayloadRejectsUnsafeStringMaterial(t *testing.T) { values := []string{"/etc/passwd", "prefix path=/var/run/run.sock", `C:\\Users\\operator\\secret.txt`, "tcp://127.0.0.1:9000", "unix:///var/run/run.sock", "http://localhost:9000", "mysql://user:password@host/db", "secret://component/key", "vault://runtime/token", "Bearer abc123", "password=leak"} for index, value := range values { request := validBridgeQueueRequest() request.IdempotencyKey = fmt.Sprintf("case-%d", index) request.Payload = map[string]any{"value": value} if err := ValidateGameClientBridgeQueueRequest(request); err == nil || !strings.Contains(err.Error(), "payload") { t.Fatalf("expected unsafe value %q rejection, got %v", value, err) } } } func TestValidateGameClientBridgePayloadRejectsNonJSONValuesAndInvalidNumbers(t *testing.T) { tests := map[string]any{"typed map": map[string]string{"key": "value"}, "typed slice": []string{"value"}, "time": time.Now(), "channel": make(chan int), "not a number": math.NaN(), "infinity": math.Inf(1), "invalid number": json.Number("01")} for name, value := range tests { t.Run(name, func(t *testing.T) { request := validBridgeQueueRequest() request.Payload = map[string]any{"value": value} if err := ValidateGameClientBridgeQueueRequest(request); err == nil { t.Fatalf("expected %s rejection", name) } }) } } func TestValidateGameClientBridgePayloadStructuralBudgets(t *testing.T) { tests := map[string]map[string]any{} tooManyKeys := map[string]any{} for index := 0; index < maxGameClientBridgeObjectKeys+1; index++ { tooManyKeys[fmt.Sprintf("key%d", index)] = index } tests["object keys"] = tooManyKeys deep := map[string]any{"value": true} for index := 0; index < maxGameClientBridgePayloadDepth+2; index++ { deep = map[string]any{"nested": deep} } tests["depth"] = deep tests["array items"] = map[string]any{"items": make([]any, maxGameClientBridgeArrayItems+1)} tests["string"] = map[string]any{"message": strings.Repeat("x", maxGameClientBridgePayloadString+1)} tests["encoded size"] = map[string]any{"one": strings.Repeat("x", 15000), "two": strings.Repeat("x", 15000), "three": strings.Repeat("x", 15000), "four": strings.Repeat("x", 15000), "five": strings.Repeat("x", 15000)} tests["invalid key"] = map[string]any{"bad key": true} tests["invalid utf8"] = map[string]any{"value": string([]byte{0xff})} wide := map[string]any{} for index := 0; index < 64; index++ { wide[fmt.Sprintf("items%d", index)] = make([]any, 128) } tests["node budget"] = wide cycle := map[string]any{} cycle["self"] = cycle tests["cycle"] = cycle for name, payload := range tests { t.Run(name, func(t *testing.T) { request := validBridgeQueueRequest() request.Payload = payload if err := ValidateGameClientBridgeQueueRequest(request); err == nil { t.Fatalf("expected %s rejection", name) } }) } }