bearcat 1.6.5.beta1 → 1.6.5.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd97acc800c267140f131ef12af13e3e2303b1fd440f146a0889ba40fcb47c9b
4
- data.tar.gz: 57518d64fcca4a308711f63488ee51ba946eb10eccc2d7b956874282ad65329b
3
+ metadata.gz: e391dc610d18bc23fbfc10173270a88d3f9dca109b63ea75320bcc8c683c811d
4
+ data.tar.gz: 602353626c34e485f421e46e81cf7391b4647aa44dd2555c79a6ba5517fc8d0f
5
5
  SHA512:
6
- metadata.gz: e9c543b8d57865beebf6a86c6f505b620ff85196050ae8883ca3b84c63c94e7442a33c31ca47d4daf61f749e559d8bfa97b1ce0cd901345f18f533abf74cd94f
7
- data.tar.gz: 0db04f590f1437e072d0ecc40e003fc664b4b8fb56fccb81ae701e0891ac25db92689cc536b6fd481db38bfbd6914561f62eed8719e5ab484d20370acffc3af3
6
+ metadata.gz: 1a58bd62214f1b522ef0fbce0185b686009dd057f2e4e94c1e348ced20e8932819760bb4e2103acc2a115ffc31abaf8e6efa1897dcfbe4b688add87d395eb1b4
7
+ data.tar.gz: c355451022375fc33951674974bfd7ca8986a70a43fa05a07aba5f93352cd8d18f2c0ace525aeb3c88e33262a9f7ce3f248fcb9243a98b70cdab356c1602ae7c
@@ -25,9 +25,43 @@ module Bearcat
25
25
  @registered_endpoints
26
26
  end
27
27
 
28
+ def retry_request
29
+ response = nil
30
+ retries_count = 0
31
+ begin
32
+ response = yield
33
+ rescue => e
34
+ # Handle 5xx server errors with retry logic
35
+ if e.respond_to?(:status) && [500, 501, 502, 503, 504].include?(e.status)
36
+ # Retry if we haven't exhausted max_retries
37
+ if config[:max_retries].present? && retries_count != -1 && retries_count < config[:max_retries]
38
+ retries_count += 1
39
+ sleep(config[:retry_delay] || 0) if (config[:retry_delay] || 0) > 0
40
+ retry
41
+ end
42
+
43
+ # Retries exhausted or max_retries reached, call callback if present
44
+ # This is called when: retries are exhausted OR we never retried but max_retries is configured
45
+ if config[:on_retries_exceeded].present? && retries_count != -1
46
+ result = config[:on_retries_exceeded].call(e)
47
+ # If callback returns :retry, allow one final retry after modifying parameters
48
+ if result == :retry
49
+ retries_count = -1
50
+ retry
51
+ end
52
+ end
53
+ end
54
+
55
+ raise
56
+ end
57
+ response
58
+ end
59
+
28
60
  def request(method, &block)
29
- response = rate_limited_request do
30
- connection.send(method, &block)
61
+ response = retry_request do
62
+ rate_limited_request do
63
+ connection.send(method, &block)
64
+ end
31
65
  end
32
66
  ApiArray.process_response(response, self)
33
67
  end
@@ -59,7 +93,7 @@ module Bearcat
59
93
  def rate_limited_request
60
94
  return yield unless rate_limiter
61
95
 
62
- canvas_rate_limits= 0
96
+ canvas_rate_limits = 0
63
97
  response = nil
64
98
 
65
99
  begin
@@ -1,3 +1,3 @@
1
1
  module Bearcat
2
- VERSION = '1.6.5.beta1' unless defined?(Bearcat::VERSION)
2
+ VERSION = '1.6.5.beta2' unless defined?(Bearcat::VERSION)
3
3
  end
@@ -10,4 +10,249 @@ describe Bearcat::Client do
10
10
  client = Bearcat::Client.new(token: "test_token")
11
11
  client.config[:token].should == "test_token"
12
12
  end
