bridge_api 0.1.66 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9ca9ab902293aba483f19e3672a1c0c60aa19c2aca4175bf34a2a4db7bae6d8
4
- data.tar.gz: 004f3fee360e5c413572bf7f636ed75f8e765936668bf03a6fa7437e6b5b0441
3
+ metadata.gz: 2f7777215ef9e217d636798e5de92127e20b1506f37d7846d34cc7d435cc4ffe
4
+ data.tar.gz: 17c5e2693f9b6db371058568776c8b0c6efb09d214f694a93a19e502b5223960
5
5
  SHA512:
6
- metadata.gz: 9c5475c3391696f44fc7564fe112bfb07c850bdd9ba4c8d29164f4db30ebbdd7d17f02a096a4dbcbc4b5193c57716d66f733f2ab6e913544216389f13a8c1871
7
- data.tar.gz: 82a529c351861196e9a0cce6bcc0e581120af60cc709d75526ac8de6dd6c5f5c8b9ba20c9c4e15b00b646b8144a350f40fa5a1c91ec96b3d28644eb1dca3716a
6
+ metadata.gz: 8040ca103e5f27865337b03ceb8d761eff6b209f89c2405ec5bdcfaa842f8acc66cce2b365dfc9270b06e867c84d2dcc6a55dbd339314e8f87cf868d034860c6
7
+ data.tar.gz: cbb40a9001208af3be7056d95ad35696e682667acfcdb8dcd576a715099cca5091eb7725b3fdd85b386a896d34e6c01983e32351fa27f7d8b80fe87e9c934189
@@ -10,7 +10,7 @@ jobs:
10
10
  - name: Setup Ruby # Step 2 (we gave the name 'Setup Ruby' to this step)
11
11
  uses: ruby/setup-ruby@v1
12
12
  with:
13
- ruby-version: 2.5.0
13
+ ruby-version: 2.7.0
14
14
  - name: Install bundle and gems # Step 4
15
15
  run: |
16
16
  gem install bundler
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bridge_api (0.1.60)
5
- faraday (~> 0.9.0)
4
+ bridge_api (0.2.0)
5
+ faraday (~> 0.17.3)
6
6
  faraday_middleware (>= 0.12.2)
7
7
  footrest (>= 0.5.1)
8
8
  paul_walker (>= 0.1.1)
@@ -10,35 +10,37 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- activesupport (5.2.3)
13
+ activesupport (7.0.3)
14
14
  concurrent-ruby (~> 1.0, >= 1.0.2)
15
- i18n (>= 0.7, < 2)
16
- minitest (~> 5.1)
17
- tzinfo (~> 1.1)
15
+ i18n (>= 1.6, < 2)
16
+ minitest (>= 5.1)
17
+ tzinfo (~> 2.0)
18
18
  addressable (2.5.2)
19
19
  public_suffix (>= 2.0.2, < 4.0)
20
20
  byebug (8.2.5)
21
21
  coderay (1.1.2)
22
- concurrent-ruby (1.1.5)
22
+ concurrent-ruby (1.1.10)
23
23
  crack (0.4.3)
24
24
  safe_yaml (~> 1.0.0)
25
25
  diff-lcs (1.3)
26
- faraday (0.9.2)
26
+ faraday (0.17.5)
27
27
  multipart-post (>= 1.2, < 3)
28
- faraday_middleware (0.13.1)
28
+ faraday_middleware (0.14.0)
29
29
  faraday (>= 0.7.4, < 1.0)
30
30
  footrest (0.5.3)
31
31
  activesupport (>= 3.0.0)
32
32
  faraday (>= 0.9.0, < 1)
33
33
  link_header (>= 0.0.7)
34
34
  hashdiff (0.3.7)
35
- i18n (1.6.0)
35
+ i18n (1.10.0)
36
36
  concurrent-ruby (~> 1.0)
37
37
  link_header (0.0.8)
38
38
  method_source (0.9.0)
