rdstation-ruby-client 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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(authorization_header:)
8
- @authorization_header = authorization_header
8
+ def initialize(authorization:)
9
+ @authorization = authorization
9
10
  end
10
11
 
11
12
  def create(payload)
12
- response = self.class.post(EVENTS_ENDPOINT, headers: @authorization_header.to_h, body: payload.to_json)
13
- response_body = JSON.parse(response.body)
14
- return response_body unless errors?(response_body)
15
- RDStation::ErrorHandler.new(response).raise_error
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
@@ -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(authorization_header:)
10
- @authorization_header = authorization_header
10
+ def initialize(authorization:)
11
+ @authorization = authorization
11
12
  end
12
13
 
13
14
  def all
14
- response = self.class.get(BASE_URL, headers: @authorization_header.to_h)
15
- ApiResponse.build(response)
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
@@ -1,3 +1,3 @@
1
1
  module RDStation
2
- VERSION = '2.1.0'.freeze
2
+ VERSION = '2.2.0'.freeze
3
3
  end
@@ -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(authorization_header:)
6
- @authorization_header = authorization_header
6
+ def initialize(authorization:)
7
+ @authorization = authorization
7
8
  end
8
9
 
9
10
  def all
10
- response = self.class.get(base_url, headers: @authorization_header.to_h)
11
- ApiResponse.build(response)
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
- response = self.class.get(base_url(uuid), headers: @authorization_header.to_h)
16
- ApiResponse.build(response)
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
- response = self.class.post(base_url, headers: @authorization_header.to_h, body: payload.to_json)
21
- ApiResponse.build(response)
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
- response = self.class.put(base_url(uuid), headers: @authorization_header.to_h, body: payload.to_json)
26
- ApiResponse.build(response)
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
- response = self.class.delete(base_url(uuid), headers: @authorization_header.to_h)
31
- return webhook_deleted_message unless response.body
32
- RDStation::ErrorHandler.new(response).raise_error
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", "~> 1.3"
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::AuthorizationHeader do
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 "#to_h" do
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).to_h
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(:mock_authorization_header) { double(RDStation::AuthorizationHeader) }
7
+ let(:mock_authorization) { double(RDStation::Authorization) }
8
8
 
9
- before { allow(RDStation::AuthorizationHeader).to receive(:new).and_return mock_authorization_header }
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({ authorization_header: mock_authorization_header }).and_call_original
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({ authorization_header: mock_authorization_header }).and_call_original
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({ authorization_header: mock_authorization_header }).and_call_original
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({ authorization_header: mock_authorization_header }).and_call_original
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(authorization_header: RDStation::AuthorizationHeader.new(access_token: valid_access_token))
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(authorization_header: RDStation::AuthorizationHeader.new(access_token: expired_access_token))
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(authorization_header: RDStation::AuthorizationHeader.new(access_token: invalid_access_token))
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