opinionated_http 0.0.1 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +53 -0
 - data/Rakefile +7 -7
 - data/lib/opinionated_http.rb +5 -64
 - data/lib/opinionated_http/client.rb +149 -54
 - data/lib/opinionated_http/version.rb +1 -1
 - data/test/client_test.rb +148 -11
 - data/test/test_helper.rb +10 -10
 - metadata +3 -3
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 5c63d8fd6c65797a7afcef26ff0ef72507dcf75f8dbbd4bc93e768cf0ddffc9a
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 0a56d817ad5a0a988886faf17b034df697712c3958719cbcd72558ebb1bd8ee1
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: f38a9f47681824038a271244c560639b119ff9ff138210c93eeb2e9d2e0955b05198f0956d5fba2f1b60b965bf4084e8f3aca0b89af759d6ecf78419d9f05903
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 4b882e2ec2025a9d626c1a3e6d52edff1a20db6fb6dc48bc10203f14117bb1856764f373c4a0ca0bd81f97fc75a4798a68a57b2cdeafd1c3ad7d6b36ef93a14d
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -17,3 +17,56 @@ PersistentHTTP with the following enhancements: 
     | 
|
| 
       17 
17 
     | 
    
         
             
            * Standardized Service Exception.
         
     | 
| 
       18 
18 
     | 
    
         
             
            * Retries on HTTP 5XX errors
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
      
 20 
     | 
    
         
            +
            # Example
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            # Configuration
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            # Usage
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            Create a new Opinionated HTTP instance.
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            Parameters:
         
     | 
| 
      
 29 
     | 
    
         
            +
              secret_config_prefix:
         
     | 
| 
      
 30 
     | 
    
         
            +
                Required
         
     | 
| 
      
 31 
     | 
    
         
            +
              metric_prefix:
         
     | 
| 
      
 32 
     | 
    
         
            +
                Required
         
     | 
| 
      
 33 
     | 
    
         
            +
              error_class:
         
     | 
| 
      
 34 
     | 
    
         
            +
                Whenever exceptions are raised it is important that every client gets its own exception / error class
         
     | 
| 
      
 35 
     | 
    
         
            +
                so that failures to specific http servers can be easily identified.
         
     | 
| 
      
 36 
     | 
    
         
            +
                Required.
         
     | 
| 
      
 37 
     | 
    
         
            +
              logger:
         
     | 
| 
      
 38 
     | 
    
         
            +
                Default: SemanticLogger[OpinionatedHTTP]
         
     | 
| 
      
 39 
     | 
    
         
            +
              Other options as supported by PersistentHTTP
         
     | 
| 
      
 40 
     | 
    
         
            +
              #TODO: Expand PersistentHTTP options here
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            Configuration:
         
     | 
| 
      
 43 
     | 
    
         
            +
               Off of the `secret_config_path` path above, Opinionated HTTP uses specific configuration entry names
         
     | 
| 
      
 44 
     | 
    
         
            +
               to configure the underlying HTTP setup:
         
     | 
| 
      
 45 
     | 
    
         
            +
                  url: [String]
         
     | 
| 
      
 46 
     | 
    
         
            +
                    The host url to the site to connect to.
         
     | 
| 
      
 47 
     | 
    
         
            +
                    Exclude any path, since that will be supplied when `#get` or `#post` is called.
         
     | 
| 
      
 48 
     | 
    
         
            +
                    Required.
         
     | 
| 
      
 49 
     | 
    
         
            +
                    Examples:
         
     | 
| 
      
 50 
     | 
    
         
            +
                      "https://example.com"
         
     | 
| 
      
 51 
     | 
    
         
            +
                      "https://example.com:8443/"
         
     | 
| 
      
 52 
     | 
    
         
            +
                  pool_size: [Integer]
         
     | 
| 
      
 53 
     | 
    
         
            +
                    default: 100
         
     | 
| 
      
 54 
     | 
    
         
            +
                  open_timeout: [Float]
         
     | 
| 
      
 55 
     | 
    
         
            +
                    default: 10
         
     | 
| 
      
 56 
     | 
    
         
            +
                  read_timeout: [Float]
         
     | 
| 
      
 57 
     | 
    
         
            +
                    default: 10
         
     | 
| 
      
 58 
     | 
    
         
            +
                  idle_timeout: [Float]
         
     | 
| 
      
 59 
     | 
    
         
            +
                    default: 300
         
     | 
| 
      
 60 
     | 
    
         
            +
                  keep_alive: [Float]
         
     | 
| 
      
 61 
     | 
    
         
            +
                    default: 300
         
     | 
| 
      
 62 
     | 
    
         
            +
                  pool_timeout: [Float]
         
     | 
| 
      
 63 
     | 
    
         
            +
                    default: 5
         
     | 
| 
      
 64 
     | 
    
         
            +
                  warn_timeout: [Float]
         
     | 
| 
      
 65 
     | 
    
         
            +
                    default: 0.25
         
     | 
| 
      
 66 
     | 
    
         
            +
                  proxy: [Symbol]
         
     | 
| 
      
 67 
     | 
    
         
            +
                    default: :ENV
         
     | 
| 
      
 68 
     | 
    
         
            +
                  force_retry: [true|false]
         
     | 
| 
      
 69 
     | 
    
         
            +
                    default: true
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
            Metrics:
         
     | 
| 
      
 72 
     | 
    
         
            +
              During each call to `#get` or `#put`, the following metrics are logged using the
         
     | 
    
        data/Rakefile
    CHANGED
    
    | 
         @@ -1,23 +1,23 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Setup bundler to avoid having to run bundle exec all the time.
         
     | 
| 
       2 
     | 
    
         
            -
            require  
     | 
| 
       3 
     | 
    
         
            -
            require  
     | 
| 
      
 2 
     | 
    
         
            +
            require "rubygems"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "bundler/setup"
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
            require  
     | 
| 
       6 
     | 
    
         
            -
            require_relative  
     | 
| 
      
 5 
     | 
    
         
            +
            require "rake/testtask"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative "lib/opinionated_http/version"
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
            task :gem do
         
     | 
| 
       9 
     | 
    
         
            -
              system  
     | 
| 
      
 9 
     | 
    
         
            +
              system "gem build opinionated_http.gemspec"
         
     | 
