quiz_api_client 4.2.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/quiz_api_client/services/courses_service.rb +26 -0
  4. data/lib/quiz_api_client/services/items_service.rb +11 -0
  5. data/lib/quiz_api_client/services/quiz_clone_jobs_service.rb +11 -0
  6. data/lib/quiz_api_client/services/quiz_sync_job_service.rb +17 -0
  7. data/lib/quiz_api_client/services/quiz_sync_jobs_service.rb +16 -0
  8. data/lib/quiz_api_client/version.rb +1 -1
  9. data/lib/quiz_api_client.rb +15 -0
  10. data/spec/config_spec.rb +66 -0
  11. data/spec/contracts/interaction_types_service_spec.rb +22 -0
  12. data/spec/contracts/item_analyses_service_spec.rb +59 -0
  13. data/spec/contracts/items_service_spec.rb +59 -0
  14. data/spec/contracts/qti_imports_service_spec.rb +34 -0
  15. data/spec/contracts/quiz_clone_job_service_spec.rb +20 -0
  16. data/spec/contracts/quiz_clone_jobs_service_spec.rb +21 -0
  17. data/spec/contracts/quiz_entries_service_spec.rb +125 -0
  18. data/spec/contracts/quiz_service_spec.rb +68 -0
  19. data/spec/contracts/quiz_session_events_service_spec.rb +30 -0
  20. data/spec/contracts/quiz_session_result_service_spec.rb +42 -0
  21. data/spec/contracts/quiz_session_service_spec.rb +56 -0
  22. data/spec/contracts/quiz_sessions_service_spec.rb +28 -0
  23. data/spec/contracts/quiz_sync_job_service_spec.rb +21 -0
  24. data/spec/contracts/quiz_sync_jobs_service_spec.rb +21 -0
  25. data/spec/contracts/quizzes_service_spec.rb +80 -0
  26. data/spec/contracts/session_item_results_service_spec.rb +60 -0
  27. data/spec/contracts/session_items_service_spec.rb +21 -0
  28. data/spec/contracts/shared_banks_spec.rb +366 -0
  29. data/spec/contracts/shared_examples/http_delete_example.rb +56 -0
  30. data/spec/contracts/shared_examples/http_get_example.rb +139 -0
  31. data/spec/contracts/shared_examples/http_patch_example.rb +60 -0
  32. data/spec/contracts/shared_examples/http_post_example.rb +68 -0
  33. data/spec/contracts/shared_examples/http_put_example.rb +60 -0
  34. data/spec/http_client_spec.rb +347 -0
  35. data/spec/json_formatter_spec.rb +32 -0
  36. data/spec/quiz_api_client/http_request/failure_spec.rb +100 -0
  37. data/spec/quiz_api_client/http_request/metrics_spec.rb +75 -0
  38. data/spec/quiz_api_client_spec.rb +124 -0
  39. data/spec/services/base_api_service_spec.rb +50 -0
  40. data/spec/services/courses_service_spec.rb +59 -0
  41. data/spec/services/interaction_types_service_spec.rb +25 -0
  42. data/spec/services/item_analyses_service_spec.rb +76 -0
  43. data/spec/services/items_service_spec.rb +56 -0
  44. data/spec/services/jwt_service_spec.rb +66 -0
  45. data/spec/services/qti_imports_service_spec.rb +114 -0
  46. data/spec/services/quiz_analyses_service_spec.rb +44 -0
  47. data/spec/services/quiz_clone_job_service_spec.rb +41 -0
  48. data/spec/services/quiz_clone_jobs_service_spec.rb +77 -0
  49. data/spec/services/quiz_entries_service_spec.rb +71 -0
  50. data/spec/services/quiz_service_spec.rb +49 -0
  51. data/spec/services/quiz_session_events_service_spec.rb +42 -0
  52. data/spec/services/quiz_session_result_service_spec.rb +26 -0
  53. data/spec/services/quiz_session_service_spec.rb +49 -0
  54. data/spec/services/quiz_sessions_service_spec.rb +42 -0
  55. data/spec/services/quiz_sync_job_service_spec.rb +41 -0
  56. data/spec/services/quiz_sync_jobs_service_spec.rb +77 -0
  57. data/spec/services/quizzes_service_spec.rb +71 -0
  58. data/spec/services/session_item_results_service_spec.rb +33 -0
  59. data/spec/services/session_items_service_spec.rb +26 -0
  60. data/spec/spec_helper.rb +42 -0
  61. data/spec/support/pact_config.rb +64 -0
  62. data/spec/support/pact_helper.rb +19 -0
  63. metadata +121 -39
  64. data/.dockerignore +0 -7
  65. data/.editorconfig +0 -16
  66. data/.gitignore +0 -13
  67. data/.rspec +0 -3
  68. data/.rubocop.yml +0 -72
  69. data/CHANGELOG.md +0 -35
  70. data/Dockerfile +0 -12
  71. data/Gemfile +0 -5
  72. data/Jenkinsfile +0 -86
  73. data/bin/console +0 -7
  74. data/bin/contracts-generate +0 -26
  75. data/bin/setup +0 -65
  76. data/docker-compose.dev.override.yml +0 -11
  77. data/docker-compose.yml +0 -10
  78. data/quiz_api_client.gemspec +0 -60
