cronofy 0.0.5 → 0.37.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,559 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe Cronofy::Auth do
4
+ let(:client_id) { 'client_id_123' }
5
+ let(:client_secret) { 'client_secret_456' }
6
+
7
+ let(:code) { 'code_789' }
8
+ let(:redirect_uri) { 'http://red.ire.ct/Uri' }
9
+ let(:access_token) { 'access_token_123' }
10
+ let(:refresh_token) { 'refresh_token_456' }
11
+
12
+ let(:new_access_token) { "new_access_token_2342" }
13
+ let(:new_refresh_token) { "new_refresh_token_7898" }
14
+ let(:expires_in) { 10000 }
15
+ let(:scope) { 'read_events list_calendars create_event' }
16
+
17
+ let(:linking_profile_hash) { nil }
18
+ let(:account_id) { nil }
19
+
20
+ before(:all) do
21
+ WebMock.reset!
22
+ WebMock.disable_net_connect!(allow_localhost: true)
23
+ end
24
+
25
+ let(:api_token_url) { "https://api.cronofy.com/oauth/token" }
26
+ let(:app_token_url) { "https://app.cronofy.com/oauth/token" }
27
+
28
+ let(:response_status) { 200 }
29
+
30
+ before(:each) do
31
+ stub_request(:post, api_token_url)
32
+ .with(
33
+ body: {
34
+ client_id: client_id,
35
+ client_secret: client_secret,
36
+ grant_type: "refresh_token",
37
+ refresh_token: refresh_token,
38
+ },
39
+ headers: {
40
+ 'Content-Type' => 'application/x-www-form-urlencoded',
41
+ 'User-Agent' => "Cronofy Ruby #{Cronofy::VERSION}",
42
+ }
43
+ )
44
+ .to_return(
45
+ status: response_status,
46
+ body: {
47
+ access_token: new_access_token,
48
+ token_type: 'bearer',
49
+ expires_in: expires_in,
50
+ refresh_token: new_refresh_token,
51
+ scope: scope,
52
+ }.to_json,
53
+ headers: {
54
+ "Content-Type" => "application/json; charset=utf-8"
55
+ }
56
+ )
57
+
58
+ stub_request(:post, app_token_url)
59
+ .with(
60
+ body: {
61
+ client_id: client_id,
62
+ client_secret: client_secret,
63
+ code: code,
64
+ grant_type: "authorization_code",
65
+ redirect_uri: redirect_uri,
66
+ },
67
+ headers: {
68
+ 'Content-Type' => 'application/x-www-form-urlencoded',
69
+ 'User-Agent' => "Cronofy Ruby #{Cronofy::VERSION}",
70
+ }
71
+ )
72
+ .to_return(
73
+ status: response_status,
74
+ body: {
75
+ access_token: new_access_token,
76
+ token_type: 'bearer',
77
+ expires_in: expires_in,
78
+ refresh_token: new_refresh_token,
79
+ scope: scope,
80
+ account_id: account_id,
81
+ linking_profile: linking_profile_hash,
82
+ }.to_json,
83
+ headers: {
84
+ "Content-Type" => "application/json; charset=utf-8"
85
+ }
86
+ )
87
+ end
88
+
89
+ describe '#user_auth_link' do
90
+ let(:input_scope) { %w{read_events list_calendars create_event} }
91
+ let(:state) { nil }
92
+ let(:scheme) { 'https' }
93
+ let(:host) { 'app.cronofy.com' }
94
+ let(:path) { '/oauth/authorize' }
95
+ let(:data_center_override) { nil }
96
+ let(:default_params) do
97
+ {
98
+ 'client_id' => client_id,
99
+ 'redirect_uri' => redirect_uri,
100
+ 'response_type' => 'code',
101
+ 'scope' => scope,
102
+ }
103
+ end
104
+
105
+ let(:auth) do
106
+ Cronofy::Auth.new(
107
+ client_id: client_id,
108
+ client_secret: client_secret,
109
+ data_center: data_center_override,
110
+ )
111
+ end
112
+
113
+ subject do
114
+ url = auth.user_auth_link(redirect_uri, scope: input_scope, state: state)
115
+ URI.parse(url)
116
+ end
117
+
118
+ shared_examples 'a user auth link provider' do
119
+ it 'contains the correct scheme' do
120
+ expect(subject.scheme).to eq scheme
121
+ end
122
+
123
+ it 'contains the correct host' do
124
+ expect(subject.host).to eq host
125
+ end
126
+
127
+ it 'contains the correct path' do
128
+ expect(subject.path).to eq path
129
+ end
130
+
131
+ it 'contains the correct query params' do
132
+ expect(Rack::Utils.parse_query(subject.query)).to eq params
133
+ end
134
+ end
135
+
136
+ context 'when no state' do
137
+ let(:state) { nil }
138
+ let(:params) { default_params }
139
+
140
+ it_behaves_like 'a user auth link provider'
141
+ end
142
+
143
+ context 'when state is passed' do
144
+ let(:state) { SecureRandom.hex }
145
+ let(:params) do
146
+ default_params.merge('state' => state)
147
+ end
148
+
149
+ it_behaves_like 'a user auth link provider'
150
+ end
151
+
152
+ context 'when scope is a string' do
153
+ let(:input_scope) { scope }
154
+ let(:params) { default_params }
155
+
156
+ it_behaves_like 'a user auth link provider'
157
+ end
158
+
159
+ context 'when data center overridden' do
160
+ let(:data_center_override) { :de }
161
+ let(:host) { 'app-de.cronofy.com' }
162
+ let(:params) { default_params }
163
+
164
+ it_behaves_like 'a user auth link provider'
165
+ end
166
+ end
167
+
168
+ shared_examples 'an authorization request' do
169
+ context 'when succeeds' do
170
+ it 'returns a correct Credentials object' do
171
+ expect(subject.access_token).to eq new_access_token
172
+ expect(subject.expires_in).to eq expires_in
173
+ expect(subject.refresh_token).to eq new_refresh_token
174
+ expect(subject.scope).to eq scope
175
+ end
176
+ end
177
+
178
+ context 'when fails' do
179
+ context 'with 400' do
180
+ let(:response_status) { 400 }
181
+
182
+ it 'throws BadRequestError' do
183
+ expect{ subject }.to raise_error(Cronofy::BadRequestError)
184
+ end
185
+ end
186
+
187
+ context 'with unrecognized code' do
188
+ let(:response_status) { 418 }
189
+
190
+ it 'throws Unknown error' do
191
+ expect{ subject }.to raise_error(Cronofy::UnknownError)
192
+ end
193
+ end
194
+
195
+ context "client_id and client_secret not set" do
196
+ let(:client_id) { " " }
197
+ let(:client_secret) { " " }
198
+
199
+ it "throws a credentials missing error" do
200
+ expect { subject }.to raise_error(Cronofy::CredentialsMissingError, "OAuth client_id and client_secret must be set")
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ describe '#get_token_from_code' do
207
+ let(:data_center_override) { nil }
208
+
209
+ subject do
210
+ Cronofy::Auth.new(
211
+ client_id: client_id,
212
+ client_secret: client_secret,
213
+ data_center: data_center_override,
214
+ ).get_token_from_code(code, redirect_uri)
215
+ end
216
+
217
+ context "with account_id" do
218
+ let(:account_id) { "acc_0123456789abc" }
219
+
220
+ it 'exposes the account_id' do
221
+ expect(subject.account_id).to eq account_id
222
+ end
223
+
224
+ it "includes the account_id in its hash" do
225
+ expected = {
226
+ :access_token => subject.access_token,
227
+ :expires_at => subject.expires_at,
228
+ :expires_in => subject.expires_in,
229
+ :refresh_token => subject.refresh_token,
230
+ :scope => subject.scope,
231
+ :account_id => account_id,
232
+ }
233
+
234
+ expect(subject.to_h).to eq(expected)
235
+ end
236
+ end
237
+
238
+ describe '#application_calendar' do
239
+ let(:data_center_override) { nil }
240
+
241
+ let(:application_calendar_id) { "my-application-calendar" }
242
+ let(:cronofy_application_calendar_id) { "apc_54475485743" }
243
+
244
+ let(:application_calendar_url) { "https://api.cronofy.com/v1/application_calendars" }
245
+
246
+ before do
247
+ stub_request(:post, application_calendar_url)
248
+ .with(
249
+ body: {
250
+ client_id: client_id,
251
+ client_secret: client_secret,
252
+ application_calendar_id: application_calendar_id,
253
+ },
254
+ )
255
+ .to_return(
256
+ status: response_status,
257
+ body: {
258
+ access_token: new_access_token,
259
+ token_type: 'bearer',
260
+ expires_in: expires_in,
261
+ refresh_token: new_refresh_token,
262
+ scope: scope,
263
+ application_calendar_id: application_calendar_id,
264
+ sub: cronofy_application_calendar_id,
265
+ }.to_json,
266
+ headers: {
267
+ "Content-Type" => "application/json; charset=utf-8"
268
+ }
269
+ )
270
+ end
271
+
272
+ subject do
273
+ Cronofy::Auth.new(
274
+ client_id: client_id,
275
+ client_secret: client_secret,
276
+ data_center: data_center_override,
277
+ ).application_calendar(application_calendar_id)
278
+ end
279
+
280
+ it_behaves_like 'an authorization request'
281
+ end
282
+
283
+ context "with linking profile" do
284
+ let(:linking_profile_hash) do
285
+ {
286
+ provider_name: "google",
287
+ profile_id: "pro_VmrZnDitjScsAAAG",
288
+ profile_name: "bob@example.com",
289
+ }
290
+ end
291
+
292
+ it "exposes the linking profile" do
293
+ expected = Cronofy::Credentials::LinkingProfile.new(linking_profile_hash)
294
+ expect(subject.linking_profile).to eq expected
295
+ end
296
+
297
+ it "includes the linking profile in its hash" do
298
+ expected = {
299
+ :access_token => subject.access_token,
300
+ :expires_at => subject.expires_at,
301
+ :expires_in => subject.expires_in,
302
+ :refresh_token => subject.refresh_token,
303
+ :scope => subject.scope,
304
+ :linking_profile => linking_profile_hash,
305
+ }
306
+
307
+ expect(subject.to_h).to eq(expected)
308
+ end
309
+ end
310
+
311
+ context "with data center overridden" do
312
+ let(:data_center_override) { :de }
313
+ let(:api_token_url) { "https://api-de.cronofy.com/oauth/token" }
314
+ let(:app_token_url) { "https://app-de.cronofy.com/oauth/token" }
315
+
316
+ it_behaves_like 'an authorization request'
317
+ end
318
+
319
+ it_behaves_like 'an authorization request'
320
+ end
321
+
322
+ describe '#refresh!' do
323
+ context "access_token and refresh_token present" do
324
+ subject do
325
+ Cronofy::Auth.new(
326
+ client_id: client_id,
327
+ client_secret: client_secret,
328
+ access_token: access_token,
329
+ refresh_token: refresh_token,
330
+ ).refresh!
331
+ end
332
+
333
+ it_behaves_like 'an authorization request'
334
+ end
335
+
336
+ context "no refresh_token" do
337
+ subject do
338
+ Cronofy::Auth.new(
339
+ client_id: client_id,
340
+ client_secret: client_secret,
341
+ access_token: access_token,
342
+ ).refresh!
343
+ end
344
+
345
+ it "raises a credentials missing error" do
346
+ expect { subject }.to raise_error(Cronofy::CredentialsMissingError)
347
+ end
348
+ end
349
+
350
+ context "no access_token or refresh_token" do
351
+ subject do
352
+ Cronofy::Auth.new(
353
+ client_id: client_id,
354
+ client_secret: client_secret,
355
+ ).refresh!
356
+ end
357
+
358
+ it "raises a credentials missing error" do
359
+ expect { subject }.to raise_error(Cronofy::CredentialsMissingError)
360
+ end
361
+ end
362
+
363
+ context "only refresh_token" do
364
+ subject do
365
+ Cronofy::Auth.new(
366
+ client_id: client_id,
367
+ client_secret: client_secret,
368
+ refresh_token: refresh_token,
369
+ ).refresh!
370
+ end
371
+
372
+ it_behaves_like 'an authorization request'
373
+ end
374
+
375
+ context "with data center overridden" do
376
+ subject do
377
+ Cronofy::Auth.new(
378
+ client_id: client_id,
379
+ client_secret: client_secret,
380
+ access_token: access_token,
381
+ refresh_token: refresh_token,
382
+ data_center: :de,
383
+ ).refresh!
384
+ end
385
+
386
+ let(:api_token_url) { "https://api-de.cronofy.com/oauth/token" }
387
+ let(:app_token_url) { "https://app-de.cronofy.com/oauth/token" }
388
+
389
+ it_behaves_like 'an authorization request'
390
+ end
391
+ end
392
+
393
+ describe "#revoke!" do
394
+ shared_examples 'successful revocation' do
395
+ let!(:revocation_request) do
396
+ stub_request(:post, "https://api.cronofy.com/oauth/token/revoke")
397
+ .with(
398
+ body: {
399
+ client_id: client_id,
400
+ client_secret: client_secret,
401
+ token: revoke_token,
402
+ },
403
+ headers: {
404
+ 'Content-Type' => 'application/x-www-form-urlencoded',
405
+ 'User-Agent' => "Cronofy Ruby #{Cronofy::VERSION}",
406
+ }
407
+ )
408
+ .to_return(
409
+ status: response_status,
410
+ )
411
+ end
412
+
413
+ before do
414
+ auth.revoke!
415
+ end
416
+
417
+ it "unsets the access token" do
418
+ expect(auth.access_token).to be_nil
419
+ end
420
+
421
+ it "makes the revocation request" do
422
+ expect(revocation_request).to have_been_requested
423
+ end
424
+ end
425
+
426
+ context "access_token and refresh_token present" do
427
+ let(:auth) do
428
+ Cronofy::Auth.new(
429
+ client_id: client_id,
430
+ client_secret: client_secret,
431
+ access_token: access_token,
432
+ refresh_token: refresh_token,
433
+ )
434
+ end
435
+
436
+ let(:revoke_token) { refresh_token }
437
+
438
+ it_behaves_like 'successful revocation'
439
+ end
440
+
441
+ context "only refresh_token" do
442
+ let(:auth) do
443
+ Cronofy::Auth.new(
444
+ client_id: client_id,
445
+ client_secret: client_secret,
446
+ refresh_token: refresh_token,
447
+ )
448
+ end
449
+
450
+ let(:revoke_token) { refresh_token }
451
+
452
+ it_behaves_like 'successful revocation'
453
+ end
454
+
455
+ context "only access_token" do
456
+ let(:auth) do
457
+ Cronofy::Auth.new(
458
+ client_id: client_id,
459
+ client_secret: client_secret,
460
+ access_token: access_token,
461
+ )
462
+ end
463
+
464
+ let(:revoke_token) { access_token }
465
+
466
+ it_behaves_like 'successful revocation'
467
+ end
468
+
469
+ context "no access_token or refresh_token" do
470
+ let(:auth) do
471
+ Cronofy::Auth.new(client_id: client_id, client_secret: client_secret)
472
+ end
473
+
474
+ it "raises a credentials missing error" do
475
+ expect { auth.revoke! }.to raise_error(Cronofy::CredentialsMissingError)
476
+ end
477
+ end
478
+ end
479
+
480
+ describe "#revoke_by_sub" do
481
+ let(:auth) do
482
+ Cronofy::Auth.new(
483
+ client_id: client_id,
484
+ client_secret: client_secret,
485
+ access_token: access_token,
486
+ refresh_token: refresh_token,
487
+ )
488
+ end
489
+
490
+ let!(:revocation_request) do
491
+ stub_request(:post, "https://api.cronofy.com/oauth/token/revoke")
492
+ .with(
493
+ body: {
494
+ client_id: client_id,
495
+ client_secret: client_secret,
496
+ sub: sub,
497
+ },
498
+ headers: {
499
+ 'Content-Type' => 'application/x-www-form-urlencoded',
500
+ 'User-Agent' => "Cronofy Ruby #{Cronofy::VERSION}",
501
+ }
502
+ ).to_return(status: response_status)
503
+ end
504
+
505
+ let(:sub) { "random_sub_value" }
506
+
507
+ before do
508
+ auth.revoke_by_sub(sub)
509
+ end
510
+
511
+ it "does not unset the access token for the current auth" do
512
+ expect(auth.access_token).not_to be_nil
513
+ end
514
+
515
+ it "makes the revocation request" do
516
+ expect(revocation_request).to have_been_requested
517
+ end
518
+ end
519
+
520
+ describe "#revoke_by_token" do
521
+ let(:auth) do
522
+ Cronofy::Auth.new(
523
+ client_id: client_id,
524
+ client_secret: client_secret,
525
+ access_token: access_token,
526
+ refresh_token: refresh_token,
527
+ )
528
+ end
529
+
530
+ let!(:revocation_request) do
531
+ stub_request(:post, "https://api.cronofy.com/oauth/token/revoke")
532
+ .with(
533
+ body: {
534
+ client_id: client_id,
535
+ client_secret: client_secret,
536
+ token: token,
537
+ },
538
+ headers: {
539
+ 'Content-Type' => 'application/x-www-form-urlencoded',
540
+ 'User-Agent' => "Cronofy Ruby #{Cronofy::VERSION}",
541
+ }
542
+ ).to_return(status: response_status)
543
+ end
544
+
545
+ let(:token) { "random_token_value" }
546
+
547
+ before do
548
+ auth.revoke_by_token(token)
549
+ end
550
+
551
+ it "does not unset the access token for the current auth" do
552
+ expect(auth.access_token).not_to be_nil
553
+ end
554
+
555
+ it "makes the revocation request" do
556
+ expect(revocation_request).to have_been_requested
557
+ end
558
+ end
559
+ end