dhc 1.0.0
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 +7 -0
- data/.github/workflows/rubocop.yml +27 -0
- data/.github/workflows/test.yml +27 -0
- data/.gitignore +37 -0
- data/.rubocop.yml +105 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/LICENSE +674 -0
- data/README.md +999 -0
- data/Rakefile +25 -0
- data/dhc.gemspec +40 -0
- data/lib/core_ext/hash/deep_transform_values.rb +48 -0
- data/lib/dhc.rb +77 -0
- data/lib/dhc/concerns/dhc/basic_methods_concern.rb +42 -0
- data/lib/dhc/concerns/dhc/configuration_concern.rb +20 -0
- data/lib/dhc/concerns/dhc/fix_invalid_encoding_concern.rb +42 -0
- data/lib/dhc/concerns/dhc/formats_concern.rb +25 -0
- data/lib/dhc/concerns/dhc/request/user_agent_concern.rb +25 -0
- data/lib/dhc/config.rb +47 -0
- data/lib/dhc/endpoint.rb +119 -0
- data/lib/dhc/error.rb +82 -0
- data/lib/dhc/errors/client_error.rb +73 -0
- data/lib/dhc/errors/parser_error.rb +4 -0
- data/lib/dhc/errors/server_error.rb +28 -0
- data/lib/dhc/errors/timeout.rb +4 -0
- data/lib/dhc/errors/unknown_error.rb +4 -0
- data/lib/dhc/format.rb +18 -0
- data/lib/dhc/formats.rb +8 -0
- data/lib/dhc/formats/form.rb +45 -0
- data/lib/dhc/formats/json.rb +55 -0
- data/lib/dhc/formats/multipart.rb +45 -0
- data/lib/dhc/formats/plain.rb +42 -0
- data/lib/dhc/interceptor.rb +36 -0
- data/lib/dhc/interceptors.rb +26 -0
- data/lib/dhc/interceptors/auth.rb +94 -0
- data/lib/dhc/interceptors/caching.rb +148 -0
- data/lib/dhc/interceptors/default_timeout.rb +16 -0
- data/lib/dhc/interceptors/logging.rb +37 -0
- data/lib/dhc/interceptors/monitoring.rb +92 -0
- data/lib/dhc/interceptors/prometheus.rb +51 -0
- data/lib/dhc/interceptors/retry.rb +41 -0
- data/lib/dhc/interceptors/rollbar.rb +36 -0
- data/lib/dhc/interceptors/throttle.rb +86 -0
- data/lib/dhc/interceptors/zipkin.rb +110 -0
- data/lib/dhc/railtie.rb +9 -0
- data/lib/dhc/request.rb +161 -0
- data/lib/dhc/response.rb +60 -0
- data/lib/dhc/response/data.rb +28 -0
- data/lib/dhc/response/data/base.rb +18 -0
- data/lib/dhc/response/data/collection.rb +16 -0
- data/lib/dhc/response/data/item.rb +29 -0
- data/lib/dhc/rspec.rb +11 -0
- data/lib/dhc/test/cache_helper.rb +3 -0
- data/lib/dhc/version.rb +5 -0
- data/script/ci/build.sh +19 -0
- data/spec/basic_methods/delete_spec.rb +34 -0
- data/spec/basic_methods/get_spec.rb +49 -0
- data/spec/basic_methods/post_spec.rb +42 -0
- data/spec/basic_methods/put_spec.rb +48 -0
- data/spec/basic_methods/request_spec.rb +19 -0
- data/spec/basic_methods/request_without_rails_spec.rb +29 -0
- data/spec/config/endpoints_spec.rb +63 -0
- data/spec/config/placeholders_spec.rb +32 -0
- data/spec/core_ext/hash/deep_transform_values_spec.rb +24 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +8 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +7 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +4 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +5 -0
- data/spec/dummy/bin/rails +6 -0
- data/spec/dummy/bin/rake +6 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/config/application.rb +16 -0
- data/spec/dummy/config/boot.rb +7 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config/environments/development.rb +36 -0
- data/spec/dummy/config/environments/production.rb +77 -0
- data/spec/dummy/config/environments/test.rb +41 -0
- data/spec/dummy/config/initializers/assets.rb +10 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/spec/dummy/config/initializers/inflections.rb +18 -0
- data/spec/dummy/config/initializers/mime_types.rb +6 -0
- data/spec/dummy/config/initializers/session_store.rb +5 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/endpoint/compile_spec.rb +35 -0
- data/spec/endpoint/match_spec.rb +41 -0
- data/spec/endpoint/placeholders_spec.rb +30 -0
- data/spec/endpoint/remove_interpolated_params_spec.rb +17 -0
- data/spec/endpoint/values_as_params_spec.rb +31 -0
- data/spec/error/dup_spec.rb +12 -0
- data/spec/error/find_spec.rb +57 -0
- data/spec/error/response_spec.rb +17 -0
- data/spec/error/timeout_spec.rb +14 -0
- data/spec/error/to_s_spec.rb +85 -0
- data/spec/formats/form_spec.rb +27 -0
- data/spec/formats/json_spec.rb +66 -0
- data/spec/formats/multipart_spec.rb +26 -0
- data/spec/formats/plain_spec.rb +29 -0
- data/spec/interceptors/after_request_spec.rb +20 -0
- data/spec/interceptors/after_response_spec.rb +39 -0
- data/spec/interceptors/auth/basic_auth_spec.rb +17 -0
- data/spec/interceptors/auth/bearer_spec.rb +19 -0
- data/spec/interceptors/auth/body_spec.rb +36 -0
- data/spec/interceptors/auth/long_basic_auth_credentials_spec.rb +17 -0
- data/spec/interceptors/auth/no_instance_var_for_options_spec.rb +27 -0
- data/spec/interceptors/auth/reauthentication_configuration_spec.rb +61 -0
- data/spec/interceptors/auth/reauthentication_spec.rb +44 -0
- data/spec/interceptors/before_request_spec.rb +21 -0
- data/spec/interceptors/before_response_spec.rb +20 -0
- data/spec/interceptors/caching/hydra_spec.rb +26 -0
- data/spec/interceptors/caching/main_spec.rb +73 -0
- data/spec/interceptors/caching/methods_spec.rb +42 -0
- data/spec/interceptors/caching/multilevel_cache_spec.rb +139 -0
- data/spec/interceptors/caching/options_spec.rb +78 -0
- data/spec/interceptors/caching/parameters_spec.rb +24 -0
- data/spec/interceptors/caching/response_status_spec.rb +29 -0
- data/spec/interceptors/caching/to_cache_spec.rb +16 -0
- data/spec/interceptors/default_interceptors_spec.rb +15 -0
- data/spec/interceptors/default_timeout/main_spec.rb +34 -0
- data/spec/interceptors/define_spec.rb +30 -0
- data/spec/interceptors/dup_spec.rb +19 -0
- data/spec/interceptors/logging/main_spec.rb +37 -0
- data/spec/interceptors/monitoring/caching_spec.rb +66 -0
- data/spec/interceptors/monitoring/main_spec.rb +97 -0
- data/spec/interceptors/prometheus_spec.rb +54 -0
- data/spec/interceptors/response_competition_spec.rb +39 -0
- data/spec/interceptors/retry/main_spec.rb +73 -0
- data/spec/interceptors/return_response_spec.rb +38 -0
- data/spec/interceptors/rollbar/invalid_encoding_spec.rb +43 -0
- data/spec/interceptors/rollbar/main_spec.rb +57 -0
- data/spec/interceptors/throttle/main_spec.rb +236 -0
- data/spec/interceptors/throttle/reset_track_spec.rb +53 -0
- data/spec/interceptors/zipkin/distributed_tracing_spec.rb +135 -0
- data/spec/rails_helper.rb +6 -0
- data/spec/request/body_spec.rb +39 -0
- data/spec/request/encoding_spec.rb +38 -0
- data/spec/request/error_handling_spec.rb +88 -0
- data/spec/request/headers_spec.rb +12 -0
- data/spec/request/ignore_errors_spec.rb +73 -0
- data/spec/request/option_dup_spec.rb +13 -0
- data/spec/request/parallel_requests_spec.rb +59 -0
- data/spec/request/params_encoding_spec.rb +26 -0
- data/spec/request/request_without_rails_spec.rb +15 -0
- data/spec/request/url_patterns_spec.rb +54 -0
- data/spec/request/user_agent_spec.rb +26 -0
- data/spec/request/user_agent_without_rails_spec.rb +27 -0
- data/spec/response/body_spec.rb +16 -0
- data/spec/response/code_spec.rb +16 -0
- data/spec/response/data_accessor_spec.rb +29 -0
- data/spec/response/data_spec.rb +85 -0
- data/spec/response/effective_url_spec.rb +16 -0
- data/spec/response/headers_spec.rb +18 -0
- data/spec/response/options_spec.rb +18 -0
- data/spec/response/success_spec.rb +13 -0
- data/spec/response/time_spec.rb +21 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/fixtures/json/feedback.json +11 -0
- data/spec/support/fixtures/json/feedbacks.json +164 -0
- data/spec/support/fixtures/json/localina_content_ad.json +23 -0
- data/spec/support/load_json.rb +5 -0
- data/spec/support/reset_config.rb +8 -0
- data/spec/support/zipkin_mock.rb +114 -0
- data/spec/timeouts/no_signal_spec.rb +13 -0
- data/spec/timeouts/timings_spec.rb +55 -0
- metadata +527 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
|
|
5
|
+
describe DHC::Monitoring do
|
|
6
|
+
let(:stub) { stub_request(:get, 'http://depay.fi').to_return(status: 200, body: 'The Website') }
|
|
7
|
+
let(:endpoint_configuration) { DHC.config.endpoint(:local, 'http://depay.fi') }
|
|
8
|
+
|
|
9
|
+
module Statsd
|
|
10
|
+
def self.count(_path, _value); end
|
|
11
|
+
|
|
12
|
+
def self.timing(_path, _value); end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
before(:each) do
|
|
16
|
+
DHC.config.interceptors = [DHC::Monitoring]
|
|
17
|
+
DHC::Monitoring.statsd = Statsd
|
|
18
|
+
Rails.cache.clear
|
|
19
|
+
endpoint_configuration
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'does not report anything if no statsd is configured' do
|
|
23
|
+
stub
|
|
24
|
+
DHC.get(:local) # and also does not crash ;)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context 'statsd configured' do
|
|
28
|
+
it 'reports trial, response and timing by default ' do
|
|
29
|
+
stub
|
|
30
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.before_request', 1)
|
|
31
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.after_request', 1)
|
|
32
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.count', 1)
|
|
33
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.200', 1)
|
|
34
|
+
expect(Statsd).to receive(:timing).with('dhc.dummy.test.depay_fi.get.time', anything)
|
|
35
|
+
DHC.get(:local)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'does not report timing when response failed' do
|
|
39
|
+
stub_request(:get, 'http://depay.fi').to_return(status: 500)
|
|
40
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.before_request', 1)
|
|
41
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.after_request', 1)
|
|
42
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.count', 1)
|
|
43
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.500', 1)
|
|
44
|
+
expect(Statsd).not_to receive(:timing)
|
|
45
|
+
expect { DHC.get(:local) }.to raise_error DHC::ServerError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'reports timeout instead of status code if response timed out' do
|
|
49
|
+
stub_request(:get, 'http://depay.fi').to_timeout
|
|
50
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.before_request', 1)
|
|
51
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.after_request', 1)
|
|
52
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.count', 1)
|
|
53
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.timeout', 1)
|
|
54
|
+
expect(Statsd).not_to receive(:timing)
|
|
55
|
+
expect { DHC.get(:local) }.to raise_error DHC::Timeout
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'allows to set the stats key for request' do
|
|
59
|
+
stub
|
|
60
|
+
expect(Statsd).to receive(:count).with('defined_key.before_request', 1)
|
|
61
|
+
expect(Statsd).to receive(:count).with('defined_key.after_request', 1)
|
|
62
|
+
expect(Statsd).to receive(:count).with('defined_key.count', 1)
|
|
63
|
+
expect(Statsd).to receive(:count).with('defined_key.200', 1)
|
|
64
|
+
expect(Statsd).to receive(:timing).with('defined_key.time', anything)
|
|
65
|
+
DHC.get(:local, monitoring_key: 'defined_key')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'without protocol' do
|
|
70
|
+
let(:endpoint_configuration) { DHC.config.endpoint(:local, 'depay.fi') }
|
|
71
|
+
|
|
72
|
+
it 'reports trial, response and timing by default ' do
|
|
73
|
+
stub
|
|
74
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.before_request', 1)
|
|
75
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.after_request', 1)
|
|
76
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.count', 1)
|
|
77
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.test.depay_fi.get.200', 1)
|
|
78
|
+
expect(Statsd).to receive(:timing).with('dhc.dummy.test.depay_fi.get.time', anything)
|
|
79
|
+
DHC.get(:local)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'with configured environment' do
|
|
84
|
+
before do
|
|
85
|
+
DHC::Monitoring.env = 'beta'
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'uses the configured env' do
|
|
89
|
+
stub
|
|
90
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.beta.depay_fi.get.before_request', 1)
|
|
91
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.beta.depay_fi.get.after_request', 1)
|
|
92
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.beta.depay_fi.get.count', 1)
|
|
93
|
+
expect(Statsd).to receive(:count).with('dhc.dummy.beta.depay_fi.get.200', 1)
|
|
94
|
+
DHC.get(:local)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
require 'prometheus/client'
|
|
5
|
+
|
|
6
|
+
describe DHC::Prometheus do
|
|
7
|
+
before(:each) do
|
|
8
|
+
DHC.config.interceptors = [DHC::Prometheus]
|
|
9
|
+
DHC::Prometheus.client = Prometheus::Client
|
|
10
|
+
DHC::Prometheus.namespace = 'test_app'
|
|
11
|
+
stub_request(:get, 'http://depay.fi')
|
|
12
|
+
expect(Prometheus::Client).to receive(:registry).and_call_original.at_least(:once)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:client) { double('prometheus/client') }
|
|
16
|
+
|
|
17
|
+
context 'registering' do
|
|
18
|
+
it 'creates a counter and histogram registry in the prometheus client' do
|
|
19
|
+
expect(Prometheus::Client.registry).to receive(:counter).and_call_original.once
|
|
20
|
+
.with(:dhc_requests, 'Counter of all DHC requests.')
|
|
21
|
+
expect(Prometheus::Client.registry).to receive(:histogram).and_call_original.once
|
|
22
|
+
.with(:dhc_request_seconds, 'Request timings for all DHC requests in seconds.')
|
|
23
|
+
|
|
24
|
+
DHC.get('http://depay.fi')
|
|
25
|
+
DHC.get('http://depay.fi') # second request, registration should happen only once
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'logging' do
|
|
30
|
+
let(:requests_registry_double) { double('requests_registry_double') }
|
|
31
|
+
let(:times_registry_double) { double('times_registry_double') }
|
|
32
|
+
|
|
33
|
+
it 'logs monitoring information to the created registries' do
|
|
34
|
+
expect(Prometheus::Client.registry).to receive(:get).and_return(requests_registry_double).once
|
|
35
|
+
.with(:dhc_requests)
|
|
36
|
+
expect(Prometheus::Client.registry).to receive(:get).and_return(times_registry_double).once
|
|
37
|
+
.with(:dhc_request_seconds)
|
|
38
|
+
|
|
39
|
+
expect(requests_registry_double).to receive(:increment).once
|
|
40
|
+
.with(
|
|
41
|
+
code: 200,
|
|
42
|
+
success: true,
|
|
43
|
+
timeout: false,
|
|
44
|
+
app: 'test_app',
|
|
45
|
+
host: 'depay.fi'
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
expect(times_registry_double).to receive(:observe).once
|
|
49
|
+
.with({ host: 'depay.fi', app: 'test_app' }, 0)
|
|
50
|
+
|
|
51
|
+
DHC.get('http://depay.fi')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
|
|
5
|
+
describe DHC do
|
|
6
|
+
context 'interceptor response competition' do
|
|
7
|
+
before(:each) do
|
|
8
|
+
class LocalCacheInterceptor < DHC::Interceptor
|
|
9
|
+
@@cached = false
|
|
10
|
+
cattr_accessor :cached
|
|
11
|
+
|
|
12
|
+
def before_request
|
|
13
|
+
if @@cached
|
|
14
|
+
return DHC::Response.new(Typhoeus::Response.new(response_code: 200, return_code: :ok, response_body: 'Im served from local cache'), nil)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class RemoteCacheInterceptor < DHC::Interceptor
|
|
20
|
+
|
|
21
|
+
def before_request
|
|
22
|
+
if request.response.nil?
|
|
23
|
+
return DHC::Response.new(Typhoeus::Response.new(response_code: 200, return_code: :ok, response_body: 'Im served from remote cache'), nil)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
DHC.configure { |c| c.interceptors = [LocalCacheInterceptor, RemoteCacheInterceptor] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'can handle multiple interceptors that compete for returning the response' do
|
|
32
|
+
response = DHC.get('http://depay.fi')
|
|
33
|
+
expect(response.body).to eq 'Im served from remote cache'
|
|
34
|
+
LocalCacheInterceptor.cached = true
|
|
35
|
+
response = DHC.get('http://depay.fi')
|
|
36
|
+
expect(response.body).to eq 'Im served from local cache'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
|
|
5
|
+
describe DHC::Rollbar do
|
|
6
|
+
before(:each) do
|
|
7
|
+
DHC.config.interceptors = [DHC::Retry]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:status) { 500 }
|
|
11
|
+
|
|
12
|
+
let(:request_stub) do
|
|
13
|
+
@retry_count = 0
|
|
14
|
+
stub_request(:get, 'http://depay.fi').to_return do |_|
|
|
15
|
+
if @retry_count == max_retry_count
|
|
16
|
+
{ status: 200 }
|
|
17
|
+
else
|
|
18
|
+
@retry_count += 1
|
|
19
|
+
{ status: status }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:max_retry_count) { 3 }
|
|
25
|
+
|
|
26
|
+
it 'retries a request up to 3 times (default)' do
|
|
27
|
+
request_stub
|
|
28
|
+
response = DHC.get('http://depay.fi', retry: true)
|
|
29
|
+
expect(response.success?).to eq true
|
|
30
|
+
expect(response.code).to eq 200
|
|
31
|
+
expect(request_stub).to have_been_requested.times(4)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'retry only once' do
|
|
35
|
+
let(:retry_options) { { max: 1 } }
|
|
36
|
+
let(:max_retry_count) { 1 }
|
|
37
|
+
|
|
38
|
+
it 'retries only once' do
|
|
39
|
+
request_stub
|
|
40
|
+
response = DHC.get('http://depay.fi', retry: { max: 1 })
|
|
41
|
+
expect(response.success?).to eq true
|
|
42
|
+
expect(response.code).to eq 200
|
|
43
|
+
expect(request_stub).to have_been_requested.times(2)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'retry all' do
|
|
48
|
+
let(:max_retry_count) { 2 }
|
|
49
|
+
|
|
50
|
+
before do
|
|
51
|
+
expect(DHC::Retry).to receive(:max).at_least(:once).and_return(2)
|
|
52
|
+
expect(DHC::Retry).to receive(:all).at_least(:once).and_return(true)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'retries if configured globally' do
|
|
56
|
+
request_stub
|
|
57
|
+
response = DHC.get('http://depay.fi')
|
|
58
|
+
expect(response.success?).to eq true
|
|
59
|
+
expect(response.code).to eq 200
|
|
60
|
+
expect(request_stub).to have_been_requested.times(3)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context 'ignore error' do
|
|
65
|
+
let(:status) { 404 }
|
|
66
|
+
|
|
67
|
+
it 'does not retry if the error is explicitly ignored' do
|
|
68
|
+
request_stub
|
|
69
|
+
DHC.get('http://depay.fi', retry: { max: 1 }, ignore: [DHC::NotFound])
|
|
70
|
+
expect(request_stub).to have_been_requested.times(1)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
|
|
5
|
+
describe DHC do
|
|
6
|
+
context 'interceptor' do
|
|
7
|
+
before(:each) do
|
|
8
|
+
class CacheInterceptor < DHC::Interceptor
|
|
9
|
+
|
|
10
|
+
def before_request
|
|
11
|
+
DHC::Response.new(Typhoeus::Response.new(response_code: 200, return_code: :ok, response_body: 'Im served from cache'), nil)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
DHC.configure { |c| c.interceptors = [CacheInterceptor] }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'can return a response rather then doing a real request' do
|
|
18
|
+
response = DHC.get('http://depay.fi')
|
|
19
|
+
expect(response.body).to eq 'Im served from cache'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context 'misusage' do
|
|
23
|
+
before(:each) do
|
|
24
|
+
class AnotherInterceptor < DHC::Interceptor
|
|
25
|
+
def before_request
|
|
26
|
+
DHC::Response.new(Typhoeus::Response.new(response_code: 200, return_code: :ok), nil)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'raises an exception when two interceptors try to return a response' do
|
|
32
|
+
expect(lambda {
|
|
33
|
+
DHC.get('http://depay.fi', interceptors: [CacheInterceptor, AnotherInterceptor])
|
|
34
|
+
}).to raise_error 'Response already set from another interceptor'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
|
|
5
|
+
describe DHC::Rollbar do
|
|
6
|
+
context 'invalid encoding in rollbar payload' do
|
|
7
|
+
before(:each) do
|
|
8
|
+
DHC.config.interceptors = [DHC::Rollbar]
|
|
9
|
+
stub_request(:get, 'http://depay.fi').to_return(status: 400)
|
|
10
|
+
|
|
11
|
+
allow(described_class).to receive(:fix_invalid_encoding).and_call_original
|
|
12
|
+
# a stub that will throw a error on first call and suceed on subsequent calls
|
|
13
|
+
call_counter = 0
|
|
14
|
+
class Rollbar; end
|
|
15
|
+
::Rollbar.stub(:warning) do
|
|
16
|
+
call_counter += 1
|
|
17
|
+
raise Encoding::UndefinedConversionError if call_counter == 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# the response for the caller is still DHC::BadRequest
|
|
21
|
+
expect(-> { DHC.get('http://depay.fi', rollbar: { additional: invalid }) }).to raise_error DHC::BadRequest
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:invalid) { (+"in\xc3lid").force_encoding('ASCII-8BIT') }
|
|
25
|
+
let(:valid) { described_class.fix_invalid_encoding(invalid) }
|
|
26
|
+
|
|
27
|
+
it 'calls fix_invalid_encoding incase a Encoding::UndefinedConversionError was encountered' do
|
|
28
|
+
expect(described_class).to have_received(:fix_invalid_encoding).with(invalid)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'calls Rollbar.warn with the fixed data' do
|
|
32
|
+
expect(::Rollbar).to have_received(:warning)
|
|
33
|
+
.with(
|
|
34
|
+
'Status: 400 URL: http://depay.fi',
|
|
35
|
+
hash_including(
|
|
36
|
+
response: anything,
|
|
37
|
+
request: anything,
|
|
38
|
+
additional: valid
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
|
|
5
|
+
describe DHC::Rollbar do
|
|
6
|
+
before(:each) do
|
|
7
|
+
DHC.config.interceptors = [DHC::Rollbar]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
context 'Rollbar is undefined' do
|
|
11
|
+
before(:each) do
|
|
12
|
+
Object.send(:remove_const, 'Rollbar') if Object.const_defined?('Rollbar')
|
|
13
|
+
end
|
|
14
|
+
it 'does not report' do
|
|
15
|
+
stub_request(:get, 'http://depay.fi').to_return(status: 400)
|
|
16
|
+
expect(-> { DHC.get('http://depay.fi') })
|
|
17
|
+
.to raise_error DHC::BadRequest
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context 'Rollbar is defined' do
|
|
22
|
+
before(:each) do
|
|
23
|
+
class Rollbar; end
|
|
24
|
+
::Rollbar.stub(:warning)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'does report errors to rollbar' do
|
|
28
|
+
stub_request(:get, 'http://depay.fi').to_return(status: 400)
|
|
29
|
+
expect(-> { DHC.get('http://depay.fi') })
|
|
30
|
+
.to raise_error DHC::BadRequest
|
|
31
|
+
expect(::Rollbar).to have_received(:warning)
|
|
32
|
+
.with(
|
|
33
|
+
'Status: 400 URL: http://depay.fi',
|
|
34
|
+
response: hash_including(body: anything, code: anything, headers: anything, time: anything, timeout?: anything),
|
|
35
|
+
request: hash_including(url: anything, method: anything, headers: anything, params: anything)
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context 'additional params' do
|
|
40
|
+
it 'does report errors to rollbar with additional data' do
|
|
41
|
+
stub_request(:get, 'http://depay.fi')
|
|
42
|
+
.to_return(status: 400)
|
|
43
|
+
expect(-> { DHC.get('http://depay.fi', rollbar: { additional: 'data' }) })
|
|
44
|
+
.to raise_error DHC::BadRequest
|
|
45
|
+
expect(::Rollbar).to have_received(:warning)
|
|
46
|
+
.with(
|
|
47
|
+
'Status: 400 URL: http://depay.fi',
|
|
48
|
+
hash_including(
|
|
49
|
+
response: anything,
|
|
50
|
+
request: anything,
|
|
51
|
+
additional: 'data'
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
|
|
5
|
+
describe DHC::Throttle do
|
|
6
|
+
let(:options_break) { false }
|
|
7
|
+
let(:options_expires) { { header: 'reset' } }
|
|
8
|
+
let(:options_limit) { { header: 'limit' } }
|
|
9
|
+
let(:options_remaining) { { header: 'remaining' } }
|
|
10
|
+
let(:provider) { 'depay.fi' }
|
|
11
|
+
let(:quota_limit) { 10_000 }
|
|
12
|
+
let(:quota_remaining) { 1900 }
|
|
13
|
+
let(:quota_reset) { (Time.zone.now + 1.hour).to_i }
|
|
14
|
+
let(:options) do
|
|
15
|
+
{
|
|
16
|
+
throttle: {
|
|
17
|
+
provider: provider,
|
|
18
|
+
track: true,
|
|
19
|
+
limit: options_limit,
|
|
20
|
+
remaining: options_remaining,
|
|
21
|
+
expires: options_expires,
|
|
22
|
+
break: options_break
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
before(:each) do
|
|
28
|
+
DHC::Throttle.track = nil
|
|
29
|
+
DHC.config.interceptors = [DHC::Throttle]
|
|
30
|
+
|
|
31
|
+
stub_request(:get, 'http://depay.fi').to_return(
|
|
32
|
+
headers: { 'limit' => quota_limit, 'remaining' => quota_remaining, 'reset' => quota_reset }
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'tracks the request limits based on response data' do
|
|
37
|
+
DHC.get('http://depay.fi', options)
|
|
38
|
+
expect(DHC::Throttle.track[provider][:limit]).to eq quota_limit
|
|
39
|
+
expect(DHC::Throttle.track[provider][:remaining]).to eq quota_remaining
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context 'fix predefined integer for limit' do
|
|
43
|
+
let(:options_limit) { 1000 }
|
|
44
|
+
|
|
45
|
+
it 'tracks the limit based on initialy provided data' do
|
|
46
|
+
DHC.get('http://depay.fi', options)
|
|
47
|
+
expect(DHC::Throttle.track[provider][:limit]).to eq options_limit
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'breaks' do
|
|
52
|
+
let(:options_break) { '80%' }
|
|
53
|
+
|
|
54
|
+
it 'hit the breaks if throttling quota is reached' do
|
|
55
|
+
DHC.get('http://depay.fi', options)
|
|
56
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
|
57
|
+
DHC::Throttle::OutOfQuota,
|
|
58
|
+
'Reached predefined quota for depay.fi'
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'still within quota' do
|
|
63
|
+
let(:options_break) { '90%' }
|
|
64
|
+
|
|
65
|
+
it 'does not hit the breaks' do
|
|
66
|
+
DHC.get('http://depay.fi', options)
|
|
67
|
+
DHC.get('http://depay.fi', options)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'no response headers' do
|
|
73
|
+
before { stub_request(:get, 'http://depay.fi').to_return(status: 200) }
|
|
74
|
+
|
|
75
|
+
it 'does not raise an exception' do
|
|
76
|
+
DHC.get('http://depay.fi', options)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context 'no remaining tracked, but break enabled' do
|
|
80
|
+
let(:options_break) { '90%' }
|
|
81
|
+
|
|
82
|
+
it 'does not fail if a remaining was not tracked yet' do
|
|
83
|
+
DHC.get('http://depay.fi', options)
|
|
84
|
+
DHC.get('http://depay.fi', options)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context 'expires' do
|
|
90
|
+
let(:options_break) { '80%' }
|
|
91
|
+
|
|
92
|
+
it 'attempts another request if the quota expired' do
|
|
93
|
+
DHC.get('http://depay.fi', options)
|
|
94
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
|
95
|
+
DHC::Throttle::OutOfQuota,
|
|
96
|
+
'Reached predefined quota for depay.fi'
|
|
97
|
+
)
|
|
98
|
+
Timecop.travel(Time.zone.now + 2.hours)
|
|
99
|
+
DHC.get('http://depay.fi', options)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe 'configuration values as Procs' do
|
|
104
|
+
describe 'calculate "limit" in proc' do
|
|
105
|
+
let(:options_limit) do
|
|
106
|
+
->(*) { 10_000 }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
before(:each) do
|
|
110
|
+
DHC.get('http://depay.fi', options)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'breaks' do
|
|
114
|
+
let(:options_break) { '80%' }
|
|
115
|
+
|
|
116
|
+
it 'hit the breaks if throttling quota is reached' do
|
|
117
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
|
118
|
+
DHC::Throttle::OutOfQuota,
|
|
119
|
+
'Reached predefined quota for depay.fi'
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
context 'still within quota' do
|
|
124
|
+
let(:options_break) { '90%' }
|
|
125
|
+
|
|
126
|
+
it 'does not hit the breaks' do
|
|
127
|
+
DHC.get('http://depay.fi', options)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe 'calculate "remaining" in proc' do
|
|
134
|
+
let(:quota_current) { 8100 }
|
|
135
|
+
let(:options_remaining) do
|
|
136
|
+
->(response) { response.headers['limit'].to_i - response.headers['current'].to_i }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
before(:each) do
|
|
140
|
+
stub_request(:get, 'http://depay.fi').to_return(
|
|
141
|
+
headers: { 'limit' => quota_limit, 'current' => quota_current, 'reset' => quota_reset }
|
|
142
|
+
)
|
|
143
|
+
DHC.get('http://depay.fi', options)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
context 'breaks' do
|
|
147
|
+
let(:options_break) { '80%' }
|
|
148
|
+
|
|
149
|
+
it 'hit the breaks if throttling quota is reached' do
|
|
150
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
|
151
|
+
DHC::Throttle::OutOfQuota,
|
|
152
|
+
'Reached predefined quota for depay.fi'
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context 'still within quota' do
|
|
157
|
+
let(:options_break) { '90%' }
|
|
158
|
+
|
|
159
|
+
it 'does not hit the breaks' do
|
|
160
|
+
DHC.get('http://depay.fi', options)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe 'calculate "reset" in proc' do
|
|
167
|
+
let(:options_expires) { ->(*) { Time.zone.now + 1.second } }
|
|
168
|
+
|
|
169
|
+
before(:each) do
|
|
170
|
+
stub_request(:get, 'http://depay.fi').to_return(
|
|
171
|
+
headers: { 'limit' => quota_limit, 'remaining' => quota_remaining }
|
|
172
|
+
)
|
|
173
|
+
DHC.get('http://depay.fi', options)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context 'breaks' do
|
|
177
|
+
let(:options_break) { '80%' }
|
|
178
|
+
|
|
179
|
+
it 'hit the breaks if throttling quota is reached' do
|
|
180
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
|
181
|
+
DHC::Throttle::OutOfQuota,
|
|
182
|
+
'Reached predefined quota for depay.fi'
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
context 'still within quota' do
|
|
187
|
+
let(:options_break) { '90%' }
|
|
188
|
+
|
|
189
|
+
it 'does not hit the breaks' do
|
|
190
|
+
DHC.get('http://depay.fi', options)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
describe 'parsing reset time given in prose' do
|
|
198
|
+
let(:quota_reset) { (Time.zone.now + 1.day).strftime('%A, %B %d, %Y 12:00:00 AM GMT').to_s }
|
|
199
|
+
|
|
200
|
+
before { DHC.get('http://depay.fi', options) }
|
|
201
|
+
|
|
202
|
+
context 'breaks' do
|
|
203
|
+
let(:options_break) { '80%' }
|
|
204
|
+
|
|
205
|
+
it 'hit the breaks if throttling quota is reached' do
|
|
206
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
|
207
|
+
DHC::Throttle::OutOfQuota,
|
|
208
|
+
'Reached predefined quota for depay.fi'
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
context 'still within quota' do
|
|
213
|
+
let(:options_break) { '90%' }
|
|
214
|
+
|
|
215
|
+
it 'does not hit the breaks' do
|
|
216
|
+
DHC.get('http://depay.fi', options)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
context 'when value is empty' do
|
|
223
|
+
let(:quota_reset) { nil }
|
|
224
|
+
|
|
225
|
+
before do
|
|
226
|
+
stub_request(:get, 'http://depay.fi').to_return(
|
|
227
|
+
headers: { 'limit' => quota_limit, 'remaining' => quota_remaining }
|
|
228
|
+
)
|
|
229
|
+
DHC.get('http://depay.fi', options)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'still runs' do
|
|
233
|
+
DHC.get('http://depay.fi', options)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|