39
- minitest (5.11.3)
40
- multipart-post (2.0.0)
41
- paul_walker (0.1.1)
39
+ minitest (5.15.0)
40
+ mock_redis (0.31.0)
41
+ ruby2_keywords
42
+ multipart-post (2.1.1)
43
+ paul_walker (0.1.2)
42
44
  redis (>= 4.0)
43
45
  pry (0.11.3)
44
46
  coderay (~> 1.1.0)
@@ -48,7 +50,7 @@ GEM
48
50
  rack-protection (1.5.5)
49
51
  rack
50
52
  rake (0.9.6)
51
- redis (4.1.0)
53
+ redis (4.6.0)
52
54
  rspec (2.99.0)
53
55
  rspec-core (~> 2.99.0)
54
56
  rspec-expectations (~> 2.99.0)
@@ -57,15 +59,15 @@ GEM
57
59
  rspec-expectations (2.99.2)
58
60
  diff-lcs (>= 1.1.3, < 2.0)
59
61
  rspec-mocks (2.99.4)
62
+ ruby2_keywords (0.0.5)
60
63
  safe_yaml (1.0.4)
61
64
  sinatra (1.4.8)
62
65
  rack (~> 1.5)
63
66
  rack-protection (~> 1.4)
64
67
  tilt (>= 1.3, < 3)
65
- thread_safe (0.3.6)
66
68
  tilt (1.4.1)
67
- tzinfo (1.2.5)
68
- thread_safe (~> 0.1)
69
+ tzinfo (2.0.4)
70
+ concurrent-ruby (~> 1.0)
69
71
  webmock (1.22.6)
70
72
  addressable (>= 2.3.6)
71
73
  crack (>= 0.3.2)
@@ -78,6 +80,7 @@ DEPENDENCIES
78
80
  bridge_api!
79
81
  bundler (~> 1.0, >= 1.0.0)
80
82
  byebug (~> 8.2.2)
83
+ mock_redis
81
84
  pry (~> 0)
82
85
  rake (~> 0)
83
86
  rspec (~> 2.6)
data/bridge_api.gemspec CHANGED
@@ -26,8 +26,9 @@ Gem::Specification.new do |gem|
26
26
  gem.add_development_dependency 'sinatra', '~> 1.0'
27
27
  gem.add_development_dependency 'tilt', '>= 1.3.4', '~> 1.3'
28
28
  gem.add_development_dependency 'webmock', '~>1.22.6'
29
+ gem.add_development_dependency 'mock_redis'
29
30
 
30
- gem.add_dependency 'faraday', '~> 0.9.0'
31
+ gem.add_dependency 'faraday', '~> 0.17.3'
31
32
  gem.add_dependency 'faraday_middleware', '>= 0.12.2'
32
33
  gem.add_dependency 'footrest', '>= 0.5.1'
33
34
  gem.add_dependency 'paul_walker', '>= 0.1.1'
@@ -62,13 +62,6 @@ module BridgeAPI
62
62
 
63
63
  # Override Footrest request for ApiArray support
64
64
  def request(method, &block)
65
- if has_token_pool?(config)
66
- (config[:api_tokens] || config[:api_keys].keys).size.times do
67
- break unless rate_limit_reached?
68
-
69
- rotate_token!
70
- end
71
- end
72
65
  enforce_rate_limits if rate_limit_reached?
73
66
  response = connection.send(method, &block)
74
67
  apply_rate_limits(response)
@@ -80,45 +73,47 @@ module BridgeAPI
80
73
  config[:api_tokens].is_a?(Array) && config[:api_tokens].count >= 1
81
74
  end
82
75
 
83
- # Since a pool is passed in, initialize first token with the first token passed in
84
- def initialize_from_token_pool(config)
85
- if config[:api_keys].is_a?(Hash)
86
- creds = config[:api_keys].first
87
- config[:api_key] ||= creds[0]
88
- config[:api_secret] ||= creds[1]
89
- elsif config[:api_tokens].is_a?(Array)
90
- config[:token] ||= config[:api_tokens].first
76
+ def convert_tokens(config)
77
+ return config unless config.has_key?(:api_tokens)
78
+ return config unless config[:api_tokens].is_a?(Array)
79
+ config[:api_keys] ||= {}
80
+ config[:api_tokens].each do |token|
81
+ decoded_token_array = Base64.strict_decode64(token).split(':')
82
+ config[:api_keys][decoded_token_array[0]] = decoded_token_array[1]
91
83
  end
