bridge_api 0.1.63 → 0.1.68

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: f7d6ea6a260943cb99e83ffdd26cf7c0e68d0d383ca31e7cd96a16065261903a
4
- data.tar.gz: a8e36b9a696de1d2245dcb8bea4844a21fac2b6da185032b07101d424e66143c
3
+ metadata.gz: 2e33cd3207fe1494cbc64ea799f77ee399081ad6282283aaa5d577ecbe2b91c1
4
+ data.tar.gz: f2dea18b065d73fa9fe87598d236af598daa85d90dd884ee4b815c9f818b44d5
5
5
  SHA512:
6
- metadata.gz: c9fb10e6dc27d66c2ba3273ebfd7b0770245590256d1782be1cf9398b3a61c4ef36e2af7677da48322cabc35d0ca932a2ab3828f650b523e0ba85f71e6b3db14
7
- data.tar.gz: a828d6bef4d11150bd2b4968fdeb81908c06e44de3ab8fb044fff5bc09c61b586d15c466b12ca41496bb5e3d1d5990ab12464691d6b6876327b7094451fd58e8
6
+ metadata.gz: 89c9a1618bba16b9a707de5ca0aa056a0fc7aad2313f5304f5ba49a231be58ae89e54cdfe66e799b8f36e400f89bacac1b9b71c49fa3d40655059ebf91f8d986
7
+ data.tar.gz: 59c0dc141d8693cd88eb89f9dc1dcf9993beb549548afa4048781bc631c1a93e0b08b94cb9b79197ea0bc413f86b7d4177ab5fa230a6ffd5fb77fa2304429823
@@ -0,0 +1,31 @@
1
+ # Virtual environments with possible versions infos: https://github.com/actions/virtual-environments
2
+
3
+ name: CI
4
+ on: [push]
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2 # Step 1: github action that clones the repository
10
+ - name: Setup Ruby # Step 2 (we gave the name 'Setup Ruby' to this step)
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.5.0
14
+ - name: Install bundle and gems # Step 4
15
+ run: |
16
+ gem install bundler
17
+ bundle install --jobs 4 --retry 3
18
+ - name: Run tests # Step 6
19
+ run: COVERAGE=true bundle exec rspec
20
+ brakeman:
21
+ name: runner / brakeman # Step 8
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - name: Check out code
25
+ uses: actions/checkout@v1
26
+ - name: brakeman
27
+ uses: reviewdog/action-brakeman@v1
28
+ with:
29
+ brakeman_version: 4.8.2
30
+ github_token: ${{ secrets.github_token }}
31
+ reporter: github-pr-review # Default is github-pr-check
data/.gitignore CHANGED
@@ -4,4 +4,6 @@
4
4
  bridge_api.iml
5
5
  .byebug.*
6
6
 
7
- .*.gem
7
+ .*.gem
8
+
9
+ .history
@@ -10,6 +10,18 @@ module BridgeAPI
10
10
  def update_group(group_id, params = {})
11
11
  put("#{API_PATH}#{AUTHOR_PATH}#{GROUPS_PATH}/#{group_id}", params)
12
12
  end
13
+
14
+ def group_relevance(params = {})
15
+ get("#{API_PATH}#{AUTHOR_PATH}/group_relevance", params)
16
+ end
17
+
18
+ def update_group_relevance(params = {})
19
+ put("#{API_PATH}#{AUTHOR_PATH}/group_relevance", params)
20
+ end
21
+
22
+ def delete_group_relevance(group_id, params)
23
+ delete("#{API_PATH}#{AUTHOR_PATH}/group_relevance/#{group_id}", params)
24
+ end
13
25
  end
14
26
  end
15
27
  end
@@ -10,6 +10,10 @@ module BridgeAPI
10
10
  def get_program(program_id, params = {})
11
11
  get("#{API_PATH}#{AUTHOR_PATH}#{PROGRAM_PATH}/#{program_id}", params)
12
12
  end
13
+
14
+ def complete_program(program_id, params = {})
15
+ post("#{API_PATH}#{ADMIN_PATH}#{PROGRAM_PATH}/#{program_id}/completions", params)
16
+ end
13
17
  end
14
18
  end
15
19
  end
@@ -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.63' unless defined?(BridgeAPI::VERSION)
4
+ VERSION = '0.1.68' 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
+
@@ -60,6 +60,10 @@ class FakeBridge < Sinatra::Base
60
60
  get_json_data 204, nil
61
61
  end
62
62
 
63
+ post %r{/api/author/programs/\d+/completions} do
64
+ get_json_data 204, nil
65
+ end
66
+
63
67
  delete %r{/api/author/programs/\d+/learners} do
64
68
  get_json_data 204, nil
65
69
  end
metadata CHANGED
@@ -1,35 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bridge_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.63
4
+ version: 0.1.68
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jay Shaffer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-19 00:00:00.000000000 Z
11
+ date: 2022-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 1.0.0
20
17
  - - "~>"
21
18
  - !ruby/object:Gem::Version
22
19
  version: '1.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.0
23
23
  type: :development
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 1.0.0
30
27
  - - "~>"
31
28
  - !ruby/object:Gem::Version
32
29
  version: '1.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: byebug
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -104,22 +104,22 @@ dependencies:
104
104
  name: tilt
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: '1.3'
110
107
  - - ">="
111
108
  - !ruby/object:Gem::Version
112
109
  version: 1.3.4
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: '1.3'
113
113
  type: :development
114
114
  prerelease: false
115
115
  version_requirements: !ruby/object:Gem::Requirement
116
116
  requirements:
117
- - - "~>"
118
- - !ruby/object:Gem::Version
119
- version: '1.3'
120
117
  - - ">="
121
118
  - !ruby/object:Gem::Version
122
119
  version: 1.3.4
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '1.3'
123
123
  - !ruby/object:Gem::Dependency
124
124
  name: webmock
125
125
  requirement: !ruby/object:Gem::Requirement
@@ -197,6 +197,7 @@ executables: []
197
197
  extensions: []
198
198
  extra_rdoc_files: []
199
199
  files:
200
+ - ".github/workflows/ci.yml"
200
201
  - ".gitignore"
201
202
  - Dockerfile
202
203
  - Gemfile
@@ -297,7 +298,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
298
  - !ruby/object:Gem::Version
298
299
  version: '0'
299
300
  requirements: []
300
- rubygems_version: 3.0.3
301
+ rubygems_version: 3.1.6
301
302
  signing_key:
302
303
  specification_version: 4
303
304
  summary: Bridge API