aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcedricnjanga2017-10-24 19:10:11 +0200
committercedricnjanga2017-10-24 19:10:11 +0200
commita8ae0d3bf9105b17bfdb6dc22f527140f8edb6dd (patch)
tree50bb2f3af42be39b85c17d070971b218c2b495a5
parent545671efd71380722dbc3272f5f513ea684891bd (diff)
parent2568e7f7553c82adb0bed1521ef9f013df9df87e (diff)
downloadchouette-core-a8ae0d3bf9105b17bfdb6dc22f527140f8edb6dd.tar.bz2
Merge branch 'master' into staging
-rw-r--r--Gemfile.lock5
-rw-r--r--INSTALL.md8
-rw-r--r--app/assets/stylesheets/components/_tables.sass10
-rw-r--r--app/controllers/api/v1/imports_controller.rb6
-rw-r--r--app/controllers/compliance_control_sets_controller.rb8
-rw-r--r--app/controllers/compliance_controls_controller.rb2
-rw-r--r--app/controllers/time_tables_controller.rb1
-rw-r--r--app/decorators/compliance_control_set_decorator.rb7
-rw-r--r--app/models/compliance_control.rb1
-rw-r--r--app/models/generic_attribute_control/min_max.rb1
-rw-r--r--app/models/generic_attribute_control/pattern.rb1
-rw-r--r--app/models/generic_attribute_control/uniqueness.rb1
-rw-r--r--app/models/import.rb1
-rw-r--r--app/models/route_control/minimum_length.rb1
-rw-r--r--app/models/route_control/opposite_route.rb1
-rw-r--r--app/models/vehicle_journey_control/time_table.rb1
-rw-r--r--app/models/vehicle_journey_control/vehicle_journey_at_stops.rb1
-rw-r--r--app/services/zip_service.rb18
-rw-r--r--app/views/compliance_control_blocks/edit.html.slim2
-rw-r--r--app/views/compliance_control_blocks/new.html.slim2
-rw-r--r--app/views/compliance_control_sets/edit.html.slim2
-rw-r--r--app/views/compliance_control_sets/index.html.slim2
-rw-r--r--app/views/compliance_control_sets/new.html.slim2
-rw-r--r--app/views/compliance_control_sets/show.html.slim12
-rw-r--r--app/views/compliance_controls/edit.html.slim2
-rw-r--r--app/views/compliance_controls/new.html.slim2
-rw-r--r--app/views/compliance_controls/select_type.html.slim2
-rw-r--r--app/views/compliance_controls/show.html.slim6
-rw-r--r--app/views/imports/_form.html.slim2
-rw-r--r--app/views/time_tables/_filter.html.slim2
-rw-r--r--app/workers/compliance_control_set_cloning_worker.rb8
-rw-r--r--app/workers/workbench_import_worker.rb31
-rw-r--r--config/initializers/relationship.rb17
-rw-r--r--config/locales/compliance_control_blocks.en.yml4
-rw-r--r--config/locales/compliance_control_blocks.fr.yml4
-rw-r--r--config/locales/compliance_control_sets.en.yml7
-rw-r--r--config/locales/compliance_control_sets.fr.yml7
-rw-r--r--config/locales/compliance_controls.en.yml2
-rw-r--r--config/locales/compliance_controls.fr.yml2
-rw-r--r--config/locales/import_messages.en.yml2
-rw-r--r--config/locales/import_messages.fr.yml2
-rw-r--r--config/locales/imports.en.yml4
-rw-r--r--config/locales/imports.fr.yml4
-rw-r--r--config/routes.rb1
-rw-r--r--db/schema.rb2
-rw-r--r--lib/compliance_control_set_cloner.rb93
-rw-r--r--lib/tasks/erd.rake4
-rw-r--r--spec/controllers/compliance_controls_controller_spec.rb2
-rw-r--r--spec/factories/imports.rb17
-rw-r--r--spec/factories/netex_imports.rb2
-rw-r--r--spec/factories/workbench_imports.rb2
-rw-r--r--spec/fixtures/OFFRE_WITH_EXTRA.zipbin0 -> 5586 bytes
-rw-r--r--spec/lib/compliance_control_set_cloner_spec.rb144
-rw-r--r--spec/models/compliance_control_validations/genric_attribute_validation/min_max_validation_spec.rb10
-rw-r--r--spec/models/import_spec.rb3
-rw-r--r--spec/services/zip_service/regression_4273_spec.rb59
-rw-r--r--spec/services/zip_service_spec.rb68
-rw-r--r--spec/support/random.rb6
-rw-r--r--spec/workers/clean_up_worker_spec.rb1
-rw-r--r--spec/workers/compliance_control_set_cloning_worker_spec.rb15
-rw-r--r--spec/workers/workbench_import/workbench_import_with_corrupt_zip_spec.rb48
-rw-r--r--spec/workers/workbench_import/workbench_import_worker_spec.rb (renamed from spec/workers/workbench_import_worker_spec.rb)45
62 files changed, 613 insertions, 115 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index da7c4f3c1..3acc27613 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -256,7 +256,8 @@ GEM
htmlbeautifier (1.3.1)
httparty (0.14.0)
multi_xml (>= 0.5.2)
- i18n (0.8.6)
+ i18n (0.9.0)
+ concurrent-ruby (~> 1.0)
i18n-tasks (0.9.15)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
@@ -380,7 +381,7 @@ GEM
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6)
rails-deprecated_sanitizer (>= 1.0.1)
- rails-erd (1.5.0)
+ rails-erd (1.5.2)
activerecord (>= 3.2)
activesupport (>= 3.2)
choice (~> 0.2.0)
diff --git a/INSTALL.md b/INSTALL.md
index 45eb030ff..45624e30b 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -19,6 +19,14 @@ Go into your local repro and install the gems
bundle
+## Node and Yarn
+
+Yarn needs a node version ≥ 6, if you use Node Version Manager [NVM](https://github.com/creationix/nvm) you can rely on the content of `.nvmrc`.
+
+Otherwise please make sure to use a compatible version, still best to use the same as indicated by `.nvrmc`.
+
+Then install yarn (`brew install yarn` does nicely on macOS).
+
### Installation Caveats
#### Node Related Issue, libv8
diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass
index 8fe7be374..178ec2f36 100644
--- a/app/assets/stylesheets/components/_tables.sass
+++ b/app/assets/stylesheets/components/_tables.sass
@@ -247,6 +247,16 @@
width: 35px
height: 35px
margin: 5px
+ &.with_text
+ width: initial
+ > a, > button
+ border-radius: 4%
+ text-decoration: none
+ span
+ &.fa
+ padding-left: 10px
+ span
+ padding: 0 10px 0 0
> a, > button
display: block
diff --git a/app/controllers/api/v1/imports_controller.rb b/app/controllers/api/v1/imports_controller.rb
index 6050418d8..3d7f4ca79 100644
--- a/app/controllers/api/v1/imports_controller.rb
+++ b/app/controllers/api/v1/imports_controller.rb
@@ -5,7 +5,11 @@ class Api::V1::ImportsController < Api::V1::IbooController
def create
args = workbench_import_params.merge(creator: 'Webservice')
@import = parent.workbench_imports.create(args)
- create!
+ if @import.valid?
+ create!
+ else
+ render json: { status: "error", messages: @import.errors.full_messages }
+ end
end
private
diff --git a/app/controllers/compliance_control_sets_controller.rb b/app/controllers/compliance_control_sets_controller.rb
index 9de90c21c..e5c2e19dd 100644
--- a/app/controllers/compliance_control_sets_controller.rb
+++ b/app/controllers/compliance_control_sets_controller.rb
@@ -23,6 +23,12 @@ class ComplianceControlSetsController < BreadcrumbController
end
end
+ def clone
+ ComplianceControlSetCloner.new.copy(params[:id], current_organisation.id)
+ flash[:notice] = I18n.t("compliance_control_sets.errors.operation_in_progress")
+ redirect_to(compliance_control_sets_path)
+ end
+
protected
def begin_of_association_chain
@@ -48,4 +54,4 @@ class ComplianceControlSetsController < BreadcrumbController
def compliance_control_set_params
params.require(:compliance_control_set).permit(:name, :id)
end
-end
+end \ No newline at end of file
diff --git a/app/controllers/compliance_controls_controller.rb b/app/controllers/compliance_controls_controller.rb
index e51d1eb74..0a9c7d52b 100644
--- a/app/controllers/compliance_controls_controller.rb
+++ b/app/controllers/compliance_controls_controller.rb
@@ -1,6 +1,7 @@
class ComplianceControlsController < BreadcrumbController
defaults resource_class: ComplianceControl
belongs_to :compliance_control_set
+ actions :all, :except => [:show, :index]
def select_type
@sti_subclasses = ComplianceControl.subclasses
@@ -15,7 +16,6 @@ class ComplianceControlsController < BreadcrumbController
end
def create
- puts build_resource.inspect
create! do |success, failure|
success.html { redirect_to compliance_control_set_path(parent) }
failure.html { render( :action => 'new' ) }
diff --git a/app/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb
index 2d24d5aa6..af74f635f 100644
--- a/app/controllers/time_tables_controller.rb
+++ b/app/controllers/time_tables_controller.rb
@@ -130,7 +130,6 @@ class TimeTablesController < ChouetteController
scope = select_time_tables
if params[:q] && params[:q]["tag_search"]
tags = params[:q]["tag_search"].reject {|c| c.empty?}
- params[:q].delete("tag_search")
scope = select_time_tables.tagged_with(tags, :any => true) if tags.any?
end
scope = self.ransack_period_range(scope: scope, error_message: t('referentials.errors.validity_period'), query: :overlapping)
diff --git a/app/decorators/compliance_control_set_decorator.rb b/app/decorators/compliance_control_set_decorator.rb
index f4aa607e1..7515316ce 100644
--- a/app/decorators/compliance_control_set_decorator.rb
+++ b/app/decorators/compliance_control_set_decorator.rb
@@ -4,6 +4,13 @@ class ComplianceControlSetDecorator < Draper::Decorator
def action_links
links = []
+ # if policy.clone?
+ links << Link.new(
+ content: h.t('actions.clone'),
+ href: h.clone_compliance_control_set_path(object.id)
+ )
+ # end
+
# if h.policy(object).destroy?
links << Link.new(
content: h.destroy_link_content,
diff --git a/app/models/compliance_control.rb b/app/models/compliance_control.rb
index baf491e8a..08efa7e9a 100644
--- a/app/models/compliance_control.rb
+++ b/app/models/compliance_control.rb
@@ -24,7 +24,6 @@ class ComplianceControl < ActiveRecord::Base
end
class << self
- def default_criticity; :warning end
def default_code; "" end
def dynamic_attributes
hstore_metadata_for_control_attributes.keys
diff --git a/app/models/generic_attribute_control/min_max.rb b/app/models/generic_attribute_control/min_max.rb
index a83ab64ab..ab6f546a7 100644
--- a/app/models/generic_attribute_control/min_max.rb
+++ b/app/models/generic_attribute_control/min_max.rb
@@ -9,7 +9,6 @@ module GenericAttributeControl
class << self
def attribute_type; :integer end
- def default_criticity; :warning end
def default_code; "3-Generic-2" end
end
end
diff --git a/app/models/generic_attribute_control/pattern.rb b/app/models/generic_attribute_control/pattern.rb
index 027d6948e..3a4a55d5c 100644
--- a/app/models/generic_attribute_control/pattern.rb
+++ b/app/models/generic_attribute_control/pattern.rb
@@ -7,7 +7,6 @@ module GenericAttributeControl
class << self
def attribute_type; :string end
- def default_criticity; :warning end
def default_code; "3-Generic-1" end
end
end
diff --git a/app/models/generic_attribute_control/uniqueness.rb b/app/models/generic_attribute_control/uniqueness.rb
index 36a270d74..f707c944b 100644
--- a/app/models/generic_attribute_control/uniqueness.rb
+++ b/app/models/generic_attribute_control/uniqueness.rb
@@ -6,7 +6,6 @@ module GenericAttributeControl
class << self
def attribute_type; :string end
- def default_criticity; :warning end
def default_code; "3-Generic-3" end
end
end
diff --git a/app/models/import.rb b/app/models/import.rb
index 64f713914..4ff8326ab 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -18,6 +18,7 @@ class Import < ActiveRecord::Base
validates :file, presence: true
validates_presence_of :workbench, :creator
+ validates_format_of :file, with: %r{\.zip\z}i, message: I18n.t('activerecord.errors.models.imports.wrong_file_extension')
before_create :initialize_fields
diff --git a/app/models/route_control/minimum_length.rb b/app/models/route_control/minimum_length.rb
index 56becfb2b..7aee4da21 100644
--- a/app/models/route_control/minimum_length.rb
+++ b/app/models/route_control/minimum_length.rb
@@ -1,5 +1,6 @@
module RouteControl
class MinimumLength < ComplianceControl
+ enumerize :criticity, in: %i(error), scope: true, default: :error
def self.default_code; "3-Route-6" end
end
diff --git a/app/models/route_control/opposite_route.rb b/app/models/route_control/opposite_route.rb
index 0148087ca..3921bb0a2 100644
--- a/app/models/route_control/opposite_route.rb
+++ b/app/models/route_control/opposite_route.rb
@@ -1,5 +1,6 @@
module RouteControl
class OppositeRoute < ComplianceControl
+ enumerize :criticity, in: %i(error), scope: true, default: :error
def self.default_code; "3-Route-2" end
end
diff --git a/app/models/vehicle_journey_control/time_table.rb b/app/models/vehicle_journey_control/time_table.rb
index 617ead12b..1e3166693 100644
--- a/app/models/vehicle_journey_control/time_table.rb
+++ b/app/models/vehicle_journey_control/time_table.rb
@@ -1,5 +1,6 @@
module VehicleJourneyControl
class TimeTable < ComplianceControl
+ enumerize :criticity, in: %i(error), scope: true, default: :error
def self.default_code; "3-VehicleJourney-4" end
end
diff --git a/app/models/vehicle_journey_control/vehicle_journey_at_stops.rb b/app/models/vehicle_journey_control/vehicle_journey_at_stops.rb
index 47ee26f82..392e7764f 100644
--- a/app/models/vehicle_journey_control/vehicle_journey_at_stops.rb
+++ b/app/models/vehicle_journey_control/vehicle_journey_at_stops.rb
@@ -1,5 +1,6 @@
module VehicleJourneyControl
class VehicleJourneyAtStops < ComplianceControl
+ enumerize :criticity, in: %i(error), scope: true, default: :error
def self.default_code; "3-VehicleJourney-5" end
end
diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb
index cab301b01..7a4bdad1b 100644
--- a/app/services/zip_service.rb
+++ b/app/services/zip_service.rb
@@ -1,10 +1,9 @@
class ZipService
- # TODO: Remove me before merge https://github.com/rubyzip/rubyzip
- class Subdir < Struct.new(:name, :stream)
+ class Subdir < Struct.new(:name, :stream, :spurious)
end
- attr_reader :current_key, :current_output, :yielder
+ attr_reader :current_key, :current_output, :current_spurious, :yielder
def initialize data
@zip_data = StringIO.new(data)
@@ -36,6 +35,7 @@ class ZipService
end
def add_to_current_output entry
+ return if is_spurious! entry.name
current_output.put_next_entry entry.name
write_to_current_output entry.get_input_stream
end
@@ -51,7 +51,8 @@ class ZipService
@yielder << Subdir.new(
current_key,
# Second part of the solution, yield the closed stream
- current_output.close_buffer)
+ current_output.close_buffer,
+ current_spurious)
end
end
@@ -59,10 +60,19 @@ class ZipService
@current_key = entry_key
# First piece of the solution, use internal way to create a Zip::OutputStream
@current_output = Zip::OutputStream.new(StringIO.new(''), true, nil)
+ @current_spurious = []
end
def entry_key entry
# last dir name File.dirname.split("/").last
entry.name.split('/', -1)[-2]
end
+
+ def is_spurious! entry_name
+ segments = entry_name.split('/', 3)
+ return false if segments.size < 3
+
+ current_spurious << segments.second
+ return true
+ end
end
diff --git a/app/views/compliance_control_blocks/edit.html.slim b/app/views/compliance_control_blocks/edit.html.slim
index 0ac507ece..637bb7311 100644
--- a/app/views/compliance_control_blocks/edit.html.slim
+++ b/app/views/compliance_control_blocks/edit.html.slim
@@ -1,5 +1,5 @@
/ PageHeader
-= pageheader 'modele-calendrier',
+= pageheader 'jeux-de-controle',
t('compliance_control_blocks.edit.title', compliance_control_block: @compliance_control_block.id)
diff --git a/app/views/compliance_control_blocks/new.html.slim b/app/views/compliance_control_blocks/new.html.slim
index 654a0dc7f..49404c552 100644
--- a/app/views/compliance_control_blocks/new.html.slim
+++ b/app/views/compliance_control_blocks/new.html.slim
@@ -1,5 +1,5 @@
/ PageHeader
-= pageheader 'modele-calendrier',
+= pageheader 'jeux-de-controle',
t('compliance_control_blocks.new.title')
diff --git a/app/views/compliance_control_sets/edit.html.slim b/app/views/compliance_control_sets/edit.html.slim
index 934bd81b0..649154b91 100644
--- a/app/views/compliance_control_sets/edit.html.slim
+++ b/app/views/compliance_control_sets/edit.html.slim
@@ -1,5 +1,5 @@
/ PageHeader
-= pageheader 'modele-calendrier',
+= pageheader 'jeux-de-controle',
t('compliance_control_sets.edit.title', name: @compliance_control_set.name)
/ PageContent
diff --git a/app/views/compliance_control_sets/index.html.slim b/app/views/compliance_control_sets/index.html.slim
index 68173fee9..1120ed186 100644
--- a/app/views/compliance_control_sets/index.html.slim
+++ b/app/views/compliance_control_sets/index.html.slim
@@ -1,5 +1,5 @@
/ PageHeader
-- header_params = ['jeux-de-donnees',
+- header_params = ['jeux-de-controle',
t('compliance_control_sets.index.title'),
'']
- header_params << link_to(t('compliance_control_sets.actions.new'), new_compliance_control_set_path, class: 'btn btn-default') if policy(Calendar).create?
diff --git a/app/views/compliance_control_sets/new.html.slim b/app/views/compliance_control_sets/new.html.slim
index d6be41ee8..35654b4d6 100644
--- a/app/views/compliance_control_sets/new.html.slim
+++ b/app/views/compliance_control_sets/new.html.slim
@@ -1,5 +1,5 @@
/ PageHeader
-= pageheader 'modele-calendrier',
+= pageheader 'jeux-de-controle',
t('compliance_control_sets.index.new')
diff --git a/app/views/compliance_control_sets/show.html.slim b/app/views/compliance_control_sets/show.html.slim
index b6e203a9e..f45b0227a 100644
--- a/app/views/compliance_control_sets/show.html.slim
+++ b/app/views/compliance_control_sets/show.html.slim
@@ -1,5 +1,5 @@
/ PageHeader
-= pageheader 'jeux-de-donnees',
+= pageheader 'jeux-de-controle',
t('compliance_control_sets.show.title', name: @compliance_control_set.name),
'Lorem ipsum dolor sit amet'
@@ -96,9 +96,13 @@
cls: 'table has-filter has-search'
.select_toolbox
ul
- li.st_action
+ li.st_action.with_text
= link_to select_type_compliance_control_set_compliance_controls_path(@compliance_control_set.id)
span.fa.fa-plus
- li.st_action
+ span
+ = t('compliance_control_sets.actions.add_compliance_control')
+ li.st_action.with_text
= link_to new_compliance_control_set_compliance_control_block_path(@compliance_control_set.id)
- span.fa.fa-plus-square
+ span.fa.fa-plus
+ span
+ = t('compliance_control_sets.actions.add_compliance_control_block')
diff --git a/app/views/compliance_controls/edit.html.slim b/app/views/compliance_controls/edit.html.slim
index d7497c0e2..1d478e845 100644
--- a/app/views/compliance_controls/edit.html.slim
+++ b/app/views/compliance_controls/edit.html.slim
@@ -1,4 +1,4 @@
-= pageheader 'compliance-control',
+= pageheader 'jeux-de-controle',
t('compliance_controls.edit.title')
diff --git a/app/views/compliance_controls/new.html.slim b/app/views/compliance_controls/new.html.slim
index 962f70ecc..181f49a15 100644
--- a/app/views/compliance_controls/new.html.slim
+++ b/app/views/compliance_controls/new.html.slim
@@ -1,5 +1,5 @@
/ PageHeader
-- header_params = ['jeux-de-donnees',
+- header_params = ['jeux-de-controle',
t('compliance_controls.new.title'),
'']
= pageheader(*header_params) do
diff --git a/app/views/compliance_controls/select_type.html.slim b/app/views/compliance_controls/select_type.html.slim
index c6e3b0427..98cc5a943 100644
--- a/app/views/compliance_controls/select_type.html.slim
+++ b/app/views/compliance_controls/select_type.html.slim
@@ -1,5 +1,5 @@
/ PageHeader
-- header_params = ['jeux-de-donnees',
+- header_params = ['jeux-de-controle',
t('compliance_controls.select_type.title'),
'']
= pageheader(*header_params) do
diff --git a/app/views/compliance_controls/show.html.slim b/app/views/compliance_controls/show.html.slim
index 8232dbe28..12a28cd4b 100644
--- a/app/views/compliance_controls/show.html.slim
+++ b/app/views/compliance_controls/show.html.slim
@@ -1,7 +1,9 @@
/ PageHeader
-= pageheader 'jeux-de-donnees',
+= pageheader 'jeux-de-controle',
t('compliance_controls.show.title'),
- ''
+ '',
+ link_to(t('actions.edit'), edit_compliance_control_set_compliance_control_path(params[:compliance_control_set_id], params[:id]), class: 'btn btn-default') do
+
/ PageContent
.page_content
.container-fluid
diff --git a/app/views/imports/_form.html.slim b/app/views/imports/_form.html.slim
index 0fbf578be..95d97c534 100644
--- a/app/views/imports/_form.html.slim
+++ b/app/views/imports/_form.html.slim
@@ -9,6 +9,6 @@
.form-group
= form.label :file, t('activerecord.attributes.import.resources'), class: 'control-label col-sm-4 col-xs-5'
.col-sm-8.col-xs-7
- = form.input_field :file, label: false, class: 'form-control'
+ = form.input :file, label: false, class: 'form-control'
= form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'wb_import_form'
diff --git a/app/views/time_tables/_filter.html.slim b/app/views/time_tables/_filter.html.slim
index 2672f7dfc..11e9987c4 100644
--- a/app/views/time_tables/_filter.html.slim
+++ b/app/views/time_tables/_filter.html.slim
@@ -9,7 +9,7 @@
.ffg-row
.form-group
= f.label Chouette::TimeTable.human_attribute_name(:tag_search), required: false, class: 'control-label'
- = f.input :tag_search, as: :tags, collection: Chouette::TimeTable.tags_on(:tags).pluck(:name), label: false, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': 'Indiquez une étiquette...' }, wrapper_html: { class: 'select2ed'}, include_blank: false
+ = f.input :tag_search, as: :tags, collection: Chouette::TimeTable.tags_on(:tags).pluck(:name), label: false, input_html: { 'data-select2ed': 'true', 'data-select2ed-placeholder': 'Indiquez une étiquette...' }, wrapper_html: { class: 'select2ed'}, include_blank: false, selected: params[:q] ? params[:q]['tag_search'] : nil
.form-group.togglable
= f.label Chouette::TimeTable.human_attribute_name(:bounding_dates), required: false, class: 'control-label'
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/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb
index 994493944..300fad9e2 100644
--- a/app/workers/workbench_import_worker.rb
+++ b/app/workers/workbench_import_worker.rb
@@ -14,11 +14,13 @@ class WorkbenchImportWorker
zip_service = ZipService.new(downloaded)
upload zip_service
@workbench_import.update(ended_at: Time.now)
+ rescue Zip::Error
+ handle_corrupt_zip_file
end
def download
logger.info "HTTP GET #{import_url}"
- @zipfile_data = HTTPService.get_resource(
+ HTTPService.get_resource(
host: import_host,
path: import_path,
params: {token: @workbench_import.token_download}).body
@@ -32,6 +34,10 @@ class WorkbenchImportWorker
params: params(eg_file, eg_name))
end
+ def handle_corrupt_zip_file
+ @workbench_import.messages.create(criticity: :error, message_key: 'corrupt_zip_file', message_attributes: {import_name: @workbench_import.name})
+ end
+
def upload zip_service
entry_group_streams = zip_service.subdirs
@workbench_import.update total_steps: entry_group_streams.size
@@ -42,11 +48,24 @@ class WorkbenchImportWorker
raise
end
- def upload_entry_group entry_pair, element_count
- @workbench_import.update( current_step: element_count.succ )
- # status = retry_service.execute(&upload_entry_group_proc(entry_pair))
- eg_name = entry_pair.name
- eg_stream = entry_pair.stream
+ def update_object_state entry, count
+ @workbench_import.update( current_step: count )
+ unless entry.spurious.empty?
+ @workbench_import.messages.create(
+ criticity: :warning,
+ message_key: 'inconsistent_zip_file',
+ message_attributes: {
+ 'import_name' => @workbench_import.name,
+ 'spurious_dirs' => entry.spurious.join(', ')
+ })
+ end
+ end
+
+ def upload_entry_group entry, element_count
+ update_object_state entry, element_count.succ
+ # status = retry_service.execute(&upload_entry_group_proc(entry))
+ eg_name = entry.name
+ eg_stream = entry.stream
FileUtils.mkdir_p(Rails.root.join('tmp', 'imports'))
diff --git a/config/initializers/relationship.rb b/config/initializers/relationship.rb
new file mode 100644
index 000000000..492aa627f
--- /dev/null
+++ b/config/initializers/relationship.rb
@@ -0,0 +1,17 @@
+if Rails.env.development?
+ require 'rails_erd/domain/relationship'
+
+ module RailsERD
+ class Domain
+ class Relationship
+ class << self
+ private
+
+ def association_identity(association)
+ Set[association_owner(association), association_target(association)]
+ end
+ end
+ end
+ end
+ 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..7361edacf 100644
--- a/config/locales/compliance_control_sets.en.yml
+++ b/config/locales/compliance_control_sets.en.yml
@@ -1,5 +1,9 @@
en:
compliance_control_sets:
+ clone:
+ prefix: 'Copy of'
+ errors:
+ operation_in_progress: "The clone operation is in progress. Please wait and refresh the page in a few moments"
index:
title: Compliance control set
new: New compliance control set
@@ -11,7 +15,8 @@ en:
edit: Edit
show: Show
destroy: Destroy
- add_compliance_control: Add a compliance control
+ add_compliance_control: Compliance Control
+ add_compliance_control_block: Compliance Control Block
destroy_confirm: Are you sur ?
filters:
name: 'Enter name ...'
diff --git a/config/locales/compliance_control_sets.fr.yml b/config/locales/compliance_control_sets.fr.yml
index 37851d7c4..19f6f08ee 100644
--- a/config/locales/compliance_control_sets.fr.yml
+++ b/config/locales/compliance_control_sets.fr.yml
@@ -1,5 +1,9 @@
fr:
compliance_control_sets:
+ clone:
+ prefix: 'Copie de'
+ errors:
+ operation_in_progress: "L'opération de clone est en cours. Veuillez patienter et raffraichir la page dans quelques instants"
index:
title: "Liste des jeux de contrôles"
edit:
@@ -13,7 +17,8 @@ fr:
edit: Editer
show: Consulter
destroy: Supprimer
- add_compliance_control: Ajouter un JDC
+ add_compliance_control: Contrôle
+ add_compliance_control_block: Groupe de contrôles
loaded: Charger le contrôle
destroy_confirm: Etes vous sûr de supprimer ce jeux de contrôle ?
filters:
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/config/locales/import_messages.en.yml b/config/locales/import_messages.en.yml
index 4009d7c77..528ab3477 100644
--- a/config/locales/import_messages.en.yml
+++ b/config/locales/import_messages.en.yml
@@ -1,6 +1,8 @@
en:
import_messages:
compliance_check_messages:
+ corrupt_zip_file: "The zip file of WorkbenchImport %{import_name} is corrupted and cannot be read"
+ inconsistent_zip_file: "The zip file of WorkbenchImport %{import_name} contains the following spurious directories %{spurious_dirs}, which are ignored"
referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les même périodes et lignes"
1_netexstif_2: "Le fichier %{source_filename} ne respecte pas la syntaxe XML ou la XSD NeTEx : erreur '%{error_value}' rencontré"
1_netexstif_5: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet %{source_label} d'identifiant %{source_objectid} a une date de mise à jour dans le futur"
diff --git a/config/locales/import_messages.fr.yml b/config/locales/import_messages.fr.yml
index 085299bb4..15de6eed8 100644
--- a/config/locales/import_messages.fr.yml
+++ b/config/locales/import_messages.fr.yml
@@ -1,6 +1,8 @@
fr:
import_messages:
compliance_check_messages:
+ corrupt_zip_file: "Le fichier zip du WorkbenchImport %{import_name} est corrompu, et ne peut être lu"
+ inconsistent_zip_file: "Le fichier zip du WorkbenchImport %{import_name} contient les repertoirs illegeaux %{spurious_dirs} qui seront ignorés"
referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les même périodes et lignes"
1_netexstif_2: "Le fichier %{source_filename} ne respecte pas la syntaxe XML ou la XSD NeTEx : erreur '%{error_value}' rencontré"
1_netexstif_5: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet %{source_label} d'identifiant %{source_objectid} a une date de mise à jour dans le futur"
diff --git a/config/locales/imports.en.yml b/config/locales/imports.en.yml
index 9bf877c86..f3bcad9e9 100644
--- a/config/locales/imports.en.yml
+++ b/config/locales/imports.en.yml
@@ -53,6 +53,10 @@ en:
zero: "import"
one: "NeTEx import"
other: "imports"
+ errors:
+ models:
+ imports:
+ wrong_file_extension: "The imported file must be a zip file"
attributes:
import:
resources: "File to import"
diff --git a/config/locales/imports.fr.yml b/config/locales/imports.fr.yml
index 6998c89d2..6e74fa33c 100644
--- a/config/locales/imports.fr.yml
+++ b/config/locales/imports.fr.yml
@@ -53,6 +53,10 @@ fr:
zero: "import"
one: "import NeTEx"
other: "imports"
+ errors:
+ models:
+ imports:
+ wrong_file_extension: "Le fichier importé doit être au format zip"
attributes:
import:
resources: "Fichier à importer"
diff --git a/config/routes.rb b/config/routes.rb
index b9e318f91..b105e77d6 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -74,6 +74,7 @@ ChouetteIhm::Application.routes.draw do
resources :api_keys, :only => [:edit, :update, :new, :create, :destroy]
resources :compliance_control_sets do
+ get :clone, on: :member
resources :compliance_controls, except: :index do
get :select_type, on: :collection
end
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..12e1eccb5
--- /dev/null
+++ b/lib/compliance_control_set_cloner.rb
@@ -0,0 +1,93 @@
+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,
+ 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/lib/tasks/erd.rake b/lib/tasks/erd.rake
index 6b79967de..e2665374e 100644
--- a/lib/tasks/erd.rake
+++ b/lib/tasks/erd.rake
@@ -7,9 +7,9 @@ namespace :generate do
sh "bundle exec rake erd only='Organisation,StopAreaReferential,StopAreaReferentialSync,StopAreaReferentialSyncMessage,StopAreaReferentialMembership,LineReferential,LineReferentialSync,LineReferentialSyncMessage,LineReferentialMembership' filename='referentiels_externes' title='Référentiels externes'"
sh "bundle exec rake erd only='NetexImport,Import,WorkbenchImport,ImportResource,ImportMessage' filename='import' title='Import'"
sh "bundle exec rake erd only='ComplianceControlSet,ComplianceControlBlock,ComplianceControl,ComplianceCheckSet,ComplianceCheckBlock,ComplianceCheck,ComplianceCheckResource,ComplianceCheckMessage' filename='validation' title='Validation'"
+ sh "bundle exec rake erd only='Organisation,Workbench,ReferentialSuite,Referential' filename='merge' title='Merge'"
#sh "bundle exec rake erd only='VehicleJourney,VehicleJourneyExport' filename='export' title='Export'"
- #sh "bundle exec rake erd only='' filename='intégration' title='Integration'"
- #sh "bundle exec rake erd only='' filename='fusion' title='Fusion'"
+ #sh "bundle exec rake erd only='' filename='integration' title='Integration'"
#sh "bundle exec rake erd only='' filename='publication' title='Publication'"
end
diff --git a/spec/controllers/compliance_controls_controller_spec.rb b/spec/controllers/compliance_controls_controller_spec.rb
index 34b27530d..61e94025d 100644
--- a/spec/controllers/compliance_controls_controller_spec.rb
+++ b/spec/controllers/compliance_controls_controller_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe ComplianceControlsController, type: :controller do
describe 'POST #update' do
it 'should be successful' do
post :update, compliance_control_set_id: compliance_control_set.id, id: compliance_control.id, compliance_control: compliance_control.as_json.merge(type: 'GenericAttributeControl::MinMax')
- expect(response).to redirect_to compliance_control_set_compliance_control_path(compliance_control_set, compliance_control)
+ expect(response).to redirect_to compliance_control_set_path(compliance_control_set)
end
end
diff --git a/spec/factories/imports.rb b/spec/factories/imports.rb
index 2c53106c3..e07447b60 100644
--- a/spec/factories/imports.rb
+++ b/spec/factories/imports.rb
@@ -5,6 +5,23 @@ FactoryGirl.define do
current_step_progress 1.5
association :workbench
association :referential
+ file {File.open(File.join(Rails.root, 'spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip'))}
+ status :new
+ started_at nil
+ ended_at nil
+ creator 'rspec'
+
+ after(:build) do |import|
+ import.class.skip_callback(:create, :before, :initialize_fields)
+ end
+ end
+
+ factory :bad_import do
+ sequence(:name) { |n| "Import #{n}" }
+ current_step_id "MyString"
+ current_step_progress 1.5
+ association :workbench
+ association :referential
file {File.open(File.join(Rails.root, 'spec', 'fixtures', 'terminated_job.json'))}
status :new
started_at nil
diff --git a/spec/factories/netex_imports.rb b/spec/factories/netex_imports.rb
index 057e47730..9e9d836e4 100644
--- a/spec/factories/netex_imports.rb
+++ b/spec/factories/netex_imports.rb
@@ -1,5 +1,5 @@
FactoryGirl.define do
factory :netex_import, class: NetexImport, parent: :import do
- file { File.open(Rails.root.join('spec', 'fixtures', 'terminated_job.json')) }
+ file { File.open(Rails.root.join('spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip')) }
end
end
diff --git a/spec/factories/workbench_imports.rb b/spec/factories/workbench_imports.rb
index 5cdcfd15f..466bfe688 100644
--- a/spec/factories/workbench_imports.rb
+++ b/spec/factories/workbench_imports.rb
@@ -1,5 +1,5 @@
FactoryGirl.define do
factory :workbench_import, class: WorkbenchImport, parent: :import do
- file { File.open(Rails.root.join('spec', 'fixtures', 'terminated_job.json')) }
+ file { File.open(Rails.root.join('spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip')) }
end
end
diff --git a/spec/fixtures/OFFRE_WITH_EXTRA.zip b/spec/fixtures/OFFRE_WITH_EXTRA.zip
new file mode 100644
index 000000000..97ea3f513
--- /dev/null
+++ b/spec/fixtures/OFFRE_WITH_EXTRA.zip
Binary files differ
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/models/import_spec.rb b/spec/models/import_spec.rb
index cd5a30982..c06d05dab 100644
--- a/spec/models/import_spec.rb
+++ b/spec/models/import_spec.rb
@@ -10,6 +10,9 @@ RSpec.describe Import, type: :model do
it { should validate_presence_of(:workbench) }
it { should validate_presence_of(:creator) }
+ it { should allow_value('file.zip').for(:file).with_message(I18n.t('activerecord.errors.models.imports.wrong_file_extension')) }
+ it { should_not allow_values('file.json', 'file.png', 'file.pdf').for(:file) }
+
let(:workbench_import) { build_stubbed(:workbench_import) }
let(:workbench_import_with_completed_steps) do
workbench_import = build_stubbed(
diff --git a/spec/services/zip_service/regression_4273_spec.rb b/spec/services/zip_service/regression_4273_spec.rb
deleted file mode 100644
index 4fe0f6539..000000000
--- a/spec/services/zip_service/regression_4273_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-RSpec.describe ZipService do
- describe 'Regression Issue # 4273 https://projects.af83.io/issues/4273' do
- let( :zip_service ){ described_class }
- let( :unzipper ){ zip_service.new(zip_data) }
- let( :zip_data ){ File.read zip_file }
-
- context 'real test data' do
- let( :subdir_names ){ %w<OFFRE_TRANSDEV_20170301122517 OFFRE_TRANSDEV_20170301122519> }
- let( :expected_chksums ){
- checksum_trees( subdir_names.map{ |sn| subdir_file(sn, prefix: 'source_') } )
- }
-
- let( :zip_file ){ fixtures_path 'OFFRE_TRANSDEV_2017030112251.zip' }
- #
- # Remove potential test artefacts
- before do
- subdir_names.each do | subdir_name |
- File.unlink( subdir_file subdir_name, suffix: '.zip' ) rescue nil
- Dir.unlink( subdir_file subdir_name ) rescue nil
- end
- end
-
- it "yields the correct content" do
- subdir_contents = {}
- # Write ZipService Streams to files and inflate them to file system
- unzipper.subdirs.each do | subdir |
- File.open(subdir_file( subdir.name, suffix: '.zip' ), 'wb'){ |f| f.write subdir.stream.string }
- unzip_subdir subdir
- end
- # Represent the inflated file_system as a checksum tree
- actual_checksums =
- checksum_trees( subdir_names.map{ |sn| subdir_file(sn, prefix: 'target/') } )
- expect( actual_checksums ).to eq( expected_chksums )
- end
-
- end
-
- end
-
- def checksum_trees *dirs
- dirs.flatten.inject({},&method(:checksum_tree))
- end
- def checksum_tree repr, dir
- Dir.glob("#{dir}/**/*").each do |file|
- if !File.directory?(file)
- repr.merge!( File.basename(file) => %x{cksum #{file}}.split.first ){ |_, ov, nv| Array(ov) << nv }
- end
- end
- repr
- end
-
- def subdir_file( subdir, prefix: 'target_', suffix: '' )
- fixtures_path("#{prefix}#{subdir}#{suffix}")
- end
-
- def unzip_subdir subdir
- %x{unzip -oqq #{subdir_file subdir.name, suffix: '.zip'} -d #{fixture_path}/target}
- end
-end
diff --git a/spec/services/zip_service_spec.rb b/spec/services/zip_service_spec.rb
new file mode 100644
index 000000000..98cb9026d
--- /dev/null
+++ b/spec/services/zip_service_spec.rb
@@ -0,0 +1,68 @@
+RSpec.describe ZipService do
+
+ let( :zip_service ){ described_class }
+ let( :unzipper ){ zip_service.new(zip_data) }
+ let( :zip_data ){ File.read zip_file }
+
+
+ context 'correct test data' do
+ before do
+ subdir_names.each do | subdir_name |
+ File.unlink( subdir_file subdir_name, suffix: '.zip' ) rescue nil
+ Dir.unlink( subdir_file subdir_name ) rescue nil
+ end
+ end
+ let( :subdir_names ){ %w<OFFRE_TRANSDEV_20170301122517 OFFRE_TRANSDEV_20170301122519> }
+ let( :expected_chksums ){
+ checksum_trees( subdir_names.map{ |sn| subdir_file(sn, prefix: 'source_') } )
+ }
+
+ let( :zip_file ){ fixtures_path 'OFFRE_TRANSDEV_2017030112251.zip' }
+ #
+ # Remove potential test artefacts
+
+ it 'yields the correct content' do
+ # Write ZipService Streams to files and inflate them to file system
+ unzipper.subdirs.each do | subdir |
+ expect( subdir.spurious ).to be_empty
+ File.open(subdir_file( subdir.name, suffix: '.zip' ), 'wb'){ |f| f.write subdir.stream.string }
+ unzip_subdir subdir
+ end
+ # Represent the inflated file_system as a checksum tree
+ actual_checksums =
+ checksum_trees( subdir_names.map{ |sn| subdir_file(sn, prefix: 'target/') } )
+ expect( actual_checksums ).to eq( expected_chksums )
+ end
+
+ end
+
+ context 'test data with spurious directories' do
+ let( :zip_file ){ fixtures_path 'OFFRE_WITH_EXTRA.zip' }
+
+ it 'returns the extra dir in the spurious field of the entry' do
+ expect( unzipper.subdirs.first.spurious ).to eq(%w{EXTRA})
+ end
+ end
+
+
+ def checksum_trees *dirs
+ dirs.flatten.inject({},&method(:checksum_tree))
+ end
+ def checksum_tree repr, dir
+ Dir.glob("#{dir}/**/*").each do |file|
+ if !File.directory?(file)
+ repr.merge!( File.basename(file) => %x{cksum #{file}}.split.first ){ |_, ov, nv| Array(ov) << nv }
+ end
+ end
+ repr
+ end
+
+ def subdir_file( subdir, prefix: 'target_', suffix: '' )
+ fixtures_path("#{prefix}#{subdir}#{suffix}")
+ end
+
+ def unzip_subdir subdir
+ %x{unzip -oqq #{subdir_file subdir.name, suffix: '.zip'} -d #{fixture_path}/target}
+ end
+end
+
diff --git a/spec/support/random.rb b/spec/support/random.rb
index 59e1a1475..0ebc2ee5e 100644
--- a/spec/support/random.rb
+++ b/spec/support/random.rb
@@ -22,6 +22,12 @@ module Support
def random_string
SecureRandom.urlsafe_base64
end
+
+ def very_random(veryness=3, joiner: '-')
+ raise ArgumentError, 'not very random' unless veryness > 1
+ veryness.times.map{ SecureRandom.uuid }.join(joiner)
+ end
+
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
diff --git a/spec/workers/workbench_import/workbench_import_with_corrupt_zip_spec.rb b/spec/workers/workbench_import/workbench_import_with_corrupt_zip_spec.rb
new file mode 100644
index 000000000..5e34b208a
--- /dev/null
+++ b/spec/workers/workbench_import/workbench_import_with_corrupt_zip_spec.rb
@@ -0,0 +1,48 @@
+RSpec.describe WorkbenchImportWorker do
+
+
+ shared_examples_for 'corrupt zipfile data' do
+ subject { described_class.new }
+ let( :workbench_import ){ create :workbench_import, status: :pending }
+
+ before do
+ # Let us make sure that the name Enterprise will never be forgotten by history,
+ # ahem, I meant, that nothing is uploaded, by forbidding any message to be sent
+ # to HTTPService
+ expect_it.to receive(:download).and_return(downloaded)
+ end
+
+ it 'does not upload' do
+ stub_const 'HTTPService', double('HTTPService')
+ subject.perform(workbench_import.id)
+ end
+
+ it 'does create a message' do
+ expect{ subject.perform(workbench_import.id) }.to change{ workbench_import.messages.count }.by(1)
+
+ message = workbench_import.messages.last
+ expect( message.criticity ).to eq('error')
+ expect( message.message_key ).to eq('corrupt_zip_file')
+ expect( message.message_attributes ).to eq( 'import_name' => workbench_import.name )
+ end
+
+ it 'does not change current step' do
+ expect{ subject.perform(workbench_import.id) }.not_to change{ workbench_import.current_step }
+ end
+
+ it "sets the workbench_import.status to failed" do
+ subject.perform(workbench_import.id)
+ expect( workbench_import.reload.status ).to eq('failed')
+ end
+ end
+
+ context 'empty zip file' do
+ let( :downloaded ){ '' }
+ it_should_behave_like 'corrupt zipfile data'
+ end
+
+ context 'corrupt data' do
+ let( :downloaded ){ very_random }
+ it_should_behave_like 'corrupt zipfile data'
+ end
+end
diff --git a/spec/workers/workbench_import_worker_spec.rb b/spec/workers/workbench_import/workbench_import_worker_spec.rb
index a349b3433..9f860a6b3 100644
--- a/spec/workers/workbench_import_worker_spec.rb
+++ b/spec/workers/workbench_import/workbench_import_worker_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe WorkbenchImportWorker, type: [:worker, :request] do
let( :workbench ){ import.workbench }
let( :referential ){ import.referential }
- let( :api_key ){ build_stubbed :api_key, referential: referential, token: "#{referential.id}-#{SecureRandom.hex}" }
+ let( :api_key ){ build_stubbed :api_key, referential: referential, token: "#{referential.id}-#{random_hex}" }
# http://www.example.com/workbenches/:workbench_id/imports/:id/download
let( :host ){ Rails.configuration.rails_host }
@@ -13,16 +13,17 @@ RSpec.describe WorkbenchImportWorker, type: [:worker, :request] do
let( :downloaded_zip ){ double("downloaded zip") }
let( :download_zip_response ){ OpenStruct.new( body: downloaded_zip ) }
- let( :download_token ){ SecureRandom.urlsafe_base64 }
-
+ let( :download_token ){ random_string }
let( :upload_path ) { api_v1_netex_imports_path(format: :json) }
+ let( :spurious ){ [[], [], []] }
let( :subdirs ) do
entry_count.times.map do |i|
ZipService::Subdir.new(
"subdir #{i}",
- double("subdir #{i}", rewind: 0, read: '')
+ double("subdir #{i}", rewind: 0, read: ''),
+ spurious[i]
)
end
end
@@ -104,8 +105,44 @@ RSpec.describe WorkbenchImportWorker, type: [:worker, :request] do
expect( import ).to receive(:update).with(current_step: 3, status: 'failed')
expect { worker.perform import.id }.to raise_error(StopIteration)
+ end
+ end
+
+ context 'multireferential zipfile with spurious directories' do
+ let( :entry_count ){ 2 }
+ let( :spurious1 ){ [random_string] }
+ let( :spurious2 ){ [random_string, random_string] }
+ let( :spurious ){ [spurious1, spurious2] }
+ let( :messages ){ double('messages') }
+ let( :message_attributes ){{criticity: :warning, message_key: 'inconsistent_zip_file'}}
+ let( :message1_attributes ){ message_attributes.merge(message_attributes: {'import_name' => import.name, 'spurious_dirs' => spurious1.inspect}) }
+ let( :message2_attributes ){ message_attributes.merge(message_attributes: {'import_name' => import.name, 'spurious_dirs' => spurious2.inspect}) }
+
+ before do
+ allow(import).to receive(:messages).and_return(messages)
+ end
+
+ it 'downloads a zip file, cuts it, and uploads all pieces and adds messages' do
+
+ expect(HTTPService).to receive(:get_resource)
+ .with(host: host, path: path, params: {token: download_token})
+ .and_return( download_zip_response )
+
+ subdirs.each do |subdir|
+ mock_post subdir, post_response_ok
+ end
+
+ expect( import ).to receive(:update).with(total_steps: 2)
+ expect( import ).to receive(:update).with(current_step: 1)
+ expect( messages ).to receive(:create).with(message1_attributes)
+ expect( import ).to receive(:update).with(current_step: 2)
+ expect( messages ).to receive(:create).with(message2_attributes)
+ expect( import ).to receive(:update).with(ended_at: Time.now)
+
+ worker.perform import.id
end
+
end
def mock_post subdir, response