84
+ config.delete(:api_tokens)
85
+ config
86
+ end
87
+
88
+ def initialize_from_token_pool(config)
89
+ config = convert_tokens(config)
90
+ creds = config[:api_keys].first
91
+ config[:api_key] ||= creds[0]
92
+ config[:api_secret] ||= creds[1]
92
93
  config
93
94
  end
94
95
 
95
96
  # rotates to the next token in the pool (by order in which they were provided)
96
- def rotate_token!
97
- BridgeAPI.master_mutex.synchronize do
98
- old_api_key = config[:api_key]
99
- if config[:api_keys].is_a?(Hash)
100
- keys = config[:api_keys].keys
101
- return if keys.count <= 1
102
-
103
- key = get_next_key(keys, config[:api_key])
104
- config[:api_key] = key
105
- config[:api_secret] = config[:api_keys][key]
106
- elsif config[:api_tokens].is_a?(Array)
107
- keys = config[:api_tokens]
108
- return if keys.count <= 1
109
-
110
- token = get_next_key(keys, config[:token])
111
- config[:token] = token
112
- end
113
- BridgeAPI.logger.debug('rotating API Keys')
114
- set_connection(config)
115
- end
97
+ def rotate_token!
98
+ return unless config[:api_keys].present?
99
+ old_api_key = config[:api_key]
100
+ keys = config[:api_keys].keys
101
+ return if keys.count <= 1
102
+ key = get_next_key(keys, config[:api_key])
103
+ config[:api_key] = key
104
+ config[:api_secret] = config[:api_keys][key]
105
+ set_connection(config)
106
+ BridgeAPI.logger.debug("ROTATED TO KEY: #{config[:api_key]}")
116
107
  end
117
108
 
118
109
  def get_next_key(keys, current_key)
119
- i = keys.index(current_key) || -1
120
- i = (i + 2) > keys.count ? 0 : (i + 1)
121
- keys[i]
110
+ keys.delete(current_key)
111
+ usable_key = keys.find do |key|
112
+ limit = rate_limit(key)
113
+ current_key_limit = limit.present? ? limit.fetch('current') : 0
114
+ BridgeAPI.beginning_rate_limit - current_key_limit > BridgeAPI.rate_limit_threshold
115
+ end
116
+ usable_key || keys[rand(keys.length)]
122
117
  end
123
118
 
124
119
  def rate_limit_reached?
@@ -130,14 +125,13 @@ module BridgeAPI
130
125
  def enforce_rate_limits
131
126
  return unless rate_limit_reached?
132
127
 
133
- tts = ((BridgeAPI.beginning_rate_limit - limit_remaining) / 5).ceil
134
- tts = BridgeAPI.min_sleep_seconds if tts < BridgeAPI.min_sleep_seconds
135
- tts = BridgeAPI.max_sleep_seconds if tts > BridgeAPI.max_sleep_seconds
136
- message = "Bridge API rate limit minimum #{BridgeAPI.rate_limit_threshold} reached for key: '#{config[:api_key]}'. "\
137
- "Sleeping for #{tts} second(s) to catch up ~zzZZ~. "\
138
- "Limit Remaining: #{limit_remaining}"
139
- BridgeAPI.logger.debug(message)
140
- sleep(tts)
128
+ rotate_token!
129
+ if rate_limit_reached?
130
+ sleep_time = rand(BridgeAPI.max_sleep_seconds)
131
+ sleep_time = BridgeAPI.min_sleep_seconds if sleep_time < BridgeAPI.min_sleep_seconds
132
+ BridgeAPI.logger.debug("Rate limit reached sleeping for #{sleep_time}")
133
+ sleep(sleep_time)
134
+ end
141
135
  end
142
136
 
143
137
  def using_master_rate_limit?
@@ -148,20 +142,18 @@ module BridgeAPI
148
142
  limit = response.headers['x-rate-limit-remaining']
