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) } }