| 
       10 
10 
     | 
    
         
             
            end
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
            task publish: :gem do
         
     | 
| 
       13 
13 
     | 
    
         
             
              system "git tag -a v#{OpinionatedHTTP::VERSION} -m 'Tagging #{OpinionatedHTTP::VERSION}'"
         
     | 
| 
       14 
     | 
    
         
            -
              system  
     | 
| 
      
 14 
     | 
    
         
            +
              system "git push --tags"
         
     | 
| 
       15 
15 
     | 
    
         
             
              system "gem push opinionated_http-#{OpinionatedHTTP::VERSION}.gem"
         
     | 
| 
       16 
16 
     | 
    
         
             
              system "rm opinionated_http-#{OpinionatedHTTP::VERSION}.gem"
         
     | 
| 
       17 
17 
     | 
    
         
             
            end
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
            Rake::TestTask.new(:test) do |t|
         
     | 
| 
       20 
     | 
    
         
            -
              t.pattern =  
     | 
| 
      
 20 
     | 
    
         
            +
              t.pattern = "test/**/*_test.rb"
         
     | 
| 
       21 
21 
     | 
    
         
             
              t.verbose = true
         
     | 
| 
       22 
22 
     | 
    
         
             
              t.warning = false
         
     | 
| 
       23 
23 
     | 
    
         
             
            end
         
     | 
    
        data/lib/opinionated_http.rb
    CHANGED
    
    | 
         @@ -1,76 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "opinionated_http/version"
         
     | 
| 
       1 
2 
     | 
    
         
             
            #
         
     | 
| 
       2 
3 
     | 
    
         
             
            # Opinionated HTTP
         
     | 
| 
       3 
4 
     | 
    
         
             
            #
         
     | 
| 
       4 
5 
     | 
    
         
             
            # An opinionated HTTP Client library using convention over configuration.
         
     | 
| 
       5 
6 
     | 
    
         
             
            #
         
     | 
| 
       6 
     | 
    
         
            -
            # Uses
         
     | 
| 
       7 
     | 
    
         
            -
            # * PersistentHTTP for http connection pooling.
         
     | 
| 
       8 
     | 
    
         
            -
            # * Semantic Logger for logging and metrics.
         
     | 
| 
       9 
     | 
    
         
            -
            # * Secret Config for its configuration.
         
     | 
| 
       10 
     | 
    
         
            -
            #
         
     | 
| 
       11 
     | 
    
         
            -
            # By convention the following metrics are measured and logged:
         
     | 
| 
       12 
     | 
    
         
            -
            # *
         
     | 
| 
       13 
     | 
    
         
            -
            #
         
     | 
| 
       14 
     | 
    
         
            -
            # PersistentHTTP with the following enhancements:
         
     | 
| 
       15 
     | 
    
         
            -
            # * Read config from Secret Config, just supply the `secret_config_path`.
         
     | 
| 
       16 
     | 
    
         
            -
            # * Redirect logging into standard Semantic Logger.
         
     | 
| 
       17 
     | 
    
         
            -
            # * Implements metrics and measure call durations.
         
     | 
| 
       18 
     | 
    
         
            -
            # * Standardized Service Exception.
         
     | 
| 
       19 
     | 
    
         
            -
            # * Retries on HTTP 5XX errors
         
     | 
| 
       20 
     | 
    
         
            -
            #
         
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
            require 'opinionated_http/version'
         
     | 
| 
       23 
7 
     | 
    
         
             
            module OpinionatedHTTP
         
     | 
| 
       24 
     | 
    
         
            -
              autoload :Client,  
     | 
| 
       25 
     | 
    
         
            -
              autoload :Logger,  
     | 
| 
      
 8 
     | 
    
         
            +
              autoload :Client, "opinionated_http/client"
         
     | 
| 
      
 9 
     | 
    
         
            +
              autoload :Logger, "opinionated_http/logger"
         
     | 
| 
       26 
10 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
              # Create a new Opinionated HTTP instance.
         
     | 
| 
       28 
     | 
    
         
            -
              #
         
     | 
| 
       29 
     | 
    
         
            -
              # Parameters:
         
     | 
| 
       30 
     | 
    
         
            -
              #   secret_config_prefix:
         
     | 
| 
       31 
     | 
    
         
            -
              #     Required
         
     | 
| 
       32 
     | 
    
         
            -
              #   metric_prefix:
         
     | 
| 
       33 
     | 
    
         
            -
              #     Required
         
     | 
| 
       34 
     | 
    
         
            -
              #   error_class:
         
     | 
| 
       35 
     | 
    
         
            -
              #     Whenever exceptions are raised it is important that every client gets its own exception / error class
         
     | 
| 
       36 
     | 
    
         
            -
              #     so that failures to specific http servers can be easily identified.
         
     | 
| 
       37 
     | 
    
         
            -
              #     Required.
         
     | 
| 
       38 
     | 
    
         
            -
              #   logger:
         
     | 
| 
       39 
     | 
    
         
            -
              #     Default: SemanticLogger[OpinionatedHTTP]
         
     | 
| 
       40 
     | 
    
         
            -
              #   Other options as supported by PersistentHTTP
         
     | 
| 
       41 
     | 
    
         
            -
              #   #TODO: Expand PersistentHTTP options here
         
     | 
| 
       42 
11 
     | 
    
         
             
              #
         
     | 
| 
       43 
     | 
    
         
            -
              #  
     | 
| 
       44 
     | 
    
         
            -
              #    Off of the `secret_config_path` path above, Opinionated HTTP uses specific configuration entry names
         
     | 
| 
       45 
     | 
    
         
            -
              #    to configure the underlying HTTP setup:
         
     | 
| 
       46 
     | 
    
         
            -
              #       url: [String]
         
     | 
| 
       47 
     | 
    
         
            -
              #         The host url to the site to connect to.
         
     | 
| 
       48 
     | 
    
         
            -
              #         Exclude any path, since that will be supplied when `#get` or `#post` is called.
         
     | 
| 
       49 
     | 
    
         
            -
              #         Required.
         
     | 
| 
       50 
     | 
    
         
            -
              #         Examples:
         
     | 
| 
       51 
     | 
    
         
            -
              #           "https://example.com"
         
     | 
