132 lines
6.6 KiB
Go
132 lines
6.6 KiB
Go
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)
|
|
}
|
|
}
|