rdstation-ruby-client 0.1.1 → 1.0.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.
@@ -0,0 +1,27 @@
1
+ module RDStation
2
+ class ErrorHandler
3
+ class ExpiredCodeGrant
4
+ attr_reader :api_response, :response_headers, :error
5
+
6
+ ERROR_CODE = 'EXPIRED_CODE_GRANT'.freeze
7
+ EXCEPTION_CLASS = RDStation::Error::ExpiredCodeGrant
8
+
9
+ def initialize(api_response)
10
+ @api_response = api_response
11
+ @error = JSON.parse(api_response.body)['errors']
12
+ @response_headers = api_response.headers
13
+ end
14
+
15
+ def raise_error
16
+ return unless expired_code?
17
+ raise EXCEPTION_CLASS.new(error['error_message'], api_response)
18
+ end
19
+
20
+ private
21
+
22
+ def expired_code?
23
+ error['error_type'] == ERROR_CODE
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module RDStation
2
+ class ErrorHandler
3
+ class InvalidCredentials
4
+ attr_reader :api_response, :error
5
+
6
+ ERROR_CODE = 'ACCESS_DENIED'.freeze
7
+ EXCEPTION_CLASS = RDStation::Error::InvalidCredentials
8
+
9
+ def initialize(api_response)
10
+ @api_response = api_response
11
+ @error = JSON.parse(api_response.body)['errors']
12
+ end
13
+
14
+ def raise_error
15
+ return unless credentials_errors?
16
+ raise EXCEPTION_CLASS.new(error['error_message'], api_response)
17
+ end
18
+
19
+ private
20
+
21
+ def credentials_errors?
22
+ error['error_type'] == ERROR_CODE
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module RDStation
2
+ class ErrorHandler
3
+ class ResourceNotFound
4
+ attr_reader :api_response, :error
5
+
6
+ ERROR_CODE = 'RESOURCE_NOT_FOUND'.freeze
7
+ EXCEPTION_CLASS = RDStation::Error::ResourceNotFound
8
+
9
+ def initialize(api_response)
10
+ @api_response = api_response
11
+ @error = JSON.parse(api_response.body)['errors']
12
+ end
13
+
14
+ def raise_error
15
+ return unless resource_not_found?
16
+ raise EXCEPTION_CLASS.new(error['error_message'], api_response)
17
+ end
18
+
19
+ private
20
+
21
+ def resource_not_found?
22
+ error['error_type'] == ERROR_CODE
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module RDStation
2
+ class ErrorHandler
3
+ class Unauthorized
4
+ attr_reader :api_response, :response_body, :error
5
+
6
+ ERROR_CODE = 'UNAUTHORIZED'.freeze
7
+ EXCEPTION_CLASS = RDStation::Error::Unauthorized
8
+
9
+ def initialize(api_response)
10
+ @api_response = api_response
11
+ @error = JSON.parse(api_response.body)['errors']
12
+ end
13
+
14
+ def raise_error
15
+ return unless unauthorized?
16
+ raise EXCEPTION_CLASS.new(error['error_message'], api_response)
17
+ end
18
+
19
+ private
20
+
21
+ def unauthorized?
22
+ error['error_type'] == ERROR_CODE
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module RDStation
3
+ # More info: https://developers.rdstation.com/pt-BR/reference/contacts
4
+ class Fields
5
+ include HTTParty
6
+
7
+ BASE_URL = 'https://api.rd.services/platform/contacts/fields'.freeze
8
+
9
+ def initialize(auth_token)
10
+ @auth_token = auth_token
11
+ end
12
+
13
+ def all
14
+ response = self.class.get(BASE_URL, headers: required_headers)
15
+ response_body = JSON.parse(response.body)
16
+ return response_body unless response_body['errors']
17
+ RDStation::ErrorHandler.new(response).raise_errors
18
+ end
19
+
20
+ private
21
+
22
+ def required_headers
23
+ { "Authorization" => "Bearer #{@auth_token}", "Content-Type" => "application/json" }
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module RDStation
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -21,8 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency 'rspec'
24
- spec.add_development_dependency 'vcr'
25
- spec.add_development_dependency 'webmock'
24
+ spec.add_development_dependency 'webmock', '~> 2.1'
26
25
  spec.add_development_dependency 'turn'
