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.
Files changed (185) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rubocop.yml +27 -0
  3. data/.github/workflows/test.yml +27 -0
  4. data/.gitignore +37 -0
  5. data/.rubocop.yml +105 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +674 -0
  9. data/README.md +999 -0
  10. data/Rakefile +25 -0
  11. data/dhc.gemspec +40 -0
  12. data/lib/core_ext/hash/deep_transform_values.rb +48 -0
  13. data/lib/dhc.rb +77 -0
  14. data/lib/dhc/concerns/dhc/basic_methods_concern.rb +42 -0
  15. data/lib/dhc/concerns/dhc/configuration_concern.rb +20 -0
  16. data/lib/dhc/concerns/dhc/fix_invalid_encoding_concern.rb +42 -0
  17. data/lib/dhc/concerns/dhc/formats_concern.rb +25 -0
  18. data/lib/dhc/concerns/dhc/request/user_agent_concern.rb +25 -0
  19. data/lib/dhc/config.rb +47 -0
  20. data/lib/dhc/endpoint.rb +119 -0
  21. data/lib/dhc/error.rb +82 -0
  22. data/lib/dhc/errors/client_error.rb +73 -0
  23. data/lib/dhc/errors/parser_error.rb +4 -0
  24. data/lib/dhc/errors/server_error.rb +28 -0
  25. data/lib/dhc/errors/timeout.rb +4 -0
  26. data/lib/dhc/errors/unknown_error.rb +4 -0
  27. data/lib/dhc/format.rb +18 -0
  28. data/lib/dhc/formats.rb +8 -0
  29. data/lib/dhc/formats/form.rb +45 -0
  30. data/lib/dhc/formats/json.rb +55 -0
  31. data/lib/dhc/formats/multipart.rb +45 -0
  32. data/lib/dhc/formats/plain.rb +42 -0
  33. data/lib/dhc/interceptor.rb +36 -0
  34. data/lib/dhc/interceptors.rb +26 -0
  35. data/lib/dhc/interceptors/auth.rb +94 -0
  36. data/lib/dhc/interceptors/caching.rb +148 -0
  37. data/lib/dhc/interceptors/default_timeout.rb +16 -0
  38. data/lib/dhc/interceptors/logging.rb +37 -0
  39. data/lib/dhc/interceptors/monitoring.rb +92 -0
  40. data/lib/dhc/interceptors/prometheus.rb +51 -0
  41. data/lib/dhc/interceptors/retry.rb +41 -0
  42. data/lib/dhc/interceptors/rollbar.rb +36 -0
  43. data/lib/dhc/interceptors/throttle.rb +86 -0
  44. data/lib/dhc/interceptors/zipkin.rb +110 -0
  45. data/lib/dhc/railtie.rb +9 -0
  46. data/lib/dhc/request.rb +161 -0
  47. data/lib/dhc/response.rb +60 -0
  48. data/lib/dhc/response/data.rb +28 -0
  49. data/lib/dhc/response/data/base.rb +18 -0
  50. data/lib/dhc/response/data/collection.rb +16 -0
  51. data/lib/dhc/response/data/item.rb +29 -0
  52. data/lib/dhc/rspec.rb +11 -0
  53. data/lib/dhc/test/cache_helper.rb +3 -0
  54. data/lib/dhc/version.rb +5 -0
  55. data/script/ci/build.sh +19 -0
  56. data/spec/basic_methods/delete_spec.rb +34 -0
  57. data/spec/basic_methods/get_spec.rb +49 -0
  58. data/spec/basic_methods/post_spec.rb +42 -0
  59. data/spec/basic_methods/put_spec.rb +48 -0
  60. data/spec/basic_methods/request_spec.rb +19 -0
  61. data/spec/basic_methods/request_without_rails_spec.rb +29 -0
  62. data/spec/config/endpoints_spec.rb +63 -0
  63. data/spec/config/placeholders_spec.rb +32 -0
  64. data/spec/core_ext/hash/deep_transform_values_spec.rb +24 -0
  65. data/spec/dummy/README.rdoc +28 -0
  66. data/spec/dummy/Rakefile +8 -0
  67. data/spec/dummy/app/assets/config/manifest.js +3 -0
  68. data/spec/dummy/app/assets/images/.keep +0 -0
  69. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  70. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  71. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  72. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  73. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  74. data/spec/dummy/app/mailers/.keep +0 -0
  75. data/spec/dummy/app/models/.keep +0 -0
  76. data/spec/dummy/app/models/concerns/.keep +0 -0
  77. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  78. data/spec/dummy/bin/bundle +5 -0
  79. data/spec/dummy/bin/rails +6 -0
  80. data/spec/dummy/bin/rake +6 -0
  81. data/spec/dummy/config.ru +6 -0
  82. data/spec/dummy/config/application.rb +16 -0
  83. data/spec/dummy/config/boot.rb +7 -0
  84. data/spec/dummy/config/environment.rb +7 -0
  85. data/spec/dummy/config/environments/development.rb +36 -0
  86. data/spec/dummy/config/environments/production.rb +77 -0
  87. data/spec/dummy/config/environments/test.rb +41 -0
  88. data/spec/dummy/config/initializers/assets.rb +10 -0
  89. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  90. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  91. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  92. data/spec/dummy/config/initializers/inflections.rb +18 -0
  93. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  94. data/spec/dummy/config/initializers/session_store.rb +5 -0
  95. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  96. data/spec/dummy/config/locales/en.yml +23 -0
  97. data/spec/dummy/config/routes.rb +58 -0
  98. data/spec/dummy/config/secrets.yml +22 -0
  99. data/spec/dummy/lib/assets/.keep +0 -0
  100. data/spec/dummy/log/.keep +0 -0
  101. data/spec/dummy/public/404.html +67 -0
  102. data/spec/dummy/public/422.html +67 -0
  103. data/spec/dummy/public/500.html +66 -0
  104. data/spec/dummy/public/favicon.ico +0 -0
  105. data/spec/endpoint/compile_spec.rb +35 -0
  106. data/spec/endpoint/match_spec.rb +41 -0
  107. data/spec/endpoint/placeholders_spec.rb +30 -0
  108. data/spec/endpoint/remove_interpolated_params_spec.rb +17 -0
  109. data/spec/endpoint/values_as_params_spec.rb +31 -0
  110. data/spec/error/dup_spec.rb +12 -0
  111. data/spec/error/find_spec.rb +57 -0
  112. data/spec/error/response_spec.rb +17 -0
  113. data/spec/error/timeout_spec.rb +14 -0
  114. data/spec/error/to_s_spec.rb +85 -0
  115. data/spec/formats/form_spec.rb +27 -0
  116. data/spec/formats/json_spec.rb +66 -0
  117. data/spec/formats/multipart_spec.rb +26 -0
  118. data/spec/formats/plain_spec.rb +29 -0
  119. data/spec/interceptors/after_request_spec.rb +20 -0
  120. data/spec/interceptors/after_response_spec.rb +39 -0
  121. data/spec/interceptors/auth/basic_auth_spec.rb +17 -0
  122. data/spec/interceptors/auth/bearer_spec.rb +19 -0
  123. data/spec/interceptors/auth/body_spec.rb +36 -0
  124. data/spec/interceptors/auth/long_basic_auth_credentials_spec.rb +17 -0
  125. data/spec/interceptors/auth/no_instance_var_for_options_spec.rb +27 -0
  126. data/spec/interceptors/auth/reauthentication_configuration_spec.rb +61 -0
  127. data/spec/interceptors/auth/reauthentication_spec.rb +44 -0
  128. data/spec/interceptors/before_request_spec.rb +21 -0
  129. data/spec/interceptors/before_response_spec.rb +20 -0
  130. data/spec/interceptors/caching/hydra_spec.rb +26 -0
  131. data/spec/interceptors/caching/main_spec.rb +73 -0
  132. data/spec/interceptors/caching/methods_spec.rb +42 -0
  133. data/spec/interceptors/caching/multilevel_cache_spec.rb +139 -0
  134. data/spec/interceptors/caching/options_spec.rb +78 -0
  135. data/spec/interceptors/caching/parameters_spec.rb +24 -0
  136. data/spec/interceptors/caching/response_status_spec.rb +29 -0
  137. data/spec/interceptors/caching/to_cache_spec.rb +16 -0
  138. data/spec/interceptors/default_interceptors_spec.rb +15 -0
  139. data/spec/interceptors/default_timeout/main_spec.rb +34 -0
  140. data/spec/interceptors/define_spec.rb +30 -0
  141. data/spec/interceptors/dup_spec.rb +19 -0
  142. data/spec/interceptors/logging/main_spec.rb +37 -0
  143. data/spec/interceptors/monitoring/caching_spec.rb +66 -0
  144. data/spec/interceptors/monitoring/main_spec.rb +97 -0
  145. data/spec/interceptors/prometheus_spec.rb +54 -0
  146. data/spec/interceptors/response_competition_spec.rb +39 -0
  147. data/spec/interceptors/retry/main_spec.rb +73 -0
  148. data/spec/interceptors/return_response_spec.rb +38 -0
  149. data/spec/interceptors/rollbar/invalid_encoding_spec.rb +43 -0
  150. data/spec/interceptors/rollbar/main_spec.rb +57 -0
  151. data/spec/interceptors/throttle/main_spec.rb +236 -0
  152. data/spec/interceptors/throttle/reset_track_spec.rb +53 -0
  153. data/spec/interceptors/zipkin/distributed_tracing_spec.rb +135 -0
  154. data/spec/rails_helper.rb +6 -0
  155. data/spec/request/body_spec.rb +39 -0
  156. data/spec/request/encoding_spec.rb +38 -0
  157. data/spec/request/error_handling_spec.rb +88 -0
  158. data/spec/request/headers_spec.rb +12 -0
  159. data/spec/request/ignore_errors_spec.rb +73 -0
  160. data/spec/request/option_dup_spec.rb +13 -0
  161. data/spec/request/parallel_requests_spec.rb +59 -0
  162. data/spec/request/params_encoding_spec.rb +26 -0
  163. data/spec/request/request_without_rails_spec.rb +15 -0
  164. data/spec/request/url_patterns_spec.rb +54 -0
  165. data/spec/request/user_agent_spec.rb +26 -0
  166. data/spec/request/user_agent_without_rails_spec.rb +27 -0
  167. data/spec/response/body_spec.rb +16 -0
  168. data/spec/response/code_spec.rb +16 -0
  169. data/spec/response/data_accessor_spec.rb +29 -0
  170. data/spec/response/data_spec.rb +85 -0
  171. data/spec/response/effective_url_spec.rb +16 -0
  172. data/spec/response/headers_spec.rb +18 -0
  173. data/spec/response/options_spec.rb +18 -0
  174. data/spec/response/success_spec.rb +13 -0
  175. data/spec/response/time_spec.rb +21 -0
  176. data/spec/spec_helper.rb +9 -0
  177. data/spec/support/fixtures/json/feedback.json +11 -0
  178. data/spec/support/fixtures/json/feedbacks.json +164 -0
  179. data/spec/support/fixtures/json/localina_content_ad.json +23 -0
  180. data/spec/support/load_json.rb +5 -0
  181. data/spec/support/reset_config.rb +8 -0
  182. data/spec/support/zipkin_mock.rb +114 -0
  183. data/spec/timeouts/no_signal_spec.rb +13 -0
  184. data/spec/timeouts/timings_spec.rb +55 -0
  185. 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