package service import ( "errors" "strings" "testing" "time" "browser.local/platform/domain" "browser.local/platform/repo" ) type generatedPackageConfig struct { Kind string ServerInstanceID string PluginID string RunEndpointID string ProfileKey string TargetOS string TargetArch string SecretRef string KeyGeneration int AuthKey string } func TestCoreServiceGeneratesRunDistributionWithEncryptedSingletonKey(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-run-generate", }) if err != nil { t.Fatalf("generate run distribution: %v", err) } if distribution.KeyGeneration != 1 || distribution.SecretRef == "" || distribution.PackageFormat != "raw-executable" || distribution.Status != domain.DistributionStatusBuilding || distribution.BuildJobID == "" || distribution.Checksum != "" { t.Fatalf("unexpected run distribution: %+v", distribution) } job, err := svc.GetJob(distribution.BuildJobID) if err != nil || job.Capability != domain.JobCapabilityDistributionBuild || job.State != domain.JobStateQueued { t.Fatalf("expected queued backend build job, job=%+v err=%v", job, err) } if _, err := svc.GetArtifact(distribution.ArtifactID); !errors.Is(err, repo.ErrNotFound) { t.Fatalf("generation must not publish config JSON as an artifact, got %v", err) } keys, err := svc.store.EncryptedComponentKeys().List(domain.EncryptedComponentKeyFilter{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentRun, Status: domain.ComponentKeyStatusActive, }) if err != nil { t.Fatalf("list component keys: %v", err) } if len(keys) != 1 || keys[0].Generation != 1 || !strings.HasPrefix(keys[0].EncryptedKey, "enc:v1:") { t.Fatalf("expected one active encrypted run key, got %+v", keys) } config := readGeneratedPackageConfig(t, svc, session, distribution.ArtifactID) if config.AuthKey == "" || config.AuthKey == keys[0].EncryptedKey || strings.Contains(distribution.SecretRef, config.AuthKey) { t.Fatalf("run package key leaked through metadata or was not encrypted, config=%+v key=%+v distribution=%+v", config, keys[0], distribution) } if len(config.AuthKey) < 80 { t.Fatalf("expected longer run component key, got length %d", len(config.AuthKey)) } auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentRun, Generation: config.KeyGeneration, Key: config.AuthKey, }) if err != nil { t.Fatalf("authenticate run: %v", err) } if !auth.Allowed { t.Fatalf("expected current run key to authenticate, got %+v", auth) } second, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-run-generate-second", }) if err != nil { t.Fatalf("generate second run distribution: %v", err) } if second.KeyGeneration != 1 || second.SecretRef != distribution.SecretRef { t.Fatalf("expected second package to reuse current singleton key, got first=%+v second=%+v", distribution, second) } keys, err = svc.store.EncryptedComponentKeys().List(domain.EncryptedComponentKeyFilter{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentRun, Status: domain.ComponentKeyStatusActive, }) if err != nil || len(keys) != 1 { t.Fatalf("expected one active key after second generation, keys=%+v err=%v", keys, err) } } func TestCoreServiceBuildsDedicatedRunInPlatformBuilder(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) targetID := instance.RunEndpointID instance.State = domain.ServerInstanceStateDraft instance.DeploymentTargetID = targetID instance.RunEndpointID = "server-run-" + instance.ID if err := svc.store.ServerInstances().Update(instance); err != nil { t.Fatalf("prepare target-bound draft: %v", err) } distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "dedicated-target-build"}) if err != nil { t.Fatalf("generate dedicated Run: %v", err) } job, err := svc.GetJob(distribution.BuildJobID) if err != nil || job.RunEndpointID != platformDistributionBuilderEndpointID || distribution.RunEndpointID != instance.RunEndpointID { t.Fatalf("expected platform build for dedicated Run %q, job=%+v distribution=%+v err=%v", instance.RunEndpointID, job, distribution, err) } } func TestCoreServiceBuildsSCUMGuidedRunWithoutCompleteRuntimeBinding(t *testing.T) { svc := newTestCoreService() plugin := scumDeploymentTestPlugin() plugin.Name = "SCUM" plugin.Version = "0.1.1" plugin.ServerType = "scum" plugin.Status = domain.GamePluginStatusInstalled plugin.SupportedOS = []string{"windows"} plugin.DeclaredPermissions = []string{"server.run.distribution"} plugin.LifecycleActions = domain.PluginLifecycleActions{Install: "actions/install.json", Start: "actions/start.json", Stop: "actions/stop.json", Status: "actions/status.json"} plugin.RuntimeProfiles.LifecycleProfiles = []domain.RuntimeLifecycleProfile{{Key: "run-local", Mode: "local-process", Capabilities: []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus}, ActionRefs: domain.PluginLifecycleActions{Install: "actions/install.json", Start: "actions/start.json", Stop: "actions/stop.json", Status: "actions/status.json"}, Platforms: []string{"windows"}}} requireManualSCUMRuntimeBindings(&plugin, "run-local") if err := svc.store.GamePlugins().Create(plugin); err != nil { t.Fatalf("create SCUM plugin: %v", err) } session := createServiceUserAndLogin(t, svc, domain.User{ID: "scum-run-builder-owner", DisplayName: "SCUM Run Builder Owner", Email: "scum-run-builder@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"}) created, err := svc.CreateServerInstanceWorkflowForSession(session, domain.ServerLifecycleCreate{ ID: "scum-guided-run-build", PluginID: plugin.ID, Name: "SCUM Guided Run Build", IdempotencyKey: "scum-guided-run-build-create", ProfileKey: "run-local", Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, ServerRoot: `D:\scum-guided-run-build`, CreateInputs: map[string]string{"serverName": "Moon", "gamePort": "27000", "queryPort": "27015", "maxPlayers": "128"}}, }) if err != nil { t.Fatalf("create SCUM guided draft: %v", err) } if _, err := svc.runtimeBindingForServer(created.Instance.ID); !errors.Is(err, repo.ErrNotFound) { t.Fatalf("SCUM guided draft should not need a complete runtime binding before run build: %v", err) } distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ServerInstanceID: created.Instance.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: "scum-guided-run-build"}) if err != nil { t.Fatalf("generate SCUM guided run without runtime binding: %v", err) } if distribution.RunEndpointID != dedicatedRunEndpointID(created.Instance.ID) { t.Fatalf("expected generated run endpoint to be promoted, got %+v", distribution) } } func TestCoreServicePromotesLegacyRunBindingBeforeDistributionBuild(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) legacyEndpointID := instance.RunEndpointID instance.State = domain.ServerInstanceStateFailed if err := svc.store.ServerInstances().Update(instance); err != nil { t.Fatalf("mark legacy server failed: %v", err) } distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ServerInstanceID: instance.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: "legacy-promote-build"}) if err != nil { t.Fatalf("generate promoted legacy Run: %v", err) } migrated, err := svc.GetServerInstance(instance.ID) if err != nil { t.Fatalf("get migrated server: %v", err) } if migrated.DeploymentTargetID != legacyEndpointID || migrated.RunEndpointID != "server-run-"+instance.ID { t.Fatalf("expected legacy binding promotion, got %+v", migrated) } job, err := svc.GetJob(distribution.BuildJobID) if err != nil || job.RunEndpointID != platformDistributionBuilderEndpointID || distribution.RunEndpointID != migrated.RunEndpointID { t.Fatalf("expected platform build and dedicated package endpoint %q, job=%+v distribution=%+v err=%v", migrated.RunEndpointID, job, distribution, err) } } func TestCoreServiceDistributionBuildIgnoresStaleRunEndpoint(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) svc.now = func() time.Time { return fixedTime.Add(capacityHeartbeatStaleAfter + time.Second) } distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-stale-run-endpoint", }) if err != nil { t.Fatalf("platform build must ignore stale Run endpoint: %v", err) } completeDistributionBuild(t, svc, distribution, nil) actions, err := svc.GetServerRuntimeActionsForSession(session, instance.ID) if err != nil { t.Fatalf("get runtime actions: %v", err) } for _, action := range actions.Actions { if action.Key == "generate-run" && !action.Available { t.Fatalf("stale Run endpoint must not gate platform build availability: %+v", action) } } } func TestCoreServiceRuntimeActionsGateDependenciesOnPluginPermission(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { t.Fatalf("get plugin: %v", err) } permissions := plugin.DeclaredPermissions[:0] for _, permission := range plugin.DeclaredPermissions { if permission != "server.dependencies.manage" { permissions = append(permissions, permission) } } plugin.DeclaredPermissions = permissions if err := svc.store.GamePlugins().Update(plugin); err != nil { t.Fatalf("update plugin: %v", err) } actions, err := svc.GetServerRuntimeActionsForSession(session, instance.ID) if err != nil { t.Fatalf("get runtime actions: %v", err) } seen := 0 for _, action := range actions.Actions { if action.Key != "dependencies-check" && action.Key != "dependencies-install" { continue } seen++ if action.Available || action.Reason != "plugin permission is not declared" { t.Fatalf("expected dependency action gated by plugin permission, got %+v", action) } } if seen != 2 { t.Fatalf("expected both dependency actions in %+v", actions.Actions) } } func TestCoreServiceMachineEndpointCannotClaimPlatformDistributionBuild(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-platform-build-ownership", }) if err != nil { t.Fatalf("generate run distribution: %v", err) } helloRequest := validRunControlHello() helloRequest.CapabilityReport.Capabilities = append(helloRequest.CapabilityReport.Capabilities, domain.JobCapabilityDistributionBuild) hello, err := svc.RegisterRunHello(helloRequest) if err != nil { t.Fatalf("register machine endpoint: %v", err) } claim, err := svc.ClaimRunJob(domain.RunJobClaim{ RunEndpointID: instance.RunEndpointID, SessionToken: hello.SessionToken, Capabilities: []string{domain.JobCapabilityDistributionBuild}, Capacity: domain.RunCapacity{MaxJobs: 1}, }) if err != nil { t.Fatalf("claim machine-side build: %v", err) } if claim.HasJob { t.Fatalf("machine-side endpoint must not receive platform build job: %+v", claim) } distribution = completeDistributionBuild(t, svc, distribution, nil) job, err := svc.GetJob(distribution.BuildJobID) if err != nil || job.State != domain.JobStateSucceeded || job.RunEndpointID != platformDistributionBuilderEndpointID { t.Fatalf("expected completed platform build job, job=%+v err=%v", job, err) } artifact, err := svc.GetArtifact(distribution.ArtifactID) if err != nil || artifact.OwnerKind != domain.ArtifactOwnerKindJob || artifact.OwnerID != job.ID { t.Fatalf("expected build job-owned artifact, artifact=%+v err=%v", artifact, err) } } func TestCoreServiceRunDistributionDownloadUsesTargetPackageName(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: "idem-run-download-name", }) if err != nil { t.Fatalf("generate run distribution: %v", err) } payload := []byte("windows raw run executable") completeDistributionBuild(t, svc, distribution, payload) reference, err := svc.OpenArtifactDownloadForSession(session, domain.ArtifactDownloadReferenceRequest{ArtifactID: distribution.ArtifactID}) if err != nil { t.Fatalf("open run artifact download: %v", err) } if reference.Filename != "run-windows-amd64.exe" || reference.ContentType != "application/octet-stream" { t.Fatalf("expected windows run package metadata, got %+v", reference) } content, err := svc.ReadArtifactContentForSession(session, domain.ArtifactContentRequest{ArtifactID: distribution.ArtifactID, Limit: len(payload)}) if err != nil { t.Fatalf("read run artifact content: %v", err) } if content.Filename != reference.Filename || content.ContentType != reference.ContentType { t.Fatalf("expected content metadata to match reference, reference=%+v content=%+v", reference, content) } } func TestCoreServiceRunDistributionRetryReusesPartialArtifact(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) key, plainKey, err := svc.ensureActiveComponentKey(instance.ID, domain.DistributionComponentRun, "") if err != nil { t.Fatalf("ensure run key: %v", err) } idempotencyKey := "web:run.generate:scum-alpha:1784043453685" distributionID := distributionID("run-dist", instance.ID, "windows", "amd64", key.Generation, idempotencyKey) artifactID := artifactIDForDistribution(distributionID) payload := []byte("legacy generated config is not a binary distribution") partialArtifact, err := svc.createPlatformArtifactPayload(artifactID, domain.ArtifactOwnerKindServerInstance, instance.ID, payload) if err != nil { t.Fatalf("create partial artifact: %v", err) } distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: idempotencyKey, }) if err != nil { t.Fatalf("retry run generation should recover partial artifact: %v", err) } if distribution.ID != distributionID || distribution.ArtifactID == partialArtifact.ID || distribution.Checksum != "" { t.Fatalf("expected real binary build to ignore legacy config artifact, distribution=%+v artifact=%+v", distribution, partialArtifact) } if distribution.PackageFormat != "raw-executable" || distribution.Status != domain.DistributionStatusBuilding || distribution.BuildJobID == "" { t.Fatalf("unexpected recovered distribution: %+v", distribution) } recoveredConfig := readGeneratedPackageConfig(t, svc, session, distribution.ArtifactID) if recoveredConfig.AuthKey != plainKey || recoveredConfig.KeyGeneration != key.Generation { t.Fatalf("expected recovered artifact payload to remain downloadable, got %+v", recoveredConfig) } } func TestCoreServicePushRunUpdateReusesExistingUpdateJob(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) payload := []byte("compiled run archive") svc.ConfigureDistributionBuilder(staticDistributionBuilder{payload: payload}) distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-run-before-update", }) if err != nil { t.Fatalf("generate run distribution: %v", err) } distribution = completeDistributionBuild(t, svc, distribution, payload) request := domain.RunUpdateRequest{ ServerInstanceID: instance.ID, ArtifactID: distribution.ArtifactID, Checksum: distribution.Checksum, IdempotencyKey: "idem-run-update-repeat", } first, err := svc.PushRunUpdateForSession(session, request) if err != nil { t.Fatalf("push run update: %v", err) } second, err := svc.PushRunUpdateForSession(session, request) if err != nil { t.Fatalf("push run update again should reuse existing update job: %v", err) } if second.ID != first.ID || second.JobID != first.JobID || second.ArtifactID != first.ArtifactID { t.Fatalf("expected repeated push to return existing update job, first=%+v second=%+v", first, second) } if second.Status != domain.DistributionJobStatusQueued { t.Fatalf("expected queued existing update job, got %+v", second) } } func TestCoreServiceResetRunKeyRevokesOldPackagesAndRequiresRegeneration(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-run-before-reset", }) if err != nil { t.Fatalf("generate run distribution: %v", err) } oldConfig := readGeneratedPackageConfig(t, svc, session, distribution.ArtifactID) distribution = completeDistributionBuild(t, svc, distribution, []byte("compiled run archive before reset")) helloRequest := validRunControlHello() helloRequest.RunEndpointID = instance.RunEndpointID helloRequest.RegistrationToken = oldConfig.AuthKey helloRequest.ServerInstanceID = instance.ID helloRequest.PluginID = instance.PluginID helloRequest.ComponentKind = domain.DistributionComponentRun helloRequest.KeyGeneration = oldConfig.KeyGeneration hello, err := svc.RegisterRunHello(helloRequest) if err != nil || !hello.Accepted { t.Fatalf("register old run before reset: hello=%+v err=%v", hello, err) } reset, err := svc.ResetComponentKeyForSession(session, domain.ComponentKeyResetRequest{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentRun, }) if err != nil { t.Fatalf("reset run key: %v", err) } if reset.Generation != 2 || reset.Status != domain.ComponentKeyStatusActive { t.Fatalf("expected reset key generation 2, got %+v", reset) } if _, err := svc.AcceptRunHeartbeat(domain.RunControlHeartbeat{ RunEndpointID: instance.RunEndpointID, SessionToken: hello.SessionToken, Status: domain.RunEndpointStatusOnline, Version: "stale-run", Capacity: domain.RunCapacity{MaxJobs: 1}, CapabilityFingerprint: helloRequest.CapabilityReport.Fingerprint, }); err == nil { t.Fatal("expected reset to revoke the active run control session") } endpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID) if err != nil { t.Fatalf("get endpoint after reset: %v", err) } if endpoint.Status != domain.RunEndpointStatusOffline { t.Fatalf("expected reset endpoint to be marked offline, got %+v", endpoint) } endpoint.Status = domain.RunEndpointStatusOnline if err := svc.store.RunEndpoints().Update(endpoint); err != nil { t.Fatalf("restore endpoint for regeneration: %v", err) } oldDistribution, err := svc.store.RunDistributions().Get(distribution.ID) if err != nil { t.Fatalf("get old distribution: %v", err) } if oldDistribution.Status != domain.DistributionStatusRevoked { t.Fatalf("expected old distribution revoked, got %+v", oldDistribution) } if _, err := svc.OpenArtifactDownloadForSession(session, domain.ArtifactDownloadReferenceRequest{ArtifactID: distribution.ArtifactID}); err == nil || !strings.Contains(err.Error(), "available") { t.Fatalf("expected old artifact download to be unavailable, got %v", err) } auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentRun, Generation: oldConfig.KeyGeneration, Key: oldConfig.AuthKey, }) if err != nil { t.Fatalf("authenticate old key: %v", err) } if auth.Allowed || !strings.Contains(auth.Reason, "generation") { t.Fatalf("expected old package authentication denial, got %+v", auth) } newDistribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-run-after-reset", }) if err != nil { t.Fatalf("generate run distribution after reset: %v", err) } newConfig := readGeneratedPackageConfig(t, svc, session, newDistribution.ArtifactID) if newDistribution.KeyGeneration != 2 || newConfig.AuthKey == oldConfig.AuthKey { t.Fatalf("expected regenerated package with new generation/key, old=%+v new=%+v", oldConfig, newConfig) } auth, err = svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentRun, Generation: newConfig.KeyGeneration, Key: newConfig.AuthKey, }) if err != nil { t.Fatalf("authenticate new key: %v", err) } if !auth.Allowed { t.Fatalf("expected regenerated package to authenticate, got %+v", auth) } } func TestCoreServiceBuildsClientManagerWithDistinctKeyAndAuditsSensitiveOperations(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) runDistribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-run-for-client", }) if err != nil { t.Fatalf("generate run distribution: %v", err) } runDistribution = completeDistributionBuild(t, svc, runDistribution, []byte("compiled run archive")) if _, err := svc.OpenArtifactDownloadForSession(session, domain.ArtifactDownloadReferenceRequest{ArtifactID: runDistribution.ArtifactID}); err != nil { t.Fatalf("open run download: %v", err) } runConfig := readGeneratedPackageConfig(t, svc, session, runDistribution.ArtifactID) clientDistribution, err := svc.GenerateClientManagerDistributionForSession(session, domain.ClientManagerBuildRequest{ ServerInstanceID: instance.ID, ProfileKey: "scum-client-manager", TargetOS: "windows", TargetArch: "amd64", RepositoryURL: "https://github.com/F88888/scum_client.git", SourceRevision: "main", IdempotencyKey: "idem-client-manager", }) if err != nil { t.Fatalf("generate client-manager distribution: %v", err) } clientConfig := readGeneratedPackageConfig(t, svc, session, clientDistribution.ArtifactID) if clientDistribution.KeyGeneration != 1 || clientDistribution.BuildJobID == "" || clientDistribution.Status != domain.DistributionStatusBuilding { t.Fatalf("unexpected client-manager distribution: %+v", clientDistribution) } if clientConfig.AuthKey == runConfig.AuthKey || clientDistribution.SecretRef == runDistribution.SecretRef { t.Fatalf("client-manager must use a distinct key/ref, run=%+v client=%+v", runConfig, clientConfig) } build, err := svc.store.ClientManagerBuildJobs().Get(clientDistribution.BuildJobID) if err != nil { t.Fatalf("get build job: %v", err) } if build.Status != domain.DistributionJobStatusQueued || build.RepositoryURL != "https://github.com/F88888/scum_client.git" || build.SourceRevision != "main" { t.Fatalf("unexpected build job: %+v", build) } clientDistribution = completeClientDistributionBuild(t, svc, clientDistribution, []byte("compiled client archive")) build, err = svc.store.ClientManagerBuildJobs().Get(clientDistribution.BuildJobID) if err != nil || build.Status != domain.DistributionJobStatusSucceeded || build.Checksum == "" { t.Fatalf("expected uploaded client build to project as succeeded, build=%+v err=%v", build, err) } _, err = svc.GenerateClientManagerDistributionForSession(session, domain.ClientManagerBuildRequest{ ServerInstanceID: instance.ID, ProfileKey: "scum-client-manager", TargetOS: "darwin", TargetArch: "amd64", RepositoryURL: "https://github.com/F88888/scum_client.git", IdempotencyKey: "idem-client-manager-denied", }) if err == nil || !strings.Contains(err.Error(), "targetOs") { t.Fatalf("expected unsupported target denial, got %v", err) } if _, err := svc.ResetComponentKeyForSession(session, domain.ComponentKeyResetRequest{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentClientManager, ComponentKey: "scum-client-manager", }); err != nil { t.Fatalf("reset client-manager key: %v", err) } auth, err := svc.AuthenticateComponent(domain.ComponentAuthenticationRequest{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentClientManager, ComponentKey: "scum-client-manager", Generation: clientConfig.KeyGeneration, Key: clientConfig.AuthKey, }) if err != nil { t.Fatalf("authenticate old client key: %v", err) } if auth.Allowed { t.Fatalf("expected old client-manager key to be denied after reset, got %+v", auth) } audits, err := svc.ListAuditEvents(domain.AuditEventFilter{ResourceID: instance.ID}) if err != nil { t.Fatalf("list audits: %v", err) } actions := map[string]bool{} for _, audit := range audits { actions[audit.Action] = true for _, forbidden := range []string{runConfig.AuthKey, clientConfig.AuthKey, "password=", "unix://", "/Users/"} { if strings.Contains(audit.Summary, forbidden) { t.Fatalf("audit leaked forbidden fragment %q in %+v", forbidden, audit) } } } for _, action := range []string{"run.generate", "run.download", "client-manager.build", "client-manager.build.denied", "runtime-key.reset"} { if !actions[action] { t.Fatalf("expected audit action %q in %+v", action, audits) } } } func newDistributionTestFixture(t *testing.T) (*CoreService, string, domain.ServerInstance) { t.Helper() svc := newTestCoreService() plugin, endpoint := createPluginAndRunEndpoint(t, svc) plugin.SupportedOS = []string{"linux", "windows"} plugin.DeclaredPermissions = append(plugin.DeclaredPermissions, "server.run.distribution", "server.client-manager.manage", "server.dependencies.manage", ) plugin.RequiredRunCapabilities = append(plugin.RequiredRunCapabilities, domain.JobCapabilityRunSelfUpdate, domain.JobCapabilityDependenciesCheck, domain.JobCapabilityDependenciesInstall, domain.JobCapabilityLogsBackfill, domain.JobCapabilityClientManagerDeploy, domain.JobCapabilityClientManagerControl, domain.JobCapabilityClientManagerUpdate, domain.JobCapabilityClientManagerRollback, domain.JobCapabilityClientManagerUninstall, ) plugin.BridgeActions = append(plugin.BridgeActions, string(domain.PluginBridgeActionRunDistribution), string(domain.PluginBridgeActionClientManager), string(domain.PluginBridgeActionDependenciesRequest), string(domain.PluginBridgeActionLogsBackfillRequest), ) plugin.RuntimeProfiles.DependencyProbes = []domain.RuntimeDependencyProbe{{Key: "java-runtime", Kind: "command.version", TargetKey: "java", Platforms: []string{"linux"}}} plugin.RuntimeProfiles.InstallPlans = []domain.RuntimeInstallPlan{{Key: "java-install", Title: "Install Java", Platforms: []string{"linux"}, Steps: []domain.RuntimeInstallStep{{Type: "package", TargetKey: "java", PackageManager: "apt", PackageName: "openjdk-21-jre"}}}} plugin.RuntimeProfiles.LogSources = []domain.RuntimeLogSource{{Key: "latest", Kind: "file.tail", TargetKey: "logs/latest", StreamKey: "latest-log", CursorKind: "offset", RetentionDays: 30}} plugin.RuntimeProfiles.ClientManagers = []domain.RuntimeClientManagerProfile{{Key: "scum-client-manager", DisplayName: "SCUM Client Manager", Version: "1.0.0", RepositoryURL: "https://github.com/F88888/scum_client.git", RevisionPolicy: "branch", Branch: "main", SupportedTargets: []domain.RuntimeTarget{{OS: "windows", Arch: "amd64"}, {OS: "linux", Arch: "amd64"}}, BuildSystem: "go", EntryRef: "main.go", OutputArtifacts: []string{"scum_client.exe"}, Deployment: domain.RuntimeClientManagerDeployment{Mode: "run-supervised", ExecutableRef: "scum_client.exe", RequiredRunCapabilities: []string{domain.JobCapabilityClientManagerDeploy, domain.JobCapabilityClientManagerControl, domain.JobCapabilityClientManagerUpdate, domain.JobCapabilityClientManagerRollback, domain.JobCapabilityClientManagerUninstall}}, Lifecycle: domain.RuntimeClientManagerLifecycle{Actions: []string{"start", "stop", "restart", "status", "update", "rollback", "uninstall"}, StartupTimeoutSeconds: 60, StopTimeoutSeconds: 30}, Health: domain.RuntimeClientManagerHealth{Mode: "component-heartbeat", IntervalSeconds: 15, DegradedAfterSeconds: 45, OfflineAfterSeconds: 120, RequiredCapabilities: []string{"component.register", "component.heartbeat", "component.health"}}, Compatibility: domain.RuntimeClientManagerCompatibility{MinimumVersion: "1.0.0"}, UpdatePolicy: domain.RuntimeClientManagerUpdatePolicy{Strategy: "manual-staged", RequireApproval: true, HealthConfirmationSeconds: 60, RetainPrevious: true}}} if err := svc.store.GamePlugins().Update(plugin); err != nil { t.Fatalf("update plugin fixture: %v", err) } endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityRunSelfUpdate, domain.JobCapabilityDependenciesCheck, domain.JobCapabilityDependenciesInstall, domain.JobCapabilityLogsBackfill, domain.JobCapabilityClientManagerDeploy, domain.JobCapabilityClientManagerControl, domain.JobCapabilityClientManagerUpdate, domain.JobCapabilityClientManagerRollback, domain.JobCapabilityClientManagerUninstall, ) endpoint.Platform = "linux" endpoint.Architecture = "amd64" if err := svc.store.RunEndpoints().Update(endpoint); err != nil { t.Fatalf("update endpoint fixture: %v", err) } session := createServiceUserAndLogin(t, svc, domain.User{ ID: "user-distribution-owner", DisplayName: "Distribution Owner", Email: "distribution-owner@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password", }) instance, err := svc.CreateServerInstanceForSession(session, domain.ServerInstance{ ID: "server-distribution", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "Distribution Server", State: domain.ServerInstanceStateReady, }) if err != nil { t.Fatalf("create distribution server: %v", err) } binding, err := svc.buildRuntimeBinding(instance, plugin, domain.RuntimeBindingUpdate{ProfileKey: "local", Bindings: map[string]string{"logs/latest": "runtime.logs.latest"}}, true) if err != nil { t.Fatalf("build complete runtime binding: %v", err) } if err := svc.store.RuntimeBindings().Create(binding); err != nil { t.Fatalf("create runtime binding: %v", err) } return svc, session, instance } func TestQueueLogBackfillFreezesDeclaredFileLogSource(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) job, err := svc.QueueLogBackfillForSession(session, domain.LogBackfillRequest{ServerInstanceID: instance.ID, SourceKey: "latest", CheckpointRef: "artifact://logs/checkpoint/1", IdempotencyKey: "log-source-freeze"}) if err != nil { t.Fatalf("queue log backfill: %v", err) } if job.ExecutionInput.LogSource == nil || job.ExecutionInput.LogSource.Key != "latest" || job.ExecutionInput.LogSource.StreamKey != "latest-log" || job.ExecutionInput.LogSource.TargetKey != "logs/latest" { t.Fatalf("expected frozen declared log source, got %+v", job.ExecutionInput.LogSource) } stream, err := svc.GetLogStream(jobLogStreamID(job.ID, "latest-log")) if err != nil || stream.Source != domain.LogStreamSourceFile || stream.StreamKey != "latest-log" { t.Fatalf("expected file log stream for backfill job, stream=%+v err=%v", stream, err) } if _, err := svc.QueueLogBackfillForSession(session, domain.LogBackfillRequest{ServerInstanceID: instance.ID, SourceKey: "missing", IdempotencyKey: "log-source-missing"}); err == nil || !strings.Contains(err.Error(), "not declared") { t.Fatalf("expected undeclared source rejection, got %v", err) } } func readGeneratedPackageConfig(t *testing.T, svc *CoreService, session string, artifactID string) generatedPackageConfig { t.Helper() _ = session runs, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{}) if err != nil { t.Fatalf("list run distributions: %v", err) } for _, distribution := range runs { if distribution.ArtifactID != artifactID { continue } key, err := svc.activeComponentKey(distribution.ServerInstanceID, domain.DistributionComponentRun, "") if err != nil { t.Fatalf("get run key: %v", err) } plain, err := decryptRuntimeKey(key.EncryptedKey) if err != nil { t.Fatalf("decrypt run key: %v", err) } return generatedPackageConfig{Kind: "run", ServerInstanceID: distribution.ServerInstanceID, PluginID: distribution.PluginID, RunEndpointID: distribution.RunEndpointID, TargetOS: distribution.TargetOS, TargetArch: distribution.TargetArch, SecretRef: distribution.SecretRef, KeyGeneration: distribution.KeyGeneration, AuthKey: plain} } clients, err := svc.store.ClientManagerDistributions().List(domain.ClientManagerDistributionFilter{}) if err != nil { t.Fatalf("list client distributions: %v", err) } for _, distribution := range clients { if distribution.ArtifactID != artifactID { continue } key, err := svc.activeComponentKey(distribution.ServerInstanceID, domain.DistributionComponentClientManager, distribution.ProfileKey) if err != nil { t.Fatalf("get client key: %v", err) } plain, err := decryptRuntimeKey(key.EncryptedKey) if err != nil { t.Fatalf("decrypt client key: %v", err) } return generatedPackageConfig{Kind: "client-manager", ServerInstanceID: distribution.ServerInstanceID, PluginID: distribution.PluginID, ProfileKey: distribution.ProfileKey, TargetOS: distribution.TargetOS, TargetArch: distribution.TargetArch, SecretRef: distribution.SecretRef, KeyGeneration: distribution.KeyGeneration, AuthKey: plain} } t.Fatalf("distribution for artifact %s was not found", artifactID) return generatedPackageConfig{} } func completeDistributionBuild(t *testing.T, svc *CoreService, distribution domain.RunDistribution, _ []byte) domain.RunDistribution { t.Helper() deadline := time.Now().Add(time.Second) for time.Now().Before(deadline) { updated, err := svc.store.RunDistributions().Get(distribution.ID) if err != nil { t.Fatalf("get run distribution: %v", err) } if updated.Status == domain.DistributionStatusAvailable { return updated } if updated.Status == domain.DistributionStatusFailed { t.Fatalf("platform run build failed: %+v", updated) } time.Sleep(time.Millisecond) } t.Fatalf("platform run build did not complete") return domain.RunDistribution{} } func completeClientDistributionBuild(t *testing.T, svc *CoreService, distribution domain.ClientManagerDistribution, _ []byte) domain.ClientManagerDistribution { t.Helper() deadline := time.Now().Add(time.Second) for time.Now().Before(deadline) { updated, err := svc.store.ClientManagerDistributions().Get(distribution.ID) if err != nil { t.Fatalf("get client distribution: %v", err) } if updated.Status == domain.DistributionStatusAvailable { return updated } if updated.Status == domain.DistributionStatusFailed { t.Fatalf("platform client build failed: %+v", updated) } time.Sleep(time.Millisecond) } t.Fatalf("platform client build did not complete") return domain.ClientManagerDistribution{} } func TestCoreServiceDeniesRunDistributionWithoutPluginDeclaration(t *testing.T) { svc := newTestCoreService() plugin, endpoint := createPluginAndRunEndpoint(t, svc) session := createServiceUserAndLogin(t, svc, domain.User{ ID: "user-distribution-denied", DisplayName: "Distribution Denied", Email: "distribution-denied@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password", }) instance, err := svc.CreateServerInstanceForSession(session, domain.ServerInstance{ ID: "server-distribution-denied", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "Distribution Denied Server", State: domain.ServerInstanceStateReady, }) if err != nil { t.Fatalf("create denied server: %v", err) } _, err = svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-run-denied", }) if !errors.Is(err, ErrForbidden) { t.Fatalf("expected plugin declaration denial, got %v", err) } }