| 
       52 
     | 
    
         
            -
              #           "https://example.com:8443/"
         
     | 
| 
       53 
     | 
    
         
            -
              #       pool_size: [Integer]
         
     | 
| 
       54 
     | 
    
         
            -
              #         default: 100
         
     | 
| 
       55 
     | 
    
         
            -
              #       open_timeout: [Float]
         
     | 
| 
       56 
     | 
    
         
            -
              #         default: 10
         
     | 
| 
       57 
     | 
    
         
            -
              #       read_timeout: [Float]
         
     | 
| 
       58 
     | 
    
         
            -
              #         default: 10
         
     | 
| 
       59 
     | 
    
         
            -
              #       idle_timeout: [Float]
         
     | 
| 
       60 
     | 
    
         
            -
              #         default: 300
         
     | 
| 
       61 
     | 
    
         
            -
              #       keep_alive: [Float]
         
     | 
| 
       62 
     | 
    
         
            -
              #         default: 300
         
     | 
| 
       63 
     | 
    
         
            -
              #       pool_timeout: [Float]
         
     | 
| 
       64 
     | 
    
         
            -
              #         default: 5
         
     | 
| 
       65 
     | 
    
         
            -
              #       warn_timeout: [Float]
         
     | 
| 
       66 
     | 
    
         
            -
              #         default: 0.25
         
     | 
| 
       67 
     | 
    
         
            -
              #       proxy: [Symbol]
         
     | 
| 
       68 
     | 
    
         
            -
              #         default: :ENV
         
     | 
| 
       69 
     | 
    
         
            -
              #       force_retry: [true|false]
         
     | 
| 
       70 
     | 
    
         
            -
              #         default: true
         
     | 
| 
      
 12 
     | 
    
         
            +
              # Create a new Opinionated HTTP instance.
         
     | 
| 
       71 
13 
     | 
    
         
             
              #
         
     | 
| 
       72 
     | 
    
         
            -
              #  
     | 
| 
       73 
     | 
    
         
            -
              #   During each call to `#get` or `#put`, the following metrics are logged using the
         
     | 
| 
      
 14 
     | 
    
         
            +
              # See README.md for more info.
         
     | 
| 
       74 
15 
     | 
    
         
             
              def self.new(**args)
         
     | 
| 
       75 
16 
     | 
    
         
             
                Client.new(**args)
         
     | 
| 
       76 
17 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,32 +1,80 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require  
     | 
| 
       2 
     | 
    
         
            -
            require  
     | 
| 
       3 
     | 
    
         
            -
            require  
     | 
| 
      
 1 
     | 
    
         
            +
            require "persistent_http"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "secret_config"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "semantic_logger"
         
     | 
| 
       4 
4 
     | 
    
         
             
            #
         
     | 
| 
       5 
5 
     | 
    
         
             
            # Client http implementation
         
     | 
| 
       6 
6 
     | 
    
         
             
            #
         
     | 
| 
      
 7 
     | 
    
         
            +
            # See README.md for more info.
         
     | 
| 
       7 
8 
     | 
    
         
             
            module OpinionatedHTTP
         
     | 
| 
       8 
9 
     | 
    
         
             
              class Client
         
     | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
      
 10 
     | 
    
         
            +
                # 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout
         
     | 
| 
      
 11 
     | 
    
         
            +
                HTTP_RETRY_CODES = %w[502 503 504].freeze
         
     | 
| 
       10 
12 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
      
 13 
     | 
    
         
            +
                attr_reader :secret_config_prefix, :logger, :metric_prefix, :error_class, :driver,
         
     | 
| 
      
 14 
     | 
    
         
            +
                            :retry_count, :retry_interval, :retry_multiplier, :http_retry_codes,
         
     | 
| 
      
 15 
     | 
    
         
            +
                            :url, :pool_size, :keep_alive, :proxy, :force_retry, :max_redirects,
         
     | 
| 
      
 16 
     | 
    
         
            +
                            :open_timeout, :read_timeout, :idle_timeout, :pool_timeout, :warn_timeout
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # Any option supplied here can be overridden if that corresponding value is set in Secret Config.
         
     | 
| 
      
 19 
     | 
    
         
            +
                # Except for any values passed directly to Persistent HTTP under `**options`.
         
     | 
| 
      
 20 
     | 
    
         
            +
                def initialize(
         
     | 
| 
      
 21 
     | 
    
         
            +
                  secret_config_prefix:,
         
     | 
| 
      
 22 
     | 
    
         
            +
                  metric_prefix:,
         
     | 
| 
      
 23 
     | 
    
         
            +
                  error_class:,
         
     | 
| 
      
 24 
     | 
    
         
            +
                  logger: nil,
         
     | 
| 
      
 25 
     | 
    
         
            +
                  retry_count: 11,
         
     | 
| 
      
 26 
     | 
    
         
            +
                  retry_interval: 0.01,
         
     | 
| 
      
 27 
     | 
    
         
            +
                  retry_multiplier: 1.8,
         
     | 
| 
      
 28 
     | 
    
         
            +
                  http_retry_codes: HTTP_RETRY_CODES.join(","),
         
     | 
| 
      
 29 
     | 
    
         
            +
                  url: nil,
         
     | 
| 
      
 30 
     | 
    
         
            +
                  pool_size: 100,
         
     | 
| 
      
 31 
     | 
    
         
            +
                  open_timeout: 10,
         
     | 
| 
      
 32 
     | 
    
         
            +
                  read_timeout: 10,
         
     | 
| 
      
 33 
     | 
    
         
            +
                  idle_timeout: 300,
         
     | 
| 
      
 34 
     | 
    
         
            +
                  keep_alive: 300,
         
     | 
| 
      
 35 
     | 
    
         
            +
                  pool_timeout: 5,
         
     | 
| 
      
 36 
     | 
    
         
            +
                  warn_timeout: 0.25,
         
     | 
| 
      
 37 
     | 
    
         
            +
                  proxy: :ENV,
         
     | 
| 
      
 38 
     | 
    
         
            +
                  force_retry: true,
         
     | 
| 
      
 39 
     | 
    
         
            +
                  max_redirects: 10,
         
     | 
| 
      
 40 
     | 
    
         
            +
                  **options
         
     | 
| 
      
 41 
     | 
    
         
            +
                )
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @metric_prefix    = metric_prefix
         
     | 