13
+
14
+ describe "#retry_request" do
15
+ let(:client) { Bearcat::Client.new(prefix: "http://canvas.test", token: "test_token") }
16
+
17
+ context "when request succeeds on first attempt" do
18
+ it "returns the response without retrying" do
19
+ call_count = 0
20
+ response = client.retry_request do
21
+ call_count += 1
22
+ "success"
23
+ end
24
+
25
+ expect(response).to eq("success")
26
+ expect(call_count).to eq(1)
27
+ end
28
+ end
29
+
30
+ context "when request fails with 500 error" do
31
+ it "raises error immediately when max_retries is not configured" do
32
+ client = Bearcat::Client.new(prefix: "http://canvas.test", token: "test_token")
33
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
34
+ .to_return(status: 500)
35
+
36
+ expect {
37
+ client.list_accounts
38
+ }.to raise_error(Footrest::HttpError::InternalServerError)
39
+ end
40
+
41
+ it "retries the specified number of times before raising" do
42
+ client = Bearcat::Client.new(
43
+ prefix: "http://canvas.test",
44
+ token: "test_token",
45
+ max_retries: 3
46
+ )
47
+
48
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
49
+ .to_return(status: 500)
50
+
51
+ expect {
52
+ client.list_accounts
53
+ }.to raise_error(Footrest::HttpError::InternalServerError)
54
+
55
+ expect(WebMock).to have_requested(:get, "http://canvas.test/api/v1/accounts").times(4) # 1 initial + 3 retries
56
+ end
57
+
58
+ it "succeeds if retry succeeds before max_retries" do
59
+ client = Bearcat::Client.new(
60
+ prefix: "http://canvas.test",
61
+ token: "test_token",
62
+ max_retries: 3
63
+ )
64
+
65
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
66
+ .to_return(status: 500)
67
+ .to_return(status: 500)
68
+ .to_return(status: 200, body: '[]', headers: { 'Content-Type' => 'application/json' })
69
+
70
+ result = client.list_accounts
71
+ expect(result.members).to eq([])
72
+ expect(WebMock).to have_requested(:get, "http://canvas.test/api/v1/accounts").times(3) # 1 initial + 2 retries
73
+ end
74
+
75
+ it "calls on_retries_exceeded callback when retries are exhausted" do
76
+ callback_called = false
77
+ received_error = nil
78
+ client = Bearcat::Client.new(
79
+ prefix: "http://canvas.test",
80
+ token: "test_token",
81
+ max_retries: 2,
82
+ on_retries_exceeded: ->(error) {
83
+ callback_called = true
84
+ received_error = error
85
+ }
86
+ )
87
+
88
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
89
+ .to_return(status: 500)
90
+
91
+ client.list_accounts rescue nil
92
+ expect(callback_called).to be_truthy
93
+ expect(received_error).to be_a(Footrest::HttpError::InternalServerError)
94
+ end
95
+
96
+ it "passes error details to on_retries_exceeded callback" do
97
+ received_error = nil
98
+ client = Bearcat::Client.new(
99
+ prefix: "http://canvas.test",
100
+ token: "test_token",
101
+ max_retries: 1,
102
+ on_retries_exceeded: ->(error) { received_error = error }
103
+ )
104
+
105
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
106
+ .to_return(status: 503, body: "Service Unavailable")
107
+
108
+ client.list_accounts rescue nil
109
+ expect(received_error).to be_a(Footrest::HttpError::ServiceUnavailable)
110
+ expect(received_error.response[:status]).to eq(503)
111
+ end
112
+
113
+ it "raises error when retries are exhausted and no callback is provided" do
114
+ client = Bearcat::Client.new(
115
+ prefix: "http://canvas.test",
116
+ token: "test_token",
117
+ max_retries: 2
118
+ )
119
+
120
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
121
+ .to_return(status: 500)
122
+
123
+ expect {
124
+ client.list_accounts
125
+ }.to raise_error(Footrest::HttpError::InternalServerError)
126
+ end
127
+ end
128
+
129
+ context "when request fails with different 5xx errors" do
130
+ it "retries on 501 error" do
131
+ client = Bearcat::Client.new(
132
+ prefix: "http://canvas.test",
133
+ token: "test_token",
134
+ max_retries: 2
135
+ )
136
+
137
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
138
+ .to_return(status: 501)
139
+
140
+ expect {
141
+ client.list_accounts
142
+ }.to raise_error(Footrest::HttpError::NotImplemented)
143
+
144
+ expect(WebMock).to have_requested(:get, "http://canvas.test/api/v1/accounts").times(3)
145
+ end
146
+
147
+ it "retries on 502 error" do
148
+ client = Bearcat::Client.new(
149
+ prefix: "http://canvas.test",
150
+ token: "test_token",
151
+ max_retries: 2
152
+ )
153
+
154
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
155
+ .to_return(status: 502)
156
+
157
+ expect {
158
+ client.list_accounts
159
+ }.to raise_error(Footrest::HttpError::BadGateway)
160
+
161
+ expect(WebMock).to have_requested(:get, "http://canvas.test/api/v1/accounts").times(3)
162
+ end
163
+
164
+ it "retries on 503 error" do
165
+ client = Bearcat::Client.new(
166
+ prefix: "http://canvas.test",
167
+ token: "test_token",
168
+ max_retries: 2
169
+ )
170
+
171
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
172
+ .to_return(status: 503)
173
+
174
+ expect {
175
+ client.list_accounts
176
+ }.to raise_error(Footrest::HttpError::ServiceUnavailable)
177
+
178
+ expect(WebMock).to have_requested(:get, "http://canvas.test/api/v1/accounts").times(3)
179
+ end
180
+ end
181
+
182
+ context "when request fails with non-5xx errors" do
183
+ it "does not retry on 404 error" do
184
+ client = Bearcat::Client.new(
185
+ prefix: "http://canvas.test",
186
+ token: "test_token",
187
+ max_retries: 3
188
+ )
189
+
190
+ stub_request(:get, "http://canvas.test/api/v1/accounts/999")
191
+ .to_return(status: 404)
192
+
193
+ expect {
194
+ client.account(999)
195
+ }.to raise_error(Footrest::HttpError::NotFound)
196
+
197
+ expect(WebMock).to have_requested(:get, "http://canvas.test/api/v1/accounts/999").times(1)
198
+ end
199
+
200
+ it "does not retry on 401 error" do
201
+ client = Bearcat::Client.new(
202
+ prefix: "http://canvas.test",
203
+ token: "test_token",
204
+ max_retries: 3
205
+ )
206
+
207
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
208
+ .to_return(status: 401)
209
+
210
+ expect {
211
+ client.list_accounts
212
+ }.to raise_error(Footrest::HttpError::Unauthorized)
213
+
214
+ expect(WebMock).to have_requested(:get, "http://canvas.test/api/v1/accounts").times(1)
215
+ end
216
+ end
217
+
218
+ context "retry delay" do
219
+ it "uses custom retry_delay between retries" do
220
+ client = Bearcat::Client.new(
221
+ prefix: "http://canvas.test",
222
+ token: "test_token",
223
+ max_retries: 2,
224
+ retry_delay: 0.5
225
+ )
226
+
227
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
228
+ .to_return(status: 500)
229
+
230
+ start_time = Time.now
231
+ client.list_accounts rescue nil
232
+ elapsed_time = Time.now - start_time
233
+
234
+ # Should have slept twice (2 retries) * 0.5 seconds = ~1 second
235
+ expect(elapsed_time).to be >= 1.0
236
+ expect(elapsed_time).to be < 1.5
237
+ end
238
+
239
+ it "defaults to no delay when retry_delay is not specified" do
240
+ client = Bearcat::Client.new(
241
+ prefix: "http://canvas.test",
242
+ token: "test_token",
243
+ max_retries: 2
244
+ )
245
+
246
+ stub_request(:get, "http://canvas.test/api/v1/accounts")
247
+ .to_return(status: 500)
248
+
249
+ start_time = Time.now
250
+ client.list_accounts rescue nil
251
+ elapsed_time = Time.now - start_time
252
+
253
+ # Should complete quickly with no sleep (default retry_delay: 0)
254
+ expect(elapsed_time).to be < 0.5
255
+ end
256
+ end
257
+ end
13
258
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bearcat
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.5.beta1
4
+ version: 1.6.5.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Instructure CustomDev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-17 00:00:00.000000000 Z
11
+ date: 2026-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake