bridge_api 0.1.66 → 0.2.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.
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