| 
      
 43 
     | 
    
         
            +
                  @logger           = logger || SemanticLogger[self]
         
     | 
| 
      
 44 
     | 
    
         
            +
                  @error_class      = error_class
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @retry_count      = SecretConfig.fetch("#{secret_config_prefix}/retry_count", type: :integer, default: retry_count)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @retry_interval   = SecretConfig.fetch("#{secret_config_prefix}/retry_interval", type: :float, default: retry_interval)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  @retry_multiplier = SecretConfig.fetch("#{secret_config_prefix}/retry_multiplier", type: :float, default: retry_multiplier)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @max_redirects    = SecretConfig.fetch("#{secret_config_prefix}/max_redirects", type: :integer, default: max_redirects)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  http_retry_codes  = SecretConfig.fetch("#{secret_config_prefix}/http_retry_codes", type: :string, default: http_retry_codes)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  @http_retry_codes = http_retry_codes.split(",").collect(&:strip)
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  @url = url.nil? ? SecretConfig["#{secret_config_prefix}/url"] : SecretConfig.fetch("#{secret_config_prefix}/url", default: url)
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  @pool_size    = SecretConfig.fetch("#{secret_config_prefix}/pool_size", type: :integer, default: pool_size)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  @open_timeout = SecretConfig.fetch("#{secret_config_prefix}/open_timeout", type: :float, default: open_timeout)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @read_timeout = SecretConfig.fetch("#{secret_config_prefix}/read_timeout", type: :float, default: read_timeout)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  @idle_timeout = SecretConfig.fetch("#{secret_config_prefix}/idle_timeout", type: :float, default: idle_timeout)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  @keep_alive   = SecretConfig.fetch("#{secret_config_prefix}/keep_alive", type: :float, default: keep_alive)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @pool_timeout = SecretConfig.fetch("#{secret_config_prefix}/pool_timeout", type: :float, default: pool_timeout)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @warn_timeout = SecretConfig.fetch("#{secret_config_prefix}/warn_timeout", type: :float, default: warn_timeout)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  @proxy        = SecretConfig.fetch("#{secret_config_prefix}/proxy", type: :symbol, default: proxy)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  @force_retry  = SecretConfig.fetch("#{secret_config_prefix}/force_retry", type: :boolean, default: force_retry)
         
     | 
| 
       15 
63 
     | 
    
         | 
| 
       16 
64 
     | 
    
         
             
                  internal_logger = OpinionatedHTTP::Logger.new(@logger)
         
     | 
| 
       17 
65 
     | 
    
         
             
                  new_options     = {
         
     | 
| 
       18 
66 
     | 
    
         
             
                    logger:       internal_logger,
         
     | 
| 
       19 
67 
     | 
    
         
             
                    debug_output: internal_logger,
         
     | 
| 
       20 
68 
     | 
    
         
             
                    name:         "",
         
     | 
| 
       21 
     | 
    
         
            -
                    pool_size:     
     | 
| 
       22 
     | 
    
         
            -
                    open_timeout:  
     | 
| 
       23 
     | 
    
         
            -
                    read_timeout:  
     | 
| 
       24 
     | 
    
         
            -
                    idle_timeout:  
     | 
| 
       25 
     | 
    
         
            -
                    keep_alive:    
     | 
| 
       26 
     | 
    
         
            -
                    pool_timeout:  
     | 
| 
       27 
     | 
    
         
            -
                    warn_timeout:  
     | 
| 
       28 
     | 
    
         
            -
                    proxy:         
     | 
| 
       29 
     | 
    
         
            -
                    force_retry:   
     | 
| 
      
 69 
     | 
    
         
            +
                    pool_size:    @pool_size,
         
     | 
| 
      
 70 
     | 
    
         
            +
                    open_timeout: @open_timeout,
         
     | 
| 
      
 71 
     | 
    
         
            +
                    read_timeout: @read_timeout,
         
     | 
| 
      
 72 
     | 
    
         
            +
                    idle_timeout: @idle_timeout,
         
     | 
| 
      
 73 
     | 
    
         
            +
                    keep_alive:   @keep_alive,
         
     | 
| 
      
 74 
     | 
    
         
            +
                    pool_timeout: @pool_timeout,
         
     | 
| 
      
 75 
     | 
    
         
            +
                    warn_timeout: @warn_timeout,
         
     | 
| 
      
 76 
     | 
    
         
            +
                    proxy:        @proxy,
         
     | 
| 
      
 77 
     | 
    
         
            +
                    force_retry:  @force_retry
         
     | 
| 
       30 
78 
     | 
    
         
             
                  }
         
     | 
| 
       31 
79 
     | 
    
         | 
| 
       32 
80 
     | 
    
         
             
                  url               = SecretConfig["#{secret_config_prefix}/url"]
         
     | 
| 
         @@ -35,64 +83,111 @@ module OpinionatedHTTP 
     | 
|
| 
       35 
83 
     | 
    
         
             
                end
         
     | 
| 
       36 
84 
     | 
    
         | 
| 
       37 
85 
     | 
    
         
             
                # Perform an HTTP Get against the supplied path
         
     | 
