From 705ddf715f110cca10f438b0c5332aea55a81eb3 Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Mon, 3 Apr 2023 16:20:54 -0700 Subject: [PATCH 1/3] fix double encryption on job retry --- .github/workflows/continuous_integration.yml | 59 +++++++------- .gitignore | 1 + .standard.yml | 6 +- Appraisals | 2 +- CHANGE_LOG.md | 43 +++++++--- Gemfile | 2 +- Gemfile.lock | 79 ------------------- VERSION | 2 +- lib/sidekiq/encrypted_args.rb | 10 ++- .../encrypted_args/client_middleware.rb | 2 +- spec/encrypted_args/client_middleware_spec.rb | 15 ++++ spec/encypted_args_spec.rb | 20 ++--- spec/spec_helper.rb | 24 ++++-- 13 files changed, 120 insertions(+), 145 deletions(-) delete mode 100644 Gemfile.lock diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 7d6fa2f..ad685cf 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -3,7 +3,6 @@ on: push: branches: - master - - actions-* tags: - v* pull_request: @@ -14,41 +13,45 @@ env: BUNDLE_RETRY: 3 jobs: specs: - name: ${{ matrix.job }} ruby-${{ matrix.ruby }} ${{ matrix.sidekiq && format('sidekiq-{0}', matrix.sidekiq) }} + name: ruby-${{ matrix.ruby }} ${{ matrix.appraisal }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - ruby: [ 2.4, 2.5, 2.6, 2.7 ] - sidekiq: [4, 5, 6] - job: [ rspec ] - exclude: - - ruby: 2.4 - sidekiq: 6 include: - - ruby: 2.7 - sidekiq: original - job: rspec - - ruby: 2.7 - job: standardrb + - ruby: "ruby" + standardrb: true + coverage: true + - ruby: "3.1" + appraisal: "sidekiq_7" + - ruby: "3.0" + appraisal: "sidekiq_6" + - ruby: "2.7" + appraisal: "sidekiq_5" + - ruby: "2.4" + appraisal: "sidekiq_4" steps: - - name: checkout - uses: actions/checkout@v2 - - name: set up Ruby + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Ruby ${{ matrix.ruby }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - - name: inject sidekiq ${{ matrix.sidekiq }} - if: matrix.sidekiq != 'original' && matrix.sidekiq != null - run: | # inject a specific version of sidekiq into the Gemfile + - name: Setup bundler + if: matrix.bundler != '' + run: | + gem uninstall bundler --all + gem install bundler --no-document --version ${{ matrix.bundler }} + - name: Set Appraisal bundle + if: matrix.appraisal != '' + run: | + echo "using gemfile gemfiles/${{ matrix.appraisal }}.gemfile" + bundle config set gemfile "gemfiles/${{ matrix.appraisal }}.gemfile" + - name: Install bundle + run: | bundle update - bundle exec appraisal generate - bundle config set gemfile "gemfiles/sidekiq_${{ matrix.sidekiq }}.gemfile" - - name: install dependencies - run: bundle install - - name: specs - if: matrix.job == 'rspec' + - name: Run specs run: bundle exec rake spec - - name: standardrb - if: matrix.job == 'standardrb' - run: bundle exec rake standard + - name: Run standardrb + if: matrix.standardrb == true + run: bundle exec standardrb diff --git a/.gitignore b/.gitignore index b79a026..6040813 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .tm_properties .bundle +Gemfile.lock /gemfiles/*.lock .yardoc /_yardoc/ diff --git a/.standard.yml b/.standard.yml index a5a881a..bfb4d23 100644 --- a/.standard.yml +++ b/.standard.yml @@ -1,9 +1,9 @@ -# I really just have issues with the automatic "semantic blocks" - ruby_version: 2.4 format: progress ignore: - '**/*': - - Standard/SemanticBlocks + - Style/RedundantBegin + - 'spec/**/*': + - Lint/UselessAssignment diff --git a/Appraisals b/Appraisals index 0252ba5..77593ee 100644 --- a/Appraisals +++ b/Appraisals @@ -1,6 +1,6 @@ # frozen_string_literal: true -SIDEKIQ_MAJOR_RELEASES = ["6", "5", "4"].freeze +SIDEKIQ_MAJOR_RELEASES = ["7", "6", "5", "4"].freeze SIDEKIQ_MAJOR_RELEASES.each do |version| appraise "sidekiq_#{version}" do diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 1da9f13..6d578fa 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,24 +1,43 @@ -# Change Log +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 1.1.1 + +### Fixed +- Client middleware will no longer encrypt already encrypted arguments when a job is retried. ## 1.1.0 -* Use `to_json` if it is defined when serializing encrypted args to JSON. -* Add client middleware to the server default configuration. This ensures that arguments will be encrypted if a worker enqueues a job with encrypted arguments. -* Client middleware now reads sidekiq options from the job hash instead of from the worker class so that the list of encrypted arguments is always in sync on the job payload. -* Don't blow up if class name that is not defined is passed to client middleware. -* Added additional option to specify encrypted args with array of argument indexes. -* Deprecated setting encrypted args as hash or array of booleans. -* Client middleware is prepended while server middleware is appended. +### Added +- Use `to_json` if it is defined when serializing encrypted args to JSON. +- Add client middleware to the server default configuration. This ensures that arguments will be encrypted if a worker enqueues a job with encrypted arguments. +- Client middleware now reads sidekiq options from the job hash instead of from the worker class so that the list of encrypted arguments is always in sync on the job payload. +- Added additional option to specify encrypted args with array of argument indexes. + +### Changed +- Client middleware is now prepended while server middleware is appended. + +### Fixed +- Don't raise error if undefined class name is passed to client middleware as a string. + +### Deprecated +- Deprecated setting encrypted args as hash or array of booleans. ## 1.0.2 -* Remove overly noisy log warning when running without the secret set +### Changed +- Remove overly noisy log warning when running without the secret set ## 1.0.1 -* Now works with scheduled jobs - * Scheduled jobs dispatch by class name instead of `Class`, requiring a constant lookup +### Added + +### Fixed +- Added support for scheduled jobs ## 1.0.0 -* Initial release +- Initial release diff --git a/Gemfile b/Gemfile index e3b6b4a..043cda3 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ group :development, :test do gem "appraisal" # Lock standard to a particular version, esp. cause it's still 0.x.x according to Semver - gem "standard", "0.4.6" + gem "standard", "~> 1.0" end group :doc do diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index f11d134..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,79 +0,0 @@ -PATH - remote: . - specs: - sidekiq-encrypted_args (1.1.0) - secret_keys - sidekiq (>= 4.0) - -GEM - remote: https://rubygems.org/ - specs: - appraisal (2.3.0) - bundler - rake - thor (>= 0.14.0) - ast (2.4.1) - climate_control (0.2.0) - connection_pool (2.2.3) - diff-lcs (1.3) - parallel (1.19.1) - parser (2.7.1.3) - ast (~> 2.4.0) - rack (2.2.2) - rack-protection (2.0.8.1) - rack - rainbow (3.0.0) - rake (13.0.1) - redis (4.2.1) - rexml (3.2.4) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.2) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.3) - rubocop (0.83.0) - parallel (~> 1.10) - parser (>= 2.7.0.1) - rainbow (>= 2.2.2, < 4.0) - rexml - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-performance (1.5.2) - rubocop (>= 0.71.0) - ruby-progressbar (1.10.1) - secret_keys (1.0.1) - sidekiq (6.0.7) - connection_pool (>= 2.2.2) - rack (~> 2.0) - rack-protection (>= 2.0.0) - redis (>= 4.1.0) - standard (0.4.6) - rubocop (~> 0.83.0) - rubocop-performance (~> 1.5.2) - thor (1.0.1) - unicode-display_width (1.7.0) - yard (0.9.25) - -PLATFORMS - ruby - -DEPENDENCIES - appraisal - bundler (~> 2.0) - climate_control - rake - rspec (~> 3.9) - sidekiq-encrypted_args! - standard (= 0.4.6) - yard - -BUNDLED WITH - 2.1.4 diff --git a/VERSION b/VERSION index 9084fa2..524cb55 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.1.1 diff --git a/lib/sidekiq/encrypted_args.rb b/lib/sidekiq/encrypted_args.rb index 533b442..5c33db0 100644 --- a/lib/sidekiq/encrypted_args.rb +++ b/lib/sidekiq/encrypted_args.rb @@ -31,7 +31,7 @@ def secret=(value) @encryptors = make_encryptors(value) end - # Add the client and server middleware to the Sidekiq + # Add the client and server middleware to the default Sidekiq # middleware chains. If you need to ensure the order of where the middleware is # added, you can forgo this method and add it yourself. # @@ -64,6 +64,7 @@ def configure!(secret: nil) # @return [String] def encrypt(data) return nil if data.nil? + json = (data.respond_to?(:to_json) ? data.to_json : JSON.generate(data)) encrypted = encrypt_string(json) if encrypted == json @@ -85,6 +86,13 @@ def decrypt(encrypted_data) JSON.parse(json) end + # Check if a value is encrypted. + # + # @return [Boolean] + def encrypted?(value) + SecretKeys::Encryptor.encrypted?(value) + end + # Private helper method to get the encrypted args option from an options hash. The value of this option # can be `true` or an array indicating if each positional argument should be encrypted, or a hash # with keys for the argument position and true as the value. diff --git a/lib/sidekiq/encrypted_args/client_middleware.rb b/lib/sidekiq/encrypted_args/client_middleware.rb index af8a3a4..e708ad4 100644 --- a/lib/sidekiq/encrypted_args/client_middleware.rb +++ b/lib/sidekiq/encrypted_args/client_middleware.rb @@ -28,7 +28,7 @@ def encrypt_job_arguments!(job, encrypted_args) if encrypted_args job_args = job["args"] job_args.each_with_index do |value, position| - if encrypted_args.include?(position) + if encrypted_args.include?(position) && !EncryptedArgs.encrypted?(value) job_args[position] = EncryptedArgs.encrypt(value) end end diff --git a/spec/encrypted_args/client_middleware_spec.rb b/spec/encrypted_args/client_middleware_spec.rb index 6c2464f..414dd93 100644 --- a/spec/encrypted_args/client_middleware_spec.rb +++ b/spec/encrypted_args/client_middleware_spec.rb @@ -120,6 +120,21 @@ expect(job["args"].collect { |val| SecretKeys::Encryptor.encrypted?(val) }).to eq [false, true, false] end + it "should not encrypt arguments that are already encrypted" do + called = false + job["encrypted_args"] = ArrayIndexSecretWorker.sidekiq_options["encrypted_args"] + encrypted_value = Sidekiq::EncryptedArgs.encrypt(job["args"][1]) + job["args"][1] = encrypted_value + + middleware.call(ArrayIndexSecretWorker, job, queue) do + called = true + end + + expect(called).to eq true + expect(job["encrypted_args"]).to match_array [1] + expect(job["args"][1]).to eq encrypted_value + end + context "deprecated configurations", :no_warn do it "should only encrypt arguments whose position index is set to true when the encrypted_args option is a hash" do called = false diff --git a/spec/encypted_args_spec.rb b/spec/encypted_args_spec.rb index 6fb1c2f..b621144 100644 --- a/spec/encypted_args_spec.rb +++ b/spec/encypted_args_spec.rb @@ -91,15 +91,15 @@ it "should configure the Sidekiq client middleware" do as_sidekiq_client! Sidekiq::EncryptedArgs.configure! - expect(Sidekiq.client_middleware.exists?(Sidekiq::EncryptedArgs::ClientMiddleware)).to eq true - expect(Sidekiq.server_middleware.exists?(Sidekiq::EncryptedArgs::ServerMiddleware)).to eq false + expect(sidekiq_config.client_middleware.exists?(Sidekiq::EncryptedArgs::ClientMiddleware)).to eq true + expect(sidekiq_config.server_middleware.exists?(Sidekiq::EncryptedArgs::ServerMiddleware)).to eq false end it "should configure the Sidekiq server middleware" do as_sidekiq_server! Sidekiq::EncryptedArgs.configure! - expect(Sidekiq.client_middleware.exists?(Sidekiq::EncryptedArgs::ClientMiddleware)).to eq true - expect(Sidekiq.server_middleware.exists?(Sidekiq::EncryptedArgs::ServerMiddleware)).to eq true + expect(sidekiq_config.client_middleware.exists?(Sidekiq::EncryptedArgs::ClientMiddleware)).to eq true + expect(sidekiq_config.server_middleware.exists?(Sidekiq::EncryptedArgs::ServerMiddleware)).to eq true end it "should set the secret from the configure! method" do @@ -119,20 +119,20 @@ it "should load client middleware first and server middleware last on server" do as_sidekiq_server! - Sidekiq.server_middleware.add(EmptyMiddleware) - Sidekiq.client_middleware.add(EmptyMiddleware) + sidekiq_config.server_middleware.add(EmptyMiddleware) + sidekiq_config.client_middleware.add(EmptyMiddleware) Sidekiq::EncryptedArgs.configure!(secret: "0xDEADBEEF") - expect(Sidekiq.client_middleware.map(&:klass)).to eq [Sidekiq::EncryptedArgs::ClientMiddleware, EmptyMiddleware] - expect(Sidekiq.server_middleware.map(&:klass)).to eq [EmptyMiddleware, Sidekiq::EncryptedArgs::ServerMiddleware] + expect(sidekiq_config.client_middleware.map(&:klass)).to eq [Sidekiq::EncryptedArgs::ClientMiddleware, EmptyMiddleware] + expect(sidekiq_config.server_middleware.map(&:klass)).to eq [EmptyMiddleware, Sidekiq::EncryptedArgs::ServerMiddleware] end it "should load client middleware first on client" do as_sidekiq_client! - Sidekiq.client_middleware.add(EmptyMiddleware) + sidekiq_config.client_middleware.add(EmptyMiddleware) Sidekiq::EncryptedArgs.configure!(secret: "0xDEADBEEF") - expect(Sidekiq.client_middleware.map(&:klass)).to eq [Sidekiq::EncryptedArgs::ClientMiddleware, EmptyMiddleware] + expect(sidekiq_config.client_middleware.map(&:klass)).to eq [Sidekiq::EncryptedArgs::ClientMiddleware, EmptyMiddleware] end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 337407e..8a04440 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -29,18 +29,26 @@ # @note Middleware args are not preserved def with_empty_middleware # Save the middleware context - server_middleware = Sidekiq.server_middleware.entries.map(&:klass) - client_middleware = Sidekiq.client_middleware.entries.map(&:klass) - Sidekiq.server_middleware.clear - Sidekiq.client_middleware.clear + server_middleware = sidekiq_config.server_middleware.entries.map(&:klass) + client_middleware = sidekiq_config.client_middleware.entries.map(&:klass) + sidekiq_config.server_middleware.clear + sidekiq_config.client_middleware.clear yield # Clear anything added and restore all previously registered middleware - Sidekiq.server_middleware.clear - Sidekiq.client_middleware.clear - server_middleware.each { |m| Sidekiq.server_middleware.add(m) } - client_middleware.each { |m| Sidekiq.client_middleware.add(m) } + sidekiq_config.server_middleware.clear + sidekiq_config.client_middleware.clear + server_middleware.each { |m| sidekiq_config.server_middleware.add(m) } + client_middleware.each { |m| sidekiq_config.client_middleware.add(m) } +end + +def sidekiq_config + if Sidekiq.respond_to?(:default_configuration) + Sidekiq.default_configuration + else + Sidekiq + end end def as_sidekiq_server! From 0ceb6314025a626cc77035c03aeb0170e7301e3e Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Mon, 3 Apr 2023 16:42:12 -0700 Subject: [PATCH 2/3] remove climate_control test dependency --- Gemfile | 1 - README.md | 29 ++++++++++++++++++++++++++++- gemfiles/sidekiq_4.gemfile | 3 +-- gemfiles/sidekiq_5.gemfile | 3 +-- gemfiles/sidekiq_6.gemfile | 3 +-- gemfiles/sidekiq_7.gemfile | 20 ++++++++++++++++++++ spec/encypted_args_spec.rb | 4 ++-- spec/spec_helper.rb | 11 ++++++++++- 8 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 gemfiles/sidekiq_7.gemfile diff --git a/Gemfile b/Gemfile index 043cda3..1ddc5d1 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,6 @@ end group :development, :test do gem "rake" gem "rspec", "~> 3.9" - gem "climate_control" gem "appraisal" # Lock standard to a particular version, esp. cause it's still 0.x.x according to Semver diff --git a/README.md b/README.md index ce49c31..d530ddd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Sidekiq Encrypted Args [![Continuous Integration](https://github.com/bdurand/sidekiq-encrypted_args/workflows/Continuous%20Integration/badge.svg?branch=master)](https://github.com/bdurand/sidekiq-encrypted_args/actions?query=workflow%3A%22Continuous+Integration%22) -[![Maintainability](https://api.codeclimate.com/v1/badges/70ab3782e4d5285eb173/maintainability)](https://codeclimate.com/github/bdurand/sidekiq-encrypted_args/maintainability) [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard) Support for encrypting arguments for [Sidekiq](https://github.com/mperham/sidekiq). @@ -106,3 +105,31 @@ You can also safely add encryption to an existing worker. Any jobs that are alre ## Encryption Encrypted arguments are stored using AES-256-GCM with a key derived from your secret using PBKDF2. For more info on the underlying encryption, refer to the [SecretKeys](https://github.com/bdurand/secret_keys) gem. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem "sidekiq-encrypted_args" +``` + +And then execute: +```bash +$ bundle +``` + +Or install it yourself as: +```bash +$ gem install sidekiq-encrypted_args +``` + +## Contributing + +Open a pull request on GitHub. + +Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting. + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/gemfiles/sidekiq_4.gemfile b/gemfiles/sidekiq_4.gemfile index 9c56718..6c7d7b8 100644 --- a/gemfiles/sidekiq_4.gemfile +++ b/gemfiles/sidekiq_4.gemfile @@ -11,9 +11,8 @@ end group :development, :test do gem "rake" gem "rspec", "~> 3.9" - gem "climate_control" gem "appraisal" - gem "standard", "0.4.6" + gem "standard", "~> 1.0" end group :doc do diff --git a/gemfiles/sidekiq_5.gemfile b/gemfiles/sidekiq_5.gemfile index 5a6d0ce..195edb5 100644 --- a/gemfiles/sidekiq_5.gemfile +++ b/gemfiles/sidekiq_5.gemfile @@ -11,9 +11,8 @@ end group :development, :test do gem "rake" gem "rspec", "~> 3.9" - gem "climate_control" gem "appraisal" - gem "standard", "0.4.6" + gem "standard", "~> 1.0" end group :doc do diff --git a/gemfiles/sidekiq_6.gemfile b/gemfiles/sidekiq_6.gemfile index 34fad7e..16471f4 100644 --- a/gemfiles/sidekiq_6.gemfile +++ b/gemfiles/sidekiq_6.gemfile @@ -11,9 +11,8 @@ end group :development, :test do gem "rake" gem "rspec", "~> 3.9" - gem "climate_control" gem "appraisal" - gem "standard", "0.4.6" + gem "standard", "~> 1.0" end group :doc do diff --git a/gemfiles/sidekiq_7.gemfile b/gemfiles/sidekiq_7.gemfile new file mode 100644 index 0000000..931377f --- /dev/null +++ b/gemfiles/sidekiq_7.gemfile @@ -0,0 +1,20 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "sidekiq", "~> 7.0" + +group :runtime do + gemspec path: "../" +end + +group :development, :test do + gem "rake" + gem "rspec", "~> 3.9" + gem "appraisal" + gem "standard", "~> 1.0" +end + +group :doc do + gem "yard" +end diff --git a/spec/encypted_args_spec.rb b/spec/encypted_args_spec.rb index b621144..6283959 100644 --- a/spec/encypted_args_spec.rb +++ b/spec/encypted_args_spec.rb @@ -44,7 +44,7 @@ it "should read the secret key from the environment if it has not been explicitly set" do Sidekiq::EncryptedArgs.secret = nil - ClimateControl.modify(SIDEKIQ_ENCRYPTED_ARGS_SECRET: "env_key") do + with_environment(SIDEKIQ_ENCRYPTED_ARGS_SECRET: "env_key") do env_encrypted = Sidekiq::EncryptedArgs.encrypt("foobar") env_decrypted = Sidekiq::EncryptedArgs.decrypt(env_encrypted) expect(env_decrypted).to eq "foobar" @@ -60,7 +60,7 @@ it "should not encrypt if the secret key is not set" do allow(Sidekiq.logger).to receive(:warn).with(instance_of(String)) Sidekiq::EncryptedArgs.secret = nil - ClimateControl.modify(SIDEKIQ_ENCRYPTED_ARGS_SECRET: "") do + with_environment(SIDEKIQ_ENCRYPTED_ARGS_SECRET: "") do expect(Sidekiq::EncryptedArgs.encrypt("foobar")).to eq "foobar" end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8a04440..cac92bc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,6 @@ require "bundler/setup" require "sidekiq" -require "climate_control" require_relative "../lib/sidekiq-encrypted_args" @@ -24,6 +23,16 @@ end end +# Helper method to temporarily set environment variables. +def with_environment(env) + save_vals = env.keys.collect { |k| [k, ENV[k.to_s]] } + begin + env.each { |k, v| ENV[k.to_s] = v } + yield + ensure + save_vals.each { |k, v| ENV[k.to_s] = v } + end +end # Reset all middleware for nested context and then restore. # # @note Middleware args are not preserved From c4cf19c2bd117a07757499157c51f2746837e92c Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Mon, 3 Apr 2023 16:43:50 -0700 Subject: [PATCH 3/3] code layout --- spec/spec_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cac92bc..9a83085 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -33,6 +33,7 @@ def with_environment(env) save_vals.each { |k, v| ENV[k.to_s] = v } end end + # Reset all middleware for nested context and then restore. # # @note Middleware args are not preserved