rdstation-ruby-client 2.0.0 → 2.4.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 +4 -4
- data/CHANGELOG.md +116 -4
- data/README.md +114 -22
- data/lib/rdstation-ruby-client.rb +6 -1
- data/lib/rdstation.rb +19 -0
- data/lib/rdstation/api_response.rb +1 -2
- data/lib/rdstation/authentication.rb +32 -3
- data/lib/rdstation/{authorization_header.rb → authorization.rb} +11 -8
- data/lib/rdstation/builder/field.rb +70 -0
- data/lib/rdstation/client.rb +17 -7
- data/lib/rdstation/contacts.rb +22 -13
- data/lib/rdstation/error.rb +2 -0
- data/lib/rdstation/error/format.rb +29 -3
- data/lib/rdstation/error/formatter.rb +69 -8
- data/lib/rdstation/error_handler.rb +6 -1
- data/lib/rdstation/events.rb +7 -12
- data/lib/rdstation/fields.rb +35 -6
- data/lib/rdstation/retryable_request.rb +35 -0
- data/lib/rdstation/version.rb +1 -1
- data/lib/rdstation/webhooks.rb +25 -13
- data/rdstation-ruby-client.gemspec +2 -1
- data/spec/lib/rdstation/api_response_spec.rb +34 -0
- data/spec/lib/rdstation/authentication_spec.rb +164 -0
- data/spec/lib/rdstation/{authorization_header_spec.rb → authorization_spec.rb} +3 -3
- data/spec/lib/rdstation/builder/field_spec.rb +69 -0
- data/spec/lib/rdstation/client_spec.rb +6 -6
- data/spec/lib/rdstation/contacts_spec.rb +23 -3
- data/spec/lib/rdstation/error/format_spec.rb +63 -0
- data/spec/lib/rdstation/error/formatter_spec.rb +113 -0
- data/spec/lib/rdstation/error_handler_spec.rb +23 -0
- data/spec/lib/rdstation/events_spec.rb +8 -3
- data/spec/lib/rdstation/fields_spec.rb +6 -1
- data/spec/lib/rdstation/retryable_request_spec.rb +142 -0
- data/spec/lib/rdstation/webhooks_spec.rb +26 -1
- data/spec/lib/rdstation_spec.rb +18 -0
- metadata +36 -11
@@ -13,6 +13,8 @@ module RDStation
|
|
13
13
|
raise error_class, array_of_errors.first if error_class < RDStation::Error
|
14
14
|
|
15
15
|
error_class.new(array_of_errors).raise_error
|
16
|
+
rescue JSON::ParserError => error
|
17
|
+
raise error_class, { 'error_message' => response.body }
|
16
18
|
end
|
17
19
|
|
18
20
|
private
|
@@ -30,11 +32,14 @@ module RDStation
|
|
30
32
|
when 409 then RDStation::Error::Conflict
|
31
33
|
when 415 then RDStation::Error::UnsupportedMediaType
|
32
34
|
when 422 then RDStation::Error::UnprocessableEntity
|
35
|
+
when 429 then RDStation::Error::TooManyRequests
|
33
36
|
when 500 then RDStation::Error::InternalServerError
|
34
37
|
when 501 then RDStation::Error::NotImplemented
|
35
38
|
when 502 then RDStation::Error::BadGateway
|
36
39
|
when 503 then RDStation::Error::ServiceUnavailable
|
37
40
|
when 500..599 then RDStation::Error::ServerError
|
41
|
+
else
|
42
|
+
RDStation::Error::UnknownError
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
@@ -53,7 +58,7 @@ module RDStation
|
|
53
58
|
end
|
54
59
|
|
55
60
|
def additional_error_attributes
|
56
|
-
{
|
61
|
+
attrs = {
|
57
62
|
'headers' => response.headers,
|
58
63
|
'body' => JSON.parse(response.body),
|
59
64
|
'http_status' => response.code,
|
data/lib/rdstation/events.rb
CHANGED
@@ -1,24 +1,19 @@
|
|
1
1
|
module RDStation
|
2
2
|
class Events
|
3
3
|
include HTTParty
|
4
|
+
include ::RDStation::RetryableRequest
|
4
5
|
|
5
6
|
EVENTS_ENDPOINT = 'https://api.rd.services/platform/events'.freeze
|
6
7
|
|
7
|
-
def initialize(
|
8
|
-
@
|
8
|
+
def initialize(authorization:)
|
9
|
+
@authorization = authorization
|
9
10
|
end
|
10
11
|
|
11
12
|
def create(payload)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def errors?(response_body)
|
21
|
-
response_body.is_a?(Array) || response_body['errors']
|
13
|
+
retryable_request(@authorization) do |authorization|
|
14
|
+
response = self.class.post(EVENTS_ENDPOINT, headers: authorization.headers, body: payload.to_json)
|
15
|
+
ApiResponse.build(response)
|
16
|
+
end
|
22
17
|
end
|
23
18
|
end
|
24
19
|
end
|
data/lib/rdstation/fields.rb
CHANGED
@@ -1,19 +1,48 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module RDStation
|
3
|
-
# More info: https://developers.rdstation.com/pt-BR/reference/
|
3
|
+
# More info: https://developers.rdstation.com/pt-BR/reference/fields
|
4
4
|
class Fields
|
5
5
|
include HTTParty
|
6
|
+
include ::RDStation::RetryableRequest
|
6
7
|
|
7
8
|
BASE_URL = 'https://api.rd.services/platform/contacts/fields'.freeze
|
8
|
-
|
9
|
-
def initialize(
|
10
|
-
@
|
9
|
+
|
10
|
+
def initialize(authorization:)
|
11
|
+
@authorization = authorization
|
11
12
|
end
|
12
13
|
|
13
14
|
def all
|
14
|
-
|
15
|
-
|
15
|
+
retryable_request(@authorization) do |authorization|
|
16
|
+
response = self.class.get(BASE_URL, headers: authorization.headers)
|
17
|
+
ApiResponse.build(response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(payload)
|
22
|
+
retryable_request(@authorization) do |authorization|
|
23
|
+
response = self.class.post(BASE_URL, headers: authorization.headers, body: payload.to_json)
|
24
|
+
ApiResponse.build(response)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(uuid, payload)
|
29
|
+
retryable_request(@authorization) do |authorization|
|
30
|
+
response = self.class.patch(base_url(uuid), headers: authorization.headers, body: payload.to_json)
|
31
|
+
ApiResponse.build(response)
|
32
|
+
end
|
16
33
|
end
|
17
34
|
|
35
|
+
def delete(uuid)
|
36
|
+
retryable_request(@authorization) do |authorization|
|
37
|
+
response = self.class.delete(base_url(uuid), headers: authorization.headers)
|
38
|
+
ApiResponse.build(response)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def base_url(path = '')
|
45
|
+
"#{BASE_URL}/#{path}"
|
46
|
+
end
|
18
47
|
end
|
19
48
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RDStation
|
4
|
+
module RetryableRequest
|
5
|
+
MAX_RETRIES = 1
|
6
|
+
def retryable_request(authorization)
|
7
|
+
retries = 0
|
8
|
+
begin
|
9
|
+
yield authorization
|
10
|
+
rescue ::RDStation::Error::ExpiredAccessToken => e
|
11
|
+
raise if !retry_possible?(authorization) || retries >= MAX_RETRIES
|
12
|
+
|
13
|
+
retries += 1
|
14
|
+
refresh_access_token(authorization)
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def retry_possible?(authorization)
|
20
|
+
[
|
21
|
+
RDStation.configuration&.client_id,
|
22
|
+
RDStation.configuration&.client_secret,
|
23
|
+
authorization.refresh_token
|
24
|
+
].all?
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh_access_token(authorization)
|
28
|
+
client = RDStation::Authentication.new
|
29
|
+
response = client.update_access_token(authorization.refresh_token)
|
30
|
+
authorization.access_token = response['access_token']
|
31
|
+
authorization.access_token_expires_in = response['expires_in']
|
32
|
+
RDStation.configuration&.access_token_refresh_callback&.call(authorization)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/rdstation/version.rb
CHANGED
data/lib/rdstation/webhooks.rb
CHANGED
@@ -1,35 +1,47 @@
|
|
1
1
|
module RDStation
|
2
2
|
class Webhooks
|
3
3
|
include HTTParty
|
4
|
+
include ::RDStation::RetryableRequest
|
4
5
|
|
5
|
-
def initialize(
|
6
|
-
@
|
6
|
+
def initialize(authorization:)
|
7
|
+
@authorization = authorization
|
7
8
|
end
|
8
9
|
|
9
10
|
def all
|
10
|
-
|
11
|
-
|
11
|
+
retryable_request(@authorization) do |authorization|
|
12
|
+
response = self.class.get(base_url, headers: authorization.headers)
|
13
|
+
ApiResponse.build(response)
|
14
|
+
end
|
12
15
|
end
|
13
16
|
|
14
17
|
def by_uuid(uuid)
|
15
|
-
|
16
|
-
|
18
|
+
retryable_request(@authorization) do |authorization|
|
19
|
+
response = self.class.get(base_url(uuid), headers: authorization.headers)
|
20
|
+
ApiResponse.build(response)
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
def create(payload)
|
20
|
-
|
21
|
-
|
25
|
+
retryable_request(@authorization) do |authorization|
|
26
|
+
response = self.class.post(base_url, headers: authorization.headers, body: payload.to_json)
|
27
|
+
ApiResponse.build(response)
|
28
|
+
end
|
22
29
|
end
|
23
30
|
|
24
31
|
def update(uuid, payload)
|
25
|
-
|
26
|
-
|
32
|
+
retryable_request(@authorization) do |authorization|
|
33
|
+
response = self.class.put(base_url(uuid), headers: authorization.headers, body: payload.to_json)
|
34
|
+
ApiResponse.build(response)
|
35
|
+
end
|
27
36
|
end
|
28
37
|
|
29
38
|
def delete(uuid)
|
30
|
-
|
31
|
-
|
32
|
-
|
39
|
+
retryable_request(@authorization) do |authorization|
|
40
|
+
response = self.class.delete(base_url(uuid), headers: authorization.headers)
|
41
|
+
return webhook_deleted_message unless response.body
|
42
|
+
|
43
|
+
RDStation::ErrorHandler.new(response).raise_error
|
44
|
+
end
|
33
45
|
end
|
34
46
|
|
35
47
|
private
|
@@ -20,12 +20,13 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.required_ruby_version = '>= 2.0.0'
|
22
22
|
|
23
|
-
spec.add_development_dependency "bundler", "
|
23
|
+
spec.add_development_dependency "bundler", "> 1.3"
|
24
24
|
spec.add_development_dependency "rake"
|
25
25
|
spec.add_development_dependency 'rspec'
|
26
26
|
spec.add_development_dependency 'webmock', '~> 2.1'
|
27
27
|
spec.add_development_dependency 'turn'
|
28
28
|
spec.add_development_dependency 'rspec_junit_formatter'
|
29
|
+
spec.add_development_dependency 'pry'
|
29
30
|
|
30
31
|
spec.add_dependency "httparty", "~> 0.12"
|
31
32
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RDStation::ApiResponse do
|
4
|
+
describe ".build" do
|
5
|
+
context "when the response HTTP status is 2xx" do
|
6
|
+
let(:response) { OpenStruct.new(code: 200, body: '{}') }
|
7
|
+
|
8
|
+
it "returns the response body" do
|
9
|
+
expect(RDStation::ApiResponse.build(response)).to eq({})
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
shared_examples_for 'call_error_handler' do
|
14
|
+
it "calls error handler" do
|
15
|
+
error_handler = instance_double(RDStation::ErrorHandler)
|
16
|
+
allow(error_handler).to receive(:raise_error)
|
17
|
+
expect(RDStation::ErrorHandler).to receive(:new).with(response).and_return(error_handler)
|
18
|
+
RDStation::ApiResponse.build(response)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when the response is not in the 2xx range" do
|
23
|
+
let(:response) { OpenStruct.new(code: 404, body: '{}') }
|
24
|
+
|
25
|
+
it_behaves_like 'call_error_handler'
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when the response body is not JSON-parseable" do
|
29
|
+
let(:response) { OpenStruct.new(code: 504, body: '<html><head></head><body></body></html>') }
|
30
|
+
|
31
|
+
it_behaves_like 'call_error_handler'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -88,6 +88,34 @@ RSpec.describe RDStation::Authentication do
|
|
88
88
|
|
89
89
|
let(:authentication) { described_class.new('client_id', 'client_secret') }
|
90
90
|
|
91
|
+
describe '#auth_url' do
|
92
|
+
let(:configuration_client_id) { 'configuration_client_id' }
|
93
|
+
let(:configuration_client_secret) { 'configuration_client_secret' }
|
94
|
+
let(:redirect_url) { 'redirect_url' }
|
95
|
+
before do
|
96
|
+
RDStation.configure do |config|
|
97
|
+
config.client_id = configuration_client_id
|
98
|
+
config.client_secret = configuration_client_secret
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when client_id and client_secret are specified in initialization' do
|
103
|
+
it 'uses those specified in initialization' do
|
104
|
+
auth = described_class.new('initialization_client_id', 'initialization_client_secret')
|
105
|
+
expected = "https://api.rd.services/auth/dialog?client_id=initialization_client_id&redirect_url=#{redirect_url}"
|
106
|
+
expect(auth.auth_url(redirect_url)).to eq expected
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when client_id and client_secret are specified only in configuration' do
|
111
|
+
it 'uses those specified in configuration' do
|
112
|
+
auth = described_class.new
|
113
|
+
expected = "https://api.rd.services/auth/dialog?client_id=#{configuration_client_id}&redirect_url=#{redirect_url}"
|
114
|
+
expect(auth.auth_url(redirect_url)).to eq expected
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
91
119
|
describe '#authenticate' do
|
92
120
|
context 'when the code is valid' do
|
93
121
|
before do
|
@@ -138,6 +166,37 @@ RSpec.describe RDStation::Authentication do
|
|
138
166
|
end.to raise_error(RDStation::Error::ExpiredCodeGrant)
|
139
167
|
end
|
140
168
|
end
|
169
|
+
|
170
|
+
context 'when client_id and client_secret are specified only in configuration' do
|
171
|
+
let(:authentication) { described_class.new }
|
172
|
+
let(:configuration_client_id) { 'configuration_client_id' }
|
173
|
+
let(:configuration_client_secret) { 'configuration_client_secret' }
|
174
|
+
let(:token_request_with_valid_code_secrets_from_config) do
|
175
|
+
{
|
176
|
+
client_id: configuration_client_id,
|
177
|
+
client_secret: configuration_client_secret,
|
178
|
+
code: 'valid_code'
|
179
|
+
}
|
180
|
+
end
|
181
|
+
before do
|
182
|
+
RDStation.configure do |config|
|
183
|
+
config.client_id = configuration_client_id
|
184
|
+
config.client_secret = configuration_client_secret
|
185
|
+
end
|
186
|
+
|
187
|
+
stub_request(:post, token_endpoint)
|
188
|
+
.with(
|
189
|
+
headers: request_headers,
|
190
|
+
body: token_request_with_valid_code_secrets_from_config.to_json
|
191
|
+
)
|
192
|
+
.to_return(credentials_response)
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'returns the credentials' do
|
196
|
+
credentials_request = authentication.authenticate('valid_code')
|
197
|
+
expect(credentials_request).to eq(credentials)
|
198
|
+
end
|
199
|
+
end
|
141
200
|
end
|
142
201
|
|
143
202
|
describe '#update_access_token' do
|
@@ -173,5 +232,110 @@ RSpec.describe RDStation::Authentication do
|
|
173
232
|
end.to raise_error(RDStation::Error::InvalidCredentials)
|
174
233
|
end
|
175
234
|
end
|
235
|
+
|
236
|
+
context 'when client_id and client_secret are specified only in configuration' do
|
237
|
+
let(:authentication) { described_class.new }
|
238
|
+
let(:configuration_client_id) { 'configuration_client_id' }
|
239
|
+
let(:configuration_client_secret) { 'configuration_client_secret' }
|
240
|
+
let(:token_request_with_valid_refresh_code_secrets_from_config) do
|
241
|
+
{
|
242
|
+
client_id: configuration_client_id,
|
243
|
+
client_secret: configuration_client_secret,
|
244
|
+
refresh_token: 'valid_refresh_token'
|
245
|
+
}
|
246
|
+
end
|
247
|
+
before do
|
248
|
+
RDStation.configure do |config|
|
249
|
+
config.client_id = configuration_client_id
|
250
|
+
config.client_secret = configuration_client_secret
|
251
|
+
end
|
252
|
+
|
253
|
+
stub_request(:post, token_endpoint)
|
254
|
+
.with(
|
255
|
+
headers: request_headers,
|
256
|
+
body: token_request_with_valid_refresh_code_secrets_from_config.to_json
|
257
|
+
)
|
258
|
+
.to_return(credentials_response)
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'returns the credentials' do
|
262
|
+
credentials_request = authentication.update_access_token('valid_refresh_token')
|
263
|
+
expect(credentials_request).to eq(credentials)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
describe ".revoke" do
|
269
|
+
let(:revoke_endpoint) { 'https://api.rd.services/auth/revoke' }
|
270
|
+
let(:request_headers) do
|
271
|
+
{
|
272
|
+
"Authorization" => "Bearer #{access_token}",
|
273
|
+
"Content-Type" => "application/x-www-form-urlencoded"
|
274
|
+
}
|
275
|
+
end
|
276
|
+
|
277
|
+
context "valid access_token" do
|
278
|
+
let(:access_token) { "valid_access_token" }
|
279
|
+
|
280
|
+
let(:ok_response) do
|
281
|
+
{
|
282
|
+
status: 200,
|
283
|
+
headers: { 'Content-Type' => 'application/json' },
|
284
|
+
body: {}.to_json
|
285
|
+
}
|
286
|
+
end
|
287
|
+
|
288
|
+
before do
|
289
|
+
stub_request(:post, revoke_endpoint)
|
290
|
+
.with(
|
291
|
+
headers: request_headers,
|
292
|
+
body: URI.encode_www_form({
|
293
|
+
token: access_token,
|
294
|
+
token_type_hint: 'access_token'
|
295
|
+
})
|
296
|
+
)
|
297
|
+
.to_return(ok_response)
|
298
|
+
end
|
299
|
+
|
300
|
+
it "returns 200 code with an empty hash in the body" do
|
301
|
+
request_response = RDStation::Authentication.revoke(access_token: access_token)
|
302
|
+
expect(request_response).to eq({})
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
context "invalid access token" do
|
307
|
+
let(:access_token) { "invalid_access_token" }
|
308
|
+
|
309
|
+
let(:unauthorized_response) do
|
310
|
+
{
|
311
|
+
status: 401,
|
312
|
+
headers: { 'Content-Type' => 'application/json' },
|
313
|
+
body: {
|
314
|
+
errors: {
|
315
|
+
error_type: 'UNAUTHORIZED',
|
316
|
+
error_message: 'Invalid token.'
|
317
|
+
}
|
318
|
+
}.to_json
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
before do
|
323
|
+
stub_request(:post, revoke_endpoint)
|
324
|
+
.with(
|
325
|
+
headers: request_headers,
|
326
|
+
body: URI.encode_www_form({
|
327
|
+
token: access_token,
|
328
|
+
token_type_hint: 'access_token'
|
329
|
+
})
|
330
|
+
)
|
331
|
+
.to_return(unauthorized_response)
|
332
|
+
end
|
333
|
+
|
334
|
+
it "raises unauthorized" do
|
335
|
+
expect do
|
336
|
+
RDStation::Authentication.revoke(access_token: access_token)
|
337
|
+
end.to raise_error(RDStation::Error::Unauthorized)
|
338
|
+
end
|
339
|
+
end
|
176
340
|
end
|
177
341
|
end
|