| 
       38 
     | 
    
         
            -
                def get(action:, path: "/#{action}",  
     | 
| 
      
 86 
     | 
    
         
            +
                def get(action:, path: "/#{action}", **args)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  request  = build_request(path: path, verb: "Get", **args)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  response = request(action: action, request: request)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  extract_body(response, 'GET', action)
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def post(action:, path: "/#{action}", **args)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  request = build_request(path: path, verb: "Post", **args)
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  response = request(action: action, request: request)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  extract_body(response, 'POST', action)
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                def build_request(verb:, path:, headers: nil, body: nil, form_data: nil, username: nil, password: nil, parameters: nil)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  unless headers_and_form_data_compatible?(headers, form_data)
         
     | 
| 
      
 101 
     | 
    
         
            +
                    raise(ArgumentError, "Setting form data will overwrite supplied content-type")
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
                  raise(ArgumentError, "Cannot supply both form_data and a body") if body && form_data
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
       39 
105 
     | 
    
         
             
                  path = "/#{path}" unless path.start_with?("/")
         
     | 
| 
       40 
106 
     | 
    
         
             
                  path = "#{path}?#{URI.encode_www_form(parameters)}" if parameters
         
     | 
| 
       41 
107 
     | 
    
         | 
| 
       42 
     | 
    
         
            -
                  request 
     | 
| 
       43 
     | 
    
         
            -
                  response =
         
     | 
| 
       44 
     | 
    
         
            -
                    begin
         
     | 
| 
       45 
     | 
    
         
            -
                      payload = {}
         
     | 
| 
       46 
     | 
    
         
            -
                      if logger.trace?
         
     | 
| 
       47 
     | 
    
         
            -
                        payload[:parameters] = parameters
         
     | 
| 
       48 
     | 
    
         
            -
                        payload[:path]       = path
         
     | 
| 
       49 
     | 
    
         
            -
                      end
         
     | 
| 
       50 
     | 
    
         
            -
                      message = "HTTP GET: #{action}" if logger.debug?
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                      logger.benchmark_info(message: message, metric: "#{metric_prefix}/#{action}", payload: payload) { driver.request(request) }
         
     | 
| 
       53 
     | 
    
         
            -
                    rescue StandardError => exc
         
     | 
| 
       54 
     | 
    
         
            -
                      message = "HTTP GET: #{action} Failure: #{exc.class.name}: #{exc.message}"
         
     | 
| 
       55 
     | 
    
         
            -
                      logger.error(message: message, metric: "#{metric_prefix}/exception", exception: exc)
         
     | 
| 
       56 
     | 
    
         
            -
                      raise(error_class, message)
         
     | 
| 
       57 
     | 
    
         
            -
                    end
         
     | 
| 
      
 108 
     | 
    
         
            +
                  request = Net::HTTP.const_get(verb).new(path, headers)
         
     | 
| 
       58 
109 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                   
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
                     
     | 
| 
       62 
     | 
    
         
            -
                    raise(error_class, message)
         
     | 
| 
      
 110 
     | 
    
         
            +
                  raise(ArgumentError, "#{request.class.name} does not support a request body") if body && !request.request_body_permitted?
         
     | 
| 
      
 111 
     | 
    
         
            +
                  if parameters && !request.response_body_permitted?
         
     | 
| 
      
 112 
     | 
    
         
            +
                    raise(ArgumentError, ":parameters cannot be supplied for #{request.class.name}")
         
     | 
| 
       63 
113 
     | 
    
         
             
                  end
         
     | 
| 
       64 
114 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
                   
     | 
| 
      
 115 
     | 
    
         
            +
                  request.body = body if body
         
     | 
| 
      
 116 
     | 
    
         
            +
                  request.set_form_data form_data if form_data
         
     | 
| 
      
 117 
     | 
    
         
            +
                  request.basic_auth(username, password) if username && password
         
     | 
| 
      
 118 
     | 
    
         
            +
                  request
         
     | 
| 
       66 
119 
     | 
    
         
             
                end
         
     | 
| 
       67 
120 
     | 
    
         | 
| 
       68 
     | 
    
         
            -
                 
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
      
 121 
     | 
    
         
            +
                # Returns [HTTP Response] after submitting the request
         
     | 
| 
      
 122 
     | 
    
         
            +
                #
         
     | 
| 
      
 123 
     | 
    
         
            +
                # Notes:
         
     | 
| 
      
 124 
     | 
    
         
            +
                # - Does not raise an exception when the http response is not an HTTP OK (200.
         
     | 
| 
      
 125 
     | 
    
         
            +
                def request(action:, request:)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  request_with_retry(action: action, request: request)
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
       72 
128 
     | 
    
         | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
      
 129 
     | 
    
         
            +
                private
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                def request_with_retry(action:, request:, try_count: 0)
         
     | 
| 
      
 132 
     | 
    
         
            +
                  http_method = request.method.upcase
         
     | 
| 
      
 133 
     | 
    
         
            +
                  response    =
         
     | 
| 
       74 
134 
     | 
    
         
             
                    begin
         
     | 
| 
       75 
135 
     | 
    
         
             
                      payload = {}
         
     | 
| 
       76 
136 
     | 
    
         
             
                      if logger.trace?
         
     | 
| 
       77 
137 
     | 
    
         
             
                        payload[:parameters] = parameters
         
     | 
| 
       78 
     | 
    
         
            -
                        payload[:path]       = path
         
     | 
| 
      
 138 
     | 
    
         
            +
                        payload[:path]       = request.path
         
     | 
| 
       79 
139 
     | 
    
         
             
                      end
         
     | 
| 
       80 
     | 
    
         
            -
                      message = "HTTP  
     | 
| 
      
 140 
     | 
    
         
            +
                      message = "HTTP #{http_method}: #{action}" if logger.debug?
         
     | 
| 
       81 
141 
     | 
    
         | 
| 
       82 
142 
     | 
    
         
             
                      logger.benchmark_info(message: message, metric: "#{metric_prefix}/#{action}", payload: payload) { driver.request(request) }
         
     | 
| 
       83 
     | 
    
         
            -
                    rescue StandardError =>  
     | 
| 
       84 
     | 
    
         
            -
                      message = "HTTP  
     | 
| 
       85 
     | 
    
         
            -
                      logger.error(message: message, metric: "#{metric_prefix}/exception", exception:  
     | 
| 
      
 143 
     | 
    
         
            +
                    rescue StandardError => e
         
     | 
| 
      
 144 
     | 
    
         
            +
                      message = "HTTP #{http_method}: #{action} Failure: #{e.class.name}: #{e.message}"
         
     | 
| 
      
 145 
     | 
    
         
            +
                      logger.error(message: message, metric: "#{metric_prefix}/exception", exception: e)
         
     | 
| 
       86 
146 
     | 
    
         
             
                      raise(error_class, message)
         
     | 
| 
       87 
147 
     | 
    
         
             
                    end
         
     | 
| 
       88 
148 
     | 
    
         | 
| 
       89 
     | 
    
         
            -
                   
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
                     
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
      
 149 
     | 
    
         
            +
                  # Retry on http 5xx errors except 500 which means internal server error.
         
     | 
| 
      
 150 
     | 
    
         
            +
                  if http_retry_codes.include?(response.code)
         
     | 
| 
      
 151 
     | 
    
         
            +
                    if try_count < retry_count
         
     | 
| 
      
 152 
     | 
    
         
            +
                      try_count += 1
         
     | 
| 
      
 153 
     | 
    
         
            +
                      duration = retry_sleep_interval(try_count)
         
     | 
| 
      
 154 
     | 
    
         
            +
                      logger.warn(message: "HTTP #{http_method}: #{action} Failure: (#{response.code}) #{response.message}. Retry: #{try_count}", metric: "#{metric_prefix}/retry", duration: duration * 1_000)
         
     | 
| 
      
 155 
     | 
    
         
            +
                      sleep(duration)
         
     | 
| 
      
 156 
     | 
    
         
            +
                      response = request_with_retry(action: action, request: request, try_count: try_count)
         
     | 
| 
      
 157 
     | 
    
         
            +
                    else
         
     | 
| 
      
 158 
     | 
    
         
            +
                      message = "HTTP #{http_method}: #{action} Failure: (#{response.code}) #{response.message}. Retries Exhausted"
         
     | 
| 
      
 159 
     | 
    
         
            +
                      logger.error(message: message, metric: "#{metric_prefix}/exception")
         
     | 
| 
      
 160 
     | 
    
         
            +
                      raise(error_class, message)
         
     | 
| 
      
 161 
     | 
    
         
            +
                    end
         
     | 
| 
       93 
162 
     | 
    
         
             
                  end
         
     | 
| 
       94 
163 
     | 
    
         | 
| 
       95 
     | 
    
         
            -
                  response 
     | 
| 
      
 164 
     | 
    
         
            +
                  response
         
     | 
| 
      
 165 
     | 
    
         
            +
                end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                def extract_body(response, http_method, action)
         
     | 
| 
      
 168 
     | 
    
         
            +
                  return response.body if response.is_a?(Net::HTTPSuccess)
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                  message = "HTTP #{http_method}: #{action} Failure: (#{response.code}) #{response.message}"
         
     | 
| 
      
 171 
     | 
    
         
            +
                  logger.error(message: message, metric: "#{metric_prefix}/exception")
         
     | 
| 
      
 172 
     | 
    
         
            +
                  raise(error_class, message)
         
     | 
| 
      
 173 
     | 
    
         
            +
                end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                def prefix_path(path)
         
     | 
| 
      
 176 
     | 
    
         
            +
                  path.start_with?("/") ? path : "/#{path}"
         
     | 
| 
      
 177 
     | 
    
         
            +
                end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                # First retry is immediate, next retry is after `retry_interval`,
         
     | 
| 
      
 180 
     | 
    
         
            +
                # each subsequent retry interval is 100% longer than the prior interval.
         
     | 
| 
      
 181 
     | 
    
         
            +
                def retry_sleep_interval(retry_count)
         
     | 
| 
      
 182 
     | 
    
         
            +
                  return 0 if retry_count <= 1
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                  (retry_multiplier**(retry_count - 1)) * retry_interval
         
     | 
| 
      
 185 
     | 
    
         
            +
                end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                def headers_and_form_data_compatible?(headers, form_data)
         
     | 
| 
      
 188 
     | 
    
         
            +
                  return true if headers.nil? || form_data.nil?
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                  !headers.keys.map(&:downcase).include?("content-type")
         
     | 
| 
       96 
191 
     | 
    
         
             
                end
         
     | 
| 
       97 
192 
     | 
    
         
             
              end
         
     | 
| 
       98 
193 
     | 
    
         
             
            end
         
     | 
    
        data/test/client_test.rb
    CHANGED
    
    | 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require  
     | 
| 
       2 
     | 
    
         
            -
            require  
     | 
| 
       3 
     | 
    
         
            -
            require_relative  
     | 
| 
      
 1 
     | 
    
         
            +
            require "net/http"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "json"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative "test_helper"
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module OpinionatedHTTP
         
     | 
| 
       6 
6 
     | 
    
         
             
              class ClientTest < Minitest::Test
         
     | 
| 
         @@ -10,22 +10,159 @@ module OpinionatedHTTP 
     | 
|
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
                  let :http do
         
     | 
| 
       12 
12 
     | 
    
         
             
                    OpinionatedHTTP.new(
         
     | 
| 
       13 
     | 
    
         
            -
                      secret_config_prefix:  
     | 
| 
       14 
     | 
    
         
            -
                      metric_prefix:         
     | 
| 
      
 13 
     | 
    
         
            +
                      secret_config_prefix: "fake_service",
         
     | 
| 
      
 14 
     | 
    
         
            +
                      metric_prefix:        "FakeService",
         
     | 
| 
       15 
15 
     | 
    
         
             
                      logger:               SemanticLogger["FakeService"],
         
     | 
| 
       16 
16 
     | 
    
         
             
                      error_class:          ServiceError,
         
     | 
| 
       17 
     | 
    
         
            -
                      header:               { 
     | 
| 
      
 17 
     | 
    
         
            +
                      header:               {"Content-Type" => "application/json"}
         
     | 
| 
       18 
18 
     | 
    
         
             
                    )
         
     | 
| 
       19 
19 
     | 
    
         
             
                  end
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
21 
     | 
    
         
             
                  describe "get" do
         
     | 
| 
       22 
     | 
    
         
            -
                    it  
     | 
| 
       23 
     | 
    
         
            -
                      output   = {zip:  
     | 
| 
      
 22 
     | 
    
         
            +
                    it "succeeds" do
         
     | 
| 
      
 23 
     | 
    
         
            +
                      output   = {zip: "12345", population: 54_321}
         
     | 
| 
       24 
24 
     | 
    
         
             
                      body     = output.to_json
         
     | 
| 
       25 
     | 
    
         
            -
                      response = Net::HTTPSuccess 
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                        http.get(action: 'lookup', parameters: {zip: '12345'})
         
     | 
| 
      
 25 
     | 
    
         
            +
                      response = stub_request(Net::HTTPSuccess, 200, "OK", body) do
         
     | 
| 
      
 26 
     | 
    
         
            +
                        http.get(action: "lookup", parameters: {zip: "12345"})
         
     | 
| 
       28 
27 
     | 
    
         
             
                      end
         
     | 
| 
      
 28 
     | 
    
         
            +
                      assert_equal body, response
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                    it "fails" do
         
     | 
| 
      
 32 
     | 
    
         
            +
                      message = "HTTP GET: lookup Failure: (403) Forbidden"
         
     | 
| 
      
 33 
     | 
    
         
            +
                      error = assert_raises ServiceError do
         
     | 
| 
      
 34 
     | 
    
         
            +
                        stub_request(Net::HTTPForbidden, 403, "Forbidden", "") do
         
     | 
| 
      
 35 
     | 
    
         
            +
                          http.get(action: "lookup", parameters: {zip: "12345"})
         
     | 
| 
      
 36 
     | 
    
         
            +
                        end
         
     | 
| 
      
 37 
     | 
    
         
            +
                      end
         
     | 
| 
      
 38 
     | 
    
         
            +
                      assert_equal message, error.message
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  describe "post" do
         
     | 
| 
      
 43 
     | 
    
         
            +
                    it "succeeds with body" do
         
     | 
| 
      
 44 
     | 
    
         
            +
                      output   = {zip: "12345", population: 54_321}
         
     | 
| 
      
 45 
     | 
    
         
            +
                      body     = output.to_json
         
     | 
| 
      
 46 
     | 
    
         
            +
                      response = stub_request(Net::HTTPSuccess, 200, "OK", body) do
         
     | 
| 
      
 47 
     | 
    
         
            +
                        http.post(action: "lookup", body: body)
         
     | 
| 
      
 48 
     | 
    
         
            +
                      end
         
     | 
| 
      
 49 
     | 
    
         
            +
                      assert_equal body, response
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    it "with form data" do
         
     | 
| 
      
 53 
     | 
    
         
            +
                      output   = {zip: "12345", population: 54_321}
         
     | 
| 
      
 54 
     | 
    
         
            +
                      body     = output.to_json
         
     | 
| 
      
 55 
     | 
    
         
            +
                      response = stub_request(Net::HTTPSuccess, 200, "OK", body) do
         
     | 
| 
      
 56 
     | 
    
         
            +
                        http.post(action: "lookup", form_data: output)
         
     | 
| 
      
 57 
     | 
    
         
            +
                      end
         
     | 
| 
      
 58 
     | 
    
         
            +
                      assert_equal body, response
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                    it "fails with body" do
         
     | 
| 
      
 62 
     | 
    
         
            +
                      message = "HTTP POST: lookup Failure: (403) Forbidden"
         
     | 
| 
      
 63 
     | 
    
         
            +
                      output  = {zip: "12345", population: 54_321}
         
     | 
| 
      
 64 
     | 
    
         
            +
                      body    = output.to_json
         
     | 
| 
      
 65 
     | 
    
         
            +
                      error   = assert_raises ServiceError do
         
     | 
| 
      
 66 
     | 
    
         
            +
                        stub_request(Net::HTTPForbidden, 403, "Forbidden", "") do
         
     | 
| 
      
 67 
     | 
    
         
            +
                          http.post(action: "lookup", body: body)
         
     | 
| 
      
 68 
     | 
    
         
            +
                        end
         
     | 
| 
      
 69 
     | 
    
         
            +
                      end
         
     | 
| 
      
 70 
     | 
    
         
            +
                      assert_equal message, error.message
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                    it "fails with form data" do
         
     | 
| 
      
 74 
     | 
    
         
            +
                      output  = {zip: "12345", population: 54_321}
         
     | 
| 
      
 75 
     | 
    
         
            +
                      message = "HTTP POST: lookup Failure: (403) Forbidden"
         
     | 
| 
      
 76 
     | 
    
         
            +
                      error   = assert_raises ServiceError do
         
     | 
| 
      
 77 
     | 
    
         
            +
                        stub_request(Net::HTTPForbidden, 403, "Forbidden", "") do
         
     | 
| 
      
 78 
     | 
    
         
            +
                          http.post(action: "lookup", form_data: output)
         
     | 
| 
      
 79 
     | 
    
         
            +
                        end
         
     | 
| 
      
 80 
     | 
    
         
            +
                      end
         
     | 
| 
      
 81 
     | 
    
         
            +
                      assert_equal message, error.message
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  describe "build_request" do
         
     | 
| 
      
 86 
     | 
    
         
            +
                    let(:path) { "/fake_action" }
         
     | 
| 
      
 87 
     | 
    
         
            +
                    let(:post_verb) { "Post" }
         
     | 
| 
      
 88 
     | 
    
         
            +
                    let(:get_verb) { "Get" }
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                    it "creates a request corresponding to the supplied verb" do
         
     | 
| 
      
 91 
     | 
    
         
            +
                      req  = http.build_request(path: path, verb: post_verb)
         
     | 
| 
      
 92 
     | 
    
         
            +
                      req2 = http.build_request(path: path, verb: get_verb)
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                      assert_kind_of Net::HTTP::Post, req
         
     | 
| 
      
 95 
     | 
    
         
            +
                      assert_kind_of Net::HTTP::Get, req2
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                    it "returns a request with supplied headers" do
         
     | 
| 
      
 99 
     | 
    
         
            +
                      test_headers = {"test1" => "yes_test_1", "test2" => "yes_test_2"}
         
     | 
| 
      
 100 
     | 
    
         
            +
                      req          = http.build_request(path: path, verb: get_verb, headers: test_headers)
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                      assert_equal test_headers["test1"], req["test1"]
         
     | 
| 
      
 103 
     | 
    
         
            +
                      assert_equal test_headers["test2"], req["test2"]
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                    it "returns a request with supplied body" do
         
     | 
| 
      
 107 
     | 
    
         
            +
                      test_body = "nice bod"
         
     | 
| 
      
 108 
     | 
    
         
            +
                      req       = http.build_request(path: path, verb: post_verb, body: test_body)
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                      assert_equal test_body, req.body
         
     | 
| 
      
 111 
     | 
    
         
            +
                    end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                    it "returns a request with supplied form data in x-www-form-urlencoded Content-Type" do
         
     | 
| 
      
 114 
     | 
    
         
            +
                      test_data       = {test1: "yes", test2: "no"}
         
     | 
| 
      
 115 
     | 
    
         
            +
                      expected_string = "test1=yes&test2=no"
         
     | 
| 
      
 116 
     | 
    
         
            +
                      req             = http.build_request(path: path, verb: post_verb, form_data: test_data)
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                      assert_equal expected_string, req.body
         
     | 
| 
      
 119 
     | 
    
         
            +
                      assert_equal "application/x-www-form-urlencoded", req["Content-Type"]
         
     | 
| 
      
 120 
     | 
    
         
            +
                    end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                    it "add supplied authentication to the request" do
         
     | 
| 
      
 123 
     | 
    
         
            +
                      test_un = "admin"
         
     | 
| 
      
 124 
     | 
    
         
            +
                      test_pw = "hunter2"
         
     | 
| 
      
 125 
     | 
    
         
            +
                      req     = http.build_request(path: path, verb: get_verb, username: test_un, password: test_pw)
         
     | 
| 
      
 126 
     | 
    
         
            +
                      req2    = Net::HTTP::Get.new(path)
         
     | 
| 
      
 127 
     | 
    
         
            +
                      req2.basic_auth test_un, test_pw
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                      assert_equal req2["authorization"], req["authorization"]
         
     | 
| 
      
 130 
     | 
    
         
            +
                    end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                    it "raise an error if supplied content-type header would be overwritten by setting form_data" do
         
     | 
| 
      
 133 
     | 
    
         
            +
                      downcase_headers    = {"unimportant" => "blank", "content-type" => "application/json"}
         
     | 
| 
      
 134 
     | 
    
         
            +
                      capitalized_headers = {"Unimportant" => "blank", "Content-Type" => "application/json"}
         
     | 
| 
      
 135 
     | 
    
         
            +
                      no_conflict_headers = {"whatever" => "blank", "irrelevant" => "test"}
         
     | 
| 
      
 136 
     | 
    
         
            +
                      form_data           = {thing1: 1, thing2: 2}
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                      assert_raises ArgumentError do
         
     | 
| 
      
 139 
     | 
    
         
            +
                        http.build_request(path: path, verb: post_verb, headers: downcase_headers, form_data: form_data)
         
     | 
| 
      
 140 
     | 
    
         
            +
                      end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                      assert_raises ArgumentError do
         
     | 
| 
      
 143 
     | 
    
         
            +
                        http.build_request(path: path, verb: post_verb, headers: capitalized_headers, form_data: form_data)
         
     | 
| 
      
 144 
     | 
    
         
            +
                      end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                      assert http.build_request(path: path, verb: post_verb, headers: no_conflict_headers, form_data: form_data)
         
     | 
| 
      
 147 
     | 
    
         
            +
                    end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                    it "raise an error if there is a collision between supplied body and form_data" do
         
     | 
| 
      
 150 
     | 
    
         
            +
                      form_data = {thing1: 1, thing2: 2}
         
     | 
| 
      
 151 
     | 
    
         
            +
                      body      = "not form data"
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                      assert_raises ArgumentError do
         
     | 
| 
      
 154 
     | 
    
         
            +
                        http.build_request(path: path, verb: post_verb, body: body, form_data: form_data)
         
     | 
| 
      
 155 
     | 
    
         
            +
                      end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                      assert http.build_request(path: path, verb: post_verb, body: body)
         
     | 
| 
      
 158 
     | 
    
         
            +
                      assert http.build_request(path: path, verb: post_verb, form_data: form_data)
         
     | 
| 
      
 159 
     | 
    
         
            +
                    end
         
     | 
| 
      
 160 
     | 
    
         
            +
                  end
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                  def stub_request(klass, code, msg, body, &block)
         
     | 
| 
      
 163 
     | 
    
         
            +
                    response = klass.new("1.1", code, msg)
         
     | 
| 
      
 164 
     | 
    
         
            +
                    response.stub(:body, body) do
         
     | 
| 
      
 165 
     | 
    
         
            +
                      http.driver.stub(:request, response, &block)
         
     | 
| 
       29 
166 
     | 
    
         
             
                    end
         
     | 
| 
       30 
167 
     | 
    
         
             
                  end
         
     | 
| 
       31 
168 
     | 
    
         
             
                end
         
     | 
    
        data/test/test_helper.rb
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            $LOAD_PATH.unshift File.dirname(__FILE__) +  
     | 
| 
       2 
     | 
    
         
            -
            ENV[ 
     | 
| 
      
 1 
     | 
    
         
            +
            $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
         
     | 
| 
      
 2 
     | 
    
         
            +
            ENV["TZ"] = "America/New_York"
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
            require  
     | 
| 
       5 
     | 
    
         
            -
            require  
     | 
| 
       6 
     | 
    
         
            -
            require  
     | 
| 
       7 
     | 
    
         
            -
            require  
     | 
| 
       8 
     | 
    
         
            -
            require  
     | 
| 
       9 
     | 
    
         
            -
            require  
     | 
| 
      
 4 
     | 
    
         
            +
            require "yaml"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "minitest/autorun"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "awesome_print"
         
     | 
| 
      
 7 
     | 
    
         
            +
            require "secret_config"
         
     | 
| 
      
 8 
     | 
    
         
            +
            require "semantic_logger"
         
     | 
| 
      
 9 
     | 
    
         
            +
            require "opinionated_http"
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
            SemanticLogger.add_appender(file_name:  
     | 
| 
      
 11 
     | 
    
         
            +
            SemanticLogger.add_appender(file_name: "test.log", formatter: :color)
         
     | 
| 
       12 
12 
     | 
    
         
             
            SemanticLogger.default_level = :debug
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
            SecretConfig.use :file, path:  
     | 
| 
      
 14 
     | 
    
         
            +
            SecretConfig.use :file, path: "test", file_name: File.expand_path("config/application.yml", __dir__)
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: opinionated_http
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.6
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Reid Morrison
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2020- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2020-06-10 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: persistent_http
         
     | 
| 
         @@ -87,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       87 
87 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       88 
88 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       89 
89 
     | 
    
         
             
            requirements: []
         
     | 
| 
       90 
     | 
    
         
            -
            rubygems_version: 3.0. 
     | 
| 
      
 90 
     | 
    
         
            +
            rubygems_version: 3.0.8
         
     | 
| 
       91 
91 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       92 
92 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       93 
93 
     | 
    
         
             
            summary: Opinionated HTTP Client
         
     |