Files
browser/platform/validator/runtime_log_events_test.go
T

123 lines
5.7 KiB
Go

package validator
import (
"strings"
"testing"
"browser.local/platform/domain"
)
func validRuntimeLogEventProfiles() domain.GamePluginRuntimeProfiles {
return domain.GamePluginRuntimeProfiles{
LogSources: []domain.RuntimeLogSource{{Key: "chat-log", Kind: "file.tail", StreamKey: "chat", CursorKind: "offset", RetentionDays: 30}},
LogEvents: []domain.RuntimeLogEvent{{
Key: "chat-message", Title: "Chat message", SourceKey: "chat-log", EventType: "chat.message",
Permission: "server.logs.read", SchemaRef: "schemas/log-events/chat-message.schema.json", RetentionDays: 30, Severity: "info",
}},
}
}
func TestValidateGamePluginRuntimeProfilesValidatesLogEvents(t *testing.T) {
if err := ValidateGamePluginRuntimeProfiles(validRuntimeLogEventProfiles()); err != nil {
t.Fatalf("expected valid runtime log event declaration, got %v", err)
}
tests := []struct {
name string
expected string
mutate func(*domain.GamePluginRuntimeProfiles)
}{
{name: "undeclared source", expected: "sourceKey must reference", mutate: func(profiles *domain.GamePluginRuntimeProfiles) { profiles.LogEvents[0].SourceKey = "missing" }},
{name: "invalid event type", expected: "eventType is invalid", mutate: func(profiles *domain.GamePluginRuntimeProfiles) { profiles.LogEvents[0].EventType = "chat message" }},
{name: "invalid permission", expected: "permission is not allowed", mutate: func(profiles *domain.GamePluginRuntimeProfiles) { profiles.LogEvents[0].Permission = "server.admin" }},
{name: "unsafe schema", expected: "schemaRef must be a bounded safe relative JSON reference", mutate: func(profiles *domain.GamePluginRuntimeProfiles) {
profiles.LogEvents[0].SchemaRef = "schemas/log events/chat.json"
}},
{name: "retention bound", expected: "retentionDays is invalid", mutate: func(profiles *domain.GamePluginRuntimeProfiles) { profiles.LogEvents[0].RetentionDays = 366 }},
{name: "retention exceeds source", expected: "retentionDays must not exceed the source retention", mutate: func(profiles *domain.GamePluginRuntimeProfiles) { profiles.LogEvents[0].RetentionDays = 31 }},
{name: "invalid severity", expected: "severity is invalid", mutate: func(profiles *domain.GamePluginRuntimeProfiles) { profiles.LogEvents[0].Severity = "emergency" }},
{name: "plugin error severity", expected: "severity is invalid", mutate: func(profiles *domain.GamePluginRuntimeProfiles) { profiles.LogEvents[0].Severity = "error" }},
{name: "duplicate key", expected: "key is duplicated", mutate: func(profiles *domain.GamePluginRuntimeProfiles) {
profiles.LogEvents = append(profiles.LogEvents, profiles.LogEvents[0])
}},
{name: "duplicate event type", expected: "eventType is duplicated", mutate: func(profiles *domain.GamePluginRuntimeProfiles) {
duplicate := profiles.LogEvents[0]
duplicate.Key = "chat-message-copy"
profiles.LogEvents = append(profiles.LogEvents, duplicate)
}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
profiles := domain.CopyGamePluginRuntimeProfiles(validRuntimeLogEventProfiles())
test.mutate(&profiles)
err := ValidateGamePluginRuntimeProfiles(profiles)
if err == nil || !strings.Contains(err.Error(), test.expected) {
t.Fatalf("expected %q validation error, got %v", test.expected, err)
}
})
}
}
func TestValidateGamePluginRuntimeProfilesAcceptsDeclaredLogEventSeverities(t *testing.T) {
for _, severity := range []domain.RuntimeLogEventSeverity{
domain.RuntimeLogEventSeverityInfo,
domain.RuntimeLogEventSeverityNotice,
domain.RuntimeLogEventSeverityWarning,
domain.RuntimeLogEventSeverityCritical,
} {
t.Run(string(severity), func(t *testing.T) {
profiles := validRuntimeLogEventProfiles()
profiles.LogEvents[0].Severity = severity
if err := ValidateGamePluginRuntimeProfiles(profiles); err != nil {
t.Fatalf("expected severity %q to validate, got %v", severity, err)
}
})
}
}
func TestValidateGamePluginRuntimeProfilesAllowsEventRetentionWhenSourceUsesDefault(t *testing.T) {
profiles := validRuntimeLogEventProfiles()
profiles.LogSources[0].RetentionDays = 0
if err := ValidateGamePluginRuntimeProfiles(profiles); err != nil {
t.Fatalf("expected source default retention to allow bounded event retention, got %v", err)
}
}
func TestValidateGamePluginRuntimeProfilesRejectsUnsafeLogEventSemantics(t *testing.T) {
unsafeEventTypes := []string{
"ops.shell.execute",
"ops.execute",
"ops.sql.query",
"ops.raw-host-path",
"run.socket.open",
"auth.credential.exposed",
"auth.api-key.exposed",
}
for _, eventType := range unsafeEventTypes {
t.Run(eventType, func(t *testing.T) {
profiles := validRuntimeLogEventProfiles()
profiles.LogEvents[0].EventType = eventType
err := ValidateGamePluginRuntimeProfiles(profiles)
if err == nil || !strings.Contains(err.Error(), "eventType contains unsafe operation semantics") {
t.Fatalf("expected unsafe event type %q to be rejected, got %v", eventType, err)
}
})
}
}
func TestValidateGamePluginManifestRegistrationRequiresDeclaredLogEventPermission(t *testing.T) {
registration := validGamePluginManifestRegistration()
registration.Manifest.RuntimeProfiles = validRuntimeLogEventProfiles()
registration.Manifest.RuntimeProfiles.LogEvents[0].Permission = "server.game-client.read"
err := ValidateGamePluginManifestRegistration(registration)
if err == nil || !strings.Contains(err.Error(), "manifest.runtimeProfiles.logEvents[0].permission must be declared by the plugin") {
t.Fatalf("expected undeclared log event permission rejection, got %v", err)
}
registration.Manifest.Permissions = append(registration.Manifest.Permissions, "server.game-client.read")
if err := ValidateGamePluginManifestRegistration(registration); err != nil {
t.Fatalf("expected declared log event permission to validate, got %v", err)
}
}