123 lines
5.7 KiB
Go
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)
|
|
}
|
|
}
|