aboutsummaryrefslogtreecommitdiffstats
path: root/spec/support
diff options
context:
space:
mode:
authorLuc Donnet2018-02-19 11:04:29 +0100
committerLuc Donnet2018-02-19 11:04:29 +0100
commit7b17deff51545358009cb417cbb9d796565e7540 (patch)
treea43a5586ad39d838dd607e600dbc15ff18a58ab3 /spec/support
parent89428163fc93a7e09ebb0ca47939f8558afeb5eb (diff)
parent5f6008d165df4499319a2121a71842657d6ac3c9 (diff)
downloadchouette-core-7b17deff51545358009cb417cbb9d796565e7540.tar.bz2
Merge branch 'master' into 0000-docker
Diffstat (limited to 'spec/support')
-rw-r--r--spec/support/checksum_support.rb67
-rw-r--r--spec/support/controller_spec_helper.rb33
-rw-r--r--spec/support/decorator_helpers.rb28
-rw-r--r--spec/support/helpers/tree_walker.rb15
-rw-r--r--spec/support/integration_spec_helper.rb78
-rw-r--r--spec/support/journey_pattern_helper.rb19
-rw-r--r--spec/support/permissions.rb1
-rw-r--r--spec/support/pundit/policies.rb5
-rw-r--r--spec/support/pundit/pundit_view_policy.rb27
-rw-r--r--spec/support/pundit/shared_examples.rb51
-rw-r--r--spec/support/referential.rb7
-rw-r--r--spec/support/shared_examples/compliance_control_validation.rb55
-rw-r--r--spec/support/snapshot_support.rb60
-rw-r--r--spec/support/zip_support.rb25
-rw-r--r--spec/support/zip_support/create_zip_data.rb70
15 files changed, 464 insertions, 77 deletions
diff --git a/spec/support/checksum_support.rb b/spec/support/checksum_support.rb
index 14ea3c55e..f8dffb1b7 100644
--- a/spec/support/checksum_support.rb
+++ b/spec/support/checksum_support.rb
@@ -1,25 +1,23 @@
-shared_examples 'checksum support' do |factory_name|
- let(:instance) { create(factory_name) }
-
+shared_examples 'checksum support' do
describe '#current_checksum_source' do
let(:attributes) { ['code_value', 'label_value'] }
- let(:seperator) { ChecksumSupport::SEPARATOR }
+ let(:separator) { ChecksumSupport::SEPARATOR }
let(:nil_value) { ChecksumSupport::VALUE_FOR_NIL_ATTRIBUTE }
before do
- allow_any_instance_of(instance.class).to receive(:checksum_attributes).and_return(attributes)
+ allow_any_instance_of(subject.class).to receive(:checksum_attributes).and_return(attributes)
end
- it 'should separate attribute by seperator' do
- expect(instance.current_checksum_source).to eq("code_value#{seperator}label_value")
+ it 'should separate attribute by separator' do
+ expect(subject.current_checksum_source).to eq("code_value#{separator}label_value")
end
context 'nil value' do
let(:attributes) { ['code_value', nil] }
it 'should replace nil attributes by default value' do
- source = "code_value#{seperator}#{nil_value}"
- expect(instance.current_checksum_source).to eq(source)
+ source = "code_value#{separator}#{nil_value}"
+ expect(subject.current_checksum_source).to eq(source)
end
end
@@ -27,27 +25,62 @@ shared_examples 'checksum support' do |factory_name|
let(:attributes) { ['code_value', []] }
it 'should convert to nil' do
- source = "code_value#{seperator}#{nil_value}"
- expect(instance.current_checksum_source).to eq(source)
+ source = "code_value#{separator}#{nil_value}"
+ expect(subject.current_checksum_source).to eq(source)
+ end
+ end
+
+ context 'array value' do
+ let(:attributes) { [['v1', 'v2', 'v3'], 'code_value'] }
+
+ it 'should convert to list' do
+ source = "v1,v2,v3#{separator}code_value"
+ expect(subject.current_checksum_source).to eq(source)
+ end
+ end
+
+ context 'array of array value' do
+ let(:attributes) { [[['a1', 'a2', 'a3'], ['b1', 'b2', 'b3']], 'code_value'] }
+
+ it 'should convert to list' do
+ source = "(a1,a2,a3),(b1,b2,b3)#{separator}code_value"
+ expect(subject.current_checksum_source).to eq(source)
+ end
+ end
+
+ context 'array of array value, with empty array' do
+ let(:attributes) { [[['a1', 'a2', 'a3'], []], 'code_value'] }
+
+ it 'should convert to list' do
+ source = "(a1,a2,a3),-#{separator}code_value"
+ expect(subject.current_checksum_source).to eq(source)
end
end
end
it 'should save checksum on create' do
- expect(instance.checksum).to_not be_nil
+ expect(subject.checksum).to_not be_nil
end
it 'should save checksum_source' do
- expect(instance.checksum_source).to_not be_nil
+ expect(subject.checksum_source).to_not be_nil
end
it 'should trigger set_current_checksum_source on save' do
- expect(instance).to receive(:set_current_checksum_source)
- instance.save
+ expect(subject).to receive(:set_current_checksum_source).at_least(:once)
+ subject.save
end
it 'should trigger update_checksum on save' do
- expect(instance).to receive(:update_checksum)
- instance.save
+ expect(subject).to receive(:update_checksum).at_least(:once)
+ subject.save
+ end
+
+ it "doesn't change the checksum on save if the source hasn't been changed" do
+ checksum = subject.checksum
+
+ subject.save
+
+ expect(subject.checksum).to eq(checksum)
end
end
diff --git a/spec/support/controller_spec_helper.rb b/spec/support/controller_spec_helper.rb
new file mode 100644
index 000000000..dbc7d582b
--- /dev/null
+++ b/spec/support/controller_spec_helper.rb
@@ -0,0 +1,33 @@
+module ControllerSpecHelper
+ def with_permission permission, &block
+ context "with permission #{permission}" do
+ login_user
+ before(:each) do
+ @user.permissions << permission
+ @user.save!
+ sign_in @user
+ end
+ context('', &block) if block_given?
+ end
+ end
+
+ def with_feature feature, &block
+ context "with feature #{feature}" do
+ login_user
+ before(:each) do
+ organisation = @user.organisation
+ unless organisation.has_feature?(feature)
+ organisation.features << feature
+ organisation.save!
+ end
+ sign_in @user
+ end
+ context('', &block) if block_given?
+ end
+ end
+
+end
+
+RSpec.configure do |config|
+ config.extend ControllerSpecHelper, type: :controller
+end
diff --git a/spec/support/decorator_helpers.rb b/spec/support/decorator_helpers.rb
index ffedd479b..b2c41e842 100644
--- a/spec/support/decorator_helpers.rb
+++ b/spec/support/decorator_helpers.rb
@@ -1,27 +1,35 @@
module Support
-
module DecoratorHelpers
def self.included(into)
into.instance_eval do
subject{ object.decorate }
let( :policy ){ ::Pundit.policy(user_context, object) }
let( :user_context ){ UserContext.new(user, referential: referential) }
-
+ let( :features ){ [] }
+ let( :filtered_action_links){}
before do
allow_any_instance_of(Draper::HelperProxy).to receive(:policy).and_return policy
+ allow_any_instance_of(AF83::Decorator::Link).to receive(:check_feature){|f|
+ features.include?(f)
+ }
end
end
end
- def expect_action_link_hrefs
- expect( subject.action_links.select(&Link.method(:===)).map(&:href) )
+ def expect_action_link_hrefs(action=:index)
+ if subject.action_links.is_a? AF83::Decorator::ActionLinks
+ expect( subject.action_links(action).map(&:href) )
+ else
+ expect( subject.action_links.select(&Link.method(:===)).map(&:href) )
+ end
end
- def expect_action_link_elements
- expect( subject.action_links.select(&HTMLElement.method(:===)).map(&:content) )
+
+ def expect_action_link_elements(action=:index)
+ if subject.action_links.is_a? AF83::Decorator::ActionLinks
+ expect( subject.action_links(action).map(&:content) )
+ else
+ expect( subject.action_links.select(&HTMLElement.method(:===)).map(&:content) )
+ end
end
end
end
-
-RSpec.configure do | c |
- c.include Support::DecoratorHelpers, type: :decorator
-end
diff --git a/spec/support/helpers/tree_walker.rb b/spec/support/helpers/tree_walker.rb
new file mode 100644
index 000000000..b86c3a8e1
--- /dev/null
+++ b/spec/support/helpers/tree_walker.rb
@@ -0,0 +1,15 @@
+module TreeWalker extend self
+ MAX_LEVEL = 5
+ def walk_tree path, max_level: MAX_LEVEL, level: 0, yield_dirs: :no, &blk
+ raise RuntimeError, "too many levels in tree walk, > #{max_level}" if level > max_level
+ Dir.glob(File.join(path, '*')) do | file |
+ if File.directory?( file )
+ blk.(:dir, file) if yield_dirs == :before
+ walk_tree(file, max_level: max_level, level: level.succ, yield_dirs: yield_dirs, &blk)
+ blk.(:dir, file) if yield_dirs == :after
+ else
+ blk.(:file, file)
+ end
+ end
+ end
+end
diff --git a/spec/support/integration_spec_helper.rb b/spec/support/integration_spec_helper.rb
new file mode 100644
index 000000000..7ba7e9f92
--- /dev/null
+++ b/spec/support/integration_spec_helper.rb
@@ -0,0 +1,78 @@
+module IntegrationSpecHelper
+
+ def paginate_collection klass, decorator, page=1, context={}
+ collection = klass.page(page)
+ if decorator
+ if decorator < AF83::Decorator
+ collection = decorator.decorate(collection, context: context)
+ else
+ collection = ModelDecorator.decorate(collection, with: decorator, context: context)
+ end
+ end
+ collection
+ end
+
+ def build_paginated_collection factory, decorator, opts={}
+ context = opts.delete(:context) || {}
+ count = opts.delete(:count) || 2
+ page = opts.delete(:page) || 1
+ klass = nil
+ count.times { klass = create(factory, opts).class }
+ paginate_collection klass, decorator, page, context
+ end
+
+ module Methods
+ def with_permission permission, &block
+ context "with permission #{permission}" do
+ let(:permissions){ [permission] }
+ context('', &block) if block_given?
+ end
+ end
+
+ def with_feature feature, &block
+ context "with feature #{feature}" do
+ let(:features){ [feature] }
+ context('', &block) if block_given?
+ end
+ end
+ end
+
+ def self.included into
+ into.extend Methods
+ end
+end
+
+RSpec.configure do |config|
+ config.include IntegrationSpecHelper, type: :view
+end
+
+RSpec::Matchers.define :have_link_for_each_item do |collection, name, opts|
+ opts = {href: opts} unless opts.is_a? Hash
+ href = opts[:href]
+ method = opts[:method]
+ method_selector = method.present? ? "[data-method='#{method.downcase}']": ""
+ match do |actual|
+ collection.each do |item|
+ @selector = "tr.#{TableBuilderHelper.item_row_class_name(collection)}-#{item.id} .actions a[href='#{href.call(item)}']#{method_selector}"
+ expect(rendered).to have_selector(@selector, count: 1)
+ end
+ end
+ description { "have #{name} link for each item" }
+ failure_message do
+ "expected view to have one #{name} link for each item, failed with selector: \"#{@selector}\""
+ end
+end
+
+RSpec::Matchers.define :have_the_right_number_of_links do |collection, count|
+ match do
+ collection.each do |item|
+ @selector = "tr.#{TableBuilderHelper.item_row_class_name(collection)}-#{item.id} .actions a"
+ expect(rendered).to have_selector(@selector, count: count)
+ end
+ end
+ description { "have #{count} links for each item" }
+ failure_message do
+ actual = Capybara::Node::Simple.new(rendered).all(@selector).count
+ "expected #{count} links for each item, got #{actual} for \"#{@selector}\""
+ end
+end
diff --git a/spec/support/journey_pattern_helper.rb b/spec/support/journey_pattern_helper.rb
new file mode 100644
index 000000000..3ba1c501b
--- /dev/null
+++ b/spec/support/journey_pattern_helper.rb
@@ -0,0 +1,19 @@
+module Support
+ module JourneyPatternHelper
+ def generate_journey_pattern_costs distance, time
+ costs = {}
+ (journey_pattern.stop_points.size - 1).times do |i|
+ start, finish = journey_pattern.stop_points[i..i+1]
+ costs["#{start.stop_area_id}-#{finish.stop_area_id}"] = {
+ distance: (distance.respond_to?(:call) ? distance.call(i) : distance),
+ time: (time.respond_to?(:call) ? time.call(i) : time)
+ }
+ end
+ costs
+ end
+ end
+end
+
+RSpec.configure do | config |
+ config.include Support::JourneyPatternHelper, type: :model
+end
diff --git a/spec/support/permissions.rb b/spec/support/permissions.rb
index dde530871..95afd6c1c 100644
--- a/spec/support/permissions.rb
+++ b/spec/support/permissions.rb
@@ -18,6 +18,7 @@ module Support
calendars
footnotes
imports
+ merges
journey_patterns
referentials
routes
diff --git a/spec/support/pundit/policies.rb b/spec/support/pundit/policies.rb
index a3489d9db..d8d12d735 100644
--- a/spec/support/pundit/policies.rb
+++ b/spec/support/pundit/policies.rb
@@ -12,11 +12,14 @@ module Support
UserContext.new(user, referential: referential)
end
+ def finalise_referential
+ referential.referential_suite_id = random_int
+ end
+
def remove_permissions(*permissions, from_user:, save: false)
from_user.permissions -= permissions.flatten
from_user.save! if save
end
-
end
module PoliciesMacros
diff --git a/spec/support/pundit/pundit_view_policy.rb b/spec/support/pundit/pundit_view_policy.rb
index b8434cac0..63970de02 100644
--- a/spec/support/pundit/pundit_view_policy.rb
+++ b/spec/support/pundit/pundit_view_policy.rb
@@ -1,16 +1,21 @@
module Pundit
module PunditViewPolicy
- extend ActiveSupport::Concern
-
- included do
- before do
- controller.singleton_class.class_eval do
- def policy(instance)
- Class.new do
- def method_missing(*args, &block); true; end
- end.new
- end
- helper_method :policy
+ def self.included into
+ into.let(:permissions){ nil }
+ into.let(:current_referential){ referential || build_stubbed(:referential, organisation: organisation) }
+ into.let(:current_user){ create :user, permissions: permissions, organisation: organisation }
+ into.let(:pundit_user){ UserContext.new(current_user, referential: current_referential) }
+ into.let(:current_offer_workbench) { create :workbench, organisation: organisation}
+ into.before do
+ allow(view).to receive(:pundit_user) { pundit_user }
+ allow(view).to receive(:current_user) { current_user }
+ allow(view).to receive(:current_organisation).and_return(organisation)
+ allow(view).to receive(:current_offer_workbench).and_return(current_offer_workbench)
+ allow(view).to receive(:current_workgroup).and_return(current_offer_workbench.workgroup)
+ allow(view).to receive(:has_feature?){ |f| features.include?(f)}
+ allow(view).to receive(:user_signed_in?).and_return true
+ allow(view).to receive(:policy) do |instance|
+ ::Pundit.policy pundit_user, instance
end
end
end
diff --git a/spec/support/pundit/shared_examples.rb b/spec/support/pundit/shared_examples.rb
index 49f915626..13f537c6d 100644
--- a/spec/support/pundit/shared_examples.rb
+++ b/spec/support/pundit/shared_examples.rb
@@ -1,6 +1,6 @@
RSpec.shared_examples 'always allowed' do
- | permission, archived: false|
+ | permission, archived_and_finalised: false |
context 'same organisation →' do
before do
user.organisation_id = referential.organisation_id
@@ -8,11 +8,16 @@ RSpec.shared_examples 'always allowed' do
it "allows a user with the same organisation" do
expect_it.to permit(user_context, record)
end
- if archived
+ if archived_and_finalised
it 'does not remove permission for archived referentials' do
referential.archived_at = 42.seconds.ago
expect_it.to permit(user_context, record)
end
+
+ it 'does not remove permission for finalised referentials' do
+ finalise_referential
+ expect_it.to permit(user_context, record)
+ end
end
end
@@ -23,27 +28,33 @@ RSpec.shared_examples 'always allowed' do
it "allows a user with a different organisation" do
expect_it.to permit(user_context, record)
end
- if archived
+ if archived_and_finalised
it 'does not remove permission for archived referentials' do
referential.archived_at = 42.seconds.ago
expect_it.to permit(user_context, record)
end
+ it 'does not remove permission for finalised referentials' do
+ finalise_referential
+ expect_it.to permit(user_context, record)
+ end
end
end
end
RSpec.shared_examples 'always forbidden' do
- | permission, archived: false|
+ | permission, archived_and_finalised: false|
context 'same organisation →' do
before do
user.organisation_id = referential.organisation_id
end
+
it "allows a user with the same organisation" do
expect_it.not_to permit(user_context, record)
end
- if archived
+
+ if archived_and_finalised
it 'still no permission for archived referentials' do
- referential.archived_at = 42.seconds.ago
+ finalise_referential
expect_it.not_to permit(user_context, record)
end
end
@@ -56,17 +67,22 @@ RSpec.shared_examples 'always forbidden' do
it "denies a user with a different organisation" do
expect_it.not_to permit(user_context, record)
end
- if archived
+ if archived_and_finalised
it 'still no permission for archived referentials' do
referential.archived_at = 42.seconds.ago
expect_it.not_to permit(user_context, record)
end
+
+ it 'still no permission for finalised referentials' do
+ finalise_referential
+ expect_it.not_to permit(user_context, record)
+ end
end
end
end
RSpec.shared_examples 'permitted policy and same organisation' do
- | permission, archived: false|
+ | permission, archived_and_finalised: false |
context 'permission absent → ' do
it "denies a user with a different organisation" do
@@ -92,18 +108,24 @@ RSpec.shared_examples 'permitted policy and same organisation' do
expect_it.to permit(user_context, record)
end
- if archived
+ if archived_and_finalised
it 'removes the permission for archived referentials' do
user.organisation_id = referential.organisation_id
referential.archived_at = 42.seconds.ago
expect_it.not_to permit(user_context, record)
end
+
+ it 'removes the permission for finalised referentials' do
+ user.organisation_id = referential.organisation_id
+ finalise_referential
+ expect_it.not_to permit(user_context, record)
+ end
end
end
end
RSpec.shared_examples 'permitted policy' do
- | permission, archived: false|
+ | permission, archived_and_finalised: false|
context 'permission absent → ' do
it "denies user" do
@@ -120,12 +142,17 @@ RSpec.shared_examples 'permitted policy' do
expect_it.to permit(user_context, record)
end
- if archived
+ if archived_and_finalised
it 'removes the permission for archived referentials' do
user.organisation_id = referential.organisation_id
referential.archived_at = 42.seconds.ago
expect_it.not_to permit(user_context, record)
end
+ it 'removes the permission for finalised referentials' do
+ user.organisation_id = referential.organisation_id
+ finalise_referential
+ expect_it.not_to permit(user_context, record)
+ end
end
end
end
@@ -148,4 +175,4 @@ RSpec.shared_examples 'permitted policy outside referential' do
expect_it.to permit(user_context, record)
end
end
-end \ No newline at end of file
+end
diff --git a/spec/support/referential.rb b/spec/support/referential.rb
index b615491da..9acdce73a 100644
--- a/spec/support/referential.rb
+++ b/spec/support/referential.rb
@@ -11,8 +11,8 @@ module ReferentialHelper
def self.included(base)
base.class_eval do
extend ClassMethods
- alias_method :referential, :first_referential
- alias_method :organisation, :first_organisation
+ base.let(:referential){ first_referential }
+ base.let(:organisation){ first_organisation }
end
end
@@ -29,7 +29,6 @@ module ReferentialHelper
end
end
-
end
end
@@ -78,7 +77,7 @@ RSpec.configure do |config|
first_referential.switch
end
- config.before(:each, :js => true) do
+ config.before(:each, truncation: true) do
DatabaseCleaner.strategy = :truncation, { except: %w[spatial_ref_sys] }
end
diff --git a/spec/support/shared_examples/compliance_control_validation.rb b/spec/support/shared_examples/compliance_control_validation.rb
index d4ab9f41d..b23c2984f 100644
--- a/spec/support/shared_examples/compliance_control_validation.rb
+++ b/spec/support/shared_examples/compliance_control_validation.rb
@@ -1,43 +1,54 @@
RSpec.shared_examples_for 'has min_max_values' do
context "is valid" do
- it 'if no value is provided' do
+ it { should validate_numericality_of(:minimum) }
+ it { should validate_numericality_of(:maximum) }
+
+ it 'if maximum is greater than minimum' do
+ min = random_int
+ max = min + 100
+ subject.assign_attributes maximum: max, minimum: min
expect_it.to be_valid
end
+ end
+
+ context "is invalid" do
+ it 'if no value is provided' do
+ subject.minimum = nil
+ subject.maximum = nil
+ expect_it.not_to be_valid
+ end
+
it 'if minimum is provided alone' do
subject.minimum = 42
- expect_it.to be_valid
+ subject.maximum = nil
+ expect_it.not_to be_valid
end
+
it 'if maximum is provided alone' do
+ subject.minimum = nil
subject.maximum = 42
- expect_it.to be_valid
- end
-
- it 'if maximum is not smaller than minimum' do
- 100.times do
- min = random_int
- max = min + random_int(20)
- subject.assign_attributes maximum: max, minimum: min
- subject.assign_attributes maximum: min, minimum: min
- expect_it.to be_valid
- end
+ expect_it.not_to be_valid
end
- end
- context "is invalid" do
it 'if maximum is smaller than minimum' do
- 100.times do
- min = random_int
- max = min - random_int(20) - 1
- subject.assign_attributes maximum: max, minimum: min
- expect_it.not_to be_valid
- end
+ min = random_int
+ max = min - 1
+ subject.assign_attributes maximum: max, minimum: min
+ expect_it.not_to be_valid
end
it 'and has a correct error message' do
subject.assign_attributes maximum: 1, minimum: 2
expect_it.not_to be_valid
- expect( subject.errors.messages[:min_max_values].first ).to match("la valeur de minimum (2) ne doit pas être superieur à la valuer du maximum (1)")
+ expect( subject.errors.messages[:minimum].first ).to match(I18n.t("compliance_controls.min_max_values", min: 2, max: 1))
end
end
end
+
+
+RSpec.shared_examples_for "has target attribute" do
+ context "is valid" do
+ it { should validate_presence_of(:target) }
+ end
+end
diff --git a/spec/support/snapshot_support.rb b/spec/support/snapshot_support.rb
new file mode 100644
index 000000000..b1ade5288
--- /dev/null
+++ b/spec/support/snapshot_support.rb
@@ -0,0 +1,60 @@
+module SnaphostSpecHelper
+
+ module Methods
+ def set_invariant expr, val=nil
+ val ||= expr
+ chain = expr.split(".")
+ method = chain.pop
+
+ before(:each) do
+ allow(eval(chain.join('.'))).to receive(method){ val }
+ end
+ end
+ end
+
+ def self.included into
+ into.extend Methods
+ end
+end
+
+RSpec.configure do |config|
+ config.include SnaphostSpecHelper, type: :view
+end
+
+
+RSpec::Matchers.define :match_actions_links_snapshot do |name|
+ match do |actual|
+ @content = Capybara::Node::Simple.new(rendered).find('.page_header').native.inner_html
+ expect(@content).to match_snapshot(name)
+ end
+
+ failure_message do |actual|
+ out = ["Snapshots did not match."]
+ snap_path = File.dirname(method_missing(:class).metadata[:file_path]) + "/__snapshots__/#{name}.snap"
+ temp_path = Pathname.new "#{Rails.root}/tmp/__snapshots__/#{name}.failed.snap"
+ FileUtils.mkdir_p temp_path.dirname
+ tmp = File.new temp_path, "w"
+ tmp.write @content
+ tmp.close()
+ expected = File.read snap_path
+ out << "Expected: #{expected}"
+ out << "Actual: #{@content}"
+ out << "\n\n --- DIFF ---"
+ out << differ.diff_as_string(@content, expected)
+ out << "\n\n --- Previews : ---"
+ out << "Expected: \n" + snapshot_url(snap: snap_path, layout: :actions_links)
+ out << " \nActual: \n" + snapshot_url(snap: tmp.path, layout: :actions_links)
+ out.join("\n")
+ end
+
+ def snapshot_url snap:, layout:
+ "http://localhost:3000/snap/?snap=#{URI.encode(snap.to_s)}&layout=#{URI.encode(layout.to_s)}"
+ end
+
+ def differ
+ RSpec::Support::Differ.new(
+ :object_preparer => lambda { |object| RSpec::Matchers::Composable.surface_descriptions_in(object) },
+ :color => RSpec::Matchers.configuration.color?
+ )
+ end
+end
diff --git a/spec/support/zip_support.rb b/spec/support/zip_support.rb
new file mode 100644
index 000000000..3d9b2f97c
--- /dev/null
+++ b/spec/support/zip_support.rb
@@ -0,0 +1,25 @@
+require_relative 'helpers/tree_walker'
+module ZipSupport
+
+ module Helper extend self
+ def remove
+ -> filetype, path do
+ filetype == :file ? File.unlink(path) : Dir.unlink(path)
+ end
+ end
+ end
+
+ def zip_fixtures_path(file_name)
+ fixtures_path(File.join('zip', file_name))
+ end
+
+ def clear_all_zip_fixtures! relpath = ''
+ raise ArgumentError, 'up dir not allowed (..)' if %r{\.\.} === relpath
+ TreeWalker.walk_tree zip_fixtures_path(relpath), yield_dirs: :after, &Helper.remove
+ end
+end
+
+RSpec.configure do |conf|
+ conf.include ZipSupport, type: :zip
+end
+
diff --git a/spec/support/zip_support/create_zip_data.rb b/spec/support/zip_support/create_zip_data.rb
new file mode 100644
index 000000000..250d67f74
--- /dev/null
+++ b/spec/support/zip_support/create_zip_data.rb
@@ -0,0 +1,70 @@
+require_relative '../helpers/tree_walker'
+module ZipSupport
+ module CreateZipData
+
+ class ZipData < Struct.new(:name, :data)
+
+ def write_to file
+ File.write(file, data)
+ end
+
+ end
+
+ class Implementation
+
+ attr_reader :name, :prefix, :zip
+
+ def initialize name
+ @name = name
+ @prefix = "#{name}/"
+ @zip = ZipData.new(name, '')
+ end
+
+ def make_from names_to_content_map
+ os = Zip::OutputStream.write_buffer do | zio |
+ names_to_content_map.each(&add_entries(zio))
+ end
+ zip.data = os.string
+ zip
+ end
+
+ def make_from_tree
+ os = Zip::OutputStream.write_buffer do | zio |
+ TreeWalker.walk_tree(name, &add_entry(zio))
+ end
+ zip.data = os.string
+ zip
+ end
+
+ private
+
+ def add_entry zio
+ -> _, path do
+ rel_path = path.sub(prefix, '')
+ zio.put_next_entry(rel_path)
+ zio.write(File.read(path))
+ end
+ end
+
+ def add_entries zio
+ -> name, content do
+ zio.put_next_entry(name)
+ zio.write(content)
+ end
+ end
+ end
+
+
+ def make_zip(name, names_to_content_map = {})
+ Implementation.new(name).make_from(names_to_content_map)
+ end
+
+ def make_zip_from_tree(dir)
+ Implementation.new(dir).make_from_tree
+ end
+ end
+end
+
+RSpec.configure do |conf|
+ conf.include ZipSupport::CreateZipData, type: :zip
+end