stormpath-sdk 0.4.0 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +6 -0
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +27 -0
  5. data/CHANGES.md +21 -1
  6. data/Gemfile +1 -2
  7. data/README.md +457 -11
  8. data/Rakefile +15 -1
  9. data/lib/stormpath-sdk.rb +52 -33
  10. data/lib/stormpath-sdk/{resource/group_list.rb → api_key.rb} +5 -9
  11. data/lib/stormpath-sdk/auth/authentication_result.rb +3 -13
  12. data/lib/stormpath-sdk/auth/basic_authenticator.rb +5 -11
  13. data/lib/stormpath-sdk/auth/basic_login_attempt.rb +6 -8
  14. data/lib/stormpath-sdk/auth/username_password_request.rb +2 -5
  15. data/lib/stormpath-sdk/cache/cache.rb +54 -0
  16. data/lib/stormpath-sdk/cache/cache_entry.rb +33 -0
  17. data/lib/stormpath-sdk/cache/cache_manager.rb +22 -0
  18. data/lib/stormpath-sdk/cache/cache_stats.rb +35 -0
  19. data/lib/stormpath-sdk/cache/memory_store.rb +29 -0
  20. data/lib/stormpath-sdk/cache/redis_store.rb +32 -0
  21. data/lib/stormpath-sdk/client.rb +111 -0
  22. data/lib/stormpath-sdk/data_store.rb +241 -0
  23. data/lib/stormpath-sdk/{client/api_key.rb → error.rb} +16 -10
  24. data/lib/stormpath-sdk/{util → ext}/hash.rb +1 -2
  25. data/lib/stormpath-sdk/http/authc/sauthc1_signer.rb +8 -4
  26. data/lib/stormpath-sdk/http/http_client_request_executor.rb +8 -7
  27. data/lib/stormpath-sdk/http/request.rb +4 -8
  28. data/lib/stormpath-sdk/{util/request_utils.rb → http/utils.rb} +17 -38
  29. data/lib/stormpath-sdk/resource/account.rb +12 -108
  30. data/lib/stormpath-sdk/resource/application.rb +35 -171
  31. data/lib/stormpath-sdk/resource/associations.rb +97 -0
  32. data/lib/stormpath-sdk/resource/base.rb +256 -0
  33. data/lib/stormpath-sdk/resource/collection.rb +94 -0
  34. data/lib/stormpath-sdk/resource/directory.rb +11 -68
  35. data/lib/stormpath-sdk/resource/email_verification_token.rb +3 -9
  36. data/lib/stormpath-sdk/resource/error.rb +4 -38
  37. data/lib/stormpath-sdk/resource/expansion.rb +28 -0
  38. data/lib/stormpath-sdk/resource/group.rb +8 -66
  39. data/lib/stormpath-sdk/resource/group_membership.rb +4 -55
  40. data/lib/stormpath-sdk/resource/{application_list.rb → instance.rb} +7 -13
  41. data/lib/stormpath-sdk/resource/password_reset_token.rb +5 -23
  42. data/lib/stormpath-sdk/resource/status.rb +22 -28
  43. data/lib/stormpath-sdk/resource/tenant.rb +5 -52
  44. data/lib/stormpath-sdk/resource/utils.rb +43 -13
  45. data/lib/stormpath-sdk/util/assert.rb +5 -15
  46. data/lib/stormpath-sdk/version.rb +3 -3
  47. data/spec/api_key_spec.rb +19 -0
  48. data/spec/auth/basic_authenticator_spec.rb +25 -0
  49. data/spec/auth/sauthc1_signer_spec.rb +42 -0
  50. data/spec/cache/cache_entry_spec.rb +157 -0
  51. data/spec/cache/cache_spec.rb +89 -0
  52. data/spec/cache/cache_stats_spec.rb +106 -0
  53. data/spec/client_spec.rb +538 -0
  54. data/spec/data_store_spec.rb +130 -0
  55. data/spec/resource/account_spec.rb +74 -0
  56. data/spec/resource/application_spec.rb +148 -0
  57. data/spec/resource/base_spec.rb +114 -0
  58. data/spec/resource/collection_spec.rb +169 -0
  59. data/spec/resource/directory_spec.rb +30 -0
  60. data/spec/resource/expansion_spec.rb +100 -0
  61. data/spec/resource/group_spec.rb +49 -0
  62. data/spec/spec_helper.rb +135 -0
  63. data/spec/support/resource_factory.rb +48 -0
  64. data/spec/support/resource_matchers.rb +27 -0
  65. data/spec/support/test_cache_stores.rb +9 -0
  66. data/spec/support/test_request_executor.rb +11 -0
  67. data/stormpath-sdk.gemspec +14 -4
  68. data/support/api.rb +55 -0
  69. metadata +214 -44
  70. data/lib/stormpath-sdk/client/client.rb +0 -38
  71. data/lib/stormpath-sdk/client/client_application.rb +0 -38
  72. data/lib/stormpath-sdk/client/client_application_builder.rb +0 -351
  73. data/lib/stormpath-sdk/client/client_builder.rb +0 -305
  74. data/lib/stormpath-sdk/ds/data_store.rb +0 -210
  75. data/lib/stormpath-sdk/ds/resource_factory.rb +0 -37
  76. data/lib/stormpath-sdk/resource/account_list.rb +0 -32
  77. data/lib/stormpath-sdk/resource/collection_resource.rb +0 -91
  78. data/lib/stormpath-sdk/resource/directory_list.rb +0 -30
  79. data/lib/stormpath-sdk/resource/group_membership_list.rb +0 -32
  80. data/lib/stormpath-sdk/resource/instance_resource.rb +0 -28
  81. data/lib/stormpath-sdk/resource/resource.rb +0 -327
  82. data/lib/stormpath-sdk/resource/resource_error.rb +0 -47
  83. data/test/client/client.yml +0 -16
  84. data/test/client/client_application_builder_spec.rb +0 -114
  85. data/test/client/client_builder_spec.rb +0 -176
  86. data/test/client/read_spec.rb +0 -254
  87. data/test/client/write_spec.rb +0 -420
  88. data/test/resource/resource_spec.rb +0 -41
  89. data/test/resource/test_resource.rb +0 -28
