From 8232c6706cc082c0b63e462ed55953f39dcb5f9d Mon Sep 17 00:00:00 2001 From: Bernhard Suttner Date: Tue, 30 Jun 2026 16:33:07 +0200 Subject: [PATCH 1/2] Refs #39448 - improve performance on host index page During testing, we found out multiple issues so that the performance of the host index page is really bad. Before this commit, using '/api/hosts?include_permissions=false&search=' took about 1min 30seconds. Simply returning "false" for "relevant?" decreased the access to 6seconds. With this commit, we are back at 9seconds - which is acceptable hopefully. Replace call to ".to_status" with ".status" to use the already calculated value of the complianace report. host_has_hostgroup_policies? is pretty fast because it answers a yes/no question with a cheap EXISTS query instead of loading and deduplicating all applicable policies in Ruby. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../foreman_openscap/host_extensions.rb | 8 +-- .../foreman_openscap/compliance_status.rb | 26 ++++++--- test/unit/compliance_status_test.rb | 57 +++++++++++++++++-- test/unit/concerns/host_extensions_test.rb | 20 +++++++ 4 files changed, 96 insertions(+), 15 deletions(-) diff --git a/app/models/concerns/foreman_openscap/host_extensions.rb b/app/models/concerns/foreman_openscap/host_extensions.rb index ade326135..da42b9b54 100644 --- a/app/models/concerns/foreman_openscap/host_extensions.rb +++ b/app/models/concerns/foreman_openscap/host_extensions.rb @@ -129,12 +129,12 @@ def reports_for_policy(policy, limit = nil) report_scope end - def compliance_status(options = {}) - @compliance_status ||= get_status(ForemanOpenscap::ComplianceStatus).to_status(options) + def compliance_status(_options = {}) + @compliance_status ||= get_status(ForemanOpenscap::ComplianceStatus).status end - def compliance_status_label(options = {}) - @compliance_status_label ||= get_status(ForemanOpenscap::ComplianceStatus).to_label(options) + def compliance_status_label(_options = {}) + @compliance_status_label ||= get_status(ForemanOpenscap::ComplianceStatus).to_label end def openscap_proxy_in_taxonomy diff --git a/app/models/foreman_openscap/compliance_status.rb b/app/models/foreman_openscap/compliance_status.rb index 4fbda9f6b..0280db081 100644 --- a/app/models/foreman_openscap/compliance_status.rb +++ b/app/models/foreman_openscap/compliance_status.rb @@ -16,8 +16,8 @@ def self.bit_mask(status) "#{ArfReport::BIT_NUM * ArfReport::METRIC.index(status)} & #{ArfReport::MAX}" end - def to_label(options = {}) - case to_status + def to_label(_options = {}) + case status when COMPLIANT N_('Compliant') when INCONCLUSIVE @@ -29,8 +29,8 @@ def to_label(options = {}) end end - def to_global(options = {}) - case to_status + def to_global(_options = {}) + case status when COMPLIANT ::HostStatus::Global::OK when INCONCLUSIVE @@ -40,17 +40,29 @@ def to_global(options = {}) end end - def relevant?(options = {}) + def relevant?(_options = {}) # May fail host status during migration return false unless ForemanOpenscap::Asset.table_exists? - host.combined_policies.present? + host.policies.any? || host_has_hostgroup_policies? end - def to_status(options = {}) + def to_status(_options = {}) latest_reports = host.combined_policies.flat_map { |p| host.last_report_for_policy p } return INCOMPLIANT if latest_reports.any?(&:failed?) return INCONCLUSIVE if latest_reports.any?(&:othered?) COMPLIANT end + + private + + def host_has_hostgroup_policies? + return false if host.hostgroup_id.blank? + + ids = [host.hostgroup_id] + host.hostgroup.ancestor_ids + ForemanOpenscap::Policy.joins(:assets) + .where('foreman_openscap_assets.assetable_type' => 'Hostgroup', + 'foreman_openscap_assets.assetable_id' => ids) + .exists? + end end end diff --git a/test/unit/compliance_status_test.rb b/test/unit/compliance_status_test.rb index 58b2ef6f2..7397020e1 100644 --- a/test/unit/compliance_status_test.rb +++ b/test/unit/compliance_status_test.rb @@ -74,12 +74,48 @@ def setup status = ForemanOpenscap::ComplianceStatus.new host = FactoryBot.create(:compliance_host, :policies => [@policy_a]) + host.expects(:combined_policies).never status.host = host assert status.relevant? end - test 'relevant? should be true for hostgroup policies and to_status should use them' do + test 'relevant? should be false for host without policies' do + status = ForemanOpenscap::ComplianceStatus.new + host = FactoryBot.create(:host) + + status.host = host + + refute status.relevant? + end + + test 'relevant? should be false for host with hostgroup but no policies' do + status = ForemanOpenscap::ComplianceStatus.new + host = FactoryBot.create(:host, :with_hostgroup) + + status.host = host + + refute status.relevant? + end + + test 'relevant? should be true for hostgroup policies without loading combined_policies' do + status = ForemanOpenscap::ComplianceStatus.new + host = FactoryBot.create(:host, :with_hostgroup) + + assert_empty host.policies + @policy_a.hostgroup_ids = [host.hostgroup.id] + assert @policy_a.save + + host.reload + assert_includes host.combined_policies, @policy_a + + status.host = host + host.expects(:combined_policies).never + + assert status.relevant? + end + + test 'to_status should use hostgroup policies' do status = ForemanOpenscap::ComplianceStatus.new host = FactoryBot.create(:host, :with_hostgroup) othered_status = { :passed => 0, :failed => 0, :othered => 1 }.with_indifferent_access @@ -95,12 +131,10 @@ def setup FactoryBot.create(:policy_arf_report, :policy_id => @policy_a.id, :arf_report_id => report.id) status.host = host - - assert status.relevant? assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, status.to_status end - test 'relevant? should be true for inherited parent hostgroup policies' do + test 'relevant? should be true for inherited parent hostgroup policies without loading combined_policies' do status = ForemanOpenscap::ComplianceStatus.new host = FactoryBot.create(:host, :with_hostgroup) hostgroup = host.hostgroup @@ -116,7 +150,22 @@ def setup assert_includes host.combined_policies, @policy_a status.host = host + host.expects(:combined_policies).never assert status.relevant? end + + test 'to_label returns label based on stored status' do + status = ForemanOpenscap::ComplianceStatus.new + status.status = ForemanOpenscap::ComplianceStatus::COMPLIANT + status.expects(:to_status).never + assert_equal 'Compliant', status.to_label + end + + test 'to_global returns global status based on stored status' do + status = ForemanOpenscap::ComplianceStatus.new + status.status = ForemanOpenscap::ComplianceStatus::COMPLIANT + status.expects(:to_status).never + assert_equal ::HostStatus::Global::OK, status.to_global + end end diff --git a/test/unit/concerns/host_extensions_test.rb b/test/unit/concerns/host_extensions_test.rb index 25140821f..ba8687419 100644 --- a/test/unit/concerns/host_extensions_test.rb +++ b/test/unit/concerns/host_extensions_test.rb @@ -162,6 +162,26 @@ class HostExtensionsTest < ActiveSupport::TestCase assert_empty ForemanOpenscap::Asset.where(:assetable_id => host.id, :assetable_type => 'Host::Base') end + test 'compliance_status returns stored persisted status without recalculation' do + status = @host.get_status(ForemanOpenscap::ComplianceStatus) + status.status = ForemanOpenscap::ComplianceStatus::INCONCLUSIVE + status.reported_at = Time.current + status.save! + status.expects(:to_status).never + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, @host.compliance_status + end + + test 'compliance_status_label returns label from stored persisted status without recalculation' do + status = @host.get_status(ForemanOpenscap::ComplianceStatus) + status.status = ForemanOpenscap::ComplianceStatus::INCOMPLIANT + status.reported_at = Time.current + status.save! + status.expects(:to_status).never + + assert_equal 'Incompliant', @host.compliance_status_label + end + private def setup_hosts_with_policy From e31ed0c07bf4863c8099e331c98670a366bdd419 Mon Sep 17 00:00:00 2001 From: Bernhard Suttner Date: Mon, 6 Jul 2026 19:24:33 +0200 Subject: [PATCH 2/2] Reset compliance status after adding/removing policies to host/hostgroup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- app/models/foreman_openscap/asset.rb | 22 ++++- app/models/foreman_openscap/policy.rb | 46 +++++++++- .../compliance_status_resetter.rb | 20 +++++ test/test_plugin_helper.rb | 7 ++ test/unit/concerns/host_extensions_test.rb | 30 +++++-- .../concerns/hostgroup_extensions_test.rb | 35 ++++++++ test/unit/policy_test.rb | 90 +++++++++++++++++++ 7 files changed, 238 insertions(+), 12 deletions(-) create mode 100644 app/services/foreman_openscap/compliance_status_resetter.rb diff --git a/app/models/foreman_openscap/asset.rb b/app/models/foreman_openscap/asset.rb index 384381a4a..73f3bb323 100644 --- a/app/models/foreman_openscap/asset.rb +++ b/app/models/foreman_openscap/asset.rb @@ -1,7 +1,9 @@ module ForemanOpenscap class Asset < ApplicationRecord has_many :asset_policies, :dependent => :delete_all - has_many :policies, :through => :asset_policies + has_many :policies, :through => :asset_policies, + :after_add => :reset_compliance_status, + :after_remove => :reset_compliance_status belongs_to :assetable, :polymorphic => true scope :hosts, lambda { where(:assetable_type => 'Host::Base') } @@ -20,6 +22,24 @@ def name private + def reset_compliance_status(_policy) + ForemanOpenscap::ComplianceStatusResetter.to_inconclusive(host_ids_reached_by_asset) + end + + def host_ids_reached_by_asset + case assetable_type + when 'Host::Base' + [assetable_id] + when 'Hostgroup' + hostgroup = Hostgroup.find_by(:id => assetable_id) + return [] unless hostgroup + + Host.where(:hostgroup_id => hostgroup.subtree_ids).pluck(:id) + else + [] + end + end + def fetch_asset(type) assetable if assetable_type == type end diff --git a/app/models/foreman_openscap/policy.rb b/app/models/foreman_openscap/policy.rb index cf72db2d2..7c27dc5a2 100644 --- a/app/models/foreman_openscap/policy.rb +++ b/app/models/foreman_openscap/policy.rb @@ -42,6 +42,7 @@ def self.deploy_by_variants validate :valid_tailoring, :valid_tailoring_profile, :no_mixed_deployments validate :valid_cron_line, :valid_weekday, :valid_day_of_month, :if => Proc.new { |policy| policy.should_validate?('Schedule') } after_save :assign_policy_to_hostgroups + after_save :reset_changed_assignment_hosts_to_inconclusive # before_destroy - ensure that the policy has no hostgroups, or classes default_scope do @@ -179,10 +180,14 @@ def unassign_hosts(hosts) :assetable_type => 'Host::Base', :assetable_id => hosts.map(&:id), :foreman_openscap_asset_policies => { :policy_id => id } - ).pluck(:id) + ).pluck(:id, :assetable_id) - self.asset_ids = self.asset_ids - policy_host_assets - ForemanOpenscap::Asset.where(:id => policy_host_assets).destroy_all + asset_ids = policy_host_assets.map(&:first) + host_ids_to_reset = policy_host_assets.map(&:second) + + self.asset_ids = self.asset_ids - asset_ids + ForemanOpenscap::Asset.where(:id => asset_ids).destroy_all + ForemanOpenscap::ComplianceStatusResetter.to_inconclusive(host_ids_to_reset) end def to_enc @@ -251,12 +256,47 @@ def profile_for_scan end def assign_ids(ids, class_name) + before_host_ids = host_ids_reached_by_assignments(class_name) new_assets = ids.uniq.reject { |id| id.respond_to?(:empty?) && id.empty? }.reduce([]) do |memo, id| memo << assets.where(:assetable_type => class_name, :assetable_id => id).first_or_initialize end complimentary_class_name = class_name == 'Host::Base' ? 'Hostgroup' : 'Host::Base' existing_assets = self.assets.select { |assigned_asset| assigned_asset.assetable_type == complimentary_class_name } self.assets = existing_assets + new_assets + after_host_ids = host_ids_reached_by_assignments(class_name, self.assets) + + return unless host_assignment_changed?(before_host_ids, after_host_ids) + + remember_hosts_for_compliance_status_reset(before_host_ids + after_host_ids) + end + + def host_ids_reached_by_assignments(class_name, assigned_assets = assets) + assetable_ids = assigned_assets.select { |asset| asset.assetable_type == class_name }.map { |asset| asset.assetable_id.to_i } + + if class_name == 'Host::Base' + assetable_ids + else + hostgroup_ids = ::Hostgroup.where(:id => assetable_ids).flat_map(&:subtree_ids).uniq + ::Host.where(:hostgroup_id => hostgroup_ids).pluck(:id) + end + end + + def remember_hosts_for_compliance_status_reset(host_ids) + @compliance_status_reset_host_ids ||= [] + @compliance_status_reset_host_ids.concat(host_ids) + end + + def host_assignment_changed?(before_host_ids, after_host_ids) + before_host_ids.to_set != after_host_ids.to_set + end + + def reset_changed_assignment_hosts_to_inconclusive + host_ids = Array(@compliance_status_reset_host_ids).compact.uniq + return if host_ids.empty? + + ForemanOpenscap::ComplianceStatusResetter.to_inconclusive(host_ids) + ensure + @compliance_status_reset_host_ids = nil end def no_mixed_deployments diff --git a/app/services/foreman_openscap/compliance_status_resetter.rb b/app/services/foreman_openscap/compliance_status_resetter.rb new file mode 100644 index 000000000..428bfee84 --- /dev/null +++ b/app/services/foreman_openscap/compliance_status_resetter.rb @@ -0,0 +1,20 @@ +module ForemanOpenscap + class ComplianceStatusResetter + def self.to_inconclusive(host_ids) + host_ids = Array(host_ids).compact.uniq + return if host_ids.empty? + + now = Time.current + rows = host_ids.map do |host_id| + { + :host_id => host_id, + :type => ComplianceStatus.to_s, + :status => ComplianceStatus::INCONCLUSIVE, + :reported_at => now + } + end + + ComplianceStatus.upsert_all(rows, :unique_by => %i[type host_id], :update_only => %i[status reported_at]) + end + end +end diff --git a/test/test_plugin_helper.rb b/test/test_plugin_helper.rb index 329d146f4..df54a1768 100644 --- a/test/test_plugin_helper.rb +++ b/test/test_plugin_helper.rb @@ -59,6 +59,13 @@ def add_smart_proxy module ScapTestCommon private + def set_compliance_status(host, status) + compliance_status = host.get_status(ForemanOpenscap::ComplianceStatus) + compliance_status.status = status + compliance_status.reported_at = Time.current + compliance_status.save! + end + def create_report_with_rules(host, rule_names, rule_results) raise "rule_names and rule_results should have the same length!" if rule_names.size != rule_results.size metrics = { diff --git a/test/unit/concerns/host_extensions_test.rb b/test/unit/concerns/host_extensions_test.rb index ba8687419..4f3960837 100644 --- a/test/unit/concerns/host_extensions_test.rb +++ b/test/unit/concerns/host_extensions_test.rb @@ -162,21 +162,35 @@ class HostExtensionsTest < ActiveSupport::TestCase assert_empty ForemanOpenscap::Asset.where(:assetable_id => host.id, :assetable_type => 'Host::Base') end + test 'should reset compliance status when assigning policy through host policies' do + host = FactoryBot.create(:compliance_host) + set_compliance_status(host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + + host.policies = [@policy] + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, host.reload.compliance_status + end + + test 'should reset compliance status when removing policy through host policies' do + host = FactoryBot.create(:compliance_host, :policies => [@policy]) + set_compliance_status(host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + + host.policies = [] + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, host.reload.compliance_status + end + test 'compliance_status returns stored persisted status without recalculation' do - status = @host.get_status(ForemanOpenscap::ComplianceStatus) - status.status = ForemanOpenscap::ComplianceStatus::INCONCLUSIVE - status.reported_at = Time.current - status.save! + set_compliance_status(@host, ForemanOpenscap::ComplianceStatus::INCONCLUSIVE) + status = @host.reload.get_status(ForemanOpenscap::ComplianceStatus) status.expects(:to_status).never assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, @host.compliance_status end test 'compliance_status_label returns label from stored persisted status without recalculation' do - status = @host.get_status(ForemanOpenscap::ComplianceStatus) - status.status = ForemanOpenscap::ComplianceStatus::INCOMPLIANT - status.reported_at = Time.current - status.save! + set_compliance_status(@host, ForemanOpenscap::ComplianceStatus::INCOMPLIANT) + status = @host.reload.get_status(ForemanOpenscap::ComplianceStatus) status.expects(:to_status).never assert_equal 'Incompliant', @host.compliance_status_label diff --git a/test/unit/concerns/hostgroup_extensions_test.rb b/test/unit/concerns/hostgroup_extensions_test.rb index e7b7502f3..b989572c5 100644 --- a/test/unit/concerns/hostgroup_extensions_test.rb +++ b/test/unit/concerns/hostgroup_extensions_test.rb @@ -7,4 +7,39 @@ class HostgroupExtensionsTest < ActiveSupport::TestCase asset_scope = ::ForemanOpenscap::Asset.where(:assetable_id => hostgroup.id, :assetable_type => 'Hostgroup') assert_difference("asset_scope.count", -3) { hostgroup.destroy } end + + test "should reset compliance status for subtree when assigning policy through hostgroup asset policies" do + parent = FactoryBot.create(:hostgroup) + child = FactoryBot.create(:hostgroup, :ancestry => parent.id.to_s) + parent_host = FactoryBot.create(:compliance_host, :hostgroup_id => parent.id) + child_host = FactoryBot.create(:compliance_host, :hostgroup_id => child.id) + policy = FactoryBot.create(:policy) + asset = FactoryBot.create(:asset, :assetable_id => parent.id, :assetable_type => 'Hostgroup') + + set_compliance_status(parent_host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + set_compliance_status(child_host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + + asset.policies = [policy] + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, parent_host.reload.compliance_status + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, child_host.reload.compliance_status + end + + test "should reset compliance status for subtree when removing policy through hostgroup asset policies" do + parent = FactoryBot.create(:hostgroup) + child = FactoryBot.create(:hostgroup, :ancestry => parent.id.to_s) + parent_host = FactoryBot.create(:compliance_host, :hostgroup_id => parent.id) + child_host = FactoryBot.create(:compliance_host, :hostgroup_id => child.id) + policy = FactoryBot.create(:policy) + asset = FactoryBot.create(:asset, :assetable_id => parent.id, :assetable_type => 'Hostgroup') + asset.policies = [policy] + + set_compliance_status(parent_host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + set_compliance_status(child_host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + + asset.policies = [] + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, parent_host.reload.compliance_status + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, child_host.reload.compliance_status + end end diff --git a/test/unit/policy_test.rb b/test/unit/policy_test.rb index 987cae0ff..dcfa0fd79 100644 --- a/test/unit/policy_test.rb +++ b/test/unit/policy_test.rb @@ -46,6 +46,90 @@ class PolicyTest < ActiveSupport::TestCase assert_equal 1, policy.hosts.count end + test "should reset compliance status when assigning policy to host" do + host = FactoryBot.create(:compliance_host) + unrelated_host = FactoryBot.create(:compliance_host) + set_compliance_status(host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + set_compliance_status(unrelated_host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + policy = FactoryBot.create(:policy, :scap_content => @scap_content, :scap_content_profile => @scap_profile) + + policy.host_ids = [host.id] + policy.save! + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, compliance_status_for(host) + assert_equal ForemanOpenscap::ComplianceStatus::COMPLIANT, compliance_status_for(unrelated_host) + end + + test "should create inconclusive compliance status when assigning policy to host without status" do + host = FactoryBot.create(:compliance_host) + policy = FactoryBot.create(:policy, :scap_content => @scap_content, :scap_content_profile => @scap_profile) + + policy.host_ids = [host.id] + policy.save! + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, compliance_status_for(host) + end + + test "should reset compliance status when removing policy from host assignment" do + host = FactoryBot.create(:compliance_host) + asset = FactoryBot.create(:asset, :assetable_id => host.id, :assetable_type => 'Host::Base') + policy = FactoryBot.create(:policy, :assets => [asset], :scap_content => @scap_content, :scap_content_profile => @scap_profile) + set_compliance_status(host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + + policy.host_ids = [] + policy.save! + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, compliance_status_for(host) + end + + test "should reset compliance status when unassigning host from policy" do + host = FactoryBot.create(:compliance_host) + asset = FactoryBot.create(:asset, :assetable_id => host.id, :assetable_type => 'Host::Base') + policy = FactoryBot.create(:policy, :assets => [asset], :scap_content => @scap_content, :scap_content_profile => @scap_profile) + set_compliance_status(host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + + policy.unassign_hosts([host]) + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, compliance_status_for(host) + end + + test "should reset compliance status for hostgroup subtree when assigning policy to hostgroup" do + parent = FactoryBot.create(:hostgroup) + child = FactoryBot.create(:hostgroup, :ancestry => parent.id.to_s) + parent_host = FactoryBot.create(:compliance_host, :hostgroup_id => parent.id) + child_host = FactoryBot.create(:compliance_host, :hostgroup_id => child.id) + unrelated_host = FactoryBot.create(:compliance_host) + [parent_host, child_host, unrelated_host].each do |host| + set_compliance_status(host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + end + policy = FactoryBot.create(:policy, :scap_content => @scap_content, :scap_content_profile => @scap_profile) + + policy.hostgroup_ids = [parent.id] + policy.save! + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, compliance_status_for(parent_host) + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, compliance_status_for(child_host) + assert_equal ForemanOpenscap::ComplianceStatus::COMPLIANT, compliance_status_for(unrelated_host) + end + + test "should reset compliance status for hostgroup subtree when removing policy from hostgroup" do + parent = FactoryBot.create(:hostgroup) + child = FactoryBot.create(:hostgroup, :ancestry => parent.id.to_s) + parent_host = FactoryBot.create(:compliance_host, :hostgroup_id => parent.id) + child_host = FactoryBot.create(:compliance_host, :hostgroup_id => child.id) + asset = FactoryBot.create(:asset, :assetable_id => parent.id, :assetable_type => 'Hostgroup') + policy = FactoryBot.create(:policy, :assets => [asset], :scap_content => @scap_content, :scap_content_profile => @scap_profile) + [parent_host, child_host].each do |host| + set_compliance_status(host, ForemanOpenscap::ComplianceStatus::COMPLIANT) + end + + policy.hostgroup_ids = [] + policy.save! + + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, compliance_status_for(parent_host) + assert_equal ForemanOpenscap::ComplianceStatus::INCONCLUSIVE, compliance_status_for(child_host) + end + test "should delete assets when unassigning hosts" do host1 = FactoryBot.create(:compliance_host) host2 = FactoryBot.create(:compliance_host) @@ -331,4 +415,10 @@ class PolicyTest < ActiveSupport::TestCase assert_equal policy.deploy_by, 'manual' assert_equal policy.name, name end + + private + + def compliance_status_for(host) + ForemanOpenscap::ComplianceStatus.find_by(:host_id => host.id).try(:status) + end end