149
143
  return if limit.nil?
150
144
 
151
- BridgeAPI.logger.debug("BRIDGE RATE LIMIT REMAINING: #{limit}")
145
+ BridgeAPI.logger.debug("BRIDGE RATE LIMIT REMAINING: #{limit} for key #{config[:api_key]}")
152
146
  self.limit_remaining = limit.to_i
153
147
  end
154
148
 
155
149
  def limit_remaining
156
150
  if using_master_rate_limit?
157
- BridgeAPI.master_mutex.synchronize do
158
- limit = PaulWalker::RateLimit.get(config[:api_key], config[:api_key])
159
- if limit.nil?
160
- PaulWalker::RateLimit.add(config[:api_key], config[:api_key], 0, BridgeAPI.beginning_rate_limit)
161
- limit = { current: 0 }.with_indifferent_access
162
- end
163
- limit['current']
151
+ limit = rate_limit(config[:api_key])
152
+ if limit.nil?
153
+ set_rate_limit(config[:api_key], 0)
154
+ limit = { current: 0 }.with_indifferent_access
164
155
  end
156
+ limit['current']
165
157
  else
166
158
  BridgeAPI.rate_limits[config[:api_key]]
167
159
  end
@@ -169,12 +161,37 @@ module BridgeAPI
169
161
 
170
162
  def limit_remaining=(value)
171
163
  if using_master_rate_limit?
172
- BridgeAPI.master_mutex.synchronize do
173
- PaulWalker::RateLimit.add(config[:api_key], config[:api_key], value, BridgeAPI.beginning_rate_limit)
174
- end
164
+ set_rate_limit(config[:api_key], value)
175
165
  else
176
166
  BridgeAPI.rate_limits[config[:api_key]] = value
177
167
  end
168
+ refresh_stale_tokens
169
+ end
170
+
171
+ def set_rate_limit(key, limit)
172
+ BridgeAPI.master_mutex.synchronize do
173
+ PaulWalker::RateLimit.add(key, key, limit, BridgeAPI.beginning_rate_limit)
174
+ end
175
+ end
176
+
177
+ def rate_limit(key)
178
+ BridgeAPI.master_mutex.synchronize do
179
+ PaulWalker::RateLimit.get(key, key)
180
+ end
181
+ end
182
+
183
+ def refresh_stale_tokens
184
+ return unless using_master_rate_limit?
185
+ return unless config[:api_keys].present?
186
+ api_keys = config[:api_keys].keys
187
+ api_keys.delete(config[:api_key])
188
+ api_keys.each do |key|
189
+ limit = rate_limit(key)
190
+ if limit['timestamp'].present? && DateTime.parse(limit['timestamp']) < 1.minute.ago
191
+ BridgeAPI.logger.debug("Refreshing: #{key}")
192
+ set_rate_limit(key, 0)
193
+ end
194
+ end
178
195
  end
179
196
 
180
197
  def set_connection(config)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BridgeAPI
4
- VERSION = '0.1.66' unless defined?(BridgeAPI::VERSION)
4
+ VERSION = '0.2.0' unless defined?(BridgeAPI::VERSION)
5
5
  end
data/lib/bridge_api.rb CHANGED
@@ -26,7 +26,7 @@ module BridgeAPI
26
26
  end
27
27
 
28
28
  def max_sleep_seconds
29
- @max_sleep_seconds ||= 60
29
+ @max_sleep_seconds ||= 30
30
30
  end
31
31
 
32
32
  def master_rate_limit
@@ -27,12 +27,6 @@ describe BridgeAPI::Client::Enrollment do
27
27
  expect(enrollments.length).to(eq(2))
28
28
  end
29
29
 
30
- it 'should update due date for enrollments' do
31
- enrollment = [{ end_at: nil }]
32
- response = @client.update_enrollment_due_date(1, enrollment)
33
- expect(response.status).to(eq(200))
34
- end
35
-
36
30
  it 'should reset an enrollment for a course template' do
37
31
  response = @client.reset_enrollment(1)
38
32
  expect(response.status).to(eq(204))
