Skip to content

Commit

Permalink
[wip]
Browse files Browse the repository at this point in the history
  • Loading branch information
romanblanco committed Apr 18, 2024
1 parent 067d008 commit 0cc067d
Show file tree
Hide file tree
Showing 15 changed files with 410 additions and 4 deletions.
34 changes: 34 additions & 0 deletions app/controllers/v2/reports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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
end
end
1 change: 1 addition & 0 deletions app/models/v2/policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Policy < ApplicationRecord

belongs_to :profile, class_name: 'V2::Profile'
has_one :security_guide, through: :profile, class_name: 'V2::SecurityGuide'
has_one :report, class_name: 'V2::Report', foreign_key: :id
has_many :tailorings, class_name: 'V2::Tailoring', dependent: :destroy
has_many :tailoring_rules, through: :tailorings, class_name: 'V2::TailoringRule', dependent: :destroy
has_many :rules, through: :tailoring_rules, class_name: 'V2::Rule'
Expand Down
38 changes: 37 additions & 1 deletion app/models/v2/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,45 @@ 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
belongs_to :account, class_name: 'Account'

has_many :tailorings, class_name: 'V2::Tailoring', through: :policy
has_many :systems, class_name: 'V2::System', through: :tailorings
has_many :test_results, class_name: 'V2::TestResult', dependent: nil, through: :tailorings
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
policy.os_major_version
end

def ref_id
policy.ref_id
end

def profile_title
policy.profile_title
end

# def account_id
# policy.account_id
# 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', dependent: nil # TODO: is `dependent: nil` correct?
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
1 change: 1 addition & 0 deletions app/models/v2/tailoring.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Tailoring < ApplicationRecord
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: nil

searchable_by :os_minor_version, %i[eq ne]

Expand Down
4 changes: 3 additions & 1 deletion app/models/v2/test_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ class TestResult < ApplicationRecord
self.primary_key = :id

belongs_to :system, optional: true
belongs_to :tailoring
belongs_to :tailoring, class_name: 'V2::Tailoring'
belongs_to :report, class_name: 'V2::Report', optional: true

has_one :policy, through: :tailoring
has_one :security_guide, through: :tailoring
end
Expand Down
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?
true # TODO: 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
# TODO: aggregated_attribute :compliant_system_count, :systems, V2::Report::COMPLIANT_SYSTEM_COUNT
# TODO: aggregated_attribute :test_result_system_count, :systems, V2::Report::TEST_RESULT_SYSTEM_COUNT
# TODO: aggregated_attribute :unsupported_system_count, :systems, 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
98 changes: 98 additions & 0 deletions spec/controllers/v2/reports_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# 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,
# TODO: system_count: :system_count,
# TODO: compliant_system_count: :compliant_system_count,
# TODO: test_result_system_count: :test_result_system_count,
# TODO: unsupported_system_count: :unsupported_system_count,
compliance_threshold: :compliance_threshold,
# ssg_version: :ssg_version
}
end

let(:current_user) { FactoryBot.create(:v2_user, :with_cert_auth) }
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 }
let(:item_count) { 3 }

# TODO: only creates duplicated records
let(:items) do
item_count.times.map do
test_result = FactoryBot.create(
:v2_test_result,
tailoring: FactoryBot.create(
:v2_tailoring,
policy: FactoryBot.create(
:v2_policy,
:for_tailoring,
account: current_user.account,
supports_minors: [0]
),
os_minor_version: 0
)
)

FactoryBot.create_list(
:v2_report, item_count,
account: current_user.account,
policy_id: test_result.tailoring.policy.id
).map(&:reload)
end
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(:policy) do
FactoryBot.create(
:v2_policy,
:for_tailoring,
account: current_user.account,
supports_minors: [0]
)
end
let(:tailoring) { FactoryBot.create(:v2_tailoring, policy: policy, os_minor_version: 0) }
let(:test_result) { FactoryBot.create(:v2_test_result, tailoring: tailoring) }

let(:item) { FactoryBot.create(:v2_report, account: current_user.account, policy_id: test_result.tailoring.policy.id) }

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
14 changes: 14 additions & 0 deletions spec/factories/report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

FactoryBot.define do
factory :v2_report, class: 'V2::Report' do
to_create do |instance, context|
instance.attributes = V2::Policy.find(context.policy_id).attributes
instance.reload
end

transient do
policy_id { nil }
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.before(Time.zone.now) }
end_time { 1.minute.before(Time.zone.now) }
score { 0 }
supported { false }
end
end
Loading

0 comments on commit 0cc067d

Please sign in to comment.