package protocol import ( "strings" "testing" "time" ) func TestValidateRunJobAssignmentScopedFilePayloads(t *testing.T) { assignment := RunJobAssignment{ JobID: "job-config-write", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityConfigWrite, TargetKey: "server.properties", InputRef: "input://server-config/server-1/server.properties/v1", IdempotencyKey: "idem-config", } if err := ValidateRunJobAssignment(assignment); err != nil { t.Fatalf("expected valid config write assignment: %v", err) } assignment.TargetKey = "/Users/tasia/server.properties" if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "targetKey") { t.Fatalf("expected raw host path rejection, got %v", err) } assignment.TargetKey = "server.properties" assignment.InputRef = "sk-raw-secret" if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "inputRef") { t.Fatalf("expected raw credential ref rejection, got %v", err) } } func TestValidateRunJobAssignmentScopedReadDoesNotRequireInputRef(t *testing.T) { assignment := RunJobAssignment{ JobID: "job-files-read", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityFilesRead, TargetKey: "logs/latest.log", IdempotencyKey: "idem-read", } if err := ValidateRunJobAssignment(assignment); err != nil { t.Fatalf("expected valid file read assignment: %v", err) } } func TestValidateRunJobAssignmentRequiresBoundedSQLiteSchemaProbe(t *testing.T) { assignment := RunJobAssignment{JobID: "job-probe", ServerInstanceID: "server-1", RunEndpointID: "run-1", Capability: RunCapabilityRemoteRunDBSQLiteProbe, TargetKey: "databases/current.db", IdempotencyKey: "idem-probe", MaxAttempts: 1, FencingToken: 1, ExecutionInput: RunJobExecutionInput{WorkspaceScope: "profile-1", SQLiteSchemaProbe: &SQLiteSchemaProbeRequest{RequestID: "probe-1", Binding: SQLiteSchemaProbeBinding{ServerInstanceID: "server-1", RunBindingID: "binding-1", RunEndpointID: "run-1", PluginID: "game.example", PluginVersion: "1.0.0", AdapterVersion: "adapter-1", DatabaseIdentity: "database-1"}, Limits: SQLiteSchemaProbeLimits{MaxObjects: 8, MaxColumnsPerObject: 8, MaxIndexesPerObject: 8, MaxForeignKeys: 8, MaxCardinalityReads: 8, MaxSampleRows: 2, TimeoutMS: 1000, MaxResultBytes: 1024}}}} if err := ValidateRunJobAssignment(assignment); err != nil { t.Fatalf("expected bounded SQLite schema probe to validate: %v", err) } assignment.ExecutionInput.SQLiteSchemaProbe.Limits.MaxSampleRows = 4 if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "limits") { t.Fatalf("expected oversized sample limit rejection, got %v", err) } assignment.ExecutionInput.SQLiteSchemaProbe.Limits.MaxSampleRows = 2 assignment.ExecutionInput.Content = "SELECT * FROM private" if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "must not carry") { t.Fatalf("expected embedded query rejection, got %v", err) } } func TestValidateRunJobAssignmentRejectsLegacyGameSpecificDeploymentPlan(t *testing.T) { assignment := RunJobAssignment{ JobID: "job-legacy-game-plan", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityProcessInstall, IdempotencyKey: "idem-legacy-game-plan", ExecutionInput: RunJobExecutionInput{ Deployment: &ServerDeploymentExecution{SchemaVersion: "1", Mode: "guided-install", ServerRoot: "C:/game-server", Revision: 1}, ServerDeploymentPlan: &ServerDeploymentPlan{SchemaVersion: "1", PluginID: "game.example", TemplateKey: "legacy-game-template"}, }, } if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "legacy unsupported") { t.Fatalf("expected legacy game-specific plan rejection, got %v", err) } } func TestValidateRunJobAssignmentAllowsDeclaredProcessLogSourcesOnlyForStart(t *testing.T) { assignment := RunJobAssignment{ JobID: "job-process-logs", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityProcessStart, TargetKey: "actions/start.json", IdempotencyKey: "idem-process-logs", ExecutionInput: RunJobExecutionInput{LogSources: []RuntimeLogSourcePlan{ {Key: "console-stdout", Kind: "process.stdout", TargetKey: "process/server", StreamKey: "game.console.stdout", CursorKind: "sequence", RetentionDays: 30}, {Key: "console-stderr", Kind: "process.stderr", TargetKey: "process/server", StreamKey: "game.console.stderr", CursorKind: "sequence", RetentionDays: 30}, }}, } if err := ValidateRunJobAssignment(assignment); err != nil { t.Fatalf("expected process log sources to validate: %v", err) } assignment.ExecutionInput.LogSources[0].Kind = "file.tail" if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "process log source kind") { t.Fatalf("expected file log source rejection on process.start, got %v", err) } assignment.ExecutionInput.LogSources[0].Kind = "process.stdout" assignment.Capability = RunCapabilityProcessStop if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "process log sources") { t.Fatalf("expected process log source rejection outside start, got %v", err) } } func TestValidateRunJobAssignmentRemoteCapabilitiesAreBounded(t *testing.T) { assignment := RunJobAssignment{ JobID: "job-remote-rcon", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityRemoteRunRCONCommand, TargetKey: "rcon/command", InputRef: "input://server-1/rcon/command/1", IdempotencyKey: "idem-rcon", } if err := ValidateRunJobAssignment(assignment); err != nil { t.Fatalf("expected valid remote rcon assignment: %v", err) } assignment.InputRef = "password=raw" if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "inputRef") { t.Fatalf("expected unsafe inputRef rejection, got %v", err) } assignment.InputRef = "input://server-1/rcon/command/1" assignment.TargetKey = "/Users/tasia/server.db" if err := ValidateRunJobAssignment(assignment); err == nil || !strings.Contains(err.Error(), "targetKey") { t.Fatalf("expected unsafe targetKey rejection, got %v", err) } } func TestValidateRunJobAssignmentDistributionCapabilitiesAreBounded(t *testing.T) { selfUpdate := RunJobAssignment{ JobID: "job-update", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityRunSelfUpdate, TargetKey: "run/update", InputRef: "artifact://artifact-run-latest", IdempotencyKey: "idem-update", } if err := ValidateRunJobAssignment(selfUpdate); err != nil { t.Fatalf("expected valid self-update assignment: %v", err) } selfUpdate.InputRef = "input://not-an-artifact" if err := ValidateRunJobAssignment(selfUpdate); err == nil || !strings.Contains(err.Error(), "artifact") { t.Fatalf("expected non-artifact self-update ref rejection, got %v", err) } check := RunJobAssignment{ JobID: "job-dependency-check", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityDependenciesCheck, TargetKey: "dependencies/java-21", IdempotencyKey: "idem-dep-check", } if err := ValidateRunJobAssignment(check); err != nil { t.Fatalf("expected valid dependency check assignment: %v", err) } check.TargetKey = "dependencies/install/java;rm" if err := ValidateRunJobAssignment(check); err == nil || !strings.Contains(err.Error(), "targetKey") { t.Fatalf("expected shell-like dependency target rejection, got %v", err) } backfill := RunJobAssignment{ JobID: "job-log-backfill", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityLogsBackfill, TargetKey: "logs/latest-log", InputRef: "artifact://logs/checkpoint/1", IdempotencyKey: "idem-log-backfill", ExecutionInput: RunJobExecutionInput{LogSource: &RuntimeLogSourcePlan{Key: "latest-log", Kind: "file.tail", TargetKey: "logs/latest.log", StreamKey: "latest-log", CursorKind: "offset", RetentionDays: 30}}, } if err := ValidateRunJobAssignment(backfill); err != nil { t.Fatalf("expected valid log backfill assignment: %v", err) } backfill.ExecutionInput.LogSource.Kind = "sql.query" if err := ValidateRunJobAssignment(backfill); err == nil || !strings.Contains(err.Error(), "unsupported") { t.Fatalf("expected unsupported log source rejection, got %v", err) } backfill.ExecutionInput.LogSource.Kind = "file.tail" backfill.InputRef = "password=raw" if err := ValidateRunJobAssignment(backfill); err == nil || !strings.Contains(err.Error(), "inputRef") { t.Fatalf("expected unsafe log checkpoint rejection, got %v", err) } } func TestValidateRunJobAssignmentDLLExtensionsAreBounded(t *testing.T) { assignment := RunJobAssignment{ JobID: "job-dll-extension", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityProcessStart, TargetKey: "actions/start.json", IdempotencyKey: "idem-dll-extension", ExecutionInput: RunJobExecutionInput{ WorkspaceScope: "run-local", DLLExtensions: []RuntimeDLLExtensionPlan{validRuntimeDLLExtensionPlan()}, }, } if err := ValidateRunJobAssignment(assignment); err != nil { t.Fatalf("expected valid DLL extension assignment: %v", err) } cases := []struct { name string mutate func(*RunJobAssignment) want string }{ {name: "not process start", mutate: func(value *RunJobAssignment) { value.Capability = RunCapabilityProcessStop }, want: "process.start"}, {name: "query URL", mutate: func(value *RunJobAssignment) { value.ExecutionInput.DLLExtensions[0].ReleaseURL += "?release=1" }, want: "release integrity"}, {name: "unsafe DLL path", mutate: func(value *RunJobAssignment) { value.ExecutionInput.DLLExtensions[0].DLLRef = "ue4ss/Mods/scum_simple_rcon/dlls/other.dll" }, want: "deployment path"}, {name: "bad checksum", mutate: func(value *RunJobAssignment) { value.ExecutionInput.DLLExtensions[0].Checksum = "sha256:bad" }, want: "release integrity"}, {name: "missing scope", mutate: func(value *RunJobAssignment) { value.ExecutionInput.WorkspaceScope = "" }, want: "process.start"}, } for _, testCase := range cases { t.Run(testCase.name, func(t *testing.T) { value := assignment value.ExecutionInput.DLLExtensions = append([]RuntimeDLLExtensionPlan(nil), assignment.ExecutionInput.DLLExtensions...) testCase.mutate(&value) if err := ValidateRunJobAssignment(value); err == nil || !strings.Contains(err.Error(), testCase.want) { t.Fatalf("expected %q validation error, got %v", testCase.want, err) } }) } } func TestValidateRunJobAssignmentSourceRCONPlanIsFrozenAndOneShot(t *testing.T) { assignment := RunJobAssignment{ JobID: "job-source-rcon", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityRemoteRunRCONCommand, TargetKey: "rcon.password", InputRef: "input://source-rcon/job-source-rcon", IdempotencyKey: "idem-source-rcon", Attempt: 1, MaxAttempts: 1, ExecutionInput: RunJobExecutionInput{ WorkspaceScope: "run-local", RemoteAdapterKey: "rcon", RemoteAdapterKind: "rcon", TimeoutSeconds: 30, SourceRCON: &RuntimeSourceRCONPlan{Protocol: "source-rcon", ExtensionKey: "scum-simple-rcon", ModKey: "scum_simple_rcon", ConfigRef: "ue4ss/Mods/scum_simple_rcon/config.ini", DeploymentStateRef: "runtime/ue4ss-dll/ue4ss/scum-simple-rcon/release.json", Port: 27015}, }, } if err := ValidateRunJobAssignment(assignment); err != nil { t.Fatalf("expected valid frozen Source RCON plan: %v", err) } protected := assignment protected.Capability = RunCapabilityRemoteRunProtectedRCON protected.TargetKey = "scum-management" protected.InputRef = "input://protected-request/job-protected-rcon" protected.FencingToken = 7 protected.ExecutionInput.RemoteAdapterKey = "scum-management" protected.ExecutionInput.RemoteAdapterKind = "protected-rcon" protected.ExecutionInput.TimeoutSeconds = 120 plan := *assignment.ExecutionInput.SourceRCON protected.ExecutionInput.SourceRCON = &plan if err := ValidateRunJobAssignment(protected); err != nil { t.Fatalf("expected valid protected Source RCON plan: %v", err) } cases := []struct { name string mutate func(*RunJobAssignment) want string }{ {name: "multiple attempts", mutate: func(value *RunJobAssignment) { value.MaxAttempts = 2 }, want: "exactly one attempt"}, {name: "wrong capability", mutate: func(value *RunJobAssignment) { value.Capability = RunCapabilityRemoteRunDBSQLiteQuery }, want: "remote.run.rcon.command or remote.run.protected.rcon"}, {name: "unsafe config reference", mutate: func(value *RunJobAssignment) { value.ExecutionInput.SourceRCON.ConfigRef = "ue4ss/Mods/scum_simple_rcon/other.ini" }, want: "config reference"}, {name: "unsafe deployment state reference", mutate: func(value *RunJobAssignment) { value.ExecutionInput.SourceRCON.DeploymentStateRef = "runtime/ue4ss-dll/../../release.json" }, want: "deployment state reference"}, {name: "unsafe port", mutate: func(value *RunJobAssignment) { value.ExecutionInput.SourceRCON.Port = 80 }, want: "port"}, {name: "ordinary input ref", mutate: func(value *RunJobAssignment) { value.InputRef = "input://server-1/rcon/command" }, want: "source-rcon input ref"}, {name: "missing plan", mutate: func(value *RunJobAssignment) { value.ExecutionInput.SourceRCON = nil }, want: "requires a Source RCON plan"}, } for _, testCase := range cases { t.Run(testCase.name, func(t *testing.T) { value := assignment plan := *assignment.ExecutionInput.SourceRCON value.ExecutionInput.SourceRCON = &plan testCase.mutate(&value) if err := ValidateRunJobAssignment(value); err == nil || !strings.Contains(err.Error(), testCase.want) { t.Fatalf("expected %q validation error, got %v", testCase.want, err) } }) } } func TestValidateProtectedRequestAssignmentAndOneTimeInput(t *testing.T) { assignment := RunJobAssignment{ JobID: "job-protected", ServerInstanceID: "server-1", RunEndpointID: "run-local", Capability: RunCapabilityRemoteRunProtectedSQL, TargetKey: "scum-database", InputRef: "input://protected-request/job-protected", IdempotencyKey: "protected-1", LeaseToken: "lease-protected", FencingToken: 7, Attempt: 1, MaxAttempts: 1, ExecutionInput: RunJobExecutionInput{ RemoteAdapterKey: "scum-database", RemoteAdapterKind: "protected-sql", TimeoutSeconds: 30, }, } if err := ValidateRunJobAssignment(assignment); err != nil { t.Fatalf("expected valid protected assignment: %v", err) } input := ProtectedRequestExecutionInputResponse{JobID: assignment.JobID, ServerInstanceID: assignment.ServerInstanceID, RunEndpointID: assignment.RunEndpointID, FencingToken: assignment.FencingToken, Authorized: true, ApprovalState: "approved", QueueState: "claimed", ExpiresAt: time.Now().UTC().Add(time.Minute), Kind: "sql", TransportKey: "scum-database", TargetKey: assignment.TargetKey, RequestText: "SELECT player_id FROM players LIMIT 1"} if !ValidProtectedRequestExecutionInput(input) { t.Fatal("expected approved unexpired protected input") } for _, testCase := range []struct { name string mutate func(*RunJobAssignment) want string }{ {name: "missing fence", mutate: func(value *RunJobAssignment) { value.FencingToken = 0 }, want: "fenced"}, {name: "multiple attempts", mutate: func(value *RunJobAssignment) { value.MaxAttempts = 2 }, want: "single"}, {name: "wrong input ref", mutate: func(value *RunJobAssignment) { value.InputRef = "input://ordinary/request" }, want: "inputRef"}, {name: "wrong adapter", mutate: func(value *RunJobAssignment) { value.ExecutionInput.RemoteAdapterKind = "database" }, want: "adapter kind"}, {name: "inline text", mutate: func(value *RunJobAssignment) { value.ExecutionInput.Content = "SELECT secret" }, want: "must not"}, } { t.Run(testCase.name, func(t *testing.T) { value := assignment testCase.mutate(&value) if err := ValidateRunJobAssignment(value); err == nil || !strings.Contains(err.Error(), testCase.want) { t.Fatalf("expected %q validation error, got %v", testCase.want, err) } }) } input.ExpiresAt = time.Now().UTC().Add(-time.Second) if ValidProtectedRequestExecutionInput(input) { t.Fatal("expired protected input was accepted") } input.ExpiresAt = time.Now().UTC().Add(time.Minute) input.QueueState = "pending" if ValidProtectedRequestExecutionInput(input) { t.Fatal("unclaimed protected input was accepted") } } func validRuntimeDLLExtensionPlan() RuntimeDLLExtensionPlan { return RuntimeDLLExtensionPlan{ Key: "scum-simple-rcon", Version: "1.0.0", ReleaseURL: "https://cdn.npc0.com/scum_simple_rcon_ue4s.dll", Checksum: "sha256:" + strings.Repeat("a", 64), SizeBytes: 1024, TargetKey: "ue4ss/scum-simple-rcon", ModKey: "scum_simple_rcon", DLLRef: "ue4ss/Mods/scum_simple_rcon/dlls/main.dll", TargetExecutableChecksum: "sha256:" + strings.Repeat("b", 64), UE4SSABI: "ue4ss-3.0", RCONPort: 27015, } }