@@ -52,7 +52,7 @@ describe BridgeAPI::Client do
52
52
  end
53
53
 
54
54
  it 'should return true for tokens' do
55
- config = { prefix: 'https://www.fake.com', api_tokens: %w[token1 token2] }
55
+ config = { prefix: 'https://www.fake.com', api_tokens: %w[dGVzdDE6dGVzdDE= dGVzdDI6dGVzdDI=] }
56
56
  client = BridgeAPI::Client.new(config)
57
57
  expect(client.has_token_pool?(config)).to be_truthy
58
58
  end
@@ -74,10 +74,10 @@ describe BridgeAPI::Client do
74
74
  end
75
75
 
76
76
  it 'should take first from tokens' do
77
- config = { prefix: 'https://www.fake.com', api_tokens: %w[token1 token2] }
77
+ config = { prefix: 'https://www.fake.com', api_tokens: %w[dGVzdDE6dGVzdDE= dGVzdDI6dGVzdDI=] }
78
78
  client = BridgeAPI::Client.new(config)
79
79
  new_config = client.initialize_from_token_pool(config)
80
- expect(new_config[:token]).to eq('token1')
80
+ expect(new_config[:api_key]).to eq('test1')
81
81
  end
82
82
  end
83
83
 
@@ -111,15 +111,16 @@ describe BridgeAPI::Client do
111
111
 
112
112
  context 'with token pool' do
113
113
  it 'should rotate to the next key' do
114
- keys = %w[token1 token2]
114
+ keys = %w[dGVzdDE6dGVzdDE= dGVzdDI6dGVzdDI=]
115
115
  config = { prefix: 'https://www.fake.com', api_tokens: keys }
116
116
  client = BridgeAPI::Client.new(config)
117
- expect(client.config[:token]).to eq('token1')
117
+ expect(client.config[:api_key]).to eq('test1')
118
118
  client.rotate_token!
119
- expect(client.config[:token]).to eq('token2')
119
+ expect(client.config[:api_key]).to eq('test2')
120
120
  client.rotate_token!
121
- expect(client.config[:token]).to eq('token1')
121
+ expect(client.config[:api_key]).to eq('test1')
122
122
  end
123
123
  end
124
124
  end
125
125
  end
126
+
data/spec/test_helper.rb CHANGED
@@ -5,6 +5,7 @@ require 'rspec'
5
5
  require 'webmock/rspec'
6
6
  require 'json'
7
7
  require 'pry'
8
+ require 'mock_redis'
8
9
 
9
10
  RSpec.configure do |config|
10
11
  Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
@@ -12,6 +13,8 @@ RSpec.configure do |config|
12
13
  config.before(:each) do
13
14
  WebMock.disable_net_connect!
14
15
  WebMock.stub_request(:any, %r{api/.*}).to_rack(FakeBridge)
16
+ $redis = MockRedis.new
17
+ PaulWalker.redis = $redis
15
18
  end
16
19
  end
17
20
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bridge_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.66
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jay Shaffer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-10 00:00:00.000000000 Z
11
+ date: 2022-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -134,20 +134,34 @@ dependencies:
134
134
  - - "~>"
135
135
  - !ruby/object:Gem::Version
136
136
  version: 1.22.6
137
+ - !ruby/object:Gem::Dependency
138
+ name: mock_redis
139
+ requirement: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ type: :development
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
137
151
  - !ruby/object:Gem::Dependency
138
152
  name: faraday
139
153
  requirement: !ruby/object:Gem::Requirement
140
154
  requirements:
141
155
  - - "~>"
142
156
  - !ruby/object:Gem::Version
143
- version: 0.9.0
157
+ version: 0.17.3
144
158
  type: :runtime
145
159
  prerelease: false
146
160
  version_requirements: !ruby/object:Gem::Requirement
147
161
  requirements:
148
162
  - - "~>"
149
163
  - !ruby/object:Gem::Version
150
- version: 0.9.0
164
+ version: 0.17.3
151
165
  - !ruby/object:Gem::Dependency
152
166
  name: faraday_middleware
153
167
  requirement: !ruby/object:Gem::Requirement