lhc 13.0.0 → 15.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yml +15 -0
  3. data/.github/workflows/test.yml +15 -0
  4. data/.rubocop.yml +344 -19
  5. data/.ruby-version +1 -1
  6. data/README.md +89 -0
  7. data/Rakefile +3 -3
  8. data/lhc.gemspec +3 -1
  9. data/lib/lhc.rb +70 -59
  10. data/lib/lhc/concerns/lhc/fix_invalid_encoding_concern.rb +1 -0
  11. data/lib/lhc/config.rb +16 -0
  12. data/lib/lhc/endpoint.rb +3 -0
  13. data/lib/lhc/error.rb +7 -4
  14. data/lib/lhc/interceptor.rb +4 -0
  15. data/lib/lhc/interceptors.rb +1 -0
  16. data/lib/lhc/interceptors/auth.rb +10 -5
  17. data/lib/lhc/interceptors/caching.rb +14 -3
  18. data/lib/lhc/interceptors/logging.rb +4 -2
  19. data/lib/lhc/interceptors/monitoring.rb +46 -11
  20. data/lib/lhc/interceptors/retry.rb +2 -0
  21. data/lib/lhc/interceptors/rollbar.rb +3 -2
  22. data/lib/lhc/interceptors/throttle.rb +7 -2
  23. data/lib/lhc/interceptors/zipkin.rb +2 -0
  24. data/lib/lhc/request.rb +37 -4
  25. data/lib/lhc/response.rb +1 -0
  26. data/lib/lhc/response/data.rb +1 -1
  27. data/lib/lhc/scrubber.rb +45 -0
  28. data/lib/lhc/scrubbers/auth_scrubber.rb +33 -0
  29. data/lib/lhc/scrubbers/body_scrubber.rb +28 -0
  30. data/lib/lhc/scrubbers/headers_scrubber.rb +38 -0
  31. data/lib/lhc/scrubbers/params_scrubber.rb +14 -0
  32. data/lib/lhc/version.rb +1 -1
  33. data/spec/config/scrubs_spec.rb +108 -0
  34. data/spec/error/to_s_spec.rb +13 -8
  35. data/spec/formats/multipart_spec.rb +2 -2
  36. data/spec/formats/plain_spec.rb +1 -1
  37. data/spec/interceptors/after_response_spec.rb +1 -1
  38. data/spec/interceptors/caching/main_spec.rb +2 -2
  39. data/spec/interceptors/caching/multilevel_cache_spec.rb +2 -1
  40. data/spec/interceptors/define_spec.rb +1 -0
  41. data/spec/interceptors/logging/main_spec.rb +21 -1
  42. data/spec/interceptors/monitoring/caching_spec.rb +66 -0
  43. data/spec/interceptors/response_competition_spec.rb +2 -2
  44. data/spec/interceptors/return_response_spec.rb +2 -2
  45. data/spec/interceptors/rollbar/main_spec.rb +27 -15
  46. data/spec/request/scrubbed_headers_spec.rb +101 -0
  47. data/spec/request/scrubbed_options_spec.rb +194 -0
  48. data/spec/request/scrubbed_params_spec.rb +35 -0
  49. data/spec/response/data_spec.rb +2 -2
  50. data/spec/support/zipkin_mock.rb +1 -0
  51. metadata +40 -21
  52. data/.rubocop.localch.yml +0 -325
  53. data/cider-ci.yml +0 -5
  54. data/cider-ci/bin/bundle +0 -51
  55. data/cider-ci/bin/ruby_install +0 -8
  56. data/cider-ci/bin/ruby_version +0 -25
  57. data/cider-ci/jobs/rspec-activesupport-5.yml +0 -27
  58. data/cider-ci/jobs/rspec-activesupport-6.yml +0 -28
  59. data/cider-ci/jobs/rubocop.yml +0 -18
  60. data/cider-ci/task_components/bundle.yml +0 -22
  61. data/cider-ci/task_components/rspec.yml +0 -36
  62. data/cider-ci/task_components/rubocop.yml +0 -29
  63. data/cider-ci/task_components/ruby.yml +0 -15
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe LHC::Monitoring do
6
+ let(:stub) do
7
+ stub_request(:get, 'http://local.ch').to_return(status: 200, body: 'The Website')
8
+ end
9
+
10
+ module Statsd
11
+ def self.count(_path, _value); end
12
+
13
+ def self.timing(_path, _value); end
14
+ end
15
+
16
+ before(:each) do
17
+ LHC::Monitoring.statsd = Statsd
18
+ Rails.cache.clear
19
+ allow(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.before_request', 1)
20
+ allow(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.count', 1)
21
+ allow(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.after_request', 1)
22
+ allow(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.200', 1)
23
+ end
24
+
25
+ context 'interceptors configured correctly' do
26
+ before do
27
+ LHC.config.interceptors = [LHC::Caching, LHC::Monitoring]
28
+ end
29
+
30
+ context 'requesting with cache option' do
31
+ it 'monitors miss/hit for caching' do
32
+ stub
33
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.cache.miss', 1)
34
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.cache.hit', 1)
35
+ LHC.get('http://local.ch', cache: true)
36
+ LHC.get('http://local.ch', cache: true)
37
+ end
38
+ end
39
+
40
+ context 'request uncached' do
41
+ it 'requesting without cache option' do
42
+ stub
43
+ expect(Statsd).not_to receive(:count).with('lhc.dummy.test.local_ch.get.cache.miss', 1)
44
+ expect(Statsd).not_to receive(:count).with('lhc.dummy.test.local_ch.get.cache.hit', 1)
45
+ LHC.get('http://local.ch')
46
+ LHC.get('http://local.ch')
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'wrong interceptor order' do
52
+ before(:each) do
53
+ LHC.config.interceptors = [LHC::Monitoring, LHC::Caching] # monitoring needs to be after Caching
54
+ end
55
+
56
+ it 'does monitors miss/hit for caching and warns about wrong order of interceptors' do
57
+ stub
58
+ expect(Statsd).not_to receive(:count).with('lhc.dummy.test.local_ch.get.cache.miss', 1)
59
+ expect(Statsd).not_to receive(:count).with('lhc.dummy.test.local_ch.get.cache.hit', 1)
60
+ expect(-> {
61
+ LHC.get('http://local.ch', cache: true)
62
+ LHC.get('http://local.ch', cache: true)
63
+ }).to output("[WARNING] Your interceptors must include LHC::Caching and LHC::Monitoring and also in that order.\n[WARNING] Your interceptors must include LHC::Caching and LHC::Monitoring and also in that order.\n").to_stderr
64
+ end
65
+ end
66
+ end
@@ -12,7 +12,7 @@ describe LHC do
12
12
 
13
13
  def before_request
14
14
  if @@cached
15
- return LHC::Response.new(Typhoeus::Response.new(response_body: 'Im served from local cache'), nil)
15
+ return LHC::Response.new(Typhoeus::Response.new(response_code: 200, return_code: :ok, response_body: 'Im served from local cache'), nil)
16
16
  end
17
17
  end
18
18
  end
@@ -22,7 +22,7 @@ describe LHC do
22
22
 
23
23
  def before_request
24
24
  if request.response.nil?
25
- return LHC::Response.new(Typhoeus::Response.new(response_body: 'Im served from remote cache'), nil)
25
+ return LHC::Response.new(Typhoeus::Response.new(response_code: 200, return_code: :ok, response_body: 'Im served from remote cache'), nil)
26
26
  end
27
27
  end
28
28
  end
@@ -8,7 +8,7 @@ describe LHC do
8
8
  class CacheInterceptor < LHC::Interceptor
9
9
 
10
10
  def before_request
11
- LHC::Response.new(Typhoeus::Response.new(response_body: 'Im served from cache'), nil)
11
+ LHC::Response.new(Typhoeus::Response.new(response_code: 200, return_code: :ok, response_body: 'Im served from cache'), nil)
12
12
  end
13
13
  end
14
14
  LHC.configure { |c| c.interceptors = [CacheInterceptor] }
@@ -23,7 +23,7 @@ describe LHC do
23
23
  before(:each) do
24
24
  class AnotherInterceptor < LHC::Interceptor
25
25
  def before_request
26
- LHC::Response.new(Typhoeus::Response.new({}), nil)
26
+ LHC::Response.new(Typhoeus::Response.new(response_code: 200, return_code: :ok), nil)
27
27
  end
28
28
  end
29
29
  end
@@ -36,22 +36,34 @@ describe LHC::Rollbar do
36
36
  )
37
37
  end
38
38
 
39
- context 'additional params' do
40
- it 'does report errors to rollbar with additional data' do
41
- stub_request(:get, 'http://local.ch')
42
- .to_return(status: 400)
43
- expect(-> { LHC.get('http://local.ch', rollbar: { additional: 'data' }) })
44
- .to raise_error LHC::BadRequest
45
- expect(::Rollbar).to have_received(:warning)
46
- .with(
47
- 'Status: 400 URL: http://local.ch',
48
- hash_including(
49
- response: anything,
50
- request: anything,
51
- additional: 'data'
52
- )
39
+ it 'does report errors to rollbar with additional data' do
40
+ stub_request(:get, 'http://local.ch')
41
+ .to_return(status: 400)
42
+ expect(-> { LHC.get('http://local.ch', rollbar: { additional: 'data' }) })
43
+ .to raise_error LHC::BadRequest
44
+ expect(::Rollbar).to have_received(:warning)
45
+ .with(
46
+ 'Status: 400 URL: http://local.ch',
47
+ hash_including(
48
+ response: anything,
49
+ request: anything,
50
+ additional: 'data'
53
51
  )
54
- end
52
+ )
53
+ end
54
+
55
+ it 'scrubs sensitive data' do
56
+ LHC.config.scrubs[:params] << 'api_key'
57
+ LHC.config.scrubs[:headers] << 'private_key'
58
+ stub_request(:get, 'http://local.ch?api_key=123-abc').to_return(status: 400)
59
+ expect(-> { LHC.get('http://local.ch', params: { api_key: '123-abc' }, headers: { private_key: 'abc-123' }) })
60
+ .to raise_error LHC::BadRequest
61
+ expect(::Rollbar).to have_received(:warning)
62
+ .with(
63
+ 'Status: 400 URL: http://local.ch',
64
+ response: hash_including(body: anything, code: anything, headers: anything, time: anything, timeout?: anything),
65
+ request: hash_including(url: anything, method: anything, headers: hash_including(private_key: LHC::Scrubber::SCRUB_DISPLAY), params: { api_key: LHC::Scrubber::SCRUB_DISPLAY })
66
+ )
55
67
  end
56
68
  end
57
69
  end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe LHC::Request do
6
+ let(:headers) { { private_key: 'xyz-123' } }
7
+ let(:response) { LHC.get(:local, headers: headers) }
8
+ let(:auth) { {} }
9
+
10
+ before :each do
11
+ LHC.config.endpoint(:local, 'http://local.ch', auth: auth)
12
+ stub_request(:get, 'http://local.ch').with(headers: headers)
13
+ end
14
+
15
+ it 'scrubs "private_key"' do
16
+ LHC.config.scrubs[:headers] << 'private_key'
17
+ expect(response.request.scrubbed_headers).to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
18
+ end
19
+
20
+ it 'does not add a new attribute when a non existing header should be scrubbed' do
21
+ LHC.config.scrubs[:headers] << 'anything'
22
+ expect(response.request.scrubbed_headers).not_to include('anything' => LHC::Scrubber::SCRUB_DISPLAY)
23
+ end
24
+
25
+ context 'when strings instead of symbols are provided' do
26
+ let(:headers) { { 'private_key' => 'xyz-123' } }
27
+
28
+ it 'scrubs "private_key"' do
29
+ LHC.config.scrubs[:headers] << 'private_key'
30
+ expect(response.request.scrubbed_headers).to include('private_key' => LHC::Scrubber::SCRUB_DISPLAY)
31
+ end
32
+ end
33
+
34
+ context 'other authentication strategy' do
35
+ let(:api_key) { '123456' }
36
+ let(:authorization_header) { { 'Authorization' => "Apikey #{api_key}" } }
37
+ let(:headers) { authorization_header }
38
+
39
+ it 'provides srubbed Authorization header' do
40
+ LHC.config.scrubs[:headers] << 'Authorization'
41
+ expect(response.request.scrubbed_headers).to include('Authorization' => LHC::Scrubber::SCRUB_DISPLAY)
42
+ expect(response.request.headers).to include(authorization_header)
43
+ end
44
+ end
45
+
46
+ describe 'auth' do
47
+ before :each do
48
+ LHC.config.interceptors = [LHC::Auth]
49
+ stub_request(:get, 'http://local.ch').with(headers: authorization_header)
50
+ end
51
+
52
+ let(:request) do
53
+ response = LHC.get(:local)
54
+ response.request
55
+ end
56
+
57
+ context 'bearer authentication' do
58
+ let(:bearer_token) { '123456' }
59
+ let(:authorization_header) { { 'Authorization' => "Bearer #{bearer_token}" } }
60
+ let(:auth) { { bearer: -> { bearer_token } } }
61
+
62
+ it 'provides srubbed request headers' do
63
+ expect(request.scrubbed_headers).to include('Authorization' => "Bearer #{LHC::Scrubber::SCRUB_DISPLAY}")
64
+ expect(request.headers).to include(authorization_header)
65
+ end
66
+
67
+ context 'when nothing should get scrubbed' do
68
+ before :each do
69
+ LHC.config.scrubs = {}
70
+ end
71
+
72
+ it 'does not filter beaerer auth' do
73
+ expect(request.scrubbed_headers).to include(authorization_header)
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'basic authentication' do
79
+ let(:username) { 'steve' }
80
+ let(:password) { 'abcdefg' }
81
+ let(:credentials_base_64_codiert) { Base64.strict_encode64("#{username}:#{password}").chomp }
82
+ let(:authorization_header) { { 'Authorization' => "Basic #{credentials_base_64_codiert}" } }
83
+ let(:auth) { { basic: { username: username, password: password } } }
84
+
85
+ it 'provides srubbed request headers' do
86
+ expect(request.scrubbed_headers).to include('Authorization' => "Basic #{LHC::Scrubber::SCRUB_DISPLAY}")
87
+ expect(request.headers).to include(authorization_header)
88
+ end
89
+
90
+ context 'when nothing should get scrubbed' do
91
+ before :each do
92
+ LHC.config.scrubs = {}
93
+ end
94
+
95
+ it 'does not filter basic auth' do
96
+ expect(request.scrubbed_headers).to include(authorization_header)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe LHC::Request do
6
+ before :each do
7
+ LHC.config.interceptors = [LHC::Auth]
8
+ LHC.config.endpoint(:local, 'http://local.ch', auth: auth)
9
+ LHC.config.scrubs[:params] << 'api_key'
10
+ LHC.config.scrubs[:headers] << 'private_key'
11
+ LHC.config.scrubs[:body] << 'user_token'
12
+ stub_request(:post, "http://local.ch?#{params.to_query}").with(headers: authorization_header.merge(headers), body: body.to_json)
13
+ end
14
+
15
+ let(:bearer_token) { '123456' }
16
+ let(:authorization_header) { { 'Authorization' => "Bearer #{bearer_token}" } }
17
+ let(:auth) { { bearer: -> { bearer_token } } }
18
+ let(:params) { { api_key: 'api-key-params' } }
19
+ let(:headers) { { private_key: 'private-key-header' } }
20
+ let(:body) { { user_token: 'user-token-body' } }
21
+
22
+ let(:request) do
23
+ response = LHC.post(:local, params: params, headers: headers, body: body)
24
+ response.request
25
+ end
26
+
27
+ it 'provides srubbed request options' do
28
+ expect(request.scrubbed_options[:params]).to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
29
+ expect(request.scrubbed_options[:headers]).to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
30
+ expect(request.scrubbed_options[:body]).to include(user_token: LHC::Scrubber::SCRUB_DISPLAY)
31
+ expect(request.scrubbed_options[:auth][:bearer_token]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
32
+ expect(request.scrubbed_options[:auth][:basic]).to be nil
33
+ end
34
+
35
+ context 'when bearer auth is not a proc' do
36
+ let(:auth) { { bearer: bearer_token } }
37
+
38
+ it 'also scrubbes the bearer' do
39
+ expect(request.scrubbed_options[:auth][:bearer]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
40
+ expect(request.scrubbed_options[:auth][:bearer_token]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
41
+ end
42
+ end
43
+
44
+ context 'when options do not have auth' do
45
+ let(:authorization_header) { {} }
46
+ let(:auth) { nil }
47
+
48
+ it 'provides srubbed request options' do
49
+ expect(request.scrubbed_options[:params]).to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
50
+ expect(request.scrubbed_options[:headers]).to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
51
+ expect(request.scrubbed_options[:body]).to include(user_token: LHC::Scrubber::SCRUB_DISPLAY)
52
+ expect(request.scrubbed_options[:auth]).to be nil
53
+ end
54
+ end
55
+
56
+ context 'when body data is nested' do
57
+ let(:body) do
58
+ {
59
+ data: {
60
+ attributes: {
61
+ employee: {
62
+ name: 'Muster',
63
+ surname: 'Hans',
64
+ password: 'test-1234',
65
+ password_confirmation: 'test-1234'
66
+ }
67
+ }
68
+ }
69
+ }
70
+ end
71
+
72
+ it 'srubbes nested attributes' do
73
+ expect(request.scrubbed_options[:params]).to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
74
+ expect(request.scrubbed_options[:headers]).to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
75
+ expect(request.scrubbed_options[:body][:data][:attributes][:employee]).to include(password: LHC::Scrubber::SCRUB_DISPLAY)
76
+ expect(request.scrubbed_options[:body][:data][:attributes][:employee]).to include(password_confirmation: LHC::Scrubber::SCRUB_DISPLAY)
77
+ expect(request.scrubbed_options[:auth][:bearer_token]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
78
+ expect(request.scrubbed_options[:auth][:basic]).to be nil
79
+ end
80
+ end
81
+
82
+ context 'basic authentication' do
83
+ let(:username) { 'steve' }
84
+ let(:password) { 'abcdefg' }
85
+ let(:credentials_base_64_codiert) { Base64.strict_encode64("#{username}:#{password}").chomp }
86
+ let(:authorization_header) { { 'Authorization' => "Basic #{credentials_base_64_codiert}" } }
87
+ let(:auth) { { basic: { username: username, password: password } } }
88
+
89
+ it 'provides srubbed request headers' do
90
+ expect(request.scrubbed_options[:auth][:basic][:username]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
91
+ expect(request.scrubbed_options[:auth][:basic][:password]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
92
+ expect(request.scrubbed_options[:auth][:basic][:base_64_encoded_credentials]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
93
+ expect(request.scrubbed_options[:auth][:bearer]).to be nil
94
+ end
95
+ end
96
+
97
+ context 'when nothing should get scrubbed' do
98
+ before :each do
99
+ LHC.config.scrubs = {}
100
+ end
101
+
102
+ it 'does not filter anything' do
103
+ expect(request.scrubbed_options[:params]).not_to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
104
+ expect(request.scrubbed_options[:headers]).not_to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
105
+ expect(request.scrubbed_options[:body]).not_to include(user_token: LHC::Scrubber::SCRUB_DISPLAY)
106
+ expect(request.scrubbed_options[:auth][:bearer_token]).not_to eq(LHC::Scrubber::SCRUB_DISPLAY)
107
+ end
108
+ end
109
+
110
+ context 'custom data structures that respond to as_json (like LHS data or record)' do
111
+ before do
112
+ class CustomStructure
113
+
114
+ def initialize(data)
115
+ @data = data
116
+ end
117
+
118
+ def as_json
119
+ @data.as_json
120
+ end
121
+
122
+ def to_json
123
+ as_json.to_json
124
+ end
125
+ end
126
+
127
+ stub_request(:post, 'http://local.ch').with(body: custom_structure.to_json)
128
+ end
129
+
130
+ let(:custom_structure) do
131
+ CustomStructure.new(user_token: '12345')
132
+ end
133
+
134
+ let(:request) do
135
+ response = LHC.post(:local, body: custom_structure)
136
+ response.request
137
+ end
138
+
139
+ it 'provides srubbed request options' do
140
+ expect(request.scrubbed_options[:body]).to include('user_token' => LHC::Scrubber::SCRUB_DISPLAY)
141
+ end
142
+ end
143
+
144
+ context 'encoded data hash' do
145
+ let(:body) { { user_token: 'user-token-body' } }
146
+
147
+ let(:request) do
148
+ response = LHC.post(:local, body: body.to_json)
149
+ response.request
150
+ end
151
+
152
+ before :each do
153
+ stub_request(:post, 'http://local.ch').with(body: body.to_json)
154
+ end
155
+
156
+ it 'provides srubbed request options' do
157
+ expect(request.scrubbed_options[:body]).to include('user_token' => LHC::Scrubber::SCRUB_DISPLAY)
158
+ end
159
+ end
160
+
161
+ context 'array' do
162
+ let(:body) { [{ user_token: 'user-token-body' }] }
163
+
164
+ let(:request) do
165
+ response = LHC.post(:local, body: body)
166
+ response.request
167
+ end
168
+
169
+ before :each do
170
+ stub_request(:post, 'http://local.ch').with(body: body.to_json)
171
+ end
172
+
173
+ it 'provides srubbed request options' do
174
+ expect(request.scrubbed_options[:body]).to eq([user_token: LHC::Scrubber::SCRUB_DISPLAY])
175
+ end
176
+ end
177
+
178
+ context 'encoded array' do
179
+ let(:body) { [{ user_token: 'user-token-body' }] }
180
+
181
+ let(:request) do
182
+ response = LHC.post(:local, body: body.to_json)
183
+ response.request
184
+ end
185
+
186
+ before :each do
187
+ stub_request(:post, 'http://local.ch').with(body: body.to_json)
188
+ end
189
+
190
+ it 'provides srubbed request options' do
191
+ expect(request.scrubbed_options[:body]).to eq(['user_token' => LHC::Scrubber::SCRUB_DISPLAY])
192
+ end
193
+ end
194
+ end