@@ -0,0 +1,60 @@
1
+ shared_examples 'a http put request to quiz_api' do
2
+ let(:quizzes_api_path) { raise 'Override in spec' }
3
+ let(:consumer_key) { 'consumer key' }
4
+ let(:consumer_request_id) { 'consumer request id' }
5
+ let(:host) { 'localhost:1234' }
6
+ let(:shared_secret) { 'secret' }
7
+ let(:scope) { raise 'Override in spec' }
8
+ let(:resource_id) { nil }
9
+ let(:response_body) { raise 'Override in spec' }
10
+ let(:service_name) { raise 'Override in spec' }
11
+ let(:status) { 200 }
12
+ let(:provider_state) { raise 'Override in spec' }
13
+ let(:user) { nil }
14
+ let(:params) { raise 'Override in spec' }
15
+ let(:body) { raise 'Override in spec' }
16
+ let(:request_description) { raise 'Override in spec (must be unique!)' }
17
+
18
+ let(:client) do
19
+ QuizApiClient::Client.new(
20
+ consumer_key: consumer_key,
21
+ consumer_request_id: consumer_request_id,
22
+ host: host,
23
+ shared_secret: shared_secret,
24
+ protocol: 'http'
25
+ )
26
+ end
27
+
28
+ context 'updating a resource' do
29
+ let(:token) do
30
+ client.jwt_service.grant_permission(
31
+ exp: token_expiration_one_year,
32
+ scope: scope,
33
+ uuid: user,
34
+ resource_id: resource_id
35
+ )
36
+ end
37
+
38
+ before do
39
+ quiz_api
40
+ .given(provider_state)
41
+ .upon_receiving(request_description)
42
+ .with(
43
+ method: :put,
44
+ path: quizzes_api_path,
45
+ headers: headers(token),
46
+ body: body
47
+ )
48
+ .will_respond_with(
49
+ status: status,
50
+ headers: { 'Content-Type' => 'application/json; charset=utf-8' },
51
+ body: response_body
52
+ )
53
+ end
54
+
55
+ it 'verifies the request is valid' do
56
+ result = client.send(service_name).update(token: token, params: params)
57
+ expect(result).to be_truthy
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,347 @@
1
+ describe QuizApiClient::HttpClient do
2
+ let(:uri) { 'http://api.quiz.docker' }
3
+ let(:jwt) { JWT.encode({ jwt: 'payload' }, 'secret') }
4
+ let(:default_request_data) { client.send(:default_request_data) }
5
+ let(:config) { QuizApiClient::Config.new { |c| c.consumer_request_id = 'hi' } }
6
+
7
+ subject(:client) { QuizApiClient::HttpClient.new(uri: uri, jwt: jwt, config: config) }
8
+
9
+ def url_for_path(path)
10
+ "http://api.quiz.docker#{path}"
11
+ end
12
+
13
+ def stub_quiz_api(path, item: 1, query: {}, headers: {}, status: 200)
14
+ stub_request(:get, url_for_path(path))
15
+ .with(query: query)
16
+ .to_return(
17
+ body: [item].to_json,
18
+ status: status,
19
+ headers: headers.merge(
20
+ 'Content-Type' => 'application/json'
21
+ )
22
+ )
23
+ end
24
+
25
+ def link_header(path, page, last_page)
26
+ link_header = "<http://api.quiz.docker#{path}?page=#{last_page}>; rel=\"last\""
27
+ link_header += ", <http://api.quiz.docker#{path}?page=#{page + 1}>; rel=\"next\"" if page < last_page
28
+ link_header
29
+ end
30
+
31
+ def mock_time(duration)
32
+ mock = Time.now
33
+ allow(Time).to receive(:now).and_return(mock, mock + duration)
34
+ mock
35
+ end
36
+
37
+ def mock_metrics(mock_time)
38
+ mock = instance_double(QuizApiClient::HttpRequest::Metrics)
39
+ expect(mock).to receive(:increment)
40
+ expect(mock).to receive(:duration).with(mock_time, mock_time + 10)
41
+ mock
42
+ end
43
+
44
+ def expect_metrics_calls(config, method, url, code)
45
+ expect(QuizApiClient::HttpRequest::Metrics).to receive(:new)
46
+ .with(config, method, url, code)
47
+ .and_return(mock_metrics(mock_time(10)))
48
+ end
49
+
50
+ def expect_raise_error_call(config, method, url, response, current_error)
51
+ mock_failure = instance_double(QuizApiClient::HttpRequest::Failure)
52
+
53
+ expect(QuizApiClient::HttpRequest::Failure).to receive(:new).with(config).and_return(mock_failure)
54
+
55
+ expect(mock_failure).to(
56
+ receive(:raise_error)
57
+ .with(method, url, response: response, current_error: current_error)
58
+ .and_raise(QuizApiClient::HttpClient::RequestFailed.new(:context))
59
+ )
60
+ end
61
+
62
+ it { is_expected.to be_a HTTParty }
63
+
64
+ describe 'get' do
65
+ it 'makes a get request' do
66
+ path = '/api/quizzes'
67
+ stub_quiz_api path, query: { sort: 'alpha' }
68
+ expect_metrics_calls(client.config, :get, url_for_path(path), 200)
69
+ response = client.get(path, all: false, query: { sort: 'alpha' })
70
+ expect(response.parsed_response).to eq [1]
71
+ end
72
+
73
+ describe 'all' do
74
+ it 'retrieves single page when all is true' do
75
+ stub_quiz_api '/api/quizzes'
76
+ expect(client.get('/api/quizzes', all: true)).to eq [1]
77
+ end
78
+
79
+ context 'link pagination' do
80
+ it 'retrieves subsequent pages when all is true' do
81
+ path = '/api/quizzes'
82
+ stub_quiz_api path, headers: { link: link_header(path, 1, 2) }
83
+ stub_quiz_api path, item: 2, query: { page: 2 }, headers: { link: link_header(path, 2, 2) }
84
+ expect(client.get('/api/quizzes', all: true)).to eq [1, 2]
85
+ end
86
+ end
87
+
88
+ context 'dynamo pagination' do
89
+ let(:dynamo_headers) { { 'x-last-evaluated-hash-key' => 'foo', 'x-last-evaluated-range-key' => 'bar' } }
90
+ let(:dynamo_params) { { last_evaluated_hash_key: 'foo', last_evaluated_range_key: 'bar' } }
91
+
92
+ it 'retrieves subsequent pages when all is true' do
93
+ path = '/api/quizzes'
94
+ stub_quiz_api path, headers: dynamo_headers
95
+ stub_quiz_api path, item: 2, query: dynamo_params
96
+ expect(client.get('/api/quizzes', all: true)).to eq [1, 2]
97
+ end
98
+
99
+ it 'retrieves subsequent pages when all is true and query is present' do
100
+ path = '/api/quizzes'
101
+ stub_quiz_api path, query: { my_id: 12 }, headers: dynamo_headers
102
+ stub_quiz_api path, item: 2, query: { my_id: 12, **dynamo_params }
103
+ expect(client.get('/api/quizzes', query: { my_id: 12 }, all: true)).to eq [1, 2]
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ describe 'post' do
110
+ it 'makes a post request' do
111
+ url = 'http://api.quiz.docker/api/quizzes'
112
+ expect(client.class).to receive(:post).with(
113
+ url,
114
+ body: { title: 'ohai' }.to_json,
115
+ **default_request_data
116
+ ).and_return(instance_double('HTTParty::Response', success?: true, code: 200))
117
+ expect_metrics_calls(client.config, :post, url, 200)
118
+ client.post('/api/quizzes', title: 'ohai')
119
+ end
120
+ end
121
+
122
+ describe 'patch' do
123
+ it 'makes a patch request' do
124
+ url = 'http://api.quiz.docker/api/quizzes/1'
125
+ expect(client.class).to receive(:patch).with(
126
+ url,
127
+ body: { title: 'new title' }.to_json,
128
+ **default_request_data
129
+ ).and_return(instance_double('HTTParty::Response', success?: true, code: 200))
130
+ expect_metrics_calls(client.config, :patch, url, 200)
131
+ client.patch('/api/quizzes/1', title: 'new title')
132
+ end
133
+ end
134
+
135
+ describe 'put' do
136
+ it 'makes a put request' do
137
+ url = 'http://api.quiz.docker/api/quizzes/1'
138
+ expect(client.class).to receive(:put).with(
139
+ url,
140
+ body: { title: 'new title' }.to_json,
141
+ **default_request_data
142
+ ).and_return(instance_double('HTTParty::Response', success?: true, code: 200))
143
+ expect_metrics_calls(client.config, :put, url, 200)
144
+ client.put('/api/quizzes/1', title: 'new title')
145
+ end
146
+ end
147
+
148
+ describe 'delete' do
149
+ it 'makes a delete request' do
150
+ url = 'http://api.quiz.docker/api/quizzes/1'
151
+ expect(client.class).to receive(:delete).with(
152
+ url,
153
+ **default_request_data
154
+ ).and_return(instance_double('HTTParty::Response', success?: true, code: 200))
155
+ expect_metrics_calls(client.config, :delete, url, 200)
156
+ client.delete('/api/quizzes/1')
157
+ end
158
+ end
159
+
160
+ describe 'headers' do
161
+ it 'includes the correct headers' do
162
+ expect(default_request_data[:headers]).to eq(
163
+ 'Authorization' => jwt,
164
+ 'AuthType' => 'Signature',
165
+ 'Accept' => 'application/json',
166
+ 'Content-Type' => 'application/json',
167
+ 'X-Consumer-Request-Id' => 'hi'
168
+ )
169
+ end
170
+ end
171
+
172
+ describe '#successful_response?' do
173
+ it 'returns true if the response is successful' do
174
+ resp = instance_double('HTTParty::Response', success?: true)
175
+ expect(client.send(:successful_response?, resp)).to be_truthy
176
+ end
177
+
178
+ it 'returns true if the response code is 401' do
179
+ resp = instance_double('HTTParty::Response', success?: false, code: 401)
180
+ expect(client.send(:successful_response?, resp)).to be_truthy
181
+ end
182
+
183
+ it 'returns false if the response is not successful' do
184
+ resp = instance_double('HTTParty::Response', success?: false, code: 404)
185
+ expect(client.send(:successful_response?, resp)).to be_falsey
186
+ end
187
+ end
188
+
189
+ describe 'error handling' do
190
+ context 'with :sentry_raven error handler' do
191
+ let(:error_handler) { :sentry_raven }
192
+ let(:path) { '/' }
193
+ let(:url) { "http://api.quiz.docker#{path}" }
194
+ let(:error_context) do
195
+ {
196
+ quiz_api_client: {
197
+ request: {
198
+ method: :get,
199
+ url: url
200
+ }
201
+ }
202
+ }
203
+ end
204
+
205
+ before do
206
+ client.config.error_handler = error_handler
207
+ end
208
+
209
+ context 'with non-success responses' do
210
+ let(:body) { '[2]' }
211
+ let(:code) { 404 }
212
+ let(:path) { '/api/quizzes' }
213
+ let(:context_url) { url }
214
+ let(:method) { :get }
215
+ let(:error_context) do
216
+ {
217
+ quiz_api_client: {
218
+ request: {
219
+ method: method,
220
+ url: context_url
221
+ },
222
+ response: {
223
+ body: body,
224
+ code: code
225
+ }
226
+ }
227
+ }
228
+ end
229
+ let(:mock_response) do
230
+ instance_double(
231
+ 'HTTParty::Response',
232
+ success?: false,
233
+ body: body,
234
+ code: code
235
+ )
236
+ end
237
+ let(:success_response) do
238
+ # rubocop:disable Metrics/LineLength
239
+ instance_double(
240
+ 'HTTParty::Response',
241
+ body: body,
242
+ code: 200,
243
+ parsed_response: [1],
244
+ headers: {
245
+ 'link' => '<http://api.quiz.docker/api/quizzes?page=2>; rel="last", <http://api.quiz.docker/api/quizzes?page=2>; rel="next"',
246
+ 'content-type' => ['application/json']
247
+ }
248
+ )
249
+ # rubocop:enable Metrics/LineLength
250
+ end
251
+
252
+ context ':get request' do
253
+ it 'raises error' do
254
+ expect(client).to receive(:successful_response?).with(mock_response).and_return(false)
255
+ expect(client.class).to receive(:get).and_return(mock_response)
256
+ expect_metrics_calls(client.config, method, url_for_path(path), code)
257
+ expect_raise_error_call(client.config, method, url_for_path(path), mock_response, nil)
258
+ expect { client.get(path, all: false, query: { sort: 'alpha' }) }
259
+ .to raise_error(QuizApiClient::HttpClient::RequestFailed)
260
+ end
261
+ end
262
+
263
+ context ':post request' do
264
+ let(:method) { :post }
265
+
266
+ it 'raises error' do
267
+ expect(client).to receive(:successful_response?).with(mock_response).and_return(false)
268
+ expect(client.class).to receive(:post).and_return(mock_response)
269
+ expect_metrics_calls(client.config, method, url_for_path(path), code)
270
+ expect_raise_error_call(client.config, method, url_for_path(path), mock_response, nil)
271
+ expect { client.post(path) }.to raise_error(QuizApiClient::HttpClient::RequestFailed)
272
+ end
273
+ end
274
+
275
+ context ':put request' do
276
+ let(:method) { :put }
277
+
278
+ it 'raises error' do
279
+ expect(client).to receive(:successful_response?).with(mock_response).and_return(false)
280
+ expect(client.class).to receive(:put).and_return(mock_response)
281
+ expect_metrics_calls(client.config, method, url_for_path(path), code)
282
+ expect_raise_error_call(client.config, method, url_for_path(path), mock_response, nil)
283
+ expect { client.put(path) }.to raise_error(QuizApiClient::HttpClient::RequestFailed)
284
+ end
285
+ end
286
+
287
+ context ':patch request' do
288
+ let(:method) { :patch }
289
+
290
+ it 'raises error' do
291
+ expect(client).to receive(:successful_response?).with(mock_response).and_return(false)
292
+ expect(client.class).to receive(:patch).and_return(mock_response)
293
+ expect_metrics_calls(client.config, method, url_for_path(path), code)
294
+ expect_raise_error_call(client.config, method, url_for_path(path), mock_response, nil)
295
+ expect { client.patch(path) }.to raise_error(QuizApiClient::HttpClient::RequestFailed)
296
+ end
297
+ end
298
+
299
+ context ':delete request' do
300
+ let(:method) { :delete }
301
+
302
+ it 'raises error' do
303
+ expect(client).to receive(:successful_response?).with(mock_response).and_return(false)
304
+ expect(client.class).to receive(:delete).and_return(mock_response)
305
+ expect_metrics_calls(client.config, method, url_for_path(path), code)
306
+ expect_raise_error_call(client.config, method, url_for_path(path), mock_response, nil)
307
+ expect { client.delete(path) }.to raise_error(QuizApiClient::HttpClient::RequestFailed)
308
+ end
309
+ end
310
+
311
+ context 'with pagination' do
312
+ let(:context_url) { "#{url}?page=2" }
313
+
314
+ it 'raises error if one of the linked pages does not return 200 response' do
315
+ stub_quiz_api path, headers: { link: link_header(path, 1, 2) }
316
+ stub_quiz_api path, item: 2, query: { page: 2 }, headers: { link: link_header(path, 2, 2) }, status: code
317
+ allow(HTTParty::Response).to receive(:new).and_return(success_response, mock_response)
318
+ expect(client).to receive(:successful_response?).and_return(true, false).twice
319
+ expect_raise_error_call(client.config, method, context_url, mock_response, nil)
320
+ expect { client.get('/api/quizzes', all: true) }.to raise_error(QuizApiClient::HttpClient::RequestFailed)
321
+ end
322
+ end
323
+ end
324
+
325
+ it 'handles an HTTParty::Error error' do
326
+ stub_request(:get, url).to_raise(HTTParty::Error)
327
+ expect_metrics_calls(client.config, :get, url_for_path(path), 0)
328
+ expect_raise_error_call(client.config, :get, url_for_path(path), nil, kind_of(HTTParty::Error))
329
+ expect { client.get('/') }.to raise_error(QuizApiClient::HttpClient::RequestFailed)
330
+ end
331
+
332
+ it 'handles an Errno::ECONNREFUSED error' do
333
+ stub_request(:get, url).to_raise(Errno::ECONNREFUSED)
334
+ expect_metrics_calls(client.config, :get, url_for_path(path), 0)
335
+ expect_raise_error_call(client.config, :get, url_for_path(path), nil, kind_of(Errno::ECONNREFUSED))
336
+ expect { client.get('/') }.to raise_error(QuizApiClient::HttpClient::RequestFailed)
337
+ end
338
+
339
+ it 'handles an Net::ReadTimeout error' do
340
+ stub_request(:get, url).to_raise(Net::ReadTimeout)
341
+ expect_metrics_calls(client.config, :get, url_for_path(path), 0)
342
+ expect_raise_error_call(client.config, :get, url_for_path(path), nil, kind_of(Net::ReadTimeout))
343
+ expect { client.get('/') }.to raise_error(QuizApiClient::HttpClient::RequestFailed)
344
+ end
345
+ end
346
+ end
347
+ end
@@ -0,0 +1,32 @@
1
+ describe QuizApiClient::JSONFormatter do
2
+ let(:logger) { double('logger') }
3
+ let(:level) { :info }
4
+ let(:uri) { 'uri_here' }
5
+ let(:request) do
6
+ r = double('request')
7
+ allow(r).to receive(:last_uri) { uri }
8
+ r
9
+ end
10
+ let(:status_code) { 200 }
11
+ let(:request_id) { 'request_id' }
12
+ let(:response) do
13
+ r = double('response')
14
+ allow(r).to receive(:code) { status_code }
15
+ allow(r).to receive(:headers) { { 'x-request-id' => [request_id] } }
16
+ r
17
+ end
18
+
19
+ subject(:formatter) { QuizApiClient::JSONFormatter.new(logger, level) }
20
+
21
+ describe 'logging formatter' do
22
+ it 'processes logging calls into a JSON format' do
23
+ expect(logger).to receive(level).with(
24
+ client_request_id: request_id,
25
+ request_url: uri,
26
+ response_code: status_code
27
+ )
28
+
29
+ formatter.format(request, response)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,100 @@
1
+ require 'sentry-raven'
2
+
3
+ describe QuizApiClient::HttpRequest::Failure do
4
+ let(:config) { QuizApiClient::Config.new }
5
+ let(:method) { 'GET' }
6
+ let(:url) { 'https://something.com' }
7
+ let(:response_code) { 500 }
8
+ let(:response_body) { 'I am a response body!' }
9
+ let(:response) { instance_double('HTTParty::Response', code: response_code, body: response_body) }
10
+
11
+ subject { described_class.new(config) }
12
+
13
+ describe '#raise_error' do
14
+ it 'raises the QuizApiClient::HttpClient::RequestFailed error' do
15
+ expect do
16
+ subject.raise_error(method, url)
17
+ end.to raise_error(QuizApiClient::HttpClient::RequestFailed)
18
+ end
19
+
20
+ context 'when there is a current exception' do
21
+ let(:error_context) do
22
+ {
23
+ quiz_api_client: {
24
+ request: {
25
+ method: method,
26
+ url: url
27
+ }
28
+ }
29
+ }
30
+ end
31
+ let(:current_error_message) { 'standard error message' }
32
+ let(:current_error) { StandardError.new(current_error_message) }
33
+ let(:raise_error_call) { -> { subject.raise_error(method, url, current_error: current_error) } }
34
+
35
+ it 'sets the context on the error' do
36
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed) do |error|
37
+ expect(error.context).to eq error_context
38
+ end
39
+ end
40
+
41
+ it 'sets the error message' do
42
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed) do |error|
43
+ expect(error.message).to eq current_error_message
44
+ end
45
+ end
46
+
47
+ context 'when error_handler is set to :sentry_raven' do
48
+ before do
49
+ config.error_handler = :sentry_raven
50
+ end
51
+
52
+ it 'sets the Raven context to the context on the error' do
53
+ expect(Raven).to receive(:extra_context).with(error_context)
54
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed)
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'when there is a response' do
60
+ let(:error_context) do
61
+ {
62
+ quiz_api_client: {
63
+ request: {
64
+ method: method,
65
+ url: url
66
+ },
67
+ response: {
68
+ body: response_body,
69
+ code: response_code
70
+ }
71
+ }
72
+ }
73
+ end
74
+ let(:raise_error_call) { -> { subject.raise_error(method, url, response: response) } }
75
+
76
+ it 'sets the context on the error' do
77
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed) do |error|
78
+ expect(error.context).to eq error_context
79
+ end
80
+ end
81
+
82
+ it 'sets the error message' do
83
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed) do |error|
84
+ expect(error.message).to eq "#{url} responded #{response_body} (#{response_code})"
85
+ end
86
+ end
87
+
88
+ context 'when error_handler is set to :sentry_raven' do
89
+ before do
90
+ config.error_handler = :sentry_raven
91
+ end
92
+
93
+ it 'sets the Raven context to the context on the error' do
94
+ expect(Raven).to receive(:extra_context).with(error_context)
95
+ expect(raise_error_call).to raise_error(QuizApiClient::HttpClient::RequestFailed)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,75 @@
1
+ require 'inst_statsd'
2
+
3
+ describe QuizApiClient::HttpRequest::Metrics do
4
+ let(:config) { QuizApiClient::Config.new }
5
+ let(:method) { 'GET' }
6
+ let(:url) { 'https://something.com' }
7
+ let(:code) { 200 }
8
+
9
+ subject { described_class.new(config, method, url, code) }
10
+
11
+ describe '#increment' do
12
+ it 'does nothing when the metrics handler is nil' do
13
+ allow(config).to receive(:metrics_handler).and_return(nil)
14
+ expect(InstStatsd::Statsd).to receive(:increment).never
15
+
16
+ subject.increment
17
+ end
18
+
19
+ it 'does nothing when the metrics namespace is nil' do
20
+ allow(config).to receive(:metrics_namespace).and_return(nil)
21
+ expect(InstStatsd::Statsd).to receive(:increment).never
22
+
23
+ subject.increment
24
+ end
25
+
26
+ it 'calls the #increment method for InstStatsd' do
27
+ config.setup_metrics(:inststatsd, 'fake-namespace')
28
+ expect(InstStatsd::Statsd).to receive(:increment).with(
29
+ 'fake-namespace.quiz_api_client.request.count',
30
+ tags: {
31
+ method: method,
32
+ status: code,
33
+ url: url
34
+ }
35
+ )
36
+
37
+ subject.increment
38
+ end
39
+ end
40
+
41
+ describe '#duration' do
42
+ let(:duration_in_secs) { 10 }
43
+ let(:start_time) { Time.parse('2021-11-09 15:11:48 -0500') }
44
+ let(:end_time) { start_time + duration_in_secs }
45
+
46
+ it 'does nothing when the metrics handler is nil' do
47
+ allow(config).to receive(:metrics_handler).and_return(nil)
48
+ expect(InstStatsd::Statsd).to receive(:timing).never
49
+
50
+ subject.duration(start_time, end_time)
51
+ end
52
+
53
+ it 'does nothing when the metrics namespace is nil' do
54
+ allow(config).to receive(:metrics_namespace).and_return(nil)
55
+ expect(InstStatsd::Statsd).to receive(:timing).never
56
+
57
+ subject.duration(start_time, end_time)
58
+ end
59
+
60
+ it 'calls the #timing method for InstStatsd' do
61
+ config.setup_metrics(:inststatsd, 'fake-namespace')
62
+ expect(InstStatsd::Statsd).to receive(:timing).with(
63
+ 'fake-namespace.quiz_api_client.request.duration_ms',
64
+ duration_in_secs * 1_000,
65
+ tags: {
66
+ method: method,
67
+ status: code,
68
+ url: url
69
+ }
70
+ )
71
+
72
+ subject.duration(start_time, end_time)
73
+ end
74
+ end
75
+ end