Skip to content

Commit

Permalink
[wip]
Browse files Browse the repository at this point in the history
  • Loading branch information
romanblanco committed Apr 25, 2024
1 parent 1d9da54 commit 3ee1db8
Show file tree
Hide file tree
Showing 14 changed files with 406 additions and 9 deletions.
38 changes: 38 additions & 0 deletions app/controllers/v2/reports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module V2
# API for Reports (rule results)
class ReportsController < ApplicationController
def index
render_json reports
end
permission_for_action :index, Rbac::REPORT_READ

def show
render_json report
end
permission_for_action :show, Rbac::REPORT_READ

private

def reports
@reports ||= authorize(fetch_collection)
end

def report
@report ||= authorize(expand_resource.find(permitted_params[:id]))
end

def resource
V2::Report
end

def serializer
V2::ReportSerializer
end

def extra_fields
%i[account_id]
end
end
end
39 changes: 37 additions & 2 deletions app/models/v2/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,44 @@ class Report < ApplicationRecord
self.table_name = :reports
self.primary_key = :id

SYSTEM_COUNT = lambda do
AN::NamedFunction.new(
'COUNT', [V2::System.arel_table[:id]]
).filter(Pundit.policy_scope(User.current, V2::System).arel.ast.cores.first.wheres.first)
end

# To prevent an autojoin with itself, there should not be an inverse relationship specified
belongs_to :policy, class_name: 'V2::Policy', foreign_key: :id # rubocop:disable Rails/InverseOf
has_many :tailorings, class_name: 'V2::Tailoring', through: :policy
has_many :systems, class_name: 'V2::System', through: :tailorings
belongs_to :account

belongs_to :profile, class_name: 'V2::Profile'
has_one :security_guide, through: :profile, class_name: 'V2::SecurityGuide'
has_many :tailorings, class_name: 'V2::Tailoring', foreign_key: :policy_id
has_many :test_results, class_name: 'V2::TestResult', dependent: nil, through: :tailorings
has_many :policy_systems, class_name: 'V2::PolicySystem', foreign_key: :policy_id
has_many :systems, class_name: 'V2::System', through: :test_results

sortable_by :title
sortable_by :os_major_version
sortable_by :total_host_count
sortable_by :business_objective
sortable_by :compliance_threshold

searchable_by :title, %i[like unlike eq ne in notin]

validates :account, presence: true
# TODO: validates :test_result, presense: true

def os_major_version
attributes['security_guide__os_major_version'] || security_guide.os_major_version
end

def ref_id
attributes['profile__ref_id'] || profile.ref_id
end

def profile_title
attributes['profile__title'] || profile.title
end
end
end
3 changes: 2 additions & 1 deletion app/models/v2/system.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class System < ApplicationRecord
# rubocop:enable Rails/InverseOf
has_many :policy_systems, class_name: 'V2::PolicySystem', dependent: nil
has_many :policies, through: :policy_systems
has_many :reports, class_name: 'V2::Report', dependent: nil
has_many :reports, class_name: 'V2::Report', through: :policies
has_many :test_results, class_name: 'V2::TestResult'

OS_VERSION = AN::InfixOperation.new('->', Host.arel_table[:system_profile], AN::Quoted.new('operating_system'))
OS_MINOR_VERSION = AN::InfixOperation.new('->', OS_VERSION, AN::Quoted.new('minor')).as('os_minor_version')
Expand Down
3 changes: 2 additions & 1 deletion app/models/v2/tailoring.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ class Tailoring < ApplicationRecord
belongs_to :profile, class_name: 'V2::Profile'
has_one :security_guide, through: :profile, class_name: 'V2::SecurityGuide'
has_one :account, through: :policy, class_name: 'Account'
has_one :report, class_name: 'V2::Report', through: :policy, dependent: nil
has_many :tailoring_rules,
class_name: 'V2::TailoringRule',
dependent: :destroy,
inverse_of: :tailoring
has_many :rules, class_name: 'V2::Rule', through: :tailoring_rules
has_many :reports, class_name: 'V2::Report', dependent: nil
has_many :test_results, class_name: 'V2::TestResult', dependent: :destroy

searchable_by :os_minor_version, %i[eq ne]

Expand Down
9 changes: 5 additions & 4 deletions app/models/v2/test_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class TestResult < ApplicationRecord
self.table_name = :v2_test_results
self.primary_key = :id

belongs_to :system, optional: true
belongs_to :tailoring
has_one :policy, through: :tailoring
has_one :security_guide, through: :tailoring
belongs_to :system, class_name: 'V2::System', optional: true
belongs_to :tailoring, class_name: 'V2::Tailoring'

