From d6b983d897ec608a25120af6366bddebd3a7f5e8 Mon Sep 17 00:00:00 2001 From: Adam Maas Date: Tue, 2 Apr 2024 10:00:01 -0400 Subject: [PATCH 1/2] Adds circuitbox 2.0 compatibility --- .circleci/config.yml | 78 ----------------------- .github/workflows/ci.yml | 38 +++++++++++ .gitignore | 1 + Appraisals | 7 ++ Gemfile | 1 - Rakefile | 1 - bin/bundle | 109 ++++++++++++++++++++++++++++++++ bin/rake | 8 +-- bin/setup | 10 +-- bin/test | 2 +- gemfiles/circuitbox_1.gemfile | 11 ++++ gemfiles/circuitbox_2.gemfile | 11 ++++ lib/vertex_client.rb | 31 ++++++++- lib/vertex_client/connection.rb | 2 +- lib/vertex_client/version.rb | 2 +- test/configuration_test.rb | 6 -- test/connection_test.rb | 4 ++ test/integration_test.rb | 17 ++--- test/payload_validator_test.rb | 4 ++ test/test_helper.rb | 11 ---- test/vertex_client_test.rb | 28 +++----- vertex_client.gemspec | 8 +-- 22 files changed, 245 insertions(+), 145 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/ci.yml create mode 100644 Appraisals create mode 100755 bin/bundle create mode 100644 gemfiles/circuitbox_1.gemfile create mode 100644 gemfiles/circuitbox_2.gemfile diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 9277146..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,78 +0,0 @@ ---- -# --------------------------------------------------------------------------------------------------------------------- -# CircleCI Snippets -# -# Reusable snippets are defined below this section. These are yaml fragments that can injected into the standard -# CircleCI configuration, reducing the complexity of the entire block. -# --------------------------------------------------------------------------------------------------------------------- -docker_image: &docker_image - image: 916869144969.dkr.ecr.us-east-1.amazonaws.com/customink/base-ruby-ci:2.1-v2.7 - aws_auth: - aws_access_key_id: ${PRODUCTION_AWS_ACCESS_KEY_ID} - aws_secret_access_key: ${PRODUCTION_AWS_SECRET_ACCESS_KEY} - -# --------------------------------------------------------------------------------------------------------------------- -# CircleCI Commands Configuration -# -# Commands are re-usable steps that can be shared across jobs. For example the installation of gems using bundler or -# waiting on a database connection. By defining them inside the commands section, they can be invoked as any standard -# command on the system, but will already be preconfigured. This allows us to keep the jobs definition small and clean -# --------------------------------------------------------------------------------------------------------------------- -version: 2.1 -commands: - bundle_install: - description: "Performs the bundler installation, relying on the CircleCI cache for performance" - steps: - - restore_cache: - keys: - - bundler-cache-{{ checksum "vertex_client.gemspec" }} - - run: - name: "Bundle Install" - command: bundle install --path=.bundle - - save_cache: - key: bundler-cache-{{ checksum "vertex_client.gemspec" }} - paths: - - .bundle - rubocop: - description: "Runs RuboCop with the correct configuration so the results can be parsed by CircleCI" - steps: - - run: - name: "RuboCop" - command: | - bundle exec rubocop --format junit --out tmp/test-results/rubocop/results.xml - minitest: - description: "Runs Minitest with the correct configuration so it runs in parallel and is configured for CircleCI" - steps: - - run: - name: "Minitest Test Suite" - command: | - cc-test-reporter before-build - bin/test - cc-test-reporter after-build -# --------------------------------------------------------------------------------------------------------------------- -# CircleCI Job Configuration -# -# This section defines all the available jobs that can be executed inside a Workflow. -# Think of a Job as a batch of tasks that need to be performed to setup the environment and perform a specific task -# such as running RSpec, uploading the test results to CodeClimate etc. -# --------------------------------------------------------------------------------------------------------------------- -jobs: - tests: - working_directory: ~/vertex_client - docker: - - <<: *docker_image - environment: - RAILS_ENV: test - CC_TEST_REPORTER_ID: 88a7fd75659a6698f28c8c4c6b60c20f902e26733691b2fe449a65474f22b618 - steps: - - checkout - - bundle_install - - minitest - - store_test_results: - path: test/reports/ -workflows: - version: 2.1 - vertex_client: - jobs: - - tests: - context: customink diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..58f6946 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: "CI" +on: push +jobs: + test: + strategy: + fail-fast: false + matrix: + ruby: ["2.5", "2.7", "3.0", "3.1", "3.2"] + gemfile: + - circuitbox_1 + - circuitbox_2 + exclude: + - ruby: "3.2" + gemfile: "circuitbox_1" + - ruby: "3.1" + gemfile: "circuitbox_1" + - ruby: "3.0" + gemfile: "circuitbox_1" + - ruby: "2.5" + gemfile: "circuitbox_2" + + runs-on: ubuntu-22.04 + env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile + BUNDLE_PATH_RELATIVE_TO_CWD: true + steps: + - name: Checkout code + uses: actions/checkout@v3 + # Add or replace dependency steps here + - name: Install Ruby and gems + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + cache-version: 3 + # Add or replace test runners here + - name: Run tests + run: bin/rake test diff --git a/.gitignore b/.gitignore index f289a5e..fb2f56c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /test/reports/ /tmp/ Gemfile.lock +gemfiles/*.gemfile.lock .env.* .ruby-version .byebug_history diff --git a/Appraisals b/Appraisals new file mode 100644 index 0000000..c643aa1 --- /dev/null +++ b/Appraisals @@ -0,0 +1,7 @@ +appraise "circuitbox-1" do + gem "circuitbox", "~> 1.1.1" +end + +appraise "circuitbox-2" do + gem "circuitbox", "~> 2.0.0" +end diff --git a/Gemfile b/Gemfile index 3cd6556..01ee499 100644 --- a/Gemfile +++ b/Gemfile @@ -7,5 +7,4 @@ gemspec group :test do gem 'minitest-ci', require: false - gem 'simplecov', '< 0.18', require: false end diff --git a/Rakefile b/Rakefile index bc58cb3..d433a1e 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,5 @@ require "bundler/gem_tasks" require "rake/testtask" -require 'wwtd/tasks' Rake::TestTask.new(:test) do |t| t.libs << "test" diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000..42c7fd7 --- /dev/null +++ b/bin/bundle @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + bundler_gem_version.approximate_recommendation + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/rake b/bin/rake index 9275675..4eb7d7b 100755 --- a/bin/rake +++ b/bin/rake @@ -8,14 +8,12 @@ # this file is here to facilitate running it. # -require "pathname" -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -bundle_binstub = File.expand_path("../bundle", __FILE__) +bundle_binstub = File.expand_path("bundle", __dir__) if File.file?(bundle_binstub) - if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") load(bundle_binstub) else abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. diff --git a/bin/setup b/bin/setup index df30cb9..ae9cf65 100755 --- a/bin/setup +++ b/bin/setup @@ -2,11 +2,5 @@ set -e cd "$(dirname "$0")/.." -if [[ -z "$CI" ]]; then - # Bundle each ruby version - rm -rf Gemfile.lock - gem install wwtd - wwtd --only-bundle -else - bundle --quiet -fi +bundle --quiet + diff --git a/bin/test b/bin/test index 1bd0b10..6eb5ded 100755 --- a/bin/test +++ b/bin/test @@ -8,5 +8,5 @@ # system('RAILS_ENV=test bin/rails db:setup') puts '== Running tests ==' -command = ENV['CI'] ? 'bundle exec rake' : 'wwtd' +command = 'bundle exec rake' system("#{command}") || abort diff --git a/gemfiles/circuitbox_1.gemfile b/gemfiles/circuitbox_1.gemfile new file mode 100644 index 0000000..1665428 --- /dev/null +++ b/gemfiles/circuitbox_1.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "circuitbox", "~> 1.1.1" + +group :test do + gem "minitest-ci", require: false +end + +gemspec path: "../" diff --git a/gemfiles/circuitbox_2.gemfile b/gemfiles/circuitbox_2.gemfile new file mode 100644 index 0000000..eb1a66f --- /dev/null +++ b/gemfiles/circuitbox_2.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "circuitbox", "~> 2.0.0" + +group :test do + gem "minitest-ci", require: false +end + +gemspec path: "../" diff --git a/lib/vertex_client.rb b/lib/vertex_client.rb index e43432a..82ef774 100644 --- a/lib/vertex_client.rb +++ b/lib/vertex_client.rb @@ -53,10 +53,12 @@ def configuration def reconfigure! @configuration = Configuration.new yield(@configuration) if block_given? + reconfigure_circuitbox end def configure yield(configuration) + reconfigure_circuitbox end def quotation(payload) @@ -76,12 +78,39 @@ def tax_area(payload) end def circuit - return unless configuration.circuit_config && defined?(Circuitbox) + return unless circuit_configured? + Circuitbox.circuit( Configuration::CIRCUIT_NAME, configuration.circuit_config ) end + + private + + def reconfigure_circuitbox + return unless circuitbox_defined? + + if Circuitbox.respond_to?(:reset) + Circuitbox.reset + else + Circuitbox.configure do |config| + config.default_circuit_store = configured_circuit_store + end + end + end + + def configured_circuit_store + (configuration.circuit_config && configuration.circuit_config[:circuit_store]) || Circuitbox::MemoryStore.new + end + + def circuit_configured? + configuration.circuit_config && circuitbox_defined? + end + + def circuitbox_defined? + defined?(Circuitbox) + end end class Error < StandardError; end diff --git a/lib/vertex_client/connection.rb b/lib/vertex_client/connection.rb index a9121a3..06c6d94 100644 --- a/lib/vertex_client/connection.rb +++ b/lib/vertex_client/connection.rb @@ -42,7 +42,7 @@ def resource_config def call_with_circuit_if_available if VertexClient.circuit - VertexClient.circuit.run { yield } + VertexClient.circuit.run(exception: false) { yield } else begin yield diff --git a/lib/vertex_client/version.rb b/lib/vertex_client/version.rb index c04b28a..9646930 100644 --- a/lib/vertex_client/version.rb +++ b/lib/vertex_client/version.rb @@ -1,3 +1,3 @@ module VertexClient - VERSION = '0.10.2' + VERSION = '0.11.0' end diff --git a/test/configuration_test.rb b/test/configuration_test.rb index d231552..2d9a484 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -8,13 +8,11 @@ it 'has a trusted id' do VertexClient.configuration.trusted_id = 'trusted-id' assert_equal 'trusted-id', VertexClient.configuration.trusted_id - VertexClient.reconfigure! end it 'has a soap_api, and adds a trailing slash to it' do VertexClient.configuration.soap_api = 'http://service.example.com' assert_equal 'http://service.example.com/', VertexClient.configuration.soap_api - VertexClient.reconfigure! end it 'has a read_timeout default option and can be set' do @@ -42,10 +40,6 @@ end describe 'circuit_config' do - before do - VertexClient.reconfigure! - end - it 'is an accessible attribute' do VertexClient.configuration.circuit_config = { test: :ok } assert_equal :ok, VertexClient.configuration.circuit_config[:test] diff --git a/test/connection_test.rb b/test/connection_test.rb index 645c129..65e0c6a 100644 --- a/test/connection_test.rb +++ b/test/connection_test.rb @@ -1,5 +1,9 @@ require 'test_helper' describe VertexClient::Connection do + after do + VertexClient.reconfigure! + end + let(:connection) { VertexClient::Connection.new('test', :test) } it 'initializes with an endpoint and resource_key' do diff --git a/test/integration_test.rb b/test/integration_test.rb index 99ca2f7..a779ca9 100644 --- a/test/integration_test.rb +++ b/test/integration_test.rb @@ -3,10 +3,6 @@ describe 'Integration' do include TestInput - before do - VertexClient.reconfigure! - end - after do VertexClient.reconfigure! end @@ -46,7 +42,7 @@ end end end - + it 'does an invoice' do VCR.use_cassette('invoice', :match_requests_on => []) do response = VertexClient.invoice(working_quote_params) @@ -78,7 +74,10 @@ end it 'uses circuit if it is available' do - VertexClient.configuration.circuit_config = {} + VertexClient.reconfigure! do |config| + config.circuit_config = {} + end + VCR.use_cassette('quotation', :match_requests_on => []) do VertexClient.quotation(working_quote_params) end @@ -142,8 +141,9 @@ it 'creates a fallback response for quotation when the circuit is open' do VertexClient.configuration.circuit_config = {} VertexClient.circuit.send(:open!) - assert_kind_of VertexClient::Response::QuotationFallback, - VertexClient.quotation(working_quote_params) + response = VertexClient.quotation(working_quote_params) + VertexClient.circuit.send(:close!) + assert_kind_of VertexClient::Response::QuotationFallback, response end it 'creates a fallback response for quotation when Vertex returns an error' do @@ -159,6 +159,7 @@ assert_raises VertexClient::ServerError do VertexClient.invoice(working_quote_params) end + VertexClient.circuit.send(:close!) end it 'raises if theres an error on invoice and the circuit is closed' do diff --git a/test/payload_validator_test.rb b/test/payload_validator_test.rb index 0d9f913..8a93abf 100644 --- a/test/payload_validator_test.rb +++ b/test/payload_validator_test.rb @@ -3,6 +3,10 @@ describe 'payload validation' do include TestInput + after do + VertexClient.reconfigure! + end + describe 'for incomplete location' do describe 'for US customer' do let(:payload) { working_quote_params } diff --git a/test/test_helper.rb b/test/test_helper.rb index fad76ae..91488fc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,15 +1,7 @@ # frozen_string_literal: true -# SimpleCov configuration always goes first to ensure that we are generating correct code-coverage reports. -# But we only use SimpleCov on the CI System -if ENV.fetch('CI') { false } - require 'simplecov' - SimpleCov.start -end - $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require 'circuitbox' -require "simplecov" require "dotenv/load" require "vertex_client" require "minitest/autorun" @@ -18,8 +10,6 @@ require "mocha/minitest" require 'minitest-ci' if ENV.fetch('CI') { false } -SimpleCov.start - VertexClient.configuration # make sure the client is configured VCR.configure do |config| @@ -34,7 +24,6 @@ def debug(_) end end - module TestInput def self.included(base) base.class_eval do diff --git a/test/vertex_client_test.rb b/test/vertex_client_test.rb index 827c21d..4587a8a 100644 --- a/test/vertex_client_test.rb +++ b/test/vertex_client_test.rb @@ -1,9 +1,12 @@ require 'test_helper' describe VertexClient do - include TestInput + after do + VertexClient.reconfigure! + end + it 'has a version number' do refute_nil ::VertexClient::VERSION end @@ -11,7 +14,6 @@ it 'can be configured with a block' do VertexClient.configure { |config| config.trusted_id = 'trusted-id' } assert_equal 'trusted-id', VertexClient.configuration.trusted_id - VertexClient.reconfigure! end it 'does a quotation' do @@ -43,16 +45,6 @@ end describe 'circuit' do - before do - Circuitbox.reset - VertexClient.reconfigure! - end - - after do - Circuitbox.reset - VertexClient.reconfigure! - end - it 'only exists if circuit_config is provided to configuration' do VertexClient.configuration.circuit_config = nil assert_nil VertexClient.circuit @@ -62,19 +54,19 @@ end it 'can be configured from Configuration#circuit_config' do - logger = Logger.new(STDOUT) VertexClient.configure do |c| - c.circuit_config = { logger: logger } + c.circuit_config = { sleep_window: 1234 } end - assert_equal logger, VertexClient.circuit.circuit_options[:logger] + + assert_equal 1234, VertexClient.circuit.circuit_options[:sleep_window] end it 'opens the circuit' do + VertexClient.reconfigure! + # skip "This test is flaky and I can't figure out how it get it to not be flaky. Do we really need to test that the circuit opens?" VCR.use_cassette('circuit_breaker', allow_playback_repeats: true, match_requests_on: []) do - VertexClient.configuration.circuit_config = { - logger: FakeLogger.new - } + VertexClient.configuration.circuit_config = {} # 'Not Open' means that we are actively hitting the service. VertexClient.configuration.trusted_id = '💩' diff --git a/vertex_client.gemspec b/vertex_client.gemspec index ad3d221..456a8f7 100644 --- a/vertex_client.gemspec +++ b/vertex_client.gemspec @@ -38,6 +38,7 @@ Gem::Specification.new do |spec| spec.add_dependency "activesupport" spec.add_dependency "savon", ">= 2.11" + spec.add_development_dependency "appraisal" spec.add_development_dependency "awesome_print" spec.add_development_dependency "circuitbox" spec.add_development_dependency "bundler" @@ -45,11 +46,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency "dotenv" spec.add_development_dependency "minitest", "~> 5.0" spec.add_development_dependency "mocha" - spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rake" spec.add_development_dependency "rails" - spec.add_development_dependency "simplecov" - spec.add_development_dependency "vcr", "~> 4.0" + spec.add_development_dependency "vcr" spec.add_development_dependency "webmock" - spec.add_development_dependency "wwtd" - end From a3c5ab4c11d5ad1440b64366a2a4917ad960a335 Mon Sep 17 00:00:00 2001 From: Adam Maas Date: Wed, 3 Apr 2024 13:23:45 -0400 Subject: [PATCH 2/2] Adds integration tests for distribute tax --- test/integration_test.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration_test.rb b/test/integration_test.rb index a779ca9..edadc11 100644 --- a/test/integration_test.rb +++ b/test/integration_test.rb @@ -183,4 +183,35 @@ end end end + + it 'raises if the circuit is open on distribute tax' do + VertexClient.configuration.circuit_config = {} + VertexClient.circuit.send(:open!) + assert_raises VertexClient::ServerError do + VertexClient.distribute_tax(distribute_tax_params) + end + VertexClient.circuit.send(:close!) + end + + it 'raises if theres an error on distribute tax and the circuit is closed' do + VertexClient.configuration.circuit_config = {} + resource = VertexClient::Resource::DistributeTax.new(distribute_tax_params) + raises_expection = proc { raise Savon::Error.new('something went wrong') } + resource.send(:connection).send(:client).stub(:call, raises_expection) do + assert_raises VertexClient::ServerError do + resource.result + end + end + end + + it 'raises if theres an error on distribute tax and the circuit is missing' do + assert_nil VertexClient.circuit + resource = VertexClient::Resource::DistributeTax.new(distribute_tax_params) + raises_expection = proc { raise Savon::Error.new('something went wrong') } + resource.send(:connection).send(:client).stub(:call, raises_expection) do + assert_raises VertexClient::ServerError do + resource.result + end + end + end end