bridge_api 0.1.55 → 0.1.60
Sign up to get free protection for your applications and to get access to all the features.
- 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
|