cronofy 0.0.5 → 0.37.7

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,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