rdstation-ruby-client 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +47 -0
- data/README.md +56 -22
- data/lib/rdstation-ruby-client.rb +3 -1
- data/lib/rdstation.rb +19 -0
- data/lib/rdstation/api_response.rb +1 -2
- data/lib/rdstation/authentication.rb +8 -3
- data/lib/rdstation/{authorization_header.rb → authorization.rb} +11 -8
- data/lib/rdstation/client.rb +17 -7
- data/lib/rdstation/contacts.rb +22 -13
- data/lib/rdstation/error_handler.rb +2 -0
- data/lib/rdstation/events.rb +9 -6
- data/lib/rdstation/fields.rb +7 -4
- 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 +90 -0
- data/spec/lib/rdstation/{authorization_header_spec.rb → authorization_spec.rb} +3 -3
- data/spec/lib/rdstation/client_spec.rb +6 -6
- data/spec/lib/rdstation/contacts_spec.rb +23 -3
- data/spec/lib/rdstation/error_handler_spec.rb +14 -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 +30 -9
|
@@ -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
|
data/lib/rdstation/events.rb
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
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
|
-
|
|
13
|
+
retryable_request(@authorization) do |authorization|
|
|
14
|
+
response = self.class.post(EVENTS_ENDPOINT, headers: authorization.headers, body: payload.to_json)
|
|
15
|
+
response_body = JSON.parse(response.body)
|
|
16
|
+
return response_body unless errors?(response_body)
|
|
17
|
+
RDStation::ErrorHandler.new(response).raise_error
|
|
18
|
+
end
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
private
|
data/lib/rdstation/fields.rb
CHANGED
|
@@ -3,16 +3,19 @@ module RDStation
|
|
|
3
3
|
# More info: https://developers.rdstation.com/pt-BR/reference/contacts
|
|
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
|
|
|
9
|
-
def initialize(
|
|
10
|
-
@
|
|
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
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
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,6 +232,37 @@ 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
|
|
176
266
|
end
|
|
177
267
|
|
|
178
268
|
describe ".revoke" do
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
RSpec.describe RDStation::
|
|
3
|
+
RSpec.describe RDStation::Authorization do
|
|
4
4
|
|
|
5
5
|
describe ".initialize" do
|
|
6
6
|
context "when access_token is nil" do
|
|
@@ -12,11 +12,11 @@ RSpec.describe RDStation::AuthorizationHeader do
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
describe "#
|
|
15
|
+
describe "#headers" do
|
|
16
16
|
let(:access_token) { 'access_token' }
|
|
17
17
|
|
|
18
18
|
it "generates the correct header" do
|
|
19
|
-
header = described_class.new(access_token: access_token).
|
|
19
|
+
header = described_class.new(access_token: access_token).headers
|
|
20
20
|
expect(header['Authorization']).to eq "Bearer #{access_token}"
|
|
21
21
|
expect(header['Content-Type']).to eq "application/json"
|
|
22
22
|
end
|
|
@@ -4,27 +4,27 @@ RSpec.describe RDStation::Client do
|
|
|
4
4
|
context "when access_token is given" do
|
|
5
5
|
let(:access_token) { 'access_token' }
|
|
6
6
|
let(:client) { described_class.new(access_token: access_token) }
|
|
7
|
-
let(:
|
|
7
|
+
let(:mock_authorization) { double(RDStation::Authorization) }
|
|
8
8
|
|
|
9
|
-
before { allow(RDStation::
|
|
9
|
+
before { allow(RDStation::Authorization).to receive(:new).and_return mock_authorization }
|
|
10
10
|
|
|
11
11
|
it 'returns Contacts endpoint' do
|
|
12
|
-
expect(RDStation::Contacts).to receive(:new).with({
|
|
12
|
+
expect(RDStation::Contacts).to receive(:new).with({ authorization: mock_authorization }).and_call_original
|
|
13
13
|
expect(client.contacts).to be_instance_of RDStation::Contacts
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
it 'returns Events endpoint' do
|
|
17
|
-
expect(RDStation::Events).to receive(:new).with({
|
|
17
|
+
expect(RDStation::Events).to receive(:new).with({ authorization: mock_authorization }).and_call_original
|
|
18
18
|
expect(client.events).to be_instance_of RDStation::Events
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'returns Fields endpoint' do
|
|
22
|
-
expect(RDStation::Fields).to receive(:new).with({
|
|
22
|
+
expect(RDStation::Fields).to receive(:new).with({ authorization: mock_authorization }).and_call_original
|
|
23
23
|
expect(client.fields).to be_instance_of RDStation::Fields
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it 'returns Webhooks endpoint' do
|
|
27
|
-
expect(RDStation::Webhooks).to receive(:new).with({
|
|
27
|
+
expect(RDStation::Webhooks).to receive(:new).with({ authorization: mock_authorization }).and_call_original
|
|
28
28
|
expect(client.webhooks).to be_instance_of RDStation::Webhooks
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -16,13 +16,13 @@ RSpec.describe RDStation::Contacts do
|
|
|
16
16
|
let(:expired_access_token) { 'expired_access_token' }
|
|
17
17
|
|
|
18
18
|
let(:contact_with_valid_token) do
|
|
19
|
-
described_class.new(
|
|
19
|
+
described_class.new(authorization: RDStation::Authorization.new(access_token: valid_access_token))
|
|
20
20
|
end
|
|
21
21
|
let(:contact_with_expired_token) do
|
|
22
|
-
described_class.new(
|
|
22
|
+
described_class.new(authorization: RDStation::Authorization.new(access_token: expired_access_token))
|
|
23
23
|
end
|
|
24
24
|
let(:contact_with_invalid_token) do
|
|
25
|
-
described_class.new(
|
|
25
|
+
described_class.new(authorization: RDStation::Authorization.new(access_token: invalid_access_token))
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
|
|
@@ -109,6 +109,11 @@ RSpec.describe RDStation::Contacts do
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
describe '#by_uuid' do
|
|
112
|
+
it 'calls retryable_request' do
|
|
113
|
+
expect(contact_with_valid_token).to receive(:retryable_request)
|
|
114
|
+
contact_with_valid_token.by_uuid('valid_uuid')
|
|
115
|
+
end
|
|
116
|
+
|
|
112
117
|
context 'with a valid auth token' do
|
|
113
118
|
context 'when the contact exists' do
|
|
114
119
|
let(:contact) do
|
|
@@ -172,6 +177,11 @@ RSpec.describe RDStation::Contacts do
|
|
|
172
177
|
end
|
|
173
178
|
|
|
174
179
|
describe '#by_email' do
|
|
180
|
+
it 'calls retryable_request' do
|
|
181
|
+
expect(contact_with_valid_token).to receive(:retryable_request)
|
|
182
|
+
contact_with_valid_token.by_email('x@xpto.com')
|
|
183
|
+
end
|
|
184
|
+
|
|
175
185
|
context 'with a valid auth token' do
|
|
176
186
|
context 'when the contact exists' do
|
|
177
187
|
let(:contact) do
|
|
@@ -235,6 +245,11 @@ RSpec.describe RDStation::Contacts do
|
|
|
235
245
|
end
|
|
236
246
|
|
|
237
247
|
describe '#update' do
|
|
248
|
+
it 'calls retryable_request' do
|
|
249
|
+
expect(contact_with_valid_token).to receive(:retryable_request)
|
|
250
|
+
contact_with_valid_token.update('valid_uuid', {})
|
|
251
|
+
end
|
|
252
|
+
|
|
238
253
|
context 'with a valid access_token' do
|
|
239
254
|
let(:valid_access_token) { 'valid_access_token' }
|
|
240
255
|
let(:headers) do
|
|
@@ -322,6 +337,11 @@ RSpec.describe RDStation::Contacts do
|
|
|
322
337
|
end
|
|
323
338
|
|
|
324
339
|
describe '#upsert' do
|
|
340
|
+
it 'calls retryable_request' do
|
|
341
|
+
expect(contact_with_valid_token).to receive(:retryable_request)
|
|
342
|
+
contact_with_valid_token.upsert('email', 'valid@email.com', {})
|
|
343
|
+
end
|
|
344
|
+
|
|
325
345
|
context 'with a valid access_token' do
|
|
326
346
|
let(:valid_access_token) { 'valid_access_token' }
|
|
327
347
|
|