package validator import ( "strings" "testing" "browser.local/platform/domain" ) func TestValidateAIProviderRejectsRawSecret(t *testing.T) { provider := validAIProvider() provider.APIKeyRef = "sk-test-secret" err := ValidateAIProvider(provider) if err == nil || !strings.Contains(err.Error(), "apiKeyRef must reference secret storage") { t.Fatalf("expected raw secret rejection, got %v", err) } } func TestValidateAIProviderRequiresDefaultModelInModels(t *testing.T) { provider := validAIProvider() provider.DefaultModel = "missing-model" err := ValidateAIProvider(provider) if err == nil || !strings.Contains(err.Error(), "defaultModel must be included") { t.Fatalf("expected default model validation, got %v", err) } } func TestValidateGamePluginManifestRegistration(t *testing.T) { registration := validGamePluginManifestRegistration() if err := ValidateGamePluginManifestRegistration(registration); err != nil { t.Fatalf("expected manifest registration to validate, got %v", err) } } func TestValidateGamePluginManifestRegistrationRejectsUnsafeRequests(t *testing.T) { registration := validGamePluginManifestRegistration() registration.Manifest.Description = "requires direct run socket and raw AI key material" registration.ManifestRef = "file:///etc/plugin.json" err := ValidateGamePluginManifestRegistration(registration) if err == nil { t.Fatal("expected unsafe manifest registration rejection") } message := err.Error() for _, want := range []string{"manifestRef is unsafe", "direct run access", "raw credential or AI/provider key"} { if !strings.Contains(message, want) { t.Fatalf("expected validation error to contain %q, got %v", want, err) } } } func TestValidateGamePluginManifestRegistrationValidatesRuntimeProfiles(t *testing.T) { t.Run("unsafe runtime value", func(t *testing.T) { registration := validGamePluginManifestRegistration() registration.Manifest.RuntimeProfiles = domain.GamePluginRuntimeProfiles{Discovery: []domain.RuntimeDiscoveryProbe{{Key: "server-root-check", Kind: "file.exists", TargetKey: "server-root", Required: true, Expected: "/Users/operator/server"}}} err := ValidateGamePluginManifestRegistration(registration) if err == nil || !strings.Contains(err.Error(), "unsafe runtime content") { t.Fatalf("expected unsafe runtime profile rejection, got %v", err) } }) t.Run("undeclared transport reference", func(t *testing.T) { registration := validGamePluginManifestRegistration() registration.Manifest.RuntimeProfiles = domain.GamePluginRuntimeProfiles{LifecycleProfiles: []domain.RuntimeLifecycleProfile{{Key: "local", Mode: "local-process", Capabilities: []string{"process.start"}, TransportKeys: []string{"missing-transport"}}}} err := ValidateGamePluginManifestRegistration(registration) if err == nil || !strings.Contains(err.Error(), "undeclared transport") { t.Fatalf("expected cross-profile reference rejection, got %v", err) } }) } func TestValidateGamePluginManifestRegistrationRejectsUnsafeCapabilitiesAndPermissions(t *testing.T) { registration := validGamePluginManifestRegistration() registration.Manifest.Capabilities = append(registration.Manifest.Capabilities, "run.socket") registration.Manifest.Permissions = append(registration.Manifest.Permissions, "provider.key.read") err := ValidateGamePluginManifestRegistration(registration) if err == nil || !strings.Contains(err.Error(), "manifest.capabilities") || !strings.Contains(err.Error(), "permissions") { t.Fatalf("expected unsafe capability and permission rejection, got %v", err) } } func TestValidateServerInstanceDependencies(t *testing.T) { instance := domain.ServerInstance{ ID: "server-1", PluginID: "server.scum", PluginVersion: "1.0.0", RunEndpointID: "run-local", Name: "SCUM #1", State: domain.ServerInstanceStateDraft, ConfigVersion: 1, } plugin := domain.GamePlugin{ ID: "server.scum", Version: "1.0.0", RequiredRunCapabilities: []string{"process.start", "logs.read"}, Status: domain.GamePluginStatusInstalled, } endpoint := domain.RunEndpoint{ ID: "run-local", Status: domain.RunEndpointStatusOnline, Capabilities: []string{"process.start", "logs.read", "files.read"}, } if err := ValidateServerInstanceDependencies(instance, plugin, endpoint); err != nil { t.Fatalf("expected valid dependencies, got %v", err) } endpoint.Capabilities = []string{"process.start"} err := ValidateServerInstanceDependencies(instance, plugin, endpoint) if err == nil || !strings.Contains(err.Error(), "logs.read") { t.Fatalf("expected missing capability error, got %v", err) } } func TestValidateServerInstanceRejectsDeletedCreateState(t *testing.T) { instance := domain.ServerInstance{ ID: "server-1", PluginID: "server.scum", PluginVersion: "1.0.0", RunEndpointID: "run-local", Name: "SCUM #1", State: domain.ServerInstanceStateDeleted, } err := ValidateServerInstance(instance) if err == nil || !strings.Contains(err.Error(), "state must not be deleted") { t.Fatalf("expected deleted state rejection, got %v", err) } } func TestValidateJobBoundsProgress(t *testing.T) { job := domain.Job{ ID: "job-1", RunEndpointID: "run-local", Capability: "process.start", IdempotencyKey: "idem-1", State: domain.JobStateQueued, Progress: domain.JobProgress{Percent: 101}, } err := ValidateJob(job) if err == nil || !strings.Contains(err.Error(), "progress.percent") { t.Fatalf("expected progress bounds error, got %v", err) } } func TestValidateArtifactLogAndAudit(t *testing.T) { artifact := domain.Artifact{ ID: "artifact-1", OwnerKind: domain.ArtifactOwnerKindJob, OwnerID: "job-1", SizeBytes: 10, Checksum: "sha256:abc", State: domain.ArtifactStateAvailable, } if err := ValidateArtifact(artifact); err != nil { t.Fatalf("expected artifact to validate, got %v", err) } stream := domain.LogStream{ ID: "log-1", ServerInstanceID: "server-1", Source: domain.LogStreamSourceFile, StreamKey: "server.log", StorageBackend: domain.LogStorageBackendLocalSegments, RetentionPolicy: "default", } if err := ValidateLogStream(stream); err != nil { t.Fatalf("expected log stream to validate, got %v", err) } audit := domain.AuditEvent{ ID: "audit-1", ActorID: "user-1", Action: "server.create", ResourceKind: "server-instance", ResourceID: "server-1", Result: domain.AuditResultSuccess, Summary: "created server instance", } if err := ValidateAuditEvent(audit); err != nil { t.Fatalf("expected audit event to validate, got %v", err) } audit.Summary = "bearer raw-secret" if err := ValidateAuditEvent(audit); err == nil || !strings.Contains(err.Error(), "summary must be redacted") { t.Fatalf("expected audit redaction error, got %v", err) } } func validAIProvider() domain.AIProvider { return domain.AIProvider{ ID: "ai.openai", Name: "OpenAI", Kind: domain.AIProviderKindOpenAI, BaseURL: "https://api.openai.com/v1", APIKeyRef: "secret://providers/openai", Models: []string{"gpt-4.1", "gpt-4.1-mini"}, DefaultModel: "gpt-4.1", RelayMode: domain.AIRelayModeDirect, TimeoutMS: 30000, Status: domain.AIProviderStatusActive, RedactionPolicy: "default", } } func validGamePluginManifestRegistration() domain.GamePluginManifestRegistration { return domain.GamePluginManifestRegistration{ ManifestRef: "artifact://manifests/game.example/0.1.0", Manifest: domain.GamePluginManifest{ ID: "game.example", Name: "Example Server", Description: "Development plugin", Version: "0.1.0", Kind: "game-plugin", Tags: []string{"example", "development"}, Server: domain.GamePluginManifestServer{ Type: "example", DisplayName: "Example Server", SupportedOS: []string{"linux", "darwin"}, CreateFormSchema: "schemas/create-form.schema.json", }, Capabilities: []string{"process.install", "process.start", "process.stop", "logs.read", "files.read", "artifacts.read", "ai.invoke"}, Permissions: []string{"server.read", "server.lifecycle", "server.logs.read", "server.files.read", "server.artifacts.read", "ai.invoke"}, Actions: domain.PluginLifecycleActions{ Install: "actions/install.json", Start: "actions/start.json", Stop: "actions/stop.json", Restart: "actions/restart.json", }, Pages: []domain.GamePluginPage{ {Key: "logs", Title: "Logs", Path: "/logs", Permissions: []string{"server.logs.read"}}, }, AI: domain.GamePluginManifestAI{Purposes: []string{"logs.diagnose"}}, }, } }