package service import ( "errors" "strings" "testing" "browser.local/platform/domain" "browser.local/platform/repo" ) func TestCoreServiceRegistersNewRunControlSession(t *testing.T) { svc := newTestCoreService() result, err := svc.RegisterRunHello(validRunControlHello()) if err != nil { t.Fatalf("register hello: %v", err) } if !result.Accepted || result.SessionToken == "" || result.HeartbeatIntervalSeconds <= 0 { t.Fatalf("expected accepted hello response, got %+v", result) } endpoint, err := svc.GetRunEndpoint("run-local") if err != nil { t.Fatalf("get registered endpoint: %v", err) } if endpoint.Status != domain.RunEndpointStatusOnline || !endpoint.LastHeartbeatAt.Equal(fixedTime) { t.Fatalf("expected online endpoint with heartbeat time, got %+v", endpoint) } if len(endpoint.Capabilities) != 3 || endpoint.Capacity.MaxJobs != 4 { t.Fatalf("expected capabilities and capacity, got %+v", endpoint) } } func TestCoreServiceDoesNotRevokeSharedLegacyRunEndpoint(t *testing.T) { svc := newTestCoreService() plugin, endpoint := createPluginAndRunEndpoint(t, svc) hello := validRunControlHello() hello.CapabilityReport.Capabilities = append(hello.CapabilityReport.Capabilities, plugin.RequiredRunCapabilities...) registered, err := svc.RegisterRunHello(hello) if err != nil || !registered.Accepted { t.Fatalf("register shared endpoint: result=%+v err=%v", registered, err) } first, err := svc.CreateServerInstance(domain.ServerInstance{ID: "shared-first", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "Shared First", State: domain.ServerInstanceStateReady}) if err != nil { t.Fatalf("create first legacy server: %v", err) } if _, err := svc.CreateServerInstance(domain.ServerInstance{ID: "shared-second", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "Shared Second", State: domain.ServerInstanceStateReady}); err != nil { t.Fatalf("create second legacy server: %v", err) } if err := svc.revokeRunControlSessionForInstance(first); err != nil { t.Fatalf("revoke first legacy Run: %v", err) } session, err := svc.store.RunControlSessions().Get(endpoint.ID) if err != nil || session.Status != domain.AuthSessionStatusActive { t.Fatalf("shared legacy endpoint session must remain active, session=%+v err=%v", session, err) } } func TestCoreServiceReRegistersExistingRunEndpoint(t *testing.T) { svc := newTestCoreService() first, err := svc.RegisterRunHello(validRunControlHello()) if err != nil { t.Fatalf("register first hello: %v", err) } hello := validRunControlHello() hello.DisplayName = "Local Run Updated" hello.Version = "0.2.0" hello.CapabilityReport.Capabilities = []string{"control.hello", "control.heartbeat", "jobs.claim"} hello.CapabilityReport.Fingerprint = "cap-v2" second, err := svc.RegisterRunHello(hello) if err != nil { t.Fatalf("register second hello: %v", err) } if second.SessionToken == first.SessionToken { t.Fatalf("expected re-registration to issue a new token, got %q", second.SessionToken) } endpoint, err := svc.GetRunEndpoint("run-local") if err != nil { t.Fatalf("get re-registered endpoint: %v", err) } if endpoint.DisplayName != "Local Run Updated" || endpoint.Version != "0.2.0" || len(endpoint.Capabilities) != 3 { t.Fatalf("expected endpoint metadata update, got %+v", endpoint) } } func TestCoreServiceAcceptsRunHeartbeat(t *testing.T) { svc := newTestCoreService() hello, err := svc.RegisterRunHello(validRunControlHello()) if err != nil { t.Fatalf("register hello: %v", err) } result, err := svc.AcceptRunHeartbeat(domain.RunControlHeartbeat{ RunEndpointID: "run-local", SessionToken: hello.SessionToken, Version: "0.1.1", Status: domain.RunEndpointStatusDegraded, CapabilityFingerprint: "cap-v1", Capacity: domain.RunCapacity{MaxJobs: 4, RunningJobs: 2, QueuedJobs: 1}, }) if err != nil { t.Fatalf("accept heartbeat: %v", err) } if !result.Accepted || result.RefreshCapabilities { t.Fatalf("expected accepted heartbeat without refresh, got %+v", result) } endpoint, err := svc.GetRunEndpoint("run-local") if err != nil { t.Fatalf("get heartbeat endpoint: %v", err) } if endpoint.Status != domain.RunEndpointStatusDegraded || endpoint.Version != "0.1.1" || endpoint.Capacity.RunningJobs != 2 { t.Fatalf("expected heartbeat metadata update, got %+v", endpoint) } } func TestCoreServiceRejectsInvalidRunHeartbeatToken(t *testing.T) { svc := newTestCoreService() if _, err := svc.RegisterRunHello(validRunControlHello()); err != nil { t.Fatalf("register hello: %v", err) } _, err := svc.AcceptRunHeartbeat(domain.RunControlHeartbeat{ RunEndpointID: "run-local", SessionToken: "stale-token", Version: "0.1.1", Status: domain.RunEndpointStatusOnline, CapabilityFingerprint: "cap-v1", Capacity: domain.RunCapacity{MaxJobs: 4, RunningJobs: 3}, }) if err == nil || !strings.Contains(err.Error(), "sessionToken is invalid") { t.Fatalf("expected invalid token rejection, got %v", err) } endpoint, err := svc.GetRunEndpoint("run-local") if err != nil { t.Fatalf("get endpoint after rejected heartbeat: %v", err) } if endpoint.Capacity.RunningJobs != 0 || endpoint.Version != "0.1.0" { t.Fatalf("heartbeat with invalid token must not update endpoint, got %+v", endpoint) } } func TestCoreServiceRejectsInvalidRunControlHello(t *testing.T) { svc := newTestCoreService() invalid := validRunControlHello() invalid.RegistrationToken = "" invalid.Capacity.RunningJobs = 8 _, err := svc.RegisterRunHello(invalid) if err == nil || !strings.Contains(err.Error(), "registrationToken") || !strings.Contains(err.Error(), "runningJobs") { t.Fatalf("expected validation errors, got %v", err) } if _, err := svc.GetRunEndpoint("run-local"); !errors.Is(err, repo.ErrNotFound) { t.Fatalf("invalid hello must not create endpoint, got %v", err) } } func TestCoreServiceRunHelloRejectsStalePackageKeyAfterReset(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ ServerInstanceID: instance.ID, TargetOS: "linux", TargetArch: "amd64", IdempotencyKey: "idem-control-auth", }) if err != nil { t.Fatalf("generate run distribution: %v", err) } pkg := readGeneratedPackageConfig(t, svc, session, distribution.ArtifactID) hello := validRunControlHello() hello.RunEndpointID = distribution.RunEndpointID hello.RegistrationToken = pkg.AuthKey hello.ServerInstanceID = instance.ID hello.PluginID = instance.PluginID hello.ComponentKind = domain.DistributionComponentRun hello.KeyGeneration = pkg.KeyGeneration result, err := svc.RegisterRunHello(hello) if err != nil { t.Fatalf("register current package hello: %v", err) } if !result.Accepted || result.SessionToken == "" { t.Fatalf("expected current package hello to be accepted, got %+v", result) } if _, err := svc.ResetComponentKeyForSession(session, domain.ComponentKeyResetRequest{ ServerInstanceID: instance.ID, ComponentKind: domain.DistributionComponentRun, }); err != nil { t.Fatalf("reset run key: %v", err) } result, err = svc.RegisterRunHello(hello) if err != nil { t.Fatalf("register stale package hello: %v", err) } if result.Accepted || result.SessionToken != "" { t.Fatalf("expected stale package hello to be rejected, got %+v", result) } } func TestCoreServiceRunHelloRejectsGeneratedRunOnPromotedBuildEndpoint(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) builderID := instance.RunEndpointID instance.State = domain.ServerInstanceStateFailed if err := svc.store.ServerInstances().Update(instance); err != nil { t.Fatalf("mark legacy server failed: %v", err) } if _, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ServerInstanceID: instance.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: "promoted-hello-fence"}); err != nil { t.Fatalf("generate promoted Run: %v", err) } migrated, err := svc.GetServerInstance(instance.ID) if err != nil { t.Fatalf("get migrated server: %v", err) } key, plainKey, err := svc.ensureActiveComponentKey(instance.ID, domain.DistributionComponentRun, "") if err != nil { t.Fatalf("get component key: %v", err) } hello := validRunControlHello() hello.RunEndpointID = builderID hello.RegistrationToken = plainKey hello.ServerInstanceID = instance.ID hello.PluginID = instance.PluginID hello.ComponentKind = domain.DistributionComponentRun hello.KeyGeneration = key.Generation if _, err := svc.RegisterRunHello(hello); err == nil || !strings.Contains(err.Error(), "does not match") { t.Fatalf("expected shared builder registration rejection, got %v", err) } hello.RunEndpointID = migrated.RunEndpointID if result, err := svc.RegisterRunHello(hello); err != nil || !result.Accepted { t.Fatalf("expected dedicated Run registration acceptance, result=%+v err=%v", result, err) } } func TestCoreServiceComponentRunCannotClaimDistributionBuild(t *testing.T) { svc, session, instance := newDistributionTestFixture(t) distribution, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ServerInstanceID: instance.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: "component-build-claim"}) if err != nil { t.Fatalf("generate legacy Run: %v", err) } packageConfig := readGeneratedPackageConfig(t, svc, session, distribution.ArtifactID) hello := validRunControlHello() hello.RunEndpointID = instance.RunEndpointID hello.RegistrationToken = packageConfig.AuthKey hello.ServerInstanceID = instance.ID hello.PluginID = instance.PluginID hello.ComponentKind = domain.DistributionComponentRun hello.KeyGeneration = packageConfig.KeyGeneration registered, err := svc.RegisterRunHello(hello) if err != nil || !registered.Accepted { t.Fatalf("register legacy package: result=%+v err=%v", registered, err) } storedSession, err := svc.store.RunControlSessions().Get(instance.RunEndpointID) if err != nil || !storedSession.RequireSignedRequests { t.Fatalf("expected component session to require signatures, session=%+v err=%v", storedSession, err) } if activeSession := svc.runSessions[instance.RunEndpointID]; !activeSession.RequireSignedRequests { t.Fatalf("expected in-memory component session to require signatures, session=%+v", activeSession) } claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: instance.RunEndpointID, SessionToken: registered.SessionToken, Capabilities: []string{domain.JobCapabilityDistributionBuild}, Capacity: domain.RunCapacity{MaxJobs: 1}}) if err != nil || claim.HasJob { t.Fatalf("component Run must not claim distribution builds: claim=%+v err=%v", claim, err) } } func TestCoreServiceDedicatedRunRegistrationDoesNotDispatchLifecycleBootstrap(t *testing.T) { svc, _ := newLifecycleRunService(t) plugin := createLifecyclePlugin(t, svc) endpoint, err := svc.store.RunEndpoints().Get("run-local") if err != nil { t.Fatalf("get bootstrap endpoint: %v", err) } endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityDistributionBuild, domain.JobCapabilityDeploymentPlan) if err := svc.store.RunEndpoints().Update(endpoint); err != nil { t.Fatalf("enable bootstrap capabilities: %v", err) } owner := createServiceUserAndLogin(t, svc, domain.User{ID: "managed-deploy-owner", DisplayName: "Managed Deploy Owner", Email: "managed-deploy@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"}) guided, err := svc.CreateServerInstanceWorkflowForSession(owner, domain.ServerLifecycleCreate{ID: "managed-guided", PluginID: plugin.ID, DeploymentTargetID: "run-local", Name: "Managed Guided", IdempotencyKey: "managed-guided-create", ProfileKey: "local", Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, ServerRoot: "C:\\scumserver"}}) if err != nil { t.Fatalf("create guided draft: %v", err) } if guided.Instance.State != domain.ServerInstanceStateDraft { t.Fatalf("expected draft before Run registration, got %+v", guided.Instance) } registerDedicatedRunForTest(t, svc, guided.Instance, plugin.ID) stored, err := svc.GetServerInstance(guided.Instance.ID) if err != nil || stored.State != domain.ServerInstanceStateDraft { t.Fatalf("guided registration must leave lifecycle authority with Run, server=%+v err=%v", stored, err) } jobs, err := svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: guided.Instance.ID}) if err != nil || len(jobs) != 0 { t.Fatalf("registration must not enqueue automatic lifecycle jobs, jobs=%+v err=%v", jobs, err) } registerDedicatedRunForTest(t, svc, guided.Instance, plugin.ID) jobs, _ = svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: guided.Instance.ID}) if len(jobs) != 0 { t.Fatalf("Run reconnect must not enqueue bootstrap jobs, jobs=%+v", jobs) } generatedRunDraft := domain.ServerInstance{ ID: "managed-generated", PluginID: plugin.ID, PluginVersion: plugin.Version, RunEndpointID: dedicatedRunEndpointID("managed-generated"), Name: "Managed Generated", State: domain.ServerInstanceStateDraft, ConfigVersion: 1, Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, ServerRoot: "C:\\generated-scum", ProfileKey: "local", Revision: 1}, } if err := svc.store.ServerInstances().Create(generatedRunDraft); err != nil { t.Fatalf("create generated Run draft: %v", err) } registerDedicatedRunForTest(t, svc, generatedRunDraft, plugin.ID) storedGenerated, err := svc.GetServerInstance(generatedRunDraft.ID) if err != nil || storedGenerated.State != domain.ServerInstanceStateDraft { t.Fatalf("generated Run registration must not platform-dispatch supervised start, server=%+v err=%v", storedGenerated, err) } jobs, err = svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: generatedRunDraft.ID}) if err != nil || len(jobs) != 0 { t.Fatalf("expected generated Run startup to be autonomous, jobs=%+v err=%v", jobs, err) } existing, err := svc.CreateServerInstanceWorkflowForSession(owner, domain.ServerLifecycleCreate{ID: "managed-existing", PluginID: plugin.ID, DeploymentTargetID: "run-local", Name: "Managed Existing", IdempotencyKey: "managed-existing-create", ProfileKey: "local", Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeExisting, ServerRoot: "C:\\existing-scum"}}) if err != nil { t.Fatalf("create existing draft: %v", err) } registerDedicatedRunForTest(t, svc, existing.Instance, plugin.ID) jobs, err = svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: existing.Instance.ID}) if err != nil || len(jobs) != 0 { t.Fatalf("existing-server registration must not reinstall, jobs=%+v err=%v", jobs, err) } } func TestCoreServiceGeneratedSCUMRunRegistrationDoesNotQueueGuidedStart(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.RequiredRunCapabilities = []string{ domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, "process.restart", domain.LifecycleCapabilityStatus, "files.list", domain.JobCapabilityFilesRead, "files.patch", "logs.read", domain.JobCapabilityClientManagerDeploy, domain.JobCapabilityClientManagerControl, domain.JobCapabilityClientManagerUpdate, domain.JobCapabilityClientManagerRollback, domain.JobCapabilityClientManagerUninstall, "artifacts.read", "artifacts.write", "ai.invoke", } 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 fixture: %v", err) } instance := domain.ServerInstance{ ID: "scum-generated-guided", PluginID: plugin.ID, PluginVersion: plugin.Version, RunEndpointID: dedicatedRunEndpointID("scum-generated-guided"), Name: "SCUM Generated Guided", OwnerUserID: "owner-scum-generated", State: domain.ServerInstanceStateDraft, ConfigVersion: 1, Deployment: domain.ServerDeploymentDefinition{ Mode: domain.ServerDeploymentModeGuided, ProfileKey: "run-local", ServerRoot: `D:\scum-e2e-guided`, CreateInputs: map[string]string{ "serverName": "SCUM E2E", "gamePort": "27000", "queryPort": "27015", "maxPlayers": "128", }, Revision: 1, }, } if err := svc.store.ServerInstances().Create(instance); err != nil { t.Fatalf("create generated SCUM draft: %v", err) } key, plainKey, err := svc.ensureActiveComponentKey(instance.ID, domain.DistributionComponentRun, "") if err != nil { t.Fatalf("get generated Run key: %v", err) } hello := validRunControlHello() hello.RunEndpointID = instance.RunEndpointID hello.RegistrationToken = plainKey hello.ServerInstanceID = instance.ID hello.PluginID = plugin.ID hello.ComponentKind = domain.DistributionComponentRun hello.KeyGeneration = key.Generation hello.Platform = "windows" hello.Architecture = "amd64" hello.CapabilityReport.Capabilities = []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus} registered, err := svc.RegisterRunHello(hello) if err != nil || !registered.Accepted { t.Fatalf("register generated SCUM Run: result=%+v err=%v", registered, err) } stored, err := svc.GetServerInstance(instance.ID) if err != nil || stored.State != domain.ServerInstanceStateDraft { t.Fatalf("generated SCUM registration must leave startup to Run, server=%+v err=%v", stored, err) } jobs, err := svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: instance.ID}) if err != nil || len(jobs) != 0 { t.Fatalf("generated SCUM registration must not enqueue start/status jobs, jobs=%+v err=%v", jobs, err) } } func TestCoreServiceGeneratedRunRegistrationDoesNotDispatchStatusReconciliation(t *testing.T) { svc := newTestCoreService() plugin := createGeneratedRunStatusPlugin(t, svc) instance := domain.ServerInstance{ID: "managed-stale-running", PluginID: plugin.ID, PluginVersion: plugin.Version, RunEndpointID: dedicatedRunEndpointID("managed-stale-running"), Name: "Managed Stale Running", State: domain.ServerInstanceStateRunning, ConfigVersion: 1, Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, ProfileKey: "run-local", ServerRoot: `D:\scum-stale`, Revision: 1}} if err := svc.store.ServerInstances().Create(instance); err != nil { t.Fatalf("create stale running server: %v", err) } registerGeneratedRunForStatusTest(t, svc, instance, plugin.ID) jobs, err := svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: instance.ID}) if err != nil || len(jobs) != 0 { t.Fatalf("registration must not enqueue status reconciliation, jobs=%+v err=%v", jobs, err) } stored, err := svc.GetServerInstance(instance.ID) if err != nil || stored.State != domain.ServerInstanceStateRunning { t.Fatalf("registration must not mutate projected state without Run report, server=%+v err=%v", stored, err) } } func TestCoreServiceGeneratedRunRegistrationLeavesExistingLifecycleJobUntouched(t *testing.T) { svc := newTestCoreService() plugin := createGeneratedRunStatusPlugin(t, svc) instance := domain.ServerInstance{ID: "managed-active-start", PluginID: plugin.ID, PluginVersion: plugin.Version, RunEndpointID: dedicatedRunEndpointID("managed-active-start"), Name: "Managed Active Start", State: domain.ServerInstanceStateRunning, ConfigVersion: 1, Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, ProfileKey: "run-local", ServerRoot: `D:\scum-active`, Revision: 1}} if err := svc.store.ServerInstances().Create(instance); err != nil { t.Fatalf("create active-start server: %v", err) } if err := svc.store.RunEndpoints().Create(domain.RunEndpoint{ID: instance.RunEndpointID, DisplayName: "Managed Active Start Run", Version: "0.1.0", Platform: "windows", Architecture: "amd64", Status: domain.RunEndpointStatusOnline, Capabilities: []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus}, Capacity: domain.RunCapacity{MaxJobs: 1}, LastHeartbeatAt: fixedTime}); err != nil { t.Fatalf("create active-start endpoint: %v", err) } if _, err := svc.dispatchLifecycleJob(instance, domain.ServerLifecycleActionStart, "already-active-start"); err != nil { t.Fatalf("queue active start job: %v", err) } registerGeneratedRunForStatusTest(t, svc, instance, plugin.ID) jobs, err := svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: instance.ID}) if err != nil || len(jobs) != 1 || jobs[0].Capability != domain.LifecycleCapabilityStart { t.Fatalf("registration should leave pre-existing lifecycle jobs untouched, jobs=%+v err=%v", jobs, err) } } func createGeneratedRunStatusPlugin(t *testing.T, svc *CoreService) domain.GamePlugin { t.Helper() plugin := scumDeploymentTestPlugin() plugin.Name = "SCUM" plugin.Version = "0.1.1" plugin.ServerType = "scum" plugin.Status = domain.GamePluginStatusInstalled plugin.SupportedOS = []string{"windows"} plugin.RequiredRunCapabilities = []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus} 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 status plugin fixture: %v", err) } return plugin } func registerGeneratedRunForStatusTest(t *testing.T, svc *CoreService, instance domain.ServerInstance, pluginID string) domain.RunControlHelloResult { t.Helper() key, plainKey, err := svc.ensureActiveComponentKey(instance.ID, domain.DistributionComponentRun, "") if err != nil { t.Fatalf("get generated Run key: %v", err) } hello := validRunControlHello() hello.RunEndpointID = instance.RunEndpointID hello.RegistrationToken = plainKey hello.ServerInstanceID = instance.ID hello.PluginID = pluginID hello.ComponentKind = domain.DistributionComponentRun hello.KeyGeneration = key.Generation hello.Platform = "windows" hello.Architecture = "amd64" hello.CapabilityReport.Capabilities = []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus} registered, err := svc.RegisterRunHello(hello) if err != nil || !registered.Accepted { t.Fatalf("register generated Run: result=%+v err=%v", registered, err) } return registered } func registerDedicatedRunForTest(t *testing.T, svc *CoreService, instance domain.ServerInstance, pluginID string) { t.Helper() key, plainKey, err := svc.ensureActiveComponentKey(instance.ID, domain.DistributionComponentRun, "") if err != nil { t.Fatalf("get dedicated Run key: %v", err) } hello := validRunControlHello() hello.RunEndpointID = instance.RunEndpointID hello.RegistrationToken = plainKey hello.ServerInstanceID = instance.ID hello.PluginID = pluginID hello.ComponentKind = domain.DistributionComponentRun hello.KeyGeneration = key.Generation hello.CapabilityReport.Capabilities = append(hello.CapabilityReport.Capabilities, domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.JobCapabilityDeploymentPlan, "logs.read") if result, err := svc.RegisterRunHello(hello); err != nil || !result.Accepted { t.Fatalf("register dedicated Run: result=%+v err=%v", result, err) } } func TestCoreServiceRequestsCapabilityRefreshOnFingerprintDrift(t *testing.T) { svc := newTestCoreService() hello, err := svc.RegisterRunHello(validRunControlHello()) if err != nil { t.Fatalf("register hello: %v", err) } result, err := svc.AcceptRunHeartbeat(domain.RunControlHeartbeat{ RunEndpointID: "run-local", SessionToken: hello.SessionToken, Version: "0.1.0", Status: domain.RunEndpointStatusOnline, CapabilityFingerprint: "cap-v2", Capacity: domain.RunCapacity{MaxJobs: 4}, }) if err != nil { t.Fatalf("accept drift heartbeat: %v", err) } if !result.RefreshCapabilities { t.Fatalf("expected capability refresh request, got %+v", result) } result, err = svc.AcceptRunHeartbeat(domain.RunControlHeartbeat{ RunEndpointID: "run-local", SessionToken: hello.SessionToken, Version: "0.1.0", Status: domain.RunEndpointStatusOnline, CapabilityFingerprint: "cap-v2", Capacity: domain.RunCapacity{MaxJobs: 4}, }) if err != nil { t.Fatalf("accept stable heartbeat: %v", err) } if result.RefreshCapabilities { t.Fatalf("expected refreshed fingerprint to become known, got %+v", result) } } func validRunControlHello() domain.RunControlHello { return domain.RunControlHello{ RegistrationToken: "registration-token", RunEndpointID: "run-local", DisplayName: "Local Run", Version: "0.1.0", Status: domain.RunEndpointStatusOnline, Platform: "darwin/arm64", CapabilityReport: domain.RunCapabilityReport{ Capabilities: []string{"control.hello", "control.heartbeat", domain.JobCapabilityDistributionBuild}, Fingerprint: "cap-v1", }, Capacity: domain.RunCapacity{MaxJobs: 4}, } }