功能修改

This commit is contained in:
npc0-hue
2026-07-20 16:42:33 +08:00
parent 48b8ad8d6c
commit a0e69417db
224 changed files with 22015 additions and 884 deletions
+102 -1
View File
@@ -72,6 +72,106 @@ 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.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.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"}}},
}
if err := ValidateGamePluginManifestRegistration(registration); err != nil {
t.Fatalf("expected bridge catalog to validate, got %v", err)
}
for _, commandType := range []string{"sql.execute", "sqlExecute", "database.execute", "database.query", "shell.execute", "powershell.execute", "script.run", "terminal.execute", "command.run"} {
t.Run("unsafe command type "+commandType, func(t *testing.T) {
unsafeType := registration
unsafeType.Manifest.GameClientBridge = domain.CopyGameClientBridgeManifest(registration.Manifest.GameClientBridge)
unsafeType.Manifest.GameClientBridge.Commands[0].Type = commandType
err := ValidateGamePluginManifestRegistration(unsafeType)
if err == nil || !strings.Contains(err.Error(), "type is invalid or unsafe") {
t.Fatalf("expected %q to be rejected, got %v", commandType, err)
}
})
}
unsafe := registration
unsafe.Manifest.GameClientBridge = domain.CopyGameClientBridgeManifest(registration.Manifest.GameClientBridge)
unsafe.Manifest.GameClientBridge.Commands[0].Type = "shell.execute"
unsafe.Manifest.GameClientBridge.Commands[0].ApprovalLevel = ""
unsafe.Manifest.GameClientBridge.Commands[0].PayloadSchemaRef = "/etc/command.json"
unsafe.Manifest.GameClientBridge.Pages[0].CommandTypes = []string{"undeclared.command"}
err := ValidateGamePluginManifestRegistration(unsafe)
if err == nil {
t.Fatal("expected unsafe bridge catalog rejection")
}
for _, expected := range []string{"type is invalid or unsafe", "approvalLevel is invalid", "schema references", "undeclared command"} {
if !strings.Contains(err.Error(), expected) {
t.Fatalf("expected %q in validation error: %v", expected, err)
}
}
queryTemplateTests := []struct {
name string
expected string
mutate func(*domain.GamePluginManifestRegistration)
}{
{name: "duplicate key", expected: "key is duplicated", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.QueryTemplates = append(value.Manifest.GameClientBridge.QueryTemplates, value.Manifest.GameClientBridge.QueryTemplates[0])
}},
{name: "unsupported engine", expected: "engine must be sqlite", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.QueryTemplates[0].Engine = "mysql"
}},
{name: "undeclared permission", expected: "permission must be declared", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.QueryTemplates[0].Permission = "server.database.admin"
}},
{name: "unsafe schema", expected: "schema references", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.QueryTemplates[0].ParameterSchemaRef = "/etc/query.json"
}},
{name: "row bound", expected: "maxRows is invalid", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.QueryTemplates[0].MaxRows = 501
}},
{name: "timeout bound", expected: "timeoutSeconds is invalid", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.QueryTemplates[0].TimeoutSeconds = 61
}},
{name: "unknown transport", expected: "transportKey must reference", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.QueryTemplates[0].TransportKey = "missing"
}},
{name: "target mismatch", expected: "targetKey must match", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.QueryTemplates[0].TargetKey = "db/other"
}},
{name: "missing sqlite capability", expected: "transport must be sqlite", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.RuntimeProfiles.TransportProfiles[0].Capabilities = []string{"files.read"}
}},
{name: "undeclared page template", expected: "undeclared query template", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.GameClientBridge.Pages[0].QueryTemplateKeys = []string{"missing.lookup"}
}},
{name: "page missing template permission", expected: "must declare query template permission", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.Pages[0].Permissions = []string{"server.logs.read", "server.remote.access"}
}},
{name: "page missing remote action", expected: "must declare remote.access.request", mutate: func(value *domain.GamePluginManifestRegistration) {
value.Manifest.Pages[0].BridgeActions = nil
}},
}
for _, test := range queryTemplateTests {
t.Run("query 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) {
registration := validGamePluginManifestRegistration()
registration.Manifest.Capabilities = append(registration.Manifest.Capabilities, "run.socket")
@@ -235,7 +335,8 @@ func validGamePluginManifestRegistration() domain.GamePluginManifestRegistration
Pages: []domain.GamePluginPage{
{Key: "logs", Title: "Logs", Path: "/logs", Permissions: []string{"server.logs.read"}},
},
AI: domain.GamePluginManifestAI{Purposes: []string{"logs.diagnose"}},
AI: domain.GamePluginManifestAI{Purposes: []string{"logs.diagnose"}, Mediation: "platform", ConfigWritePolicy: "review-required"},
ProductionLifecycle: domain.GamePluginProductionLifecycle{Operations: []string{"install", "enable", "disable", "upgrade", "rollback", "retire", "dependency-check"}, DependencyPolicy: "optional", ApprovalRequired: []string{"disable", "rollback", "retire"}},
},
}
}