lhc 13.0.0 → 15.0.0

Sign up to get free protection for your applications and to get access to all the features.
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