package service import ( "strings" "testing" "browser.local/platform/domain" ) func TestClientManagerLifecycleInputIncludesGeneratedCompanionConfig(t *testing.T) { svc, ownerSession, instance := newDistributionTestFixture(t) plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { t.Fatalf("get plugin: %v", err) } capabilities := []string{"component.register", "component.heartbeat", "component.health", "component.control", "game-client.bridge", "logs.stream"} for index := range plugin.RuntimeProfiles.ClientManagers { manager := &plugin.RuntimeProfiles.ClientManagers[index] if manager.Key != "scum-client-manager" { continue } manager.ConfigTemplates = []domain.RuntimeConfigTemplate{{Key: "client-config", TemplateRef: "config.yaml.example", OutputRef: "config.yaml"}} manager.Health.IntervalSeconds = 30 manager.Health.RequiredCapabilities = domain.CopyStringSlice(capabilities) } plugin.GameClientBridge.Companion = domain.GameClientBridgeCompanionDeclaration{ ProfileKey: "scum-client-manager", ConfigTemplateKey: "client-config", ConfigSchemaRef: "schemas/companion/config.schema.json", ConfigFormat: "yaml", PlatformBaseURLSource: "run-control", RegistrationProof: "hmac-sha256", ProofMaterialSource: "component-package", ProofMaterialEnv: "SCUM_COMPONENT_PROOF", SessionMode: "component-session", TLSPolicy: "verify-system-roots", HeartbeatIntervalSeconds: 30, CommandPollIntervalSeconds: 5, RequestTimeoutSeconds: 15, } if err := svc.store.GamePlugins().Update(plugin); err != nil { t.Fatalf("update companion declaration: %v", err) } distribution := buildLifecycleDistribution(t, svc, ownerSession, instance, "1.0.0", "companion-config-build-v1") view, err := svc.DeployClientManagerForSession(ownerSession, domain.ClientManagerDeployRequest{ServerInstanceID: instance.ID, ProfileKey: "scum-client-manager", DistributionID: distribution.ID, IdempotencyKey: "companion-config-deploy-v1"}) if err != nil { t.Fatalf("queue companion deployment: %v", err) } runSession := registerClientManagerRun(t, svc) claim := claimClientManagerJob(t, svc, runSession, domain.JobCapabilityClientManagerDeploy) request := domain.ClientManagerLifecycleInputRequest{RunEndpointID: instance.RunEndpointID, SessionToken: runSession, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt} input, err := svc.GetClientManagerLifecycleInput(request) if err != nil { t.Fatalf("get companion lifecycle input: %v", err) } config := input.CompanionConfig if config == nil { t.Fatal("expected generated companion config input") } if config.SchemaVersion != 1 || config.ConfigTemplateRef != "config.yaml.example" || config.ConfigOutputRef != "config.yaml" || config.ConfigSchemaRef != "schemas/companion/config.schema.json" { t.Fatalf("unexpected companion template contract: %+v", config) } if config.InstallationID != view.Installation.ID || config.ServerInstanceID != instance.ID || config.PluginID != plugin.ID || config.ProfileKey != "scum-client-manager" || config.ArtifactID != distribution.ArtifactID || config.KeyGeneration != distribution.KeyGeneration || config.DeploymentGeneration != view.Installation.DeploymentGeneration { t.Fatalf("unexpected companion identity fence: %+v", config) } if strings.Join(config.Capabilities, ",") != strings.Join(capabilities, ",") || config.PlatformBaseURLSource != "run-control" || config.RegistrationProof != "hmac-sha256" || config.ProofMaterialEnv != "SCUM_COMPONENT_PROOF" || config.SessionMode != "component-session" || config.TLSPolicy != "verify-system-roots" { t.Fatalf("unexpected companion registration policy: %+v", config) } if config.HeartbeatIntervalSeconds != 30 || config.CommandPollIntervalSeconds != 5 || config.RequestTimeoutSeconds != 15 { t.Fatalf("unexpected companion timing policy: %+v", config) } plugin, err = svc.store.GamePlugins().Get(instance.PluginID) if err != nil { t.Fatalf("reload plugin: %v", err) } plugin.GameClientBridge.Companion.TLSPolicy = "skip-verification" if err := svc.store.GamePlugins().Update(plugin); err != nil { t.Fatalf("persist unsafe companion policy: %v", err) } if _, err := svc.GetClientManagerLifecycleInput(request); err == nil || !strings.Contains(err.Error(), "security policy") { t.Fatalf("expected unsafe persisted policy to fail closed, got %v", err) } plugin.GameClientBridge.Companion.TLSPolicy = "verify-system-roots" plugin.GameClientBridge.Companion.ProofMaterialEnv = "LD_PRELOAD" if err := svc.store.GamePlugins().Update(plugin); err != nil { t.Fatalf("persist reserved proof environment: %v", err) } if _, err := svc.GetClientManagerLifecycleInput(request); err == nil || !strings.Contains(err.Error(), "proofMaterialEnv") { t.Fatalf("expected reserved proof environment to fail closed, got %v", err) } plugin.GameClientBridge.Companion.ProofMaterialEnv = "SCUM_COMPONENT_PROOF" for index := range plugin.RuntimeProfiles.ClientManagers { plugin.RuntimeProfiles.ClientManagers[index].ConfigTemplates = nil } if err := svc.store.GamePlugins().Update(plugin); err != nil { t.Fatalf("remove config template: %v", err) } if _, err := svc.GetClientManagerLifecycleInput(request); err == nil || !strings.Contains(err.Error(), "config template") { t.Fatalf("expected missing template to fail safely, got %v", err) } } func TestClientManagerCompanionConfigInputIsOptionalForOtherProfiles(t *testing.T) { config, err := clientManagerCompanionConfigInput( domain.GamePlugin{ID: "game.example", GameClientBridge: domain.GameClientBridgeManifest{Companion: domain.GameClientBridgeCompanionDeclaration{ProfileKey: "bridge-client"}}}, domain.RuntimeClientManagerProfile{Key: "metrics-client"}, domain.ClientManagerInstallation{ProfileKey: "metrics-client"}, "artifact-1", "1.0.0", "revision-1", ) if err != nil || config != nil { t.Fatalf("expected no companion config for another profile, config=%+v err=%v", config, err) } config, err = clientManagerCompanionConfigInput( domain.GamePlugin{ID: "game.example", GameClientBridge: domain.GameClientBridgeManifest{Companion: domain.GameClientBridgeCompanionDeclaration{ConfigFormat: "yaml"}}}, domain.RuntimeClientManagerProfile{Key: "metrics-client"}, domain.ClientManagerInstallation{ProfileKey: "metrics-client"}, "artifact-1", "1.0.0", "revision-1", ) if err == nil || config != nil || !strings.Contains(err.Error(), "incomplete") { t.Fatalf("expected partial companion declaration to fail closed, config=%+v err=%v", config, err) } }