27
26
 
28
27
  spec.add_dependency "httparty", "~> 0.12"
@@ -0,0 +1,177 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RDStation::Authentication do
4
+ let(:token_endpoint) { 'https://api.rd.services/auth/token' }
5
+ let(:request_headers) { { 'Content-Type' => 'application/json' } }
6
+
7
+ let(:token_request_with_valid_code) do
8
+ {
9
+ client_id: 'client_id',
10
+ client_secret: 'client_secret',
11
+ code: 'valid_code'
12
+ }
13
+ end
14
+
15
+ let(:token_request_with_invalid_code) do
16
+ {
17
+ client_id: 'client_id',
18
+ client_secret: 'client_secret',
19
+ code: 'invalid_code'
20
+ }
21
+ end
22
+
23
+ let(:token_request_with_expired_code) do
24
+ {
25
+ client_id: 'client_id',
26
+ client_secret: 'client_secret',
27
+ code: 'expired_code'
28
+ }
29
+ end
30
+
31
+ let(:token_request_with_valid_refresh_token) do
32
+ {
33
+ client_id: 'client_id',
34
+ client_secret: 'client_secret',
35
+ refresh_token: 'valid_refresh_token'
36
+ }
37
+ end
38
+
39
+ let(:token_request_with_invalid_refresh_token) do
40
+ {
41
+ client_id: 'client_id',
42
+ client_secret: 'client_secret',
43
+ refresh_token: 'invalid_refresh_token'
44
+ }
45
+ end
46
+
47
+ let(:credentials) do
48
+ {
49
+ 'access_token' => '123456',
50
+ 'expires_in' => 86_400,
51
+ 'refresh_token' => 'refreshtoken'
52
+ }
53
+ end
54
+
55
+ let(:credentials_response) do
56
+ {
57
+ status: 200,
58
+ headers: { 'Content-Type' => 'application/json' },
59
+ body: credentials.to_json
60
+ }
61
+ end
62
+
63
+ let(:invalid_code_response) do
64
+ {
65
+ status: 401,
66
+ headers: { 'Content-Type' => 'application/json' },
67
+ body: {
68
+ errors: {
69
+ error_type: 'ACCESS_DENIED',
70
+ error_message: 'Wrong credentials provided.'
71
+ }
72
+ }.to_json
73
+ }
74
+ end
75
+
76
+ let(:expired_code_response) do
77
+ {
78
+ status: 401,
79
+ headers: { 'Content-Type' => 'application/json' },
80
+ body: {
81
+ errors: {
82
+ error_type: 'EXPIRED_CODE_GRANT',
83
+ error_message: 'The authorization code grant has expired.'
84
+ }
85
+ }.to_json
86
+ }
87
+ end
88
+
89
+ let(:authentication) { described_class.new('client_id', 'client_secret') }
90
+
91
+ describe '#authenticate' do
92
+ context 'when the code is valid' do
93
+ before do
94
+ stub_request(:post, token_endpoint)
95
+ .with(
96
+ headers: request_headers,
97
+ body: token_request_with_valid_code.to_json
98
+ )
99
+ .to_return(credentials_response)
100
+ end
101
+
102
+ it 'returns the credentials' do
103
+ credentials_request = authentication.authenticate('valid_code')
104
+ expect(credentials_request).to eq(credentials)
105
+ end
106
+ end
107
+
108
+ context 'when the code is invalid' do
109
+ before do
110
+ stub_request(:post, token_endpoint)
111
+ .with(
112
+ headers: request_headers,
113
+ body: token_request_with_invalid_code.to_json
114
+ )
115
+ .to_return(invalid_code_response)
116
+ end
117
+
118
+ it 'returns an auth error' do
119
+ expect do
120
+ authentication.authenticate('invalid_code')
121
+ end.to raise_error(RDStation::Error::InvalidCredentials)
122
+ end
123
+ end
124
+
125
+ context 'when the code has expired' do
126
+ before do
127
+ stub_request(:post, token_endpoint)
128
+ .with(
129
+ headers: request_headers,
130
+ body: token_request_with_expired_code.to_json
131
+ )
132
+ .to_return(expired_code_response)
133
+ end
134
+
135
+ it 'returns an expired code error' do
136
+ expect do
137
+ authentication.authenticate('expired_code')
138
+ end.to raise_error(RDStation::Error::ExpiredCodeGrant)
139
+ end
140
+ end
141
+ end
142
+
143
+ describe '#update_access_token' do
144
+ context 'when the refresh token is valid' do
145
+ before do
146
+ stub_request(:post, token_endpoint)
147
+ .with(
148
+ headers: request_headers,
149
+ body: token_request_with_valid_refresh_token.to_json
150
+ )
151
+ .to_return(credentials_response)
152
+ end
153
+
154
+ it 'returns the credentials' do
155
+ credentials_request = authentication.update_access_token('valid_refresh_token')
156
+ expect(credentials_request).to eq(credentials)
157
+ end
158
+ end
159
+
160
+ context 'when the refresh token is invalid' do
161
+ before do
162
+ stub_request(:post, token_endpoint)
163
+ .with(
164
+ headers: request_headers,
165
+ body: token_request_with_invalid_refresh_token.to_json
166
+ )
167
+ .to_return(invalid_code_response)
168
+ end
169
+
170
+ it 'returns an auth error' do
171
+ expect do
172
+ authentication.update_access_token('invalid_refresh_token')
173
+ end.to raise_error(RDStation::Error::InvalidCredentials)
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,434 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RDStation::Contacts do
4
+ let(:valid_uuid) { 'valid_uuid' }
5
+ let(:invalid_uuid) { 'invalid_uuid' }
6
+ let(:valid_email) { 'valid@email.com' }
7
+ let(:invalid_email) { 'invalid@email.com' }
8
+
9
+ let(:endpoint_with_valid_uuid) { "https://api.rd.services/platform/contacts/#{valid_uuid}" }
10
+ let(:endpoint_with_invalid_uuid) { "https://api.rd.services/platform/contacts/#{invalid_uuid}" }
11
+ let(:endpoint_with_valid_email) { "https://api.rd.services/platform/contacts/email:#{valid_email}" }
12
+ let(:endpoint_with_invalid_email) { "https://api.rd.services/platform/contacts/email:#{invalid_email}" }
13
+
14
+ let(:valid_auth_token) { 'valid_auth_token' }
15
+ let(:invalid_auth_token) { 'invalid_auth_token' }
16
+ let(:expired_auth_token) { 'expired_auth_token' }
17
+
18
+ let(:contact_with_valid_token) { described_class.new(valid_auth_token) }
19
+ let(:contact_with_expired_token) { described_class.new(expired_auth_token) }
20
+ let(:contact_with_invalid_token) { described_class.new(invalid_auth_token) }
21
+
22
+ let(:valid_headers) do
23
+ {
24
+ 'Authorization' => "Bearer #{valid_auth_token}",
25
+ 'Content-Type' => 'application/json'
26
+ }
27
+ end
28
+
29
+ let(:invalid_token_headers) do
30
+ {
31
+ 'Authorization' => "Bearer #{invalid_auth_token}",
32
+ 'Content-Type' => 'application/json'
33
+ }
34
+ end
35
+
36
+ let(:expired_token_headers) do
37
+ {
38
+ 'Authorization' => "Bearer #{expired_auth_token}",
39
+ 'Content-Type' => 'application/json'
40
+ }
41
+ end
42
+
43
+ let(:not_found_response) do
44
+ {
45
+ status: 404,
46
+ body: {
47
+ errors: {
48
+ error_type: 'RESOURCE_NOT_FOUND',
49
+ error_message: 'Lead not found.'
50
+ }
51
+ }.to_json
52
+ }
53
+ end
54
+
55
+ let(:invalid_token_response) do
56
+ {
57
+ status: 401,
58
+ body: {
59
+ errors: {
60
+ error_type: 'UNAUTHORIZED',
61
+ error_message: 'Invalid token.'
62
+ }
63
+ }.to_json
64
+ }
65
+ end
66
+
67
+ let(:conflicting_field_response) do
68
+ {
69
+ status: 400,
70
+ body: {
71
+ errors: {
72
+ error_type: 'CONFLICTING_FIELD',
73
+ error_message: 'The payload contains an attribute that was used to identify the resource.'
74
+ }
75
+ }.to_json
76
+ }
77
+ end
78
+
79
+ let(:unrecognized_error) do
80
+ {
81
+ status: 400,
82
+ body: {
83
+ errors: {
84
+ error_type: 'unrecognized error',
85
+ error_message: 'Unexpected error.'
86
+ }
87
+ }.to_json
88
+ }
89
+ end
90
+
91
+ let(:expired_token_response) do
92
+ {
93
+ status: 401,
94
+ headers: { 'WWW-Authenticate' => 'Bearer realm="https://api.rd.services/", error="expired_token", error_description="The access token expired"' },
95
+ body: {
96
+ errors: {
97
+ error_type: 'UNAUTHORIZED',
98
+ error_message: 'Invalid token.'
99
+ }
100
+ }.to_json
101
+ }
102
+ end
103
+
104
+ describe '#by_uuid' do
105
+ context 'with a valid auth token' do
106
+ context 'when the contact exists' do
107
+ let(:contact) do
108
+ { 'name' => 'Lead', 'email' => 'valid@email.com' }
109
+ end
110
+
111
+ before do
112
+ stub_request(:get, endpoint_with_valid_uuid)
113
+ .with(headers: valid_headers)
114
+ .to_return(status: 200, body: contact.to_json)
115
+ end
116
+
117
+ it 'returns the contact' do
118
+ response = contact_with_valid_token.by_uuid('valid_uuid')
119
+ expect(response).to eq(contact)
120
+ end
121
+ end
122
+
123
+ context 'when the contact does not exist' do
124
+ before do
125
+ stub_request(:get, endpoint_with_invalid_uuid)
126
+ .with(headers: valid_headers)
127
+ .to_return(not_found_response)
128
+ end
129
+
130
+ it 'raises a not found error' do
131
+ expect do
132
+ contact_with_valid_token.by_uuid(invalid_uuid)
133
+ end.to raise_error(RDStation::Error::ResourceNotFound)
134
+ end
135
+ end
136
+ end
137
+
138
+ context 'with an invalid auth token' do
139
+ before do
140
+ stub_request(:get, endpoint_with_valid_uuid)
141
+ .with(headers: invalid_token_headers)
142
+ .to_return(invalid_token_response)
143
+ end
144
+
145
+ it 'raises an invalid token error' do
146
+ expect do
147
+ contact_with_invalid_token.by_uuid(valid_uuid)
148
+ end.to raise_error(RDStation::Error::Unauthorized)
149
+ end
150
+ end
151
+
152
+ context 'with an expired auth token' do
153
+ before do
154
+ stub_request(:get, endpoint_with_valid_uuid)
155
+ .with(headers: expired_token_headers)
156
+ .to_return(expired_token_response)
157
+ end
158
+
159
+ it 'raises a expired token error' do
160
+ expect do
161
+ contact_with_expired_token.by_uuid(valid_uuid)
162
+ end.to raise_error(RDStation::Error::ExpiredAccessToken)
163
+ end
164
+ end
165
+ end
166
+
167
+ describe '#by_email' do
168
+ context 'with a valid auth token' do
169
+ context 'when the contact exists' do
170
+ let(:contact) do
171
+ { 'name' => 'Lead', 'email' => 'valid@email.com' }
172
+ end
173
+
174
+ before do
175
+ stub_request(:get, endpoint_with_valid_email)
176
+ .with(headers: valid_headers)
177
+ .to_return(status: 200, body: contact.to_json)
178
+ end
179
+
180
+ it 'returns the contact' do
181
+ response = contact_with_valid_token.by_email(valid_email)
182
+ expect(response).to eq(contact)
183
+ end
184
+ end
185
+
186
+ context 'when the contact does not exist' do
187
+ before do
188
+ stub_request(:get, endpoint_with_invalid_email)
189
+ .with(headers: valid_headers)
190
+ .to_return(not_found_response)
191
+ end
192
+
193
+ it 'raises a not found error' do
194
+ expect do
195
+ contact_with_valid_token.by_email(invalid_email)
196
+ end.to raise_error(RDStation::Error::ResourceNotFound)
197
+ end
198
+ end
199
+ end
200
+
201
+ context 'with an invalid auth token' do
202
+ before do
203
+ stub_request(:get, endpoint_with_valid_email)
204
+ .with(headers: invalid_token_headers)
205
+ .to_return(invalid_token_response)
206
+ end
207
+
208
+ it 'raises an invalid token error' do
209
+ expect do
210
+ contact_with_invalid_token.by_email(valid_email)
211
+ end.to raise_error(RDStation::Error::Unauthorized)
212
+ end
213
+ end
214
+
215
+ context 'with an expired auth token' do
216
+ before do
217
+ stub_request(:get, endpoint_with_valid_email)
218
+ .with(headers: expired_token_headers)
219
+ .to_return(expired_token_response)
220
+ end
221
+
222
+ it 'raises a expired token error' do
223
+ expect do
224
+ contact_with_expired_token.by_email(valid_email)
225
+ end.to raise_error(RDStation::Error::ExpiredAccessToken)
226
+ end
227
+ end
228
+ end
229
+
230
+ describe '#update' do
231
+ context 'with a valid auth_token' do
232
+ let(:valid_auth_token) { 'valid_auth_token' }
233
+ let(:headers) do
234
+ {
235
+ 'Authorization' => "Bearer #{valid_auth_token}",
236
+ 'Content-Type' => 'application/json'
237
+ }
238
+ end
239
+
240
+ context 'with valid params' do
241
+ let(:contact) do
242
+ { 'name' => 'Lead', 'email' => 'valid@email.com' }
243
+ end
244
+
245
+ before do
246
+ stub_request(:patch, endpoint_with_valid_uuid)
247
+ .with(headers: headers)
248
+ .to_return(status: 200, body: contact.to_json)
249
+ end
250
+
251
+ it 'returns the updated contact' do
252
+ updated_contact = contact_with_valid_token.update('valid_uuid', contact)
253
+ expect(updated_contact).to eq(contact)
254
+ end
255
+ end
256
+
257
+ context 'when the contact does not exist' do
258
+ before do
259
+ stub_request(:patch, endpoint_with_invalid_uuid)
260
+ .with(headers: headers)
261
+ .to_return(not_found_response)
262
+ end
263
+
264
+ it 'raises a not found error' do
265
+ expect do
266
+ contact_with_valid_token.update(invalid_uuid, {})
267
+ end.to raise_error(RDStation::Error::ResourceNotFound)
268
+ end
269
+ end
270
+ end
271
+
272
+ context 'with an invalid auth token' do
273
+ let(:invalid_auth_token) { 'invalid_auth_token' }
274
+ let(:headers) do
275
+ {
276
+ 'Authorization' => "Bearer #{invalid_auth_token}",
277
+ 'Content-Type' => 'application/json'
278
+ }
279
+ end
280
+
281
+ before do
282
+ stub_request(:patch, endpoint_with_valid_uuid)
283
+ .with(headers: headers)
284
+ .to_return(invalid_token_response)
285
+ end
286
+
287
+ it 'raises an invalid token error' do
288
+ expect do
289
+ contact_with_invalid_token.update('valid_uuid', {})
290
+ end.to raise_error(RDStation::Error::Unauthorized)
291
+ end
292
+ end
293
+
294
+ context 'with an expired auth token' do
295
+ let(:expired_auth_token) { 'expired_auth_token' }
296
+ let(:headers) do
297
+ {
298
+ 'Authorization' => "Bearer #{expired_auth_token}",
299
+ 'Content-Type' => 'application/json'
300
+ }
301
+ end
302
+
303
+ before do
304
+ stub_request(:patch, endpoint_with_valid_uuid)
305
+ .with(headers: headers)
306
+ .to_return(expired_token_response)
307
+ end
308
+
309
+ it 'raises a expired token error' do
310
+ expect do
311
+ contact_with_expired_token.update('valid_uuid', {})
312
+ end.to raise_error(RDStation::Error::ExpiredAccessToken)
313
+ end
314
+ end
315
+ end
316
+
317
+ describe '#upsert' do
318
+ context 'with a valid auth_token' do
319
+ let(:valid_auth_token) { 'valid_auth_token' }
320
+
321
+ let(:headers) do
322
+ {
323
+ 'Authorization' => "Bearer #{valid_auth_token}",
324
+ 'Content-Type' => 'application/json'
325
+ }
326
+ end
327
+
328
+ context 'with valid params' do
329
+ let(:contact) do
330
+ { 'name' => 'Lead', 'job_title' => 'Developer' }
331
+ end
332
+
333
+ before do
334
+ stub_request(:patch, endpoint_with_valid_email)
335
+ .with(headers: headers)
336
+ .to_return(status: 200, body: contact.to_json)
337
+ end
338
+
339
+ it 'returns the updated contact' do
340
+ updated_contact = contact_with_valid_token.upsert('email', 'valid@email.com', contact)
341
+ expect(updated_contact).to eq(contact)
342
+ end
343
+ end
344
+
345
+ context 'when the contact does not exist' do
346
+ before do
347
+ stub_request(:patch, endpoint_with_invalid_email)
348
+ .with(headers: headers)
349
+ .to_return(not_found_response)
350
+ end
351
+
352
+ it 'raises a not found error' do
353
+ expect do
354
+ contact_with_valid_token.upsert('email', invalid_email, {})
355
+ end.to raise_error(RDStation::Error::ResourceNotFound)
356
+ end
357
+ end
358
+
359
+ context 'when the payload has a conflicting field' do
360
+ let(:conflicting_payload) { { 'email' => valid_email } }
361
+
362
+ before do
363
+ stub_request(:patch, endpoint_with_valid_email)
364
+ .with(headers: headers)
365
+ .to_return(conflicting_field_response)
366
+ end
367
+
368
+ it 'raises a conflicting field error' do
369
+ expect do
370
+ contact_with_valid_token.upsert('email', valid_email, conflicting_payload)
371
+ end.to raise_error(RDStation::Error::ConflictingField)
372
+ end
373
+ end
374
+
375
+ context 'when an unrecognized error occurs' do
376
+ before do
377
+ stub_request(:patch, endpoint_with_valid_email)
378
+ .with(headers: headers)
379
+ .to_return(unrecognized_error)
380
+ end
381
+
382
+ it 'raises an default error' do
383
+ expect do
384
+ contact_with_valid_token.upsert('email', valid_email, {})
385
+ end.to raise_error(RDStation::Error::Default)
386
+ end
387
+ end
388
+ end
389
+
390
+ context 'with an invalid auth token' do
391
+ let(:invalid_auth_token) { 'invalid_auth_token' }
392
+ let(:headers) do
393
+ {
394
+ 'Authorization' => "Bearer #{invalid_auth_token}",
395
+ 'Content-Type' => 'application/json'
396
+ }
397
+ end
398
+
399
+ before do
400
+ stub_request(:patch, endpoint_with_valid_email)
401
+ .with(headers: headers)
402
+ .to_return(invalid_token_response)
403
+ end
404
+
405
+ it 'raises an invalid token error' do
406
+ expect do
407
+ contact_with_invalid_token.upsert('email', valid_email, {})
408
+ end.to raise_error(RDStation::Error::Unauthorized)
409
+ end
410
+ end
411
+
412
+ context 'with an expired auth token' do
413
+ let(:expired_auth_token) { 'expired_auth_token' }
414
+ let(:headers) do
415
+ {
416
+ 'Authorization' => "Bearer #{expired_auth_token}",
417
+ 'Content-Type' => 'application/json'
418
+ }
419
+ end
420
+
421
+ before do
422
+ stub_request(:patch, endpoint_with_valid_email)
423
+ .with(headers: headers)
424
+ .to_return(expired_token_response)
425
+ end
426
+
427
+ it 'raises an expired token error' do
428
+ expect do
429
+ contact_with_expired_token.upsert('email', valid_email, {})
430
+ end.to raise_error(RDStation::Error::ExpiredAccessToken)
431
+ end
432
+ end
433
+ end
434
+ end