Integrate SCUM real ops workflows
This commit is contained in:
@@ -129,17 +129,25 @@ func TestValidateGamePluginManifestRegistrationValidatesRuntimeProfiles(t *testi
|
||||
|
||||
func TestValidateGamePluginManifestRegistrationValidatesGameClientBridgeCatalog(t *testing.T) {
|
||||
registration := validGamePluginManifestRegistration()
|
||||
registration.Manifest.Permissions = append(registration.Manifest.Permissions, "server.game-client.read", "server.game-client.command", "server.remote.access")
|
||||
registration.Manifest.Capabilities = append(registration.Manifest.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteQuery)
|
||||
registration.Manifest.Pages[0].Permissions = append(registration.Manifest.Pages[0].Permissions, "server.game-client.read", "server.remote.access")
|
||||
registration.Manifest.Permissions = append(registration.Manifest.Permissions, "server.game-client.read", "server.game-client.command", "server.game-client.maintenance", "server.remote.access")
|
||||
registration.Manifest.Capabilities = append(registration.Manifest.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteQuery, domain.JobCapabilityRemoteRunProtectedRCON, domain.JobCapabilityRemoteRunProtectedSQL)
|
||||
registration.Manifest.Pages[0].Permissions = append(registration.Manifest.Pages[0].Permissions, "server.game-client.read", "server.game-client.command", "server.game-client.maintenance", "server.remote.access")
|
||||
registration.Manifest.Pages[0].BridgeActions = append(registration.Manifest.Pages[0].BridgeActions, string(domain.PluginBridgeActionRemoteAccessRequest))
|
||||
registration.Manifest.RuntimeProfiles.TransportProfiles = []domain.RuntimeTransportProfile{{Key: "sqlite-db", Kind: "sqlite", TargetKey: "db/sqlite", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteQuery}}}
|
||||
registration.Manifest.RuntimeProfiles.TransportProfiles = []domain.RuntimeTransportProfile{
|
||||
{Key: "sqlite-db", Kind: "sqlite", TargetKey: "db/sqlite", Capabilities: []string{domain.JobCapabilityRemoteRunDBSQLiteQuery}},
|
||||
{Key: "scum-rcon", Kind: "rcon", TargetKey: "scum-rcon", Capabilities: []string{domain.JobCapabilityRemoteRunProtectedRCON}},
|
||||
{Key: "scum-mutation-db", Kind: "sqlite", TargetKey: "scum-mutation-db", Capabilities: []string{domain.JobCapabilityRemoteRunProtectedSQL}},
|
||||
}
|
||||
registration.Manifest.GameClientBridge = domain.GameClientBridgeManifest{
|
||||
Commands: []domain.GameClientBridgeCommandDeclaration{{Type: "announcement.send", Title: "Send announcement", Permission: "server.game-client.command", ApprovalLevel: domain.GameClientBridgeApprovalLevelOperator, PayloadSchemaRef: "schemas/bridge/announcement.schema.json", ResultSchemaRef: "schemas/bridge/announcement-result.schema.json", TimeoutSeconds: 60, MaxPayloadBytes: 4096}},
|
||||
Snapshots: []domain.GameClientBridgeSnapshotDeclaration{{Type: "players", SchemaVersion: "1", SchemaRef: "schemas/bridge/players.schema.json", Retention: domain.GameClientBridgeRetention{KeepForSeconds: 3600, MaxRecords: 100}}},
|
||||
QueryTemplates: []domain.GameClientBridgeQueryTemplateDeclaration{{Key: "player.lookup", Title: "Player lookup", Permission: "server.game-client.read", Engine: "sqlite", TransportKey: "sqlite-db", TargetKey: "db/sqlite", ParameterSchemaRef: "schemas/bridge/query/player-lookup.parameters.schema.json", ResultSchemaRef: "schemas/bridge/query/player-lookup.result.schema.json", MaxRows: 50, TimeoutSeconds: 10}},
|
||||
Retention: domain.GameClientBridgeRetention{KeepForSeconds: 86400, MaxRecords: 1000},
|
||||
Pages: []domain.GameClientBridgePageContract{{PageKey: "logs", CommandTypes: []string{"announcement.send"}, SnapshotTypes: []string{"players"}, QueryTemplateKeys: []string{"player.lookup"}}},
|
||||
OperationTemplates: []domain.GameClientBridgeOperationTemplateDeclaration{
|
||||
{Key: "player.fame.set", Title: "Set player fame", Permission: "server.game-client.command", ApprovalLevel: domain.GameClientBridgeApprovalLevelOperator, Kind: domain.GameClientBridgeOperationKindRCON, TransportKey: "scum-rcon", TargetKey: "scum-rcon", PayloadSchemaRef: "schemas/bridge/operations/player-fame-set.payload.schema.json", ResultSchemaRef: "schemas/bridge/operations/player-fame-set.result.schema.json", ConfirmationSchemaRef: "schemas/bridge/operations/player-fame-set.confirmation.schema.json", TimeoutSeconds: 60, MaxPayloadBytes: 2048, Safety: domain.GameClientBridgeOperationSafety{RequiresApproval: true, RequiresConfirmation: true}},
|
||||
{Key: "player.attribute.855.set", Title: "Set player attribute 855", Permission: "server.game-client.maintenance", ApprovalLevel: domain.GameClientBridgeApprovalLevelPlatformAdmin, Kind: domain.GameClientBridgeOperationKindSQLiteMutation, TransportKey: "scum-mutation-db", TargetKey: "scum-mutation-db", PayloadSchemaRef: "schemas/bridge/operations/player-attribute-855-set.payload.schema.json", ResultSchemaRef: "schemas/bridge/operations/player-attribute-855-set.result.schema.json", ConfirmationSchemaRef: "schemas/bridge/operations/player-attribute-855-set.confirmation.schema.json", TimeoutSeconds: 120, MaxPayloadBytes: 4096, MaxRowsAffected: 1, Mutation: domain.GameClientBridgeOperationMutationDeclaration{FieldKey: "855", TableKey: "prisoner", IdentityKey: "user_profile_id", ValueKey: "value", ConfirmationQueryKey: "player.lookup", AllowedValueType: "integer", MinValue: 0, MaxValue: 100000}, Safety: domain.GameClientBridgeOperationSafety{RequiresApproval: true, RequiresOfflinePlayer: true, RequiresBeforeValue: true, RequiresConfirmation: true, BackupRequired: true}},
|
||||
},
|
||||
Retention: domain.GameClientBridgeRetention{KeepForSeconds: 86400, MaxRecords: 1000},
|
||||
Pages: []domain.GameClientBridgePageContract{{PageKey: "logs", CommandTypes: []string{"announcement.send"}, SnapshotTypes: []string{"players"}, QueryTemplateKeys: []string{"player.lookup"}, OperationKeys: []string{"player.fame.set", "player.attribute.855.set"}}},
|
||||
}
|
||||
if err := ValidateGamePluginManifestRegistration(registration); err != nil {
|
||||
t.Fatalf("expected bridge catalog to validate, got %v", err)
|
||||
@@ -225,6 +233,55 @@ func TestValidateGamePluginManifestRegistrationValidatesGameClientBridgeCatalog(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
operationTemplateTests := []struct {
|
||||
name string
|
||||
expected string
|
||||
mutate func(*domain.GamePluginManifestRegistration)
|
||||
}{
|
||||
{name: "duplicate key", expected: "key is duplicated", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.OperationTemplates = append(value.Manifest.GameClientBridge.OperationTemplates, value.Manifest.GameClientBridge.OperationTemplates[0])
|
||||
}},
|
||||
{name: "unsafe key", expected: "key is invalid or unsafe", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.OperationTemplates[0].Key = "raw.sql.execute"
|
||||
}},
|
||||
{name: "missing approval", expected: "approvalLevel must require", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.OperationTemplates[0].ApprovalLevel = domain.GameClientBridgeApprovalLevelNone
|
||||
}},
|
||||
{name: "unsafe schema", expected: "schema references", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.OperationTemplates[0].PayloadSchemaRef = "/etc/operation.json"
|
||||
}},
|
||||
{name: "rcon wrong transport", expected: "transport must be rcon", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.OperationTemplates[0].TransportKey = "sqlite-db"
|
||||
value.Manifest.GameClientBridge.OperationTemplates[0].TargetKey = "db/sqlite"
|
||||
}},
|
||||
{name: "mutation wrong transport", expected: "transport must be sqlite", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.OperationTemplates[1].TransportKey = "scum-rcon"
|
||||
value.Manifest.GameClientBridge.OperationTemplates[1].TargetKey = "scum-rcon"
|
||||
}},
|
||||
{name: "mutation row bound", expected: "maxRowsAffected is invalid", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.OperationTemplates[1].MaxRowsAffected = 0
|
||||
}},
|
||||
{name: "mutation missing safety", expected: "safety must require", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.OperationTemplates[1].Safety.RequiresBeforeValue = false
|
||||
}},
|
||||
{name: "undeclared page operation", expected: "undeclared operation template", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.GameClientBridge.Pages[0].OperationKeys = []string{"missing.operation"}
|
||||
}},
|
||||
{name: "page missing operation permission", expected: "must declare operation template permission", mutate: func(value *domain.GamePluginManifestRegistration) {
|
||||
value.Manifest.Pages[0].Permissions = []string{"server.game-client.read", "server.remote.access"}
|
||||
}},
|
||||
}
|
||||
for _, test := range operationTemplateTests {
|
||||
t.Run("operation template "+test.name, func(t *testing.T) {
|
||||
invalid := domain.CopyGamePluginManifestRegistration(registration)
|
||||
test.mutate(&invalid)
|
||||
err := ValidateGamePluginManifestRegistration(invalid)
|
||||
if err == nil || !strings.Contains(err.Error(), test.expected) {
|
||||
t.Fatalf("expected %q rejection, got %v", test.expected, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGamePluginManifestRegistrationRejectsUnsafeCapabilitiesAndPermissions(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user