stormpath-sdk 0.4.0 → 1.0.0.beta

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