@@ -0,0 +1,538 @@
1
+ require 'spec_helper'
2
+ require 'pp'
3
+
4
+ describe Stormpath::Client, :vcr do
5
+ describe '.new' do
6
+ shared_examples 'a valid client' do
7
+ it 'can connect successfully' do
8
+ expect(client).to be
9
+ expect(client).to be_kind_of Stormpath::Client
10
+ expect(client.tenant).to be
11
+ expect(client.tenant).to be_kind_of Stormpath::Resource::Tenant
12
+ end
13
+ end
14
+
15
+ context 'given a hash' do
16
+ context 'with an api key file location', 'that points to a remote file' do
17
+ let(:api_key_file_location) { 'http://fake.server.com/apiKey.properties' }
18
+ let(:client) { Stormpath::Client.new(api_key_file_location: api_key_file_location) }
19
+
20
+ before do
21
+ stub_request(:any, api_key_file_location).to_return(body:<<properties
22
+ apiKey.id=#{test_api_key_id}
23
+ apiKey.secret=#{test_api_key_secret}
24
+ properties
25
+ )
26
+ end
27
+
28
+ it_behaves_like 'a valid client'
29
+ end
30
+
31
+ context 'with an api key file location', 'that points to a file' do
32
+ after do
33
+ File.delete(api_key_file_location) if File.exists?(api_key_file_location)
34
+ end
35
+
36
+ context 'by default' do
37
+ let(:api_key_file_location) do
38
+ File.join(File.dirname(__FILE__), 'foo.properties')
39
+ end
40
+ let(:client) { Stormpath::Client.new(api_key_file_location: api_key_file_location) }
41
+
42
+ before do
43
+ File.open(api_key_file_location, 'w') do |f|
44
+ f.write <<properties
45
+ apiKey.id=#{test_api_key_id}
46
+ apiKey.secret=#{test_api_key_secret}
47
+ properties
48
+ end
49
+ end
50
+
51
+ it_behaves_like 'a valid client'
52
+ end
53
+
54
+ context 'and with an api id property name' do
55
+ let(:api_key_file_location) do
56
+ File.join(File.dirname(__FILE__), 'testApiKey.fooId.properties')
57
+ end
58
+ let(:client) do
59
+ Stormpath::Client.new({
60
+ api_key_file_location: api_key_file_location,
61
+ api_key_id_property_name: 'foo.id'
62
+ })
63
+ end
64
+
65
+ before do
66
+ File.open(api_key_file_location, 'w') do |f|
67
+ f.write <<properties
68
+ foo.id=#{test_api_key_id}
69
+ apiKey.secret=#{test_api_key_secret}
70
+ properties
71
+ end
72
+ end
73
+
74
+ it_behaves_like 'a valid client'
75
+ end
76
+
77
+ context 'and with an api secret property name' do
78
+ let(:api_key_file_location) do
79
+ File.join(File.dirname(__FILE__), 'testApiKey.barBazSecret.properties')
80
+ end
81
+ let(:client) do
82
+ Stormpath::Client.new({
83
+ api_key_file_location: api_key_file_location,
84
+ api_key_secret_property_name: 'bar.baz'
85
+ })
86
+ end
87
+
88
+ before do
89
+ File.open(api_key_file_location, 'w') do |f|
90
+ f.write <<properties
91
+ apiKey.id=#{test_api_key_id}
92
+ bar.baz=#{test_api_key_secret}
93
+ properties
94
+ end
95
+ end
96
+
97
+ it_behaves_like 'a valid client'
98
+ end
99
+
100
+ context 'but there is no api key id property' do
101
+ let(:api_key_file_location) do
102
+ File.join(File.dirname(__FILE__), 'testApiKey.noApiKeyId.properties')
103
+ end
104
+ let(:client) do
105
+ Stormpath::Client.new({
106
+ api_key_file_location: api_key_file_location,
107
+ })
108
+ end
109
+
110
+ before do
111
+ File.open(api_key_file_location, 'w') do |f|
112
+ f.write <<properties
113
+ foo.id=#{test_api_key_id}
114
+ apiKey.secret=#{test_api_key_secret}
115
+ properties
116
+ end
117
+ end
118
+
119
+ it 'raises an error' do
120
+ expect { client }.to raise_error ArgumentError,
121
+ "No API id in properties. Please provide a 'apiKey.id' property in '" +
122
+ api_key_file_location +
123
+ "' or pass in an 'api_key_id_property_name' to the Stormpath::Client " +
124
+ "constructor to specify an alternative property."
125
+ end
126
+ end
127
+
128
+ context 'but there is no api key secret property' do
129
+ let(:api_key_file_location) do
130
+ File.join(File.dirname(__FILE__), 'testApiKey.noApiKeySecret.properties')
131
+ end
132
+ let(:client) do
133
+ Stormpath::Client.new({
134
+ api_key_file_location: api_key_file_location,
135
+ })
136
+ end
137
+
138
+ before do
139
+ File.open(api_key_file_location, 'w') do |f|
140
+ f.write <<properties
141
+ apiKey.id=#{test_api_key_id}
142
+ properties
143
+ end
144
+ end
145
+
146
+ it 'raises an error' do
147
+ expect { client }.to raise_error ArgumentError,
148
+ "No API secret in properties. Please provide a 'apiKey.secret' property in '" +
149
+ api_key_file_location +
150
+ "' or pass in an 'api_key_secret_property_name' to the Stormpath::Client " +
151
+ "constructor to specify an alternative property."
152
+ end
153
+ end
154
+
155
+ context 'but there was a problem reading the file' do
156
+ let(:api_key_file_location) do
157
+ 'no_such_file'
158
+ end
159
+ let(:client) do
160
+ Stormpath::Client.new({
161
+ api_key_file_location: api_key_file_location,
162
+ })
163
+ end
164
+
165
+ it 'raises an error' do
166
+ expect { client }.to raise_error ArgumentError,
167
+ "No API Key file could be found or loaded from '" +
168
+ api_key_file_location +
169
+ "'."
170
+ end
171
+ end
172
+ end
173
+
174
+ context 'with a base url' do
175
+ it 'creates a client that connects to that base'
176
+ end
177
+
178
+ context 'with an api key' do
179
+ context 'as a Stormpath::ApiKey' do
180
+ let(:api_key) { Stormpath::ApiKey.new(test_api_key_id, test_api_key_secret) }
181
+ let(:client) { Stormpath::Client.new(api_key: api_key) }
182
+
183
+ it_behaves_like 'a valid client'
184
+ end
185
+
186
+ context 'as a hash' do
187
+ let(:client) do
188
+ Stormpath::Client.new({
189
+ api_key: { id: test_api_key_id,
190
+ secret: test_api_key_secret
191
+ }
192
+ })
193
+ end
194
+
195
+ it_behaves_like 'a valid client'
196
+ end
197
+ end
198
+
199
+ context 'with no api key', 'and no api key file location' do
200
+ it 'raises an error' do
201
+ expect { Stormpath::Client.new({}) }.to raise_error ArgumentError,
202
+ /^No API key has been provided\./
203
+ end
204
+ end
205
+
206
+ context 'with cache configuration' do
207
+ let(:api_key_file_location) { 'http://fake.server.com/apiKey.properties' }
208
+ let(:client) do
209
+ Stormpath::Client.new( {
210
+ api_key_file_location: api_key_file_location,
211
+ cache: {
212
+ store: Stormpath::Test::FakeStore1,
213
+ regions: {
214
+ directories: { ttl_seconds: 40, tti_seconds: 20 },
215
+ groups: { ttl_seconds: 80, tti_seconds: 40, store: Stormpath::Test::FakeStore2 }
216
+ }
217
+ }
218
+ })
219
+ end
220
+
221
+ before do
222
+ stub_request(:any, api_key_file_location).to_return(body:<<properties
223
+ apiKey.id=#{test_api_key_id}
224
+ apiKey.secret=#{test_api_key_secret}
225
+ properties
226
+ )
227
+ data_store = client.instance_variable_get '@data_store'
228
+ cache_manager = data_store.cache_manager
229
+ @directories_cache = cache_manager.get_cache 'directories'
230
+ @groups_cache = cache_manager.get_cache 'groups'
231
+ end
232
+
233
+ it 'passes those params down to the caches' do
234
+ expect(@directories_cache.instance_variable_get('@ttl_seconds')).to eq(40)
235
+ expect(@directories_cache.instance_variable_get('@tti_seconds')).to eq(20)
236
+ expect(@directories_cache.instance_variable_get('@store')).to be_a(Stormpath::Test::FakeStore1)
237
+ expect(@groups_cache.instance_variable_get('@ttl_seconds')).to eq(80)
238
+ expect(@groups_cache.instance_variable_get('@tti_seconds')).to eq(40)
239
+ expect(@groups_cache.instance_variable_get('@store')).to be_a(Stormpath::Test::FakeStore2)
240
+ end
241
+ end
242
+ end
243
+
244
+ context 'with an http proxy specified' do
245
+ let(:http_proxy) do
246
+ 'http://exampleproxy.com:8080'
247
+ end
248
+
249
+ let(:request_executor) do
250
+ Stormpath::Test::TestRequestExecutor.new
251
+ end
252
+
253
+ let(:api_key) do
254
+ Stormpath::ApiKey.new test_api_key_id, test_api_key_secret
255
+ end
256
+
257
+ it 'initializes the request executor with the proxy' do
258
+ expect(Stormpath::Http::HttpClientRequestExecutor)
259
+ .to receive(:new)
260
+ .with(api_key, proxy: http_proxy)
261
+ .and_return request_executor
262
+
263
+ Stormpath::Client.new api_key: api_key, proxy: http_proxy
264
+ end
265
+ end
266
+ end
267
+
268
+ describe '#applications' do
269
+ context 'by default' do
270
+ let(:applications) do
271
+ test_api_client.applications
272
+ end
273
+
274
+ let(:application) do
275
+ applications.create(
276
+ name: 'Client Applications Test',
277
+ description: 'A test description'
278
+ )
279
+ end
280
+
281
+ it 'returns the collection' do
282
+ expect(applications).to be_kind_of(Stormpath::Resource::Collection)
283
+ expect(applications).to have_at_least(1).item
284
+ end
285
+
286
+ after do
287
+ application.delete
288
+ end
289
+ end
290
+
291
+ context 'pagination' do
292
+ let!(:applications) do
293
+ (0..2).to_a.map do |index|
294
+ test_api_client.applications.create name: "Pagination Test #{index + 1}", description: 'foo'
295
+ end
296
+ end
297
+
298
+ it 'accepts offset and limit' do
299
+ expect(test_api_client.applications.limit(2)).to have(2).items
300
+ expect(test_api_client.applications.offset(1).limit(2)).to have_at_least(2).items
301
+ end
302
+
303
+ after do
304
+ applications.each do |application|
305
+ application.delete
306
+ end
307
+ end
308
+ end
309
+
310
+ context 'expansion' do
311
+ let(:client) do
312
+ # every time a client is instantiated a new cache is created, so make
313
+ # sure we use the same client across each "it" block
314
+ test_api_client
315
+ end
316
+
317
+ let(:cache_manager) do
318
+ data_store = client.instance_variable_get '@data_store'
319
+ cache_manager = data_store.cache_manager
320
+ end
321
+
322
+ let(:accounts_cache_summary) do
323
+ cache_manager.get_cache('accounts').stats.summary
324
+ end
325
+
326
+ let(:directories_cache_summary) do
327
+ cache_manager.get_cache('directories').stats.summary
328
+ end
329
+
330
+ let(:groups_cache_summary) do
331
+ cache_manager.get_cache('groups').stats.summary
332
+ end
333
+
334
+ let(:directory) do
335
+ client.directories.create name: 'testDirectory'
336
+ end
337
+
338
+ let(:group) do
339
+ directory.groups.create name: 'someGroup'
340
+ end
341
+
342
+ let(:account) do
343
+ directory.accounts.create({
344
+ email: 'rubysdk@example.com',
345
+ given_name: 'Ruby SDK',
346
+ password: 'P@$$w0rd',
347
+ surname: 'SDK',
348
+ username: 'rubysdk'
349
+ })
350
+ end
351
+
352
+ before do
353
+ group.add_account account
354
+ end
355
+
356
+ after do
357
+ group.delete if group
358
+ directory.delete if directory
359
+ account.delete if account
360
+ end
361
+
362
+ context 'expanding a nested single resource' do
363
+ let(:cached_account) do
364
+ client.accounts.get account.href, Stormpath::Resource::Expansion.new('directory')
365
+ end
366
+
367
+ before do
368
+ client.data_store.initialize_cache(Hash.new)
369
+ end
370
+
371
+ it 'caches the nested resource' do
372
+ expect(cached_account.directory.name).to be
373
+ expect(directories_cache_summary).to eq [1, 1, 0, 0, 1]
374
+ end
375
+ end
376
+
377
+ context 'expanding a nested collection resource' do
378
+ let(:cached_account) do
379
+ client.accounts.get account.href, Stormpath::Resource::Expansion.new('groups')
380
+ end
381
+
382
+ let(:group) do
383
+ directory.groups.create name: 'someGroup'
384
+ end
385
+
386
+ before do
387
+ client.data_store.initialize_cache(Hash.new)
388
+ end
389
+
390
+ it 'caches the nested resource' do
391
+ expect(cached_account.groups.first.name).to eq(group.name)
392
+ expect(groups_cache_summary).to eq [2, 1, 0, 0, 2]
393
+ end
394
+ end
395
+
396
+ end
397
+
398
+ context 'search' do
399
+ let!(:applications) do
400
+ [
401
+ test_api_client.applications.create(name: 'Test Alpha', description: 'foo'),
402
+ test_api_client.applications.create(name: 'Test Beta', description: 'foo')
403
+ ]
404
+ end
405
+
406
+ context 'by any attribute' do
407
+ let(:search_results) do
408
+ test_api_client.applications.search('Test Alpha')
409
+ end
410
+
411
+ it 'returns the application' do
412
+ expect(search_results.count).to eq 1
413
+ end
414
+ end
415
+
416
+ context 'by an explicit attribute' do
417
+ let(:search_results) do
418
+ test_api_client.applications.search(name: 'Test Alpha')
419
+ end
420
+
421
+ it 'returns the application' do
422
+ expect(search_results.count).to eq 1
423
+ end
424
+ end
425
+
426
+ after do
427
+ applications.each do |application|
428
+ application.delete
429
+ end
430
+ end
431
+ end
432
+
433
+ describe '.create' do
434
+ let(:application_attributes) do
435
+ {
436
+ name: 'Client Application Create Test',
437
+ description: 'A test description'
438
+ }
439
+ end
440
+
441
+ let(:application) do
442
+ test_api_client.applications.create application_attributes
443
+ end
444
+
445
+ it 'creates that application' do
446
+ expect(application).to be
447
+ expect(application.name).to eq(application_attributes[:name])
448
+ expect(application.description).to eq(application_attributes[:description])
449
+ end
450
+
451
+ after do
452
+ application.delete
453
+ end
454
+ end
455
+ end
456
+
457
+ describe '#directories' do
458
+ context 'given a collection' do
459
+ let(:directories) do
460
+ test_api_client.directories
461
+ end
462
+
463
+ let(:directory) do
464
+ directories.create(
465
+ name: 'Client Directories Test',
466
+ description: 'A test description'
467
+ )
468
+ end
469
+
470
+ it 'returns the collection' do
471
+ expect(directories).to be_kind_of(Stormpath::Resource::Collection)
472
+ expect(directories).to have_at_least(1).item
473
+ end
474
+
475
+ after do
476
+ directory.delete
477
+ end
478
+ end
479
+
480
+ describe '.create' do
481
+ let(:directory_attributes) do
482
+ {
483
+ name: 'Client Directory Create Test',
484
+ description: 'A test description'
485
+ }
486
+ end
487
+
488
+ let(:directory) do
489
+ test_api_client.directories.create directory_attributes
490
+ end
491
+
492
+ it 'creates that application' do
493
+ expect(directory).to be
494
+ expect(directory.name).to eq(directory_attributes[:name])
495
+ expect(directory.description).to eq(directory_attributes[:description])
496
+ end
497
+
498
+ after do
499
+ directory.delete
500
+ end
501
+ end
502
+ end
503
+
504
+ describe '#accounts.verify_account_email' do
505
+ context 'given a verfication token of an account' do
506
+ let(:directory) { test_directory_with_verification }
507
+
508
+ let(:account) do
509
+ account = Stormpath::Resource::Account.new({
510
+ email: "test@example.com",
511
+ givenName: 'Ruby SDK',
512
+ password: 'P@$$w0rd',
513
+ surname: 'SDK',
514
+ username: "testusername"
515
+ })
516
+ directory.create_account account
517
+ end
518
+
519
+ let(:verification_token) do
520
+ account.email_verification_token.token
521
+ end
522
+
523
+ let(:verified_account) do
524
+ test_api_client.accounts.verify_email_token verification_token
525
+ end
526
+
527
+ after do
528
+ account.delete if account
529
+ end
530
+
531
+ it 'returns the account' do
532
+ expect(verified_account).to be
533
+ expect(verified_account).to be_kind_of Stormpath::Resource::Account
534
+ expect(verified_account.username).to eq(account.username)
535
+ end
536
+ end
537
+ end
538
+ end