bridge_api 0.1.55 → 0.1.60
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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +17 -0
- data/lib/bridge_api.rb +5 -3
- data/lib/bridge_api/api_array.rb +4 -2
- data/lib/bridge_api/client.rb +124 -56
- data/lib/bridge_api/client/account.rb +2 -1
- data/lib/bridge_api/client/affiliation.rb +13 -11
- data/lib/bridge_api/client/clone_object.rb +3 -3
- data/lib/bridge_api/client/course_template.rb +2 -0
- data/lib/bridge_api/client/custom_field.rb +2 -0
- data/lib/bridge_api/client/data_dump.rb +2 -0
- data/lib/bridge_api/client/enrollment.rb +2 -0
- data/lib/bridge_api/client/group.rb +2 -0
- data/lib/bridge_api/client/learner_item.rb +2 -0
- data/lib/bridge_api/client/live_course.rb +2 -0
- data/lib/bridge_api/client/live_course_enrollment.rb +2 -0
- data/lib/bridge_api/client/live_course_session.rb +3 -2
- data/lib/bridge_api/client/manager.rb +2 -0
- data/lib/bridge_api/client/program.rb +3 -1
- data/lib/bridge_api/client/program_enrollment.rb +2 -0
- data/lib/bridge_api/client/role.rb +2 -0
- data/lib/bridge_api/client/sub_account.rb +2 -0
- data/lib/bridge_api/client/user.rb +6 -5
- data/lib/bridge_api/version.rb +3 -1
- data/spec/bridge_api/client/account_spec.rb +6 -6
- data/spec/bridge_api/client/affiliations_spec.rb +3 -2
- data/spec/bridge_api/client/clone_object_spec.rb +8 -9
- data/spec/bridge_api/client/course_template_spec.rb +2 -0
- data/spec/bridge_api/client/custom_field_spec.rb +2 -0
- data/spec/bridge_api/client/data_dump_spec.rb +2 -0
- data/spec/bridge_api/client/enrollment_spec.rb +2 -0
- data/spec/bridge_api/client/group_spec.rb +2 -0
- data/spec/bridge_api/client/learner_items_spec.rb +2 -0
- data/spec/bridge_api/client/live_course_enrollments_spec.rb +2 -0
- data/spec/bridge_api/client/live_course_session_spec.rb +5 -3
- data/spec/bridge_api/client/live_course_spec.rb +2 -0
- data/spec/bridge_api/client/manager_spec.rb +2 -0
- data/spec/bridge_api/client/program_enrollment_spec.rb +2 -0
- data/spec/bridge_api/client/role_spec.rb +2 -0
- data/spec/bridge_api/client/sub_account_spec.rb +6 -4
- data/spec/bridge_api/client/user_spec.rb +2 -1
- data/spec/bridge_api/client_spec.rb +99 -5
- data/spec/support/fake_bridge.rb +4 -2
- data/spec/test_helper.rb +3 -1
- metadata +16 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba858770698bd9b2eae79613a356e002d75b7660786e33c53d999859cf9b809b
|
4
|
+
data.tar.gz: a9e81c5c0758deab4985332dbaec93a2dcc71f931a60033897f637bff9952efd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a470154415cfe7cb6b4899d4826d63dc8e0f7ad5320229c2985d079bc2fc159ce809c16ede74cb278ffefe0c13047a697f10293afd754a2d9ef8c44a01971167
|
7
|
+
data.tar.gz: db6d7da0e8afe793a2e80ddf2c45ffead4256adef5559f2f2d5c10bec578ef8ef47de3b8f4d5113187f2bbd46cf332c76ee04e514d95daa06770eb55dbc1de11
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -25,3 +25,20 @@ instantiating several instances of `client`, you can also supply your client
|
|
25
25
|
initializer with `master_rate_limit: true`. This will use a redis
|
26
26
|
keystore to track your rate limits across all instances of `client` and
|
27
27
|
properly throttle your application as needed.
|
28
|
+
|
29
|
+
## Token Pools
|
30
|
+
|
31
|
+
This supports token pooling to get around rate limiting issues in bridge (but should never be used without Core approval)
|
32
|
+
To use with Tokens:
|
33
|
+
```
|
34
|
+
client = BridgeAPI::Client.new(tokens: ['token1', 'token2'], prefix: "https://yourdomain.bridgeapp.com")
|
35
|
+
```
|
36
|
+
To use with API Keys:
|
37
|
+
```
|
38
|
+
client = BridgeAPI::Client.new(api_keys: {'key1' => 'secret1', 'key2' => 'secret2'}, prefix: "https://yourdomain.bridgeapp.com")
|
39
|
+
```
|
40
|
+
|
41
|
+
The logic will start with the first token provided, and check against the redis cache to see what its current limit
|
42
|
+
is. If its already under the limit threshold, it will rotate to the next in the pool, and perform the same check. It
|
43
|
+
will do this till it finds one that is not under the threshold, and use it. If none are found, it will sleep for the
|
44
|
+
configured time, and then try to make a call.
|
data/lib/bridge_api.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bridge_api/version'
|
2
4
|
require 'bridge_api/client'
|
3
5
|
|
4
6
|
module BridgeAPI
|
5
7
|
class << self
|
6
8
|
require 'logger'
|
7
|
-
attr_writer :enforce_rate_limits, :
|
9
|
+
attr_writer :enforce_rate_limits, :beginning_rate_limit, :rate_limits, :max_sleep_seconds, :min_sleep_seconds,
|
8
10
|
:logger, :master_rate_limit, :master_mutex, :rate_limit_threshold
|
9
11
|
|
10
12
|
def configure
|
@@ -15,8 +17,8 @@ module BridgeAPI
|
|
15
17
|
@enforce_rate_limits ||= false
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
@
|
20
|
+
def beginning_rate_limit
|
21
|
+
@beginning_rate_limit ||= 30
|
20
22
|
end
|
21
23
|
|
22
24
|
def rate_limits
|
data/lib/bridge_api/api_array.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module BridgeAPI
|
3
4
|
class ApiArray
|
@@ -18,7 +19,7 @@ module BridgeAPI
|
|
18
19
|
@linked = {}
|
19
20
|
@meta = {}
|
20
21
|
@extra_meta_fields = []
|
21
|
-
pattern =
|
22
|
+
pattern = %r{.*(/api/.*)}
|
22
23
|
path = response.env.url
|
23
24
|
matches = pattern.match(path.to_s)
|
24
25
|
mapping = nil
|
@@ -67,7 +68,6 @@ module BridgeAPI
|
|
67
68
|
!@next_page.nil?
|
68
69
|
end
|
69
70
|
|
70
|
-
|
71
71
|
def next_page
|
72
72
|
load_page(@next_page)
|
73
73
|
end
|
@@ -119,8 +119,10 @@ module BridgeAPI
|
|
119
119
|
|
120
120
|
def get_response_content(response)
|
121
121
|
return [] unless response.body.is_a?(Hash)
|
122
|
+
|
122
123
|
content = response.body.reject { |k, _v| @meta_fields.include?(k) || @extra_meta_fields.include?(k) }
|
123
124
|
return content.values[0] unless content.empty?
|
125
|
+
|
124
126
|
[]
|
125
127
|
end
|
126
128
|
|
data/lib/bridge_api/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'footrest/client'
|
2
4
|
require 'faraday'
|
3
5
|
require 'footrest'
|
@@ -12,60 +14,126 @@ module BridgeAPI
|
|
12
14
|
class Client < Footrest::Client
|
13
15
|
require 'bridge_api/api_array'
|
14
16
|
|
15
|
-
DATA_DUMP_DOWNLOAD_PATH = '/data_dumps/download'
|
16
|
-
DATA_DUMP_PATH
|
17
|
-
COURSE_TEMPLATE_PATH
|
18
|
-
ENROLLMENT_PATH
|
19
|
-
LTI_TOOLS_PATH
|
20
|
-
PROGRAM_PATH
|
21
|
-
PROGRAM_ENROLLMENT_PATH = '/learners'
|
22
|
-
USER_PATH
|
23
|
-
GROUPS_PATH
|
24
|
-
MANAGER_PATH
|
25
|
-
ADMIN_PATH
|
26
|
-
AUTHOR_PATH
|
27
|
-
LEARNER_PATH
|
28
|
-
LEARNERS_PATH
|
29
|
-
LEARNER_ITEMS_PATH
|
30
|
-
CUSTOM_FIELD_PATH
|
31
|
-
SUB_ACCOUNT_PATH
|
32
|
-
SUPPORT_PATH
|
33
|
-
ACCOUNT_PATH
|
34
|
-
CLONE_OBJECTS_PATH
|
35
|
-
API_VERSION
|
36
|
-
API_PATH
|
37
|
-
ROLE_PATH
|
38
|
-
AFFILIATED_SUBACCOUNTS
|
39
|
-
BATCH_PATH
|
40
|
-
LIVE_COURSES_PATH
|
41
|
-
SESSIONS_PATH
|
42
|
-
PUBLISH_PATH
|
43
|
-
WEB_CONFERENCE_PATH
|
44
|
-
RESTORE_PATH
|
45
|
-
DUE_DATE_PATH
|
46
|
-
RESET_PATH
|
47
|
-
RESULT_MAPPING
|
17
|
+
DATA_DUMP_DOWNLOAD_PATH = '/data_dumps/download'
|
18
|
+
DATA_DUMP_PATH = '/data_dumps'
|
19
|
+
COURSE_TEMPLATE_PATH = '/course_templates'
|
20
|
+
ENROLLMENT_PATH = '/enrollments'
|
21
|
+
LTI_TOOLS_PATH = '/lti_tools'
|
22
|
+
PROGRAM_PATH = '/programs'
|
23
|
+
PROGRAM_ENROLLMENT_PATH = '/learners'
|
24
|
+
USER_PATH = '/users'
|
25
|
+
GROUPS_PATH = '/groups'
|
26
|
+
MANAGER_PATH = '/managers'
|
27
|
+
ADMIN_PATH = '/admin'
|
28
|
+
AUTHOR_PATH = '/author'
|
29
|
+
LEARNER_PATH = '/learner'
|
30
|
+
LEARNERS_PATH = '/learners'
|
31
|
+
LEARNER_ITEMS_PATH = '/learner_items'
|
32
|
+
CUSTOM_FIELD_PATH = '/custom_fields'
|
33
|
+
SUB_ACCOUNT_PATH = '/sub_accounts'
|
34
|
+
SUPPORT_PATH = '/support'
|
35
|
+
ACCOUNT_PATH = '/accounts'
|
36
|
+
CLONE_OBJECTS_PATH = '/clone_objects'
|
37
|
+
API_VERSION = 1
|
38
|
+
API_PATH = '/api'
|
39
|
+
ROLE_PATH = '/roles'
|
40
|
+
AFFILIATED_SUBACCOUNTS = '/affiliated_sub_accounts'
|
41
|
+
BATCH_PATH = '/batch'
|
42
|
+
LIVE_COURSES_PATH = '/live_courses'
|
43
|
+
SESSIONS_PATH = '/sessions'
|
44
|
+
PUBLISH_PATH = '/publish'
|
45
|
+
WEB_CONFERENCE_PATH = '/web_conference'
|
46
|
+
RESTORE_PATH = '/restore'
|
47
|
+
DUE_DATE_PATH = '/due_date'
|
48
|
+
RESET_PATH = '/reset'
|
49
|
+
RESULT_MAPPING = {}
|
48
50
|
|
49
51
|
Dir[File.dirname(__FILE__) + '/client/*.rb'].each do |file|
|
50
52
|
require file
|
51
53
|
include const_get(File.basename(file).gsub('.rb', '').split('_').map(&:capitalize).join('').to_s)
|
52
54
|
end
|
53
55
|
|
56
|
+
def initialize(options = {}, &block)
|
57
|
+
if BridgeAPI.enforce_rate_limits && has_token_pool?(options)
|
58
|
+
options = initialize_from_token_pool(options)
|
59
|
+
end
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
54
63
|
# Override Footrest request for ApiArray support
|
55
64
|
def request(method, &block)
|
56
|
-
|
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
|
+
enforce_rate_limits if rate_limit_reached?
|
57
73
|
response = connection.send(method, &block)
|
58
74
|
apply_rate_limits(response)
|
59
75
|
ApiArray.process_response(response, self, RESULT_MAPPING)
|
60
76
|
end
|
61
77
|
|
78
|
+
def has_token_pool?(config)
|
79
|
+
config[:api_keys].is_a?(Hash) && config[:api_keys].keys.count >= 1 ||
|
80
|
+
config[:api_tokens].is_a?(Array) && config[:api_tokens].count >= 1
|
81
|
+
end
|
82
|
+
|
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
|
91
|
+
end
|
92
|
+
config
|
93
|
+
end
|
94
|
+
|
95
|
+
# 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
|
116
|
+
end
|
117
|
+
|
118
|
+
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]
|
122
|
+
end
|
123
|
+
|
124
|
+
def rate_limit_reached?
|
125
|
+
return false unless BridgeAPI.enforce_rate_limits && limit_remaining.present?
|
126
|
+
|
127
|
+
limit_remaining < BridgeAPI.rate_limit_threshold
|
128
|
+
end
|
129
|
+
|
62
130
|
def enforce_rate_limits
|
63
|
-
return unless
|
64
|
-
|
65
|
-
tts
|
66
|
-
tts
|
67
|
-
tts
|
68
|
-
message = "Bridge API rate limit minimum #{BridgeAPI.
|
131
|
+
return unless rate_limit_reached?
|
132
|
+
|
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]}'. "\
|
69
137
|
"Sleeping for #{tts} second(s) to catch up ~zzZZ~. "\
|
70
138
|
"Limit Remaining: #{limit_remaining}"
|
71
139
|
BridgeAPI.logger.debug(message)
|
@@ -79,6 +147,7 @@ module BridgeAPI
|
|
79
147
|
def apply_rate_limits(response)
|
80
148
|
limit = response.headers['x-rate-limit-remaining']
|
81
149
|
return if limit.nil?
|
150
|
+
|
82
151
|
BridgeAPI.logger.debug("BRIDGE RATE LIMIT REMAINING: #{limit}")
|
83
152
|
self.limit_remaining = limit.to_i
|
84
153
|
end
|
@@ -88,8 +157,8 @@ module BridgeAPI
|
|
88
157
|
BridgeAPI.master_mutex.synchronize do
|
89
158
|
limit = PaulWalker::RateLimit.get(config[:api_key], config[:api_key])
|
90
159
|
if limit.nil?
|
91
|
-
PaulWalker::RateLimit.add(config[:api_key], config[:api_key], 0, BridgeAPI
|
92
|
-
limit = {current: 0}.with_indifferent_access
|
160
|
+
PaulWalker::RateLimit.add(config[:api_key], config[:api_key], 0, BridgeAPI.beginning_rate_limit)
|
161
|
+
limit = { current: 0 }.with_indifferent_access
|
93
162
|
end
|
94
163
|
limit['current']
|
95
164
|
end
|
@@ -101,7 +170,7 @@ module BridgeAPI
|
|
101
170
|
def limit_remaining=(value)
|
102
171
|
if using_master_rate_limit?
|
103
172
|
BridgeAPI.master_mutex.synchronize do
|
104
|
-
PaulWalker::RateLimit.add(config[:api_key], config[:api_key], value, BridgeAPI
|
173
|
+
PaulWalker::RateLimit.add(config[:api_key], config[:api_key], value, BridgeAPI.beginning_rate_limit)
|
105
174
|
end
|
106
175
|
else
|
107
176
|
BridgeAPI.rate_limits[config[:api_key]] = value
|
@@ -110,26 +179,25 @@ module BridgeAPI
|
|
110
179
|
|
111
180
|
def set_connection(config)
|
112
181
|
config[:logger] = config[:logging] if config[:logging]
|
113
|
-
@connection
|
114
|
-
faraday.request
|
115
|
-
faraday.request
|
182
|
+
@connection = Faraday.new(url: config[:prefix]) do |faraday|
|
183
|
+
faraday.request :multipart
|
184
|
+
faraday.request :url_encoded
|
116
185
|
if config[:logger] == true
|
117
186
|
faraday.response :logger
|
118
187
|
elsif config[:logger]
|
119
188
|
faraday.use Faraday::Response::Logger, config[:logger]
|
120
189
|
end
|
121
|
-
faraday.use
|
122
|
-
faraday.adapter
|
123
|
-
faraday.use
|
124
|
-
faraday.use
|
125
|
-
faraday.use
|
126
|
-
faraday.headers[:accept]
|
127
|
-
faraday.headers[:
|
128
|
-
faraday.headers[:user_agent] = 'Footrest'
|
190
|
+
faraday.use Footrest::FollowRedirects, limit: 5 unless config[:follow_redirects] == false
|
191
|
+
faraday.adapter Faraday.default_adapter
|
192
|
+
faraday.use Footrest::ParseJson, content_type: /\bjson$/
|
193
|
+
faraday.use Footrest::RaiseFootrestErrors
|
194
|
+
faraday.use Footrest::Pagination
|
195
|
+
faraday.headers[:accept] = 'application/json'
|
196
|
+
faraday.headers[:user_agent] = 'Footrest'
|
129
197
|
if config[:api_key] && config[:api_secret]
|
130
|
-
faraday.headers[:authorization] =
|
198
|
+
faraday.headers[:authorization] = 'Basic ' + Base64.strict_encode64("#{config[:api_key]}:#{config[:api_secret]}")
|
131
199
|
elsif config[:token]
|
132
|
-
faraday.headers[:authorization] =
|
200
|
+
faraday.headers[:authorization] = "Bearer #{config[:token]}"
|
133
201
|
else
|
134
202
|
raise 'No api authorization provided'
|
135
203
|
end
|
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BridgeAPI
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
class Client
|
5
|
+
module Affiliation
|
6
|
+
def list_affiliations(params = {})
|
7
|
+
get("#{API_PATH}#{AUTHOR_PATH}#{AFFILIATED_SUBACCOUNTS}", params)
|
8
|
+
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
def revoke_affiliations_batch(params = {})
|
11
|
+
put("#{API_PATH}#{AUTHOR_PATH}#{AFFILIATED_SUBACCOUNTS}/revoke_batch", params)
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
end
|
14
|
+
def share_affiliations_batch(params = {})
|
15
|
+
put("#{API_PATH}#{AUTHOR_PATH}#{AFFILIATED_SUBACCOUNTS}/share_batch", params)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
19
|
+
end
|
@@ -1,15 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BridgeAPI
|
2
4
|
class Client
|
3
5
|
module CloneObject
|
4
|
-
|
5
6
|
def clone_objects(params = {})
|
6
7
|
post("#{API_PATH}#{ADMIN_PATH}#{CLONE_OBJECTS_PATH}", params)
|
7
8
|
end
|
8
9
|
|
9
|
-
def clone_object_status(id, params={})
|
10
|
+
def clone_object_status(id, params = {})
|
10
11
|
get("#{API_PATH}#{ADMIN_PATH}#{CLONE_OBJECTS_PATH}/#{id}", params)
|
11
12
|
end
|
12
|
-
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BridgeAPI
|
2
4
|
class Client
|
3
5
|
module LiveCourseSession
|
4
|
-
|
5
6
|
def get_live_course_sessions(live_course_id, params = {})
|
6
7
|
get("#{API_PATH}#{AUTHOR_PATH}#{LIVE_COURSES_PATH}/#{live_course_id}#{SESSIONS_PATH}", params)
|
7
8
|
end
|
@@ -35,4 +36,4 @@ module BridgeAPI
|
|
35
36
|
end
|
36
37
|
end
|
37
38
|
end
|
38
|
-
end
|
39
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BridgeAPI
|
2
4
|
class Client
|
3
5
|
module User
|
@@ -53,15 +55,14 @@ module BridgeAPI
|
|
53
55
|
users = get_user(user_id, 'includes[]' => 'custom_fields')
|
54
56
|
payload = []
|
55
57
|
return payload if users.members.empty?
|
58
|
+
|
56
59
|
existing_values = users.linked['custom_field_values']
|
57
60
|
custom_field_values.each do |field_id, value|
|
58
61
|
field_value_id = begin
|
59
62
|
existing_values.find do |v|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
false
|
64
|
-
end
|
63
|
+
v['links']['custom_field']['id'] == field_id.to_s
|
64
|
+
rescue StandardError
|
65
|
+
false
|
65
66
|
end['id']
|
66
67
|
rescue StandardError
|
67
68
|
nil
|
data/lib/bridge_api/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
describe BridgeAPI::Client::CourseTemplate do
|
@@ -9,12 +11,10 @@ describe BridgeAPI::Client::CourseTemplate do
|
|
9
11
|
|
10
12
|
it 'should create an account' do
|
11
13
|
templates = @client.create_account(
|
12
|
-
{
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
name: 'subdomain'
|
17
|
-
}
|
14
|
+
sub_account: {
|
15
|
+
locale: 'en',
|
16
|
+
subdomain: 'subdomain',
|
17
|
+
name: 'subdomain'
|
18
18
|
}
|
19
19
|
)
|
20
20
|
expect(templates.status).to(eq(200))
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
describe BridgeAPI::Client::Affiliation do
|
@@ -6,7 +8,7 @@ describe BridgeAPI::Client::Affiliation do
|
|
6
8
|
end
|
7
9
|
|
8
10
|
it 'should list the affiliations' do
|
9
|
-
response = @client.list_affiliations(
|
11
|
+
response = @client.list_affiliations(item_id: 1, item_type: 'CourseTemple')
|
10
12
|
expect(response.status).to(eq(204))
|
11
13
|
end
|
12
14
|
|
@@ -14,5 +16,4 @@ describe BridgeAPI::Client::Affiliation do
|
|
14
16
|
response = @client.share_affiliations_batch
|
15
17
|
expect(response.status).to(eq(204))
|
16
18
|
end
|
17
|
-
|
18
19
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
describe BridgeAPI::Client::CloneObject do
|
@@ -7,14 +9,12 @@ describe BridgeAPI::Client::CloneObject do
|
|
7
9
|
|
8
10
|
it 'should clone an object' do
|
9
11
|
response = @client.clone_objects(
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
]
|
17
|
-
}
|
12
|
+
clone_objects: [
|
13
|
+
{
|
14
|
+
source_type: 'CourseTemplate',
|
15
|
+
source_id: 1
|
16
|
+
}
|
17
|
+
]
|
18
18
|
).first
|
19
19
|
expect(response['destination_id']).to(eq(1))
|
20
20
|
end
|
@@ -23,5 +23,4 @@ describe BridgeAPI::Client::CloneObject do
|
|
23
23
|
response = @client.clone_object_status(1).first
|
24
24
|
expect(response['state']).to(eq('completed'))
|
25
25
|
end
|
26
|
-
|
27
26
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
describe BridgeAPI::Client::LiveCourseSession do
|
@@ -28,7 +30,7 @@ describe BridgeAPI::Client::LiveCourseSession do
|
|
28
30
|
end
|
29
31
|
|
30
32
|
it 'should delete a live course session in bridge' do
|
31
|
-
response = @client.delete_live_course_session(1,5)
|
33
|
+
response = @client.delete_live_course_session(1, 5)
|
32
34
|
expect(response.status).to eq(204)
|
33
35
|
end
|
34
36
|
|
@@ -45,7 +47,7 @@ describe BridgeAPI::Client::LiveCourseSession do
|
|
45
47
|
it 'should update web_conference' do
|
46
48
|
params = {
|
47
49
|
web_conference: {
|
48
|
-
provider:
|
50
|
+
provider: 'Bluejeans'
|
49
51
|
}
|
50
52
|
}
|
51
53
|
response = @client.update_web_conference(1, 5, params)
|
@@ -58,7 +60,7 @@ describe BridgeAPI::Client::LiveCourseSession do
|
|
58
60
|
end
|
59
61
|
|
60
62
|
it 'should get default web_conference config' do
|
61
|
-
response = @client.get_default_web_conference(
|
63
|
+
response = @client.get_default_web_conference(default_session: 1)
|
62
64
|
expect(response.status).to eq(200)
|
63
65
|
end
|
64
66
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
describe BridgeAPI::Client::SubAccount do
|
@@ -35,19 +37,19 @@ describe BridgeAPI::Client::SubAccount do
|
|
35
37
|
end
|
36
38
|
|
37
39
|
it 'should update the lti config' do
|
38
|
-
params = {lti_tool: {consumer_key:
|
40
|
+
params = { lti_tool: { consumer_key: 'YoooKey', shared_secret: SecureRandom.hex(16) } }
|
39
41
|
response = @client.update_sub_account_lti_config(2, 15, params)
|
40
42
|
expect(response.body['lti_tool']['id']).to(eq '20')
|
41
43
|
end
|
42
44
|
|
43
45
|
it 'should create new lti config' do
|
44
|
-
params = {lti_tool: {consumer_key:
|
45
|
-
response = @client.create_sub_account_lti_config(2,params)
|
46
|
+
params = { lti_tool: { consumer_key: 'SomeKey', shared_secret: SecureRandom.hex(16) } }
|
47
|
+
response = @client.create_sub_account_lti_config(2, params)
|
46
48
|
expect(response.status).to(eq 201)
|
47
49
|
end
|
48
50
|
|
49
51
|
it 'should delete a lti config' do
|
50
|
-
response = @client.delete_lti_config(2,15)
|
52
|
+
response = @client.delete_lti_config(2, 15)
|
51
53
|
expect(response.status).to(eq 204)
|
52
54
|
end
|
53
55
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
describe BridgeAPI::Client do
|
3
4
|
it 'should set the auth header to basic auth' do
|
@@ -12,20 +13,113 @@ describe BridgeAPI::Client do
|
|
12
13
|
|
13
14
|
it 'should set the meta property on client' do
|
14
15
|
client = BridgeAPI::Client.new(prefix: 'https://www.fake.com', token: 'test_token')
|
15
|
-
users
|
16
|
+
users = client.get_all_users
|
16
17
|
expect(users.meta['next']).to(eq('http://bridge.bridgeapp.com/api/author/users?after=eyJ0eXAiOiJKV1QiLCJhSDiQQ'))
|
17
18
|
end
|
18
19
|
|
19
20
|
it 'should set the linked property on client' do
|
20
21
|
client = BridgeAPI::Client.new(prefix: 'https://www.fake.com', token: 'test_token')
|
21
|
-
users
|
22
|
+
users = client.get_all_users
|
22
23
|
expect(users.linked['custom_fields'][0]['id']).to(eq('1'))
|
23
24
|
end
|
24
25
|
|
25
26
|
it 'should enforce rate limits' do
|
26
|
-
client
|
27
|
+
client = BridgeAPI::Client.new(prefix: 'https://www.fake.com', api_key: 'fake_token', api_secret: 'fake_secret')
|
27
28
|
BridgeAPI.enforce_rate_limits = true
|
28
|
-
users
|
29
|
-
users
|
29
|
+
users = client.get_all_users
|
30
|
+
users = client.get_all_users
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'initialize' do
|
34
|
+
it 'should set initial token when using a pool' do
|
35
|
+
expect_any_instance_of(BridgeAPI::Client).to receive(:initialize_from_token_pool).and_call_original
|
36
|
+
client = BridgeAPI::Client.new(prefix: 'https://www.fake.com', api_keys: { 'key1' => 'secret1', 'key2' => 'secret2' })
|
37
|
+
expect(client.config[:api_key]).to eq('key1')
|
38
|
+
expect(client.config[:api_secret]).to eq('secret1')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should not call initialize_from_token_pool for a single token' do
|
42
|
+
expect_any_instance_of(BridgeAPI::Client).to_not receive(:initialize_from_token_pool)
|
43
|
+
BridgeAPI::Client.new(prefix: 'https://www.fake.com', api_key: 'fake_token', api_secret: 'fake_secret')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'has_token_pool?' do
|
48
|
+
it 'should return true for api keys' do
|
49
|
+
config = { prefix: 'https://www.fake.com', api_keys: { 'key1' => 'secret1', 'key2' => 'secret2' } }
|
50
|
+
client = BridgeAPI::Client.new(config)
|
51
|
+
expect(client.has_token_pool?(config)).to be_truthy
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should return true for tokens' do
|
55
|
+
config = { prefix: 'https://www.fake.com', api_tokens: %w[token1 token2] }
|
56
|
+
client = BridgeAPI::Client.new(config)
|
57
|
+
expect(client.has_token_pool?(config)).to be_truthy
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should return false for single key' do
|
61
|
+
config = { prefix: 'https://www.fake.com', api_key: 'fake_token', api_secret: 'fake_secret' }
|
62
|
+
client = BridgeAPI::Client.new(config)
|
63
|
+
expect(client.has_token_pool?(config)).to be_falsey
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'initialize_from_token_pool' do
|
68
|
+
it 'should take first from api keys' do
|
69
|
+
config = { prefix: 'https://www.fake.com', api_keys: { 'key1' => 'secret1', 'key2' => 'secret2' } }
|
70
|
+
client = BridgeAPI::Client.new(config)
|
71
|
+
new_config = client.initialize_from_token_pool(config)
|
72
|
+
expect(new_config[:api_key]).to eq('key1')
|
73
|
+
expect(new_config[:api_secret]).to eq('secret1')
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should take first from tokens' do
|
77
|
+
config = { prefix: 'https://www.fake.com', api_tokens: %w[token1 token2] }
|
78
|
+
client = BridgeAPI::Client.new(config)
|
79
|
+
new_config = client.initialize_from_token_pool(config)
|
80
|
+
expect(new_config[:token]).to eq('token1')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'get_next_key' do
|
85
|
+
it 'should find next key' do
|
86
|
+
keys = { 'key1' => 'secret1', 'key2' => 'secret2' }
|
87
|
+
config = { prefix: 'https://www.fake.com', api_keys: keys }
|
88
|
+
client = BridgeAPI::Client.new(config)
|
89
|
+
expect(client.get_next_key(keys.keys, 'key1')).to eq('key2')
|
90
|
+
expect(client.get_next_key(keys.keys, 'key2')).to eq('key1')
|
91
|
+
expect(client.get_next_key(keys.keys, 'non-existent-key')).to eq('key1')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'rotate_token!' do
|
96
|
+
context 'with api key pool' do
|
97
|
+
it 'should rotate to the next key' do
|
98
|
+
keys = { 'key1' => 'secret1', 'key2' => 'secret2' }
|
99
|
+
config = { prefix: 'https://www.fake.com', api_keys: keys }
|
100
|
+
client = BridgeAPI::Client.new(config)
|
101
|
+
expect(client.config[:api_key]).to eq('key1')
|
102
|
+
expect(client.config[:api_secret]).to eq('secret1')
|
103
|
+
client.rotate_token!
|
104
|
+
expect(client.config[:api_key]).to eq('key2')
|
105
|
+
expect(client.config[:api_secret]).to eq('secret2')
|
106
|
+
client.rotate_token!
|
107
|
+
expect(client.config[:api_key]).to eq('key1')
|
108
|
+
expect(client.config[:api_secret]).to eq('secret1')
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with token pool' do
|
113
|
+
it 'should rotate to the next key' do
|
114
|
+
keys = %w[token1 token2]
|
115
|
+
config = { prefix: 'https://www.fake.com', api_tokens: keys }
|
116
|
+
client = BridgeAPI::Client.new(config)
|
117
|
+
expect(client.config[:token]).to eq('token1')
|
118
|
+
client.rotate_token!
|
119
|
+
expect(client.config[:token]).to eq('token2')
|
120
|
+
client.rotate_token!
|
121
|
+
expect(client.config[:token]).to eq('token1')
|
122
|
+
end
|
123
|
+
end
|
30
124
|
end
|
31
125
|
end
|
data/spec/support/fake_bridge.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'sinatra/base'
|
2
4
|
require 'tilt'
|
3
5
|
|
@@ -257,7 +259,7 @@ class FakeBridge < Sinatra::Base
|
|
257
259
|
get_json_data 200, 'accounts.json'
|
258
260
|
end
|
259
261
|
|
260
|
-
#Roles
|
262
|
+
# Roles
|
261
263
|
get %r{/api/author/roles} do
|
262
264
|
get_json_data 200, 'roles.json'
|
263
265
|
end
|
@@ -274,7 +276,7 @@ class FakeBridge < Sinatra::Base
|
|
274
276
|
get_json_data 204, 'roles.json'
|
275
277
|
end
|
276
278
|
|
277
|
-
#Affiliations
|
279
|
+
# Affiliations
|
278
280
|
get %r{/api/author/affiliated_sub_accounts} do
|
279
281
|
get_json_data 204, 'affiliations.json'
|
280
282
|
end
|
data/spec/test_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bridge_api'
|
2
4
|
require 'rspec'
|
3
5
|
require 'webmock/rspec'
|
@@ -9,7 +11,7 @@ RSpec.configure do |config|
|
|
9
11
|
|
10
12
|
config.before(:each) do
|
11
13
|
WebMock.disable_net_connect!
|
12
|
-
WebMock.stub_request(:any,
|
14
|
+
WebMock.stub_request(:any, %r{api/.*}).to_rack(FakeBridge)
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
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.
|
4
|
+
version: 0.1.60
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jay Shaffer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-05-
|
11
|
+
date: 2019-05-06 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
|
@@ -297,7 +297,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
297
297
|
- !ruby/object:Gem::Version
|
298
298
|
version: '0'
|
299
299
|
requirements: []
|
300
|
-
|
300
|
+
rubyforge_project:
|
301
|
+
rubygems_version: 2.7.3
|
301
302
|
signing_key:
|
302
303
|
specification_version: 4
|
303
304
|
summary: Bridge API
|