package model import ( "reflect" "testing" "browser.local/platform/domain" ) func TestTableNames(t *testing.T) { tests := map[string]string{ User{}.TableName(): "users", AIProvider{}.TableName(): "ai_providers", GamePlugin{}.TableName(): "game_plugins", ServerInstance{}.TableName(): "server_instances", RunEndpoint{}.TableName(): "run_endpoints", Job{}.TableName(): "jobs", Artifact{}.TableName(): "artifacts", LogStream{}.TableName(): "log_streams", PluginLifecycleInstallation{}.TableName(): "plugin_lifecycle_installations", AIConfigDiff{}.TableName(): "ai_config_diffs", SCUMUser{}.TableName(): "scum_user", SCUMUserTrajectory{}.TableName(): "scum_user_trajectory", SCUMVehicle{}.TableName(): "scum_vehicle", SCUMVehicleTrajectory{}.TableName(): "scum_vehicle_trajectory", SCUMVehicleLock{}.TableName(): "scum_vehicle_lock", } for got, want := range tests { if got != want { t.Fatalf("expected table name %q, got %q", want, got) } } } func TestGamePluginModelRoundTripCopiesSlices(t *testing.T) { source := domain.GamePlugin{ ID: "server.scum", Name: "SCUM", Version: "1.0.0", ServerType: "scum", ManifestRef: "artifact://manifest", CreateFormSchemaRef: "artifact://schema", RequiredRunCapabilities: []string{"process.start", "logs.read"}, DeclaredPermissions: []string{"server.logs.read"}, SupportedOS: []string{"linux"}, Pages: []domain.GamePluginPage{ {Key: "logs", Title: "Logs", Path: "/logs", Permissions: []string{"server.logs.read"}}, }, Tags: []string{"survival"}, AIPurposes: []string{"logs.diagnose"}, ProductionLifecycle: domain.GamePluginProductionLifecycle{ Operations: []string{"install", "upgrade", "rollback"}, DependencyPolicy: "required", }, RuntimeProfiles: domain.GamePluginRuntimeProfiles{LifecycleProfiles: []domain.RuntimeLifecycleProfile{{Key: "local", Mode: "local-process", Capabilities: []string{"process.start"}}}}, Permissions: domain.PluginPermissions{ Jobs: true, Logs: true, }, Status: domain.GamePluginStatusInstalled, } row := GamePluginFromDomain(source) roundTrip := row.ToDomain() roundTrip.RequiredRunCapabilities[0] = "files.read" roundTrip.DeclaredPermissions[0] = "ai.invoke" roundTrip.SupportedOS[0] = "darwin" roundTrip.Pages[0].Permissions[0] = "ai.invoke" roundTrip.Tags[0] = "mutated" roundTrip.AIPurposes[0] = "config.suggest" roundTrip.ProductionLifecycle.Operations[0] = "retire" roundTrip.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] = "process.stop" if source.RequiredRunCapabilities[0] != "process.start" { t.Fatalf("expected source plugin capabilities to remain unchanged, got %+v", source.RequiredRunCapabilities) } if row.RequiredRunCapabilities[0] != "process.start" { t.Fatalf("expected model plugin capabilities to remain unchanged, got %+v", row.RequiredRunCapabilities) } if source.DeclaredPermissions[0] != "server.logs.read" || source.Pages[0].Permissions[0] != "server.logs.read" || source.Tags[0] != "survival" || source.AIPurposes[0] != "logs.diagnose" { t.Fatalf("expected source plugin registry metadata to remain unchanged, got %+v", source) } if row.DeclaredPermissions[0] != "server.logs.read" || row.Pages[0].Permissions[0] != "server.logs.read" || row.Tags[0] != "survival" || row.AIPurposes[0] != "logs.diagnose" { t.Fatalf("expected model plugin registry metadata to remain unchanged, got %+v", row) } if source.ProductionLifecycle.Operations[0] != "install" || row.ProductionLifecycle.Operations[0] != "install" { t.Fatalf("expected production lifecycle declaration to round-trip without aliasing, source=%+v row=%+v", source.ProductionLifecycle, row.ProductionLifecycle) } if source.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] != "process.start" || row.RuntimeProfiles.LifecycleProfiles[0].Capabilities[0] != "process.start" { t.Fatalf("expected runtime profiles to round-trip without aliasing, source=%+v row=%+v", source.RuntimeProfiles, row.RuntimeProfiles) } } func TestJobExecutionInputModelRoundTripPreservesLifecycleMetadata(t *testing.T) { source := domain.JobExecutionInput{ WorkspaceScope: "server-workspace", PluginID: "game.scum", LifecycleOperation: "upgrade", TargetVersion: "2.0.0", Inputs: map[string]string{"templateKey": "players.by-id", "playerId": "steam-123"}, Deployment: &domain.ServerDeploymentDefinition{ Mode: domain.ServerDeploymentModeGuided, ProfileKey: "run-local", RuntimeBindings: map[string]string{"installRoot": "D:\\scumserver"}, CreateInputs: map[string]string{"gamePort": "27000", "maxPlayers": "128"}, ServerRoot: "D:\\scumserver", WorkingDirectory: "D:\\scumserver", InstallCommand: "install.cmd", StartCommand: "start.cmd", Shell: domain.ServerCommandShellCmd, Revision: 3, }, ServerDeploymentPlan: &domain.ServerDeploymentPlan{ SchemaVersion: "1", Operation: "install", PluginID: "game.scum", Prerequisites: []domain.RuntimeServerPrerequisite{{Key: "steamcmd", Kind: "tool"}}, }, } row := executionInputFromDomain(source) source.Inputs["playerId"] = "source-mutated" if row.Inputs["playerId"] != "steam-123" { t.Fatalf("expected model execution inputs to be isolated from source mutation, row=%+v", row.Inputs) } source.Deployment.CreateInputs["maxPlayers"] = "64" if row.Deployment == nil || row.Deployment.CreateInputs["maxPlayers"] != "128" { t.Fatalf("expected model deployment inputs to be isolated from source mutation, row=%+v", row.Deployment) } source.Inputs["playerId"] = "steam-123" source.Deployment.CreateInputs["maxPlayers"] = "128" row.Inputs["playerId"] = "row-mutated" if source.Inputs["playerId"] != "steam-123" { t.Fatalf("expected source execution inputs to be isolated from model mutation, source=%+v", source.Inputs) } row.Deployment.CreateInputs["maxPlayers"] = "32" if source.Deployment.CreateInputs["maxPlayers"] != "128" { t.Fatalf("expected source deployment inputs to be isolated from model mutation, source=%+v", source.Deployment) } row.Inputs["playerId"] = "steam-123" row.Deployment.CreateInputs["maxPlayers"] = "128" roundTrip := row.ToDomain() if !reflect.DeepEqual(roundTrip, source) { t.Fatalf("expected lifecycle execution metadata to round-trip, got %+v", roundTrip) } roundTrip.Inputs["playerId"] = "mutated" roundTrip.Deployment.CreateInputs["maxPlayers"] = "16" if source.Inputs["playerId"] != "steam-123" || row.Inputs["playerId"] != "steam-123" || source.Deployment.CreateInputs["maxPlayers"] != "128" || row.Deployment.CreateInputs["maxPlayers"] != "128" { t.Fatalf("expected execution inputs to round-trip without aliasing, source=%+v row=%+v", source, row) } } func TestAIProviderModelUsesKeyReference(t *testing.T) { source := 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"}, DefaultModel: "gpt-4.1", RelayMode: domain.AIRelayModeDirect, TimeoutMS: 30000, Status: domain.AIProviderStatusActive, RedactionPolicy: "default", } row := AIProviderFromDomain(source) if row.APIKeyRef != source.APIKeyRef { t.Fatalf("expected API key reference %q, got %q", source.APIKeyRef, row.APIKeyRef) } roundTrip := row.ToDomain() roundTrip.Models[0] = "mutated" if row.Models[0] != "gpt-4.1" { t.Fatalf("expected model provider models to remain unchanged, got %+v", row.Models) } }