diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 62470321b..6201e1ba8 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,29 +1,28 @@ -# This file is copied to spec/ when you run 'rails generate rspec:install' ENV['RAILS_ENV'] ||= 'test' require 'spec_helper' require File.expand_path('../../config/environment', __FILE__) require 'rspec/rails' require 'capybara/poltergeist' require 'paper_trail/frameworks/rspec' -PaperTrail.whodunnit = 'autotest' -require "money-rails/test_helpers" +require 'money-rails/test_helpers' if ENV['ROBOT'] require 'simplecov' SimpleCov.start 'rails' end -# Requires supporting ruby files with custom matchers and macros, etc, in -# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are -# run as spec files by default. This means that files in spec/support that end -# in _spec.rb will both be required and run as specs, causing the specs to be -# run twice. It is recommended that you do not name files matching this glob to -# end with _spec.rb. You can configure this pattern with with the --pattern -# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. -Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } +require 'support/matchers/alias_attribute' +require 'support/matchers/active_job' +require 'support/capybara' +require 'support/database_cleaner' +require 'support/epp' +require 'support/epp_doc' +require 'support/feature' +require 'support/registrar_helpers' +require 'support/request' +require 'support/autodoc' +require 'support/paper_trail' -# Checks for pending migrations before tests are run. -# If you are not using ActiveRecord, you can remove this line. ActiveRecord::Migration.maintain_test_schema! # create general settings @@ -49,81 +48,44 @@ def create_settings # speedup and easier to create fabrications @fixed_registrar = - Registrar.find_by_name('fixed registrar') || - Fabricate(:registrar, name: 'fixed registrar', code: 'FIXED') + Registrar.find_by_name('fixed registrar') || + Fabricate(:registrar, name: 'fixed registrar', code: 'FIXED') end RSpec.configure do |config| - config.filter_run focus: true - config.run_all_when_everything_filtered = true + config.include ActionView::TestCase::Behavior, type: :presenter + config.include ActiveSupport::Testing::TimeHelpers - # If you're not using ActiveRecord, or you'd prefer not to run each of your - # examples within a transaction, remove the following line or assign false - # instead of true. - config.use_transactional_fixtures = false - - config.before(:suite) do - ActiveRecord::Base.establish_connection :api_log_test - DatabaseCleaner.clean_with(:truncation) - DatabaseCleaner.strategy = nil - - ActiveRecord::Base.establish_connection :test + config.define_derived_metadata(file_path: %r{/spec/presenters/}) do |metadata| + metadata[:type] = :presenter + metadata[:db] = false end + config.use_transactional_fixtures = false + config.before(:all) do - DatabaseCleaner.clean_with(:truncation) create_settings end config.before(:all, epp: true) do - DatabaseCleaner.strategy = nil create_settings end config.before(:each, js: true) do - DatabaseCleaner.strategy = :truncation create_settings end config.before(:each, type: :request) do - DatabaseCleaner.strategy = :truncation create_settings end config.before(:each, type: :model) do create_settings - DatabaseCleaner.strategy = :transaction - DatabaseCleaner.start end - config.after(:each, type: :model) do - DatabaseCleaner.clean - end - - Capybara.javascript_driver = :poltergeist - - # RSpec Rails can automatically mix in different behaviours to your tests - # based on their file location, for example enabling you to call `get` and - # `post` in specs under `spec/controllers`. - # - # You can disable this behaviour by removing the line below, and instead - # explicitly tag your specs with their type, e.g.: - # - # RSpec.describe UsersController, :type => :controller do - # # ... - # end - # - # The different available types are documented in the features, such as in - # https://relishapp.com/rspec/rspec-rails/docs config.infer_spec_type_from_file_location! config.expect_with :rspec do |c| c.syntax = [:should, :expect] end - - Autodoc.configuration.path = 'doc/repp' - Autodoc.configuration.suppressed_request_header = ['Host'] - Autodoc.configuration.suppressed_response_header = ['ETag', 'X-Request-Id', 'X-Runtime'] - Autodoc.configuration.template = File.read('spec/requests/repp_doc_template.md.erb') end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8ef5158b2..9e1e69d61 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,76 +1,77 @@ -# This file was generated by the `rails generate rspec:install` command. Conventionally, all -# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. -# The generated `.rspec` file contains `--require spec_helper` which will cause this -# file to always be loaded, without a need to explicitly require it in any files. -# -# Given that it is always loaded, you are encouraged to keep this file as -# light-weight as possible. Requiring heavyweight dependencies from this file -# will add to the boot time of your test suite on EVERY test run, even for an -# individual file that may not need all of that loaded. Instead, make a -# separate helper file that requires this one and then use it only in the specs -# that actually need it. -# -# The `.rspec` file also contains a few flags that are not defaults but that -# users commonly want. -# -# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration -RSpec.configure do |_config| +RSpec.configure do |config| + # https://github.com/rspec/rspec-rails/issues/1076 + config.around :each, type: :view do |example| + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = false + example.run + mocks.verify_partial_doubles = true + end + end + + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. - # # These two settings work together to allow you to limit a spec run - # # to individual examples or groups you care about by tagging them with - # # `:focus` metadata. When nothing is tagged with `:focus`, all examples - # # get run. - # config.filter_run :focus - # config.run_all_when_everything_filtered = true - # - # # Many RSpec users commonly either run the entire suite or an individual - # # file, and it's useful to allow more verbose output when running an - # # individual spec file. - # if config.files_to_run.one? - # # Use the documentation formatter for detailed output, - # # unless a formatter has already been configured - # # (e.g. via a command-line flag). - # config.default_formatter = 'doc' - # end - # - # # Print the 10 slowest examples and example groups at the - # # end of the spec run, to help surface which specs are running - # # particularly slow. - # config.profile_examples = 10 - # - # # Run specs in random order to surface order dependencies. If you find an - # # order dependency and want to debug it, you can fix the order by providing - # # the seed, which is printed after each run. - # # --seed 1234 - # config.order = :random - # - # # Seed global randomization in this process using the `--seed` CLI option. - # # Setting this allows you to use `--seed` to deterministically reproduce - # # test failures related to randomization by passing the same `--seed` value - # # as the one that triggered the failure. - # Kernel.srand config.seed - # - # # rspec-expectations config goes here. You can use an alternate - # # assertion/expectation library such as wrong or the stdlib/minitest - # # assertions if you prefer. - # config.expect_with :rspec do |expectations| - # # Enable only the newer, non-monkey-patching expect syntax. - # # For more details, see: - # # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax - # expectations.syntax = :expect - # end - # - # # rspec-mocks config goes here. You can use an alternate test double - # # library (such as bogus or mocha) by changing the `mock_with` option here. - # config.mock_with :rspec do |mocks| - # # Enable only the newer, non-monkey-patching expect syntax. - # # For more details, see: - # # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ - # mocks.syntax = :expect - # - # # Prevents you from mocking or stubbing a method that does not exist on - # # a real object. This is generally recommended. - # mocks.verify_partial_doubles = true - # end +=begin + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true + + # Limits the available syntax to the non-monkey patched syntax that is recommended. + # For more details, see: + # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax + # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end end diff --git a/spec/support/autodoc.rb b/spec/support/autodoc.rb new file mode 100644 index 000000000..3ea2fee22 --- /dev/null +++ b/spec/support/autodoc.rb @@ -0,0 +1,4 @@ +Autodoc.configuration.path = 'doc/repp' +Autodoc.configuration.suppressed_request_header = ['Host'] +Autodoc.configuration.suppressed_response_header = ['ETag', 'X-Request-Id', 'X-Runtime'] +Autodoc.configuration.template = File.read('spec/requests/repp_doc_template.md.erb') diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb new file mode 100644 index 000000000..7ef5009c6 --- /dev/null +++ b/spec/support/capybara.rb @@ -0,0 +1,8 @@ +require_relative 'macros/capybara' + +RSpec.configure do |config| + config.include CapybaraViewMacros, type: :view + config.include CapybaraViewMacros, type: :presenter +end + +Capybara.javascript_driver = :poltergeist diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb new file mode 100644 index 000000000..ce6d1f7f3 --- /dev/null +++ b/spec/support/database_cleaner.rb @@ -0,0 +1,30 @@ +RSpec.configure do |config| + db_connection_names = %i(test whois_test api_log_test registrant_write_test) + + config.before :suite do + DatabaseCleaner.strategy = :truncation + + db_connection_names.each do |connection_name| + ActiveRecord::Base.establish_connection(connection_name) + DatabaseCleaner[:active_record, connection: connection_name].strategy = :truncation + end + end + + config.before :example do |example| + if example.metadata[:db] || (%i(model).include?(example.metadata[:type]) && example.metadata[:db].nil?) + db_connection_names.each do |connection_name| + ActiveRecord::Base.establish_connection(connection_name) + DatabaseCleaner[:active_record, connection: connection_name].start + end + end + end + + config.after :each do |example| + if example.metadata[:db] || (%i(model).include?(example.metadata[:type]) && example.metadata[:db].nil?) + db_connection_names.each do |connection_name| + ActiveRecord::Base.establish_connection(connection_name) + DatabaseCleaner[:active_record, connection: connection_name].clean + end + end + end +end diff --git a/spec/support/macros/capybara.rb b/spec/support/macros/capybara.rb new file mode 100644 index 000000000..0e9dbf7b0 --- /dev/null +++ b/spec/support/macros/capybara.rb @@ -0,0 +1,5 @@ +module CapybaraViewMacros + def page + Capybara::Node::Simple.new(rendered) + end +end diff --git a/spec/support/matchers/active_job.rb b/spec/support/matchers/active_job.rb new file mode 100644 index 000000000..1a3f70a7a --- /dev/null +++ b/spec/support/matchers/active_job.rb @@ -0,0 +1,252 @@ +require "active_job/base" +require "active_job/arguments" + +# This matcher is needed because it is absent it rspec-rails 3.3.2 + +module RSpec + module Rails + module Matchers + # Namespace for various implementations of ActiveJob features + # + # @api private + module ActiveJob + # rubocop: disable Style/ClassLength + # @private + class Base < RSpec::Matchers::BuiltIn::BaseMatcher + def initialize + @args = [] + @queue = nil + @at = nil + @block = Proc.new {} + set_expected_number(:exactly, 1) + end + + def with(*args, &block) + @args = args + @block = block if block.present? + self + end + + def on_queue(queue) + @queue = queue + self + end + + def at(date) + @at = date + self + end + + def exactly(count) + set_expected_number(:exactly, count) + self + end + + def at_least(count) + set_expected_number(:at_least, count) + self + end + + def at_most(count) + set_expected_number(:at_most, count) + self + end + + def times + self + end + + def once + exactly(:once) + end + + def twice + exactly(:twice) + end + + def thrice + exactly(:thrice) + end + + def failure_message + "expected to enqueue #{base_message}" + end + + def failure_message_when_negated + "expected not to enqueue #{base_message}" + end + + def message_expectation_modifier + case @expectation_type + when :exactly then "exactly" + when :at_most then "at most" + when :at_least then "at least" + end + end + + def supports_block_expectations? + true + end + + private + + def check(jobs) + @matching_jobs_count = jobs.count do |job| + if serialized_attributes.all? { |key, value| value == job[key] } + args = ::ActiveJob::Arguments.deserialize(job[:args]) + @block.call(*args) + true + else + false + end + end + + case @expectation_type + when :exactly then @expected_number == @matching_jobs_count + when :at_most then @expected_number >= @matching_jobs_count + when :at_least then @expected_number <= @matching_jobs_count + end + end + + def base_message + "#{message_expectation_modifier} #{@expected_number} jobs,".tap do |msg| + msg << " with #{@args}," if @args.any? + msg << " on queue #{@queue}," if @queue + msg << " at #{@at}," if @at + msg << " but enqueued #{@matching_jobs_count}" + end + end + + def serialized_attributes + {}.tap do |attributes| + attributes[:args] = ::ActiveJob::Arguments.serialize(@args) if @args.any? + attributes[:at] = @at.to_f if @at + attributes[:queue] = @queue if @queue + attributes[:job] = @job if @job + end + end + + def set_expected_number(relativity, count) + @expectation_type = relativity + @expected_number = case count + when :once then 1 + when :twice then 2 + when :thrice then 3 + else Integer(count) + end + end + + def queue_adapter + ::ActiveJob::Base.queue_adapter + end + end + # rubocop: enable Style/ClassLength + + # @private + class HaveEnqueuedJob < Base + def initialize(job) + super() + @job = job + end + + def matches?(proc) + raise ArgumentError, "have_enqueued_job and enqueue_job only support block expectations" unless Proc === proc + + original_enqueued_jobs_count = queue_adapter.enqueued_jobs.count + proc.call + in_block_jobs = queue_adapter.enqueued_jobs.drop(original_enqueued_jobs_count) + + check(in_block_jobs) + end + end + + # @private + class HaveBeenEnqueued < Base + def matches?(job) + @job = job + check(queue_adapter.enqueued_jobs) + end + end + end + + # @api public + # Passes if a job has been enqueued inside block. May chain at_least, at_most or exactly to specify a number of times. + # + # @example + # expect { + # HeavyLiftingJob.perform_later + # }.to have_enqueued_job + # + # # Using alias + # expect { + # HeavyLiftingJob.perform_later + # }.to enqueue_job + # + # expect { + # HelloJob.perform_later + # HeavyLiftingJob.perform_later + # }.to have_enqueued_job(HelloJob).exactly(:once) + # + # expect { + # 3.times { HelloJob.perform_later } + # }.to have_enqueued_job(HelloJob).at_least(2).times + # + # expect { + # HelloJob.perform_later + # }.to have_enqueued_job(HelloJob).at_most(:twice) + # + # expect { + # HelloJob.perform_later + # HeavyLiftingJob.perform_later + # }.to have_enqueued_job(HelloJob).and have_enqueued_job(HeavyLiftingJob) + # + # expect { + # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42) + # }.to have_enqueued_job.with(42).on_queue("low").at(Date.tomorrow.noon) + def have_enqueued_job(job = nil) + check_active_job_adapter + ActiveJob::HaveEnqueuedJob.new(job) + end + alias_method :enqueue_job, :have_enqueued_job + + # @api public + # Passes if a job has been enqueued. May chain at_least, at_most or exactly to specify a number of times. + # + # @example + # before { ActiveJob::Base.queue_adapter.enqueued_jobs.clear } + # + # HeavyLiftingJob.perform_later + # expect(HeavyLiftingJob).to have_been_enqueued + # + # HelloJob.perform_later + # HeavyLiftingJob.perform_later + # expect(HeavyLiftingJob).to have_been_enqueued.exactly(:once) + # + # 3.times { HelloJob.perform_later } + # expect(HelloJob).to have_been_enqueued.at_least(2).times + # + # HelloJob.perform_later + # expect(HelloJob).to enqueue_job(HelloJob).at_most(:twice) + # + # HelloJob.perform_later + # HeavyLiftingJob.perform_later + # expect(HelloJob).to have_been_enqueued + # expect(HeavyLiftingJob).to have_been_enqueued + # + # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42) + # expect(HelloJob).to have_been_enqueued.with(42).on_queue("low").at(Date.tomorrow.noon) + def have_been_enqueued + check_active_job_adapter + ActiveJob::HaveBeenEnqueued.new + end + + private + + # @private + def check_active_job_adapter + return if ::ActiveJob::QueueAdapters::TestAdapter === ::ActiveJob::Base.queue_adapter + raise StandardError, "To use ActiveJob matchers set `ActiveJob::Base.queue_adapter = :test`" + end + end + end +end diff --git a/spec/support/matchers/alias_attribute.rb b/spec/support/matchers/alias_attribute.rb new file mode 100644 index 000000000..93b4efbb0 --- /dev/null +++ b/spec/support/matchers/alias_attribute.rb @@ -0,0 +1,9 @@ +RSpec::Matchers.define :alias_attribute do |alias_name, original_name| + match do |actual| + actual.class.attribute_alias(alias_name) == original_name.to_s + end + + failure_message do |actual| + "expected #{actual.class.name} to alias attribute :#{alias_name} by :#{original_name}" + end +end diff --git a/spec/support/paper_trail.rb b/spec/support/paper_trail.rb new file mode 100644 index 000000000..7304b8b60 --- /dev/null +++ b/spec/support/paper_trail.rb @@ -0,0 +1 @@ +PaperTrail.whodunnit = 'autotest'