diff options
| author | Luc Donnet | 2017-10-17 15:04:54 +0200 | 
|---|---|---|
| committer | GitHub | 2017-10-17 15:04:54 +0200 | 
| commit | b5b8886e4be8806a7357ef745c293560d32da343 (patch) | |
| tree | 362cd3ab06372886a189ff366e1813f13f94bdf6 | |
| parent | 4bb7669f00b7e6e622f966a11b4c727938838f22 (diff) | |
| parent | 0b744d48fdc84445f593114fe61baaafca4e17f4 (diff) | |
| download | chouette-core-b5b8886e4be8806a7357ef745c293560d32da343.tar.bz2 | |
Merge pull request #95 from af83/4727-clone-compliance-control-set
4727 clone compliance control set 
| -rw-r--r-- | app/workers/compliance_control_set_cloning_worker.rb | 8 | ||||
| -rw-r--r-- | config/locales/compliance_control_blocks.en.yml | 4 | ||||
| -rw-r--r-- | config/locales/compliance_control_blocks.fr.yml | 4 | ||||
| -rw-r--r-- | config/locales/compliance_control_sets.en.yml | 2 | ||||
| -rw-r--r-- | config/locales/compliance_control_sets.fr.yml | 2 | ||||
| -rw-r--r-- | config/locales/compliance_controls.en.yml | 2 | ||||
| -rw-r--r-- | config/locales/compliance_controls.fr.yml | 2 | ||||
| -rw-r--r-- | db/schema.rb | 2 | ||||
| -rw-r--r-- | lib/compliance_control_set_cloner.rb | 94 | ||||
| -rw-r--r-- | spec/lib/compliance_control_set_cloner_spec.rb | 144 | ||||
| -rw-r--r-- | spec/models/compliance_control_validations/genric_attribute_validation/min_max_validation_spec.rb | 10 | ||||
| -rw-r--r-- | spec/workers/clean_up_worker_spec.rb | 1 | ||||
| -rw-r--r-- | spec/workers/compliance_control_set_cloning_worker_spec.rb | 15 | 
13 files changed, 281 insertions, 9 deletions
| diff --git a/app/workers/compliance_control_set_cloning_worker.rb b/app/workers/compliance_control_set_cloning_worker.rb new file mode 100644 index 000000000..9cbe5c81a --- /dev/null +++ b/app/workers/compliance_control_set_cloning_worker.rb @@ -0,0 +1,8 @@ +class ComplianceControlSetCloningWorker +  include Sidekiq::Worker + +  def perform id, organisation_id +    ComplianceControlSetCloner.new.copy id, organisation_id +  end + +end diff --git a/config/locales/compliance_control_blocks.en.yml b/config/locales/compliance_control_blocks.en.yml index a37b41db5..fbface6b2 100644 --- a/config/locales/compliance_control_blocks.en.yml +++ b/config/locales/compliance_control_blocks.en.yml @@ -10,9 +10,11 @@ fr:          transport_mode: Transport mode          sub_transport_mode: Transport submode    compliance_control_blocks: +    clone: +      prefix: 'Copy of'      actions:        destroy_confirm: Are you sure you want to destroy this block ?      new:        title: Create a control block      edit: -      title: "Edit the control block : %{compliance_control_block}"
\ No newline at end of file +      title: "Edit the control block : %{compliance_control_block}" diff --git a/config/locales/compliance_control_blocks.fr.yml b/config/locales/compliance_control_blocks.fr.yml index f93cafa54..66df008be 100644 --- a/config/locales/compliance_control_blocks.fr.yml +++ b/config/locales/compliance_control_blocks.fr.yml @@ -10,9 +10,11 @@ fr:          transport_mode: Mode de transport          transport_submode: Sous-mode de transport    compliance_control_blocks: +    clone: +      prefix: 'Copie de'      actions:        destroy_confirm: Etes vous sûr de supprimer ce bloc ?      new:        title: Créer un groupe de contrôle(s)      edit: -      title: "Editer le groupe de contrôle : %{compliance_control_block}"
\ No newline at end of file +      title: "Editer le groupe de contrôle : %{compliance_control_block}" diff --git a/config/locales/compliance_control_sets.en.yml b/config/locales/compliance_control_sets.en.yml index 83b14642c..f72342894 100644 --- a/config/locales/compliance_control_sets.en.yml +++ b/config/locales/compliance_control_sets.en.yml @@ -1,5 +1,7 @@  en:    compliance_control_sets: +    clone: +      prefix: 'Copie de'      index:        title: Compliance control set        new: New compliance control set diff --git a/config/locales/compliance_control_sets.fr.yml b/config/locales/compliance_control_sets.fr.yml index 37851d7c4..c31eb9423 100644 --- a/config/locales/compliance_control_sets.fr.yml +++ b/config/locales/compliance_control_sets.fr.yml @@ -1,5 +1,7 @@  fr:    compliance_control_sets: +    clone: +      prefix: 'Copy of'      index:        title: "Liste des jeux de contrôles"      edit: diff --git a/config/locales/compliance_controls.en.yml b/config/locales/compliance_controls.en.yml index 3063c35a4..887bc2009 100644 --- a/config/locales/compliance_controls.en.yml +++ b/config/locales/compliance_controls.en.yml @@ -1,5 +1,7 @@  en:    compliance_controls: +    clone: +      prefix: 'Copy of'      min_max_values: "the minimum (%{min}) is not supposed to be greater than the maximum (%{max})"      errors:        incoherent_control_sets: "Impossible to assign a control to a set (id: %{direct_set_name}) differing from the one of its group (id: %{indirect_set_name})" diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml index 2038b9eb7..2feb201bf 100644 --- a/config/locales/compliance_controls.fr.yml +++ b/config/locales/compliance_controls.fr.yml @@ -1,5 +1,7 @@  fr:    compliance_controls: +    clone: +      prefix: 'Copie de'      min_max_values: "la valeur de minimum (%{min}) ne doit pas être superieur à la valuer du maximum (%{max})"      errors:        incoherent_control_sets: "Le contrôle ne peut pas être associé à un jeu de contrôle (id: %{direct_set_name}) différent de celui de son groupe (id: %{indirect_set_name})" diff --git a/db/schema.rb b/db/schema.rb index 181c83402..a64a426de 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -408,9 +408,9 @@ ActiveRecord::Schema.define(version: 20171016074044) do      t.string   "type"      t.integer  "parent_id",             limit: 8      t.string   "parent_type" +    t.datetime "notified_parent_at"      t.integer  "current_step",                    default: 0      t.integer  "total_steps",                     default: 0 -    t.datetime "notified_parent_at"      t.string   "creator"    end diff --git a/lib/compliance_control_set_cloner.rb b/lib/compliance_control_set_cloner.rb new file mode 100644 index 000000000..1cf58a38d --- /dev/null +++ b/lib/compliance_control_set_cloner.rb @@ -0,0 +1,94 @@ +class ComplianceControlSetCloner + +  # Naming Convention: As we are in a domain with quite long names we +  #                    abbreviate compliance_control to cc and +  #                    compliance_check to cck iff used as prefixes. + +  attr_reader :organisation_id, :source_set_id +   +  def copy source_set_id, organisation_id +    @source_set_id = source_set_id +    @organisation_id = organisation_id +    copy_set +  end + + +  private + +  # Workers +  # ------- + +  # Copy Set: +  def copy_set +    # Force lazy creation of target_set, just in case source_set is _empty_. +    target_set +    copy_controls  +    copy_blocks +  end + +  # Copy Blocks: +  def copy_block source_block +    target_set.compliance_control_blocks.create( +      name: name_of_copy(:compliance_control_blocks, source_block.name), +      condition_attributes: source_block.condition_attributes).tap do | target_block | +        relink_checks_to_block source_block, target_block  +      end +  end +  def copy_blocks +    source_set.compliance_control_blocks.order(:id).each(&method(:copy_block)) +  end +  def relink_checks_to_block source_block, target_block +    source_block +      .compliance_controls +      .order(:id) +      .each do | source_control | +        control_id_map[source_control.id] +          .update(compliance_control_block_id: target_block.id) +      end +  end + +  # Copy Controls: +  def copy_controls +    source_set.compliance_controls.order(:id).each(&method(:copy_control)) +  end +  def copy_control(compliance_control) +    target_set.compliance_controls.create( +      code: compliance_control.code, +      comment: compliance_control.comment, +      control_attributes: compliance_control.control_attributes, +      criticity: compliance_control.criticity, +      name: name_of_copy(:compliance_controls, compliance_control.name), +      origin_code: compliance_control.origin_code, +      target: compliance_control.target, +      type: compliance_control.type +    ).tap do | control | +      control_id_map.update compliance_control.id => control +    end +  end + +  def name_of_copy resource, name +    [I18n.t("#{resource}.clone.prefix"), name].join(' ') +  end + +  # Lazy Values +  # ----------- +  def organisation +    @__organisation__ ||= Organisation.find(organisation_id) +  end +  def source_set +    @__source_set__ ||= ComplianceControlSet.find(source_set_id) +  end +  def target_set +    @__target_set__ ||= ComplianceControlSet.create!( +      organisation: organisation, +      name: name_of_copy(:compliance_control_sets, source_set.name) +    ) +  end +  def control_id_map +    # Map: compliance_control_id -> compliance_control (origin_id -> copied object) +    @__control_id_to_check__ ||= Hash.new +  end +  def referential +    @__referential__ ||= Referential.find(referential_id) +  end +end diff --git a/spec/lib/compliance_control_set_cloner_spec.rb b/spec/lib/compliance_control_set_cloner_spec.rb new file mode 100644 index 000000000..4305ec70b --- /dev/null +++ b/spec/lib/compliance_control_set_cloner_spec.rb @@ -0,0 +1,144 @@ +RSpec.describe ComplianceControlSetCloner do + +  subject{ described_class.new } + +  let( :new_organisation ){ create :organisation } + +  let( :source_set ){ create :compliance_control_set } +  let( :set_prefix ){ I18n.t('compliance_control_sets.clone.prefix') } +  let( :block_prefix ){ I18n.t('compliance_control_blocks.clone.prefix') } +  let( :control_prefix ){ I18n.t('compliance_controls.clone.prefix') } + + +  context 'Copying empty set' do + +    context 'correct organisation' do + +      # +      # +      # +      # +      # +      # +      # +      #                                                 +-------------------+ +      #          +---------------------+----------------| Control (direct0) | +      #          |                     |                +-------------------+ +      #          |                     | +      #          |                     |                +-------------------+ +      #          +---------------------)------------+---| Control (direct1) | +      #          |                     |            |   +-------------------+ +      #          |                     |            | +      #          |                     |            |   +-------------------+ +      #          +---------------------)------------)---| Control (direct2) | +      #          |                     |            |   +-------------------+ +      #          |                     |            | +      #          |                     |            | +      #          |                     |            | +      #          v                     v            | +      #   +------------+          +--------------+  |  +---------------------+ +      #   | ControlSet |<----+----| ControlBlock |<-)--| Control (indirect0) | +      #   +------------+     |    +--------------+  |  +---------------------+ +      #                      |                      | +      #                      |    +--------------+<-+  +---------------------+ +      #                      |<---| ControlBlock |<----| Control (indirect1) | +      #                      |    +--------------+     +---------------------+ +      #                      | +      #                      |    +--------------+     +---------------------+ +      #                      +----| ControlBlock |<----| Control (indirect2) | +      #                           +--------------+     +---------------------+ + +      context 'Directed Acyclic Graph is copied correctly' do +        let(:source_blox){ +          3.times.map{ |_| create :compliance_control_block, compliance_control_set: source_set } +        } +        let(:direct_ccs){ +          3.times.map{ |n| create :generic_attribute_control_min_max, compliance_control_set: source_set, name: "direct #{n.succ}", code: "direct-#{n.succ}" } +        } +        # Needed to check we do not dulicate a node (compliance_control) twice +        let(:indirect_ccs){ +          # Create 1 child for each block and also associate first of the direct ccs to the first block +          #                                                  seconf of the direct css to the second block +          source_blox.take(2).zip(direct_ccs.take(2)).each do | source_block, cc | +            cc.update compliance_control_block_id: source_block.id +          end +          source_blox.each_with_index.map{ | source_block, n | +            create(:generic_attribute_control_min_max, compliance_control_set: source_set, compliance_control_block: source_block, name: "indirect #{n.succ}", code: "indirect-#{n.succ}") +          } +        } +        let( :sources ){ source_set.compliance_controls.order(:id) } + +        let( :target_set ){ ComplianceControlSet.last } +        let( :target_blox ){ ComplianceControlBlock.last 3 } +        let( :targets ){ target_set.compliance_controls.order(:id) } + +        before do +          direct_ccs +          indirect_ccs +        end +        it 'correctly creates a set for a complete DAG' do +          # Slowness of tests constrains us to create a minimum of objects in the DB, +          # hence only one example :( +          # +          #  Execute copy and keep count +          counts = object_counts +          subject.copy(source_set.id, new_organisation.id) +          delta  = count_diff counts, object_counts + +          # Check correctly copied set +          expect(target_set.organisation).to eq(new_organisation) +          expect(target_set.name).to eq( [set_prefix, source_set.name].join(' ') ) + +          # Check correctly copied controls +          targets.zip(sources).each do | target, source | +            expect( target.code ).to eq(source.code ) +            expect( target.comment ).to eq(source.comment ) +            expect( target.compliance_control_set ).to eq( target_set ) +            expect( target.control_attributes ).to eq(source.control_attributes) +            expect( target.criticity ).to eq(source.criticity ) +            expect( target.name ).to eq([control_prefix, source.name].join(' ')) +            expect( target.origin_code ).to eq(source.origin_code ) +            expect( target.type ).to eq(source.type) +          end +          # Check correctly copied blocks +          target_blox.zip(source_blox).each do | target_block, source_block | +            expect( target_block.compliance_control_set ).to eq(target_set) +            expect( target_block.name ).to eq( [block_prefix, source_block.name].join(' ') ) +            expect( target_block.condition_attributes ).to eq( source_block.condition_attributes ) +          end + +          # Check correct block associations +          # See diagram above to understand the meaning of this: +          #   - The first two controls have been assigned to the first two blocks accordingly +          #   - The third has no block +          #   - The last three controls have been created from the three blocks in order +          expected_block_ids = target_blox.take(2).map(&:id) + [ nil ] + target_blox.map(&:id) +          expect( targets.pluck(:compliance_control_block_id) ).to eq( expected_block_ids ) + +          # Check overall counts (no additional creations) +          expect( delta ).to eq(counts) +        end +      end + +    end + +    def object_counts +      { +        source_set_count: ComplianceControlSet.count, +        cc_block_count: ComplianceControlBlock.count, +        cc_count: ComplianceControl.count, +        cck_set_count: ComplianceCheckSet.count, +        cck_block_count: ComplianceCheckBlock.count, +        cck_count: ComplianceCheck.count +      } +    end + +    def count_diff count1, count2 +      count1.inject({}){ |h, (k,v)| +        h.merge( k => count2[k] - v ) +      } +    end + +  end + +end diff --git a/spec/models/compliance_control_validations/genric_attribute_validation/min_max_validation_spec.rb b/spec/models/compliance_control_validations/genric_attribute_validation/min_max_validation_spec.rb index 276d09a92..4d30d61e3 100644 --- a/spec/models/compliance_control_validations/genric_attribute_validation/min_max_validation_spec.rb +++ b/spec/models/compliance_control_validations/genric_attribute_validation/min_max_validation_spec.rb @@ -1,8 +1,8 @@ -# RSpec.describe GenericAttributeControl::MinMax do +RSpec.describe GenericAttributeControl::MinMax do -#   let( :factory ){ :generic_attribute_control_min_max } -#   subject{ build factory } +  let( :factory ){ :generic_attribute_control_min_max } +  subject{ build factory } -#   it_behaves_like 'has min_max_values' +  it_behaves_like 'has min_max_values' -# end +end diff --git a/spec/workers/clean_up_worker_spec.rb b/spec/workers/clean_up_worker_spec.rb index e85768fa3..fd767db00 100644 --- a/spec/workers/clean_up_worker_spec.rb +++ b/spec/workers/clean_up_worker_spec.rb @@ -1,4 +1,3 @@ -require 'rails_helper'  RSpec.describe CleanUpWorker, type: :worker do      pending "add some examples to (or delete) #{__FILE__}"  end diff --git a/spec/workers/compliance_control_set_cloning_worker_spec.rb b/spec/workers/compliance_control_set_cloning_worker_spec.rb new file mode 100644 index 000000000..3a2332f62 --- /dev/null +++ b/spec/workers/compliance_control_set_cloning_worker_spec.rb @@ -0,0 +1,15 @@ +RSpec.describe ComplianceControlSetCloningWorker do + + +  it 'is a worker' do +    expect( described_class.new ).to be_a(Sidekiq::Worker) +  end + +  it 'delegates perform to the correct lib call' do +    id = double('id') +    organisation_id = double('organisation_id') +    expect_any_instance_of(ComplianceControlSetCloner).to receive(:copy).with(id, organisation_id)  +    described_class.new.perform(id, organisation_id) +  end +   +end | 