has_one :policy, class_name: 'V2::Policy', through: :tailoring
has_one :security_guide, class_name: 'V2::SecurityGuide', through: :tailoring
end
end
23 changes: 23 additions & 0 deletions app/policies/v2/report_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module V2
class ReportPolicy < V2::ApplicationPolicy
def index?
true # FIXME: this is handled in scoping
end

def show?
match_account?
end

# Only show Reports in our user account
class Scope < V2::ApplicationPolicy::Scope
def resolve
return scope.where('1=0') if user&.account_id.blank?

scope.where(account_id: user.account_id)
end
end
end
end

17 changes: 17 additions & 0 deletions app/serializers/v2/report_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module V2
# JSON serialization for Reports
class ReportSerializer < V2::ApplicationSerializer
attributes :title, :description, :business_objective, :compliance_threshold

derived_attribute :os_major_version, security_guide: [:os_major_version]
derived_attribute :profile_title, profile: [:title]
derived_attribute :ref_id, profile: [:ref_id]

aggregated_attribute :system_count, :systems, V2::Report::SYSTEM_COUNT
# aggregated_attribute :test_result_system_count, :test_results, V2::Report::TEST_RESULT_SYSTEM_COUNT
# aggregated_attribute :compliant_system_count, :test_results, V2::Report::COMPLIANT_SYSTEM_COUNT
# aggregated_attribute :unsupported_system_count, :test_results, V2::Report::UNSUPPORTED_SYSTEM_COUNT
end
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def draw_routes(prefix)
resources :systems, only: [:index, :show] do
resources :policies, only: [:index], parents: [:systems]
end
resources :reports, only: [:index, :show]
end
end

Expand Down
75 changes: 75 additions & 0 deletions spec/controllers/v2/reports_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

require 'rails_helper'

describe V2::ReportsController do
before do
stub_rbac_permissions(
Rbac::INVENTORY_HOSTS_READ,
Rbac::SYSTEM_READ,
Rbac::REPORT_READ
)
end

let(:attributes) do
{
title: :title,
os_major_version: :os_major_version,
ref_id: :ref_id,
description: :description,
profile_title: :profile_title,
business_objective: :business_objective,
system_count: -> { 0 }, # TODO
# TODO: compliant_system_count: -> { 0 },
# TODO: test_result_system_count: -> { 0 },
# TODO: unsupported_system_count: -> { 0 },
compliance_threshold: :compliance_threshold
}
end

let(:current_user) { FactoryBot.create(:v2_user) }
let(:rbac_allowed?) { true }

before do
request.headers['X-RH-IDENTITY'] = current_user.account.identity_header.raw
allow(StrongerParameters::InvalidValue).to receive(:new) { |value, _| value.to_sym }
allow(controller).to receive(:rbac_allowed?).and_return(rbac_allowed?)
end

context '/reports' do
describe 'GET index' do
let(:extra_params) { { account: current_user.account } }
let(:parents) { nil }

foreign = FactoryBot.create(:v2_account)
5.times do
FactoryBot.create(
:v2_report,
account: foreign
)
end

let(:items) do
FactoryBot.create_list(
:v2_report, item_count,
account: current_user.account,
).sort_by(&:id)
end

it_behaves_like 'collection'
include_examples 'with metadata'
it_behaves_like 'paginable'
it_behaves_like 'sortable'
it_behaves_like 'searchable'
end

describe 'GET show' do
let(:extra_params) { { account: current_user.account, id: item.id } }
let(:parent) { nil }

let(:item) { FactoryBot.create(:v2_report, account: current_user.account) }

it_behaves_like 'individual'
end
end
end
2 changes: 1 addition & 1 deletion spec/factories/policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

FactoryBot.define do
factory :v2_policy, class: 'V2::Policy' do
account { association(:v2_account) }
account { association :v2_account }
title { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
profile { association :v2_profile, os_major_version: os_major_version, supports_minors: supports_minors }
Expand Down
27 changes: 27 additions & 0 deletions spec/factories/report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

FactoryBot.define do
factory :v2_report, class: 'V2::Report' do
to_create do |instance, context|
policy = FactoryBot.create(
:v2_policy,
:for_tailoring,
account: context.account,
supports_minors: [0]
)

FactoryBot.create(
:v2_test_result,
tailoring: FactoryBot.create(
:v2_tailoring,
policy: policy,
os_minor_version: 0
)
)

instance.id = policy.id
instance.attributes = V2::Policy.find(instance.id).attributes
instance.reload
end
end
end
12 changes: 12 additions & 0 deletions spec/factories/test_result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

FactoryBot.define do
factory :v2_test_result, class: 'V2::TestResult' do
tailoring { association :v2_tailoring }
system { association :system }
start_time { 5.minute.ago }
end_time { 1.minute.ago }
score { SecureRandom.rand(98) + 1 }
supported { true }
end
end
Loading

0 comments on commit 3ee1db8

Please sign in to comment.