tentd 0.0.1

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 (101) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +9 -0
  5. data/Guardfile +6 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +49 -0
  8. data/Rakefile +8 -0
  9. data/bin/tent-server +3 -0
  10. data/lib/tentd.rb +31 -0
  11. data/lib/tentd/api.rb +58 -0
  12. data/lib/tentd/api/apps.rb +196 -0
  13. data/lib/tentd/api/authentication_finalize.rb +12 -0
  14. data/lib/tentd/api/authentication_lookup.rb +27 -0
  15. data/lib/tentd/api/authentication_verification.rb +50 -0
  16. data/lib/tentd/api/authorizable.rb +21 -0
  17. data/lib/tentd/api/authorization.rb +14 -0
  18. data/lib/tentd/api/core_profile_data.rb +45 -0
  19. data/lib/tentd/api/followers.rb +218 -0
  20. data/lib/tentd/api/followings.rb +241 -0
  21. data/lib/tentd/api/groups.rb +161 -0
  22. data/lib/tentd/api/middleware.rb +32 -0
  23. data/lib/tentd/api/posts.rb +373 -0
  24. data/lib/tentd/api/profile.rb +78 -0
  25. data/lib/tentd/api/router.rb +123 -0
  26. data/lib/tentd/api/router/caching_headers.rb +49 -0
  27. data/lib/tentd/api/router/extract_params.rb +88 -0
  28. data/lib/tentd/api/router/serialize_response.rb +38 -0
  29. data/lib/tentd/api/user_lookup.rb +10 -0
  30. data/lib/tentd/core_ext/hash/slice.rb +29 -0
  31. data/lib/tentd/datamapper/array_property.rb +23 -0
  32. data/lib/tentd/datamapper/query.rb +19 -0
  33. data/lib/tentd/json_patch.rb +181 -0
  34. data/lib/tentd/model.rb +30 -0
  35. data/lib/tentd/model/app.rb +68 -0
  36. data/lib/tentd/model/app_authorization.rb +113 -0
  37. data/lib/tentd/model/follower.rb +105 -0
  38. data/lib/tentd/model/following.rb +100 -0
  39. data/lib/tentd/model/group.rb +24 -0
  40. data/lib/tentd/model/mention.rb +19 -0
  41. data/lib/tentd/model/notification_subscription.rb +56 -0
  42. data/lib/tentd/model/permissible.rb +227 -0
  43. data/lib/tentd/model/permission.rb +28 -0
  44. data/lib/tentd/model/post.rb +178 -0
  45. data/lib/tentd/model/post_attachment.rb +27 -0
  46. data/lib/tentd/model/post_version.rb +64 -0
  47. data/lib/tentd/model/profile_info.rb +80 -0
  48. data/lib/tentd/model/random_public_id.rb +46 -0
  49. data/lib/tentd/model/serializable.rb +58 -0
  50. data/lib/tentd/model/type_properties.rb +36 -0
  51. data/lib/tentd/model/user.rb +39 -0
  52. data/lib/tentd/model/user_scoped.rb +14 -0
  53. data/lib/tentd/notifications.rb +13 -0
  54. data/lib/tentd/notifications/girl_friday.rb +30 -0
  55. data/lib/tentd/notifications/sidekiq.rb +50 -0
  56. data/lib/tentd/tent_type.rb +20 -0
  57. data/lib/tentd/tent_version.rb +41 -0
  58. data/lib/tentd/version.rb +3 -0
  59. data/spec/fabricators/app_authorizations_fabricator.rb +5 -0
  60. data/spec/fabricators/apps_fabricator.rb +11 -0
  61. data/spec/fabricators/followers_fabricator.rb +14 -0
  62. data/spec/fabricators/followings_fabricator.rb +17 -0
  63. data/spec/fabricators/groups_fabricator.rb +3 -0
  64. data/spec/fabricators/mentions_fabricator.rb +3 -0
  65. data/spec/fabricators/notification_subscriptions_fabricator.rb +4 -0
  66. data/spec/fabricators/permissions_fabricator.rb +1 -0
  67. data/spec/fabricators/post_attachments_fabricator.rb +8 -0
  68. data/spec/fabricators/post_versions_fabricator.rb +12 -0
  69. data/spec/fabricators/posts_fabricator.rb +12 -0
  70. data/spec/fabricators/profile_infos_fabricator.rb +30 -0
  71. data/spec/integration/api/apps_spec.rb +466 -0
  72. data/spec/integration/api/followers_spec.rb +535 -0
  73. data/spec/integration/api/followings_spec.rb +688 -0
  74. data/spec/integration/api/groups_spec.rb +207 -0
  75. data/spec/integration/api/posts_spec.rb +874 -0
  76. data/spec/integration/api/profile_spec.rb +285 -0
  77. data/spec/integration/api/router_spec.rb +102 -0
  78. data/spec/integration/model/app_authorization_spec.rb +59 -0
  79. data/spec/integration/model/app_spec.rb +63 -0
  80. data/spec/integration/model/follower_spec.rb +344 -0
  81. data/spec/integration/model/following_spec.rb +97 -0
  82. data/spec/integration/model/group_spec.rb +39 -0
  83. data/spec/integration/model/notification_subscription_spec.rb +145 -0
  84. data/spec/integration/model/post_spec.rb +658 -0
  85. data/spec/spec_helper.rb +37 -0
  86. data/spec/support/expect_server.rb +3 -0
  87. data/spec/support/json_request.rb +54 -0
  88. data/spec/support/with_constant.rb +23 -0
  89. data/spec/support/with_warnings.rb +6 -0
  90. data/spec/unit/api/authentication_finalize_spec.rb +45 -0
  91. data/spec/unit/api/authentication_lookup_spec.rb +65 -0
  92. data/spec/unit/api/authentication_verification_spec.rb +50 -0
  93. data/spec/unit/api/authorizable_spec.rb +50 -0
  94. data/spec/unit/api/authorization_spec.rb +44 -0
  95. data/spec/unit/api/caching_headers_spec.rb +121 -0
  96. data/spec/unit/core_profile_data_spec.rb +64 -0
  97. data/spec/unit/json_patch_spec.rb +407 -0
  98. data/spec/unit/tent_type_spec.rb +28 -0
  99. data/spec/unit/tent_version_spec.rb +68 -0
  100. data/tentd.gemspec +36 -0
  101. metadata +435 -0
@@ -0,0 +1,535 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::Followers do
4
+ def app
5
+ TentD::API.new
6
+ end
7
+
8
+ def link_header(entity_url)
9
+ %(<#{entity_url}/tent/profile>; rel="#{TentD::API::PROFILE_REL}")
10
+ end
11
+
12
+ def tent_profile(entity_url)
13
+ %({"https://tent.io/types/info/core/v0.1.0":{"licenses":["http://creativecommons.org/licenses/by/3.0/"],"entity":"#{entity_url}","servers":["#{entity_url}/tent"]}})
14
+ end
15
+
16
+ def authorize!(*scopes)
17
+ env['current_auth'] = stub(
18
+ :kind_of? => true,
19
+ :id => nil,
20
+ :scopes => scopes
21
+ )
22
+ end
23
+
24
+ let(:env) { Hash.new }
25
+ let(:params) { Hash.new }
26
+
27
+ let(:http_stubs) { Faraday::Adapter::Test::Stubs.new }
28
+ let(:follower) { Fabricate(:follower) }
29
+ let(:follower_entity_url) { "https://alex.example.org" }
30
+
31
+ let(:notification_subscription) do
32
+ Fabricate(:notification_subscription,
33
+ :type => 'all',
34
+ :app_authorization => Fabricate(:app_authorization,
35
+ :scopes => [:read_posts],
36
+ :post_types => ['all'],
37
+ :app => Fabricate(:app)))
38
+ end
39
+
40
+ def stub_challenge!
41
+ http_stubs.get('/tent/notifications/asdf') { |env|
42
+ [200, {}, env[:params]['challenge']]
43
+ }
44
+ end
45
+
46
+ describe 'POST /followers' do
47
+ before { env['tent.entity'] = 'https://smith.example.com' }
48
+ let(:follower_data) do
49
+ {
50
+ "entity" => follower_entity_url,
51
+ "licenses" => ["http://creativecommons.org/licenses/by-nc-sa/3.0/"],
52
+ "types" => ["https://tent.io/type/posts/status/v0.1.x#full", "https://tent.io/types/post/photo/v0.1.x#meta"],
53
+ "notification_path" => "notifications/asdf"
54
+ }
55
+ end
56
+
57
+ it 'should perform discovery' do
58
+ http_stubs.head('/') { [200, { 'Link' => link_header(follower_entity_url) }, ''] }
59
+ http_stubs.get('/tent/profile') {
60
+ [200, { 'Content-Type' => TentD::API::MEDIA_TYPE }, tent_profile(follower_entity_url)]
61
+ }
62
+ stub_challenge!
63
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
64
+
65
+ json_post '/followers', follower_data, env
66
+ http_stubs.verify_stubbed_calls
67
+ end
68
+
69
+ it 'should error if discovery fails' do
70
+ http_stubs.head('/') { [404, {}, 'Not Found'] }
71
+ http_stubs.get('/') { [404, {}, 'Not Found'] }
72
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
73
+
74
+ json_post '/followers', follower_data, env
75
+ expect(last_response.status).to eq(404)
76
+ end
77
+
78
+ it 'should fail if entity identifiers do not match' do
79
+ http_stubs.head('/') { [200, { 'Link' => link_header(follower_entity_url) }, ''] }
80
+ http_stubs.get('/tent/profile') {
81
+ [200, { 'Content-Type' => TentD::API::MEDIA_TYPE }, tent_profile('https://otherentity.example.com')]
82
+ }
83
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
84
+
85
+ json_post '/followers', follower_data, env
86
+ expect(last_response.status).to eq(409)
87
+ end
88
+
89
+ it 'should fail if entity is self' do
90
+ user = TentD::Model::User.first_or_create
91
+ info = TentD::Model::ProfileInfo.first_or_create(:type_base => TentD::Model::ProfileInfo::TENT_PROFILE_TYPE.base, :type_version => TentD::Model::ProfileInfo::TENT_PROFILE_TYPE.version, :user_id => user.id)
92
+ info.update(:content => { :entity => follower_entity_url })
93
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
94
+ expect(lambda {
95
+ json_post '/followers', follower_data, env
96
+ expect(last_response.status).to eq(406)
97
+ }).to_not change(TentD::Model::Follower, :count)
98
+ info.destroy
99
+ user.destroy
100
+ end
101
+
102
+ context 'when discovery success' do
103
+ before do
104
+ http_stubs.head('/') { [200, { 'Link' => link_header(follower_entity_url) }, ''] }
105
+ http_stubs.get('/tent/profile') {
106
+ [200, { 'Content-Type' => TentD::API::MEDIA_TYPE }, tent_profile(follower_entity_url)]
107
+ }
108
+ stub_challenge!
109
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
110
+ end
111
+
112
+ it 'should create follower db record and respond with hmac secret' do
113
+ expect(lambda { json_post '/followers', follower_data, env }).
114
+ to change(TentD::Model::Follower, :count).by(1)
115
+ expect(last_response.status).to eq(200)
116
+ follow = TentD::Model::Follower.last
117
+ body = JSON.parse(last_response.body)
118
+ expect(body['id']).to eq(follow.public_id)
119
+ %w{ mac_key_id mac_key mac_algorithm }.each { |key|
120
+ expect(body[key]).to eq(follow.send(key))
121
+ }
122
+ end
123
+
124
+ it 'should create post (notification)' do
125
+ expect(lambda {
126
+ json_post '/followers', follower_data, env
127
+ expect(last_response.status).to eq(200)
128
+ }).to change(TentD::Model::Post, :count).by(1)
129
+
130
+ post = TentD::Model::Post.last
131
+ expect(post.type.base).to eq('https://tent.io/types/post/follower')
132
+ expect(post.type.version).to eq('0.1.0')
133
+ expect(post.content['action']).to eq('create')
134
+ end
135
+
136
+ it 'should create notification subscription for each type given' do
137
+ expect(lambda { json_post '/followers', follower_data, env }).
138
+ to change(TentD::Model::NotificationSubscription, :count).by(2)
139
+ expect(last_response.status).to eq(200)
140
+ expect(TentD::Model::NotificationSubscription.last.type_view).to eq('meta')
141
+ end
142
+ end
143
+ end
144
+
145
+ describe 'POST /followers with write_followers scope authorized' do
146
+ before {
147
+ authorize!(:write_followers)
148
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
149
+ }
150
+
151
+ let(:follower_data) do
152
+ follower = Fabricate.build(:follower)
153
+ {
154
+ "entity" => follower_entity_url,
155
+ "groups" => follower.groups,
156
+ "profile" => { "info_type_uri" => { "bacon" => "chunky" } },
157
+ "notification_path" => follower.notification_path,
158
+ "licenses" => follower.licenses,
159
+ "mac_key_id" => follower.mac_key_id,
160
+ "mac_key" => follower.mac_key,
161
+ "mac_algorithm" => follower.mac_algorithm,
162
+ "mac_timestamp_delta" => follower.mac_timestamp_delta,
163
+ "types" => ["https://tent.io/types/post/status/v0.1.x#full", "https://tent.io/types/post/photo/v0.1.x#meta"]
164
+ }
165
+ end
166
+
167
+ context 'when write_secrets scope authorized' do
168
+ before { authorize!(:write_followers, :write_secrets) }
169
+
170
+ it 'should create follower without discovery' do
171
+ expect(lambda {
172
+ json_post '/followers', follower_data, env
173
+ expect(last_response.status).to eq(200)
174
+ }).to change(TentD::Model::Follower, :count).by(1)
175
+ end
176
+
177
+ it 'should create notification subscription for each type given' do
178
+ expect(lambda {
179
+ json_post '/followers', follower_data, env
180
+ expect(last_response.status).to eq(200)
181
+ }).to change(TentD::Model::NotificationSubscription, :count).by(2)
182
+ expect(TentD::Model::NotificationSubscription.last.type_view).to eq('meta')
183
+ end
184
+ end
185
+
186
+ context 'when write_secrets scope not authorized' do
187
+ it 'should respond 403' do
188
+ expect(lambda { json_post '/followers', follower_data, env }).
189
+ to_not change(TentD::Model::Follower, :count)
190
+
191
+ expect(lambda { json_post '/followers', follower_data, env }).
192
+ to_not change(TentD::Model::NotificationSubscription, :count)
193
+
194
+ expect(last_response.status).to eq(403)
195
+ end
196
+ end
197
+ end
198
+
199
+ describe 'GET /followers/count' do
200
+ it 'should return count of followers' do
201
+ TentD::Model::Follower.all.destroy
202
+ follower = Fabricate(:follower, :public => true)
203
+ json_get '/followers/count', params, env
204
+ expect(last_response.body).to eq(1.to_json)
205
+
206
+ TentD::Model::Follower.all.destroy
207
+ json_get '/followers/count', params, env
208
+ expect(last_response.body).to eq(0.to_json)
209
+ end
210
+ end
211
+
212
+ describe 'GET /followers' do
213
+ authorized_permissible = proc do
214
+ it 'should order id desc' do
215
+ TentD::Model::Follower.all.destroy
216
+ first_follower = Fabricate(:follower, :public => true)
217
+ last_follower = Fabricate(:follower, :public => true)
218
+
219
+ json_get "/followers", params, env
220
+ body = JSON.parse(last_response.body)
221
+ body_ids = body.map { |i| i['id'] }
222
+ expect(body_ids).to eq([last_follower.public_id, first_follower.public_id])
223
+ end
224
+
225
+ it 'should return a list of followers' do
226
+ TentD::Model::Follower.all.destroy!
227
+ followers = 2.times.map { Fabricate(:follower, :public => true) }
228
+ json_get '/followers', params, env
229
+ expect(last_response.status).to eq(200)
230
+ body = JSON.parse(last_response.body)
231
+ body_ids = body.map { |i| i['id'] }
232
+ followers.each do |follower|
233
+ expect(body_ids).to include(follower.public_id)
234
+ end
235
+ end
236
+ end
237
+
238
+ authorized_full = proc do
239
+ it 'should return a list of followers without mac keys' do
240
+ TentD::Model::Follower.all.destroy!
241
+ followers = 2.times.map { Fabricate(:follower, :public => false) }
242
+ json_get '/followers', params, env
243
+ blacklist = %w{ mac_key_id mac_key mac_algorithm }
244
+ body = JSON.parse(last_response.body)
245
+ body.each do |f|
246
+ blacklist.each { |k|
247
+ expect(f).to_not have_key(k)
248
+ }
249
+ end
250
+ expect(last_response.status).to eq(200)
251
+ end
252
+ end
253
+
254
+ context 'when not authorized', &authorized_permissible
255
+
256
+ context 'when authorized via scope' do
257
+ before { authorize!(:read_followers) }
258
+ context &authorized_full
259
+
260
+ context 'when read_secrets authorized' do
261
+ before { authorize!(:read_followers, :read_secrets) }
262
+
263
+ context 'when secrets param set to true' do
264
+ it 'should return a list of followers with mac keys' do
265
+ TentD::Model::Follower.all.destroy!
266
+ followers = 2.times.map { Fabricate(:follower, :public => false) }
267
+ json_get '/followers?secrets=true', params, env
268
+ whitelist = %w{ mac_key_id mac_key mac_algorithm }
269
+ body = JSON.parse(last_response.body)
270
+ body.each do |f|
271
+ whitelist.each { |k|
272
+ expect(f).to have_key(k)
273
+ }
274
+ end
275
+ expect(last_response.status).to eq(200)
276
+ end
277
+ end
278
+
279
+ context 'when secrets param not set', &authorized_full
280
+ end
281
+ end
282
+ end
283
+
284
+ describe 'GET /followers/:id' do
285
+ authorized = proc do
286
+ it 'should respond with follower json' do
287
+ json_get "/followers/#{follower.public_id}", params, env
288
+ expect(last_response.status).to eq(200)
289
+ body = JSON.parse(last_response.body)
290
+ expect(body['id']).to eq(follower.public_id)
291
+ end
292
+ end
293
+
294
+ context 'when authorized via scope' do
295
+ before { authorize!(:read_followers) }
296
+ context &authorized
297
+
298
+ context 'when follower private' do
299
+ before { follower.update(:public => false) }
300
+ context &authorized
301
+ end
302
+
303
+ context 'when read_secrets scope authorized' do
304
+ before { authorize!(:read_followers, :read_secrets) }
305
+
306
+ context 'with secrets param' do
307
+ before { params['secrets'] = true }
308
+
309
+ it 'should respond with follower json with mac_key' do
310
+ json_get "/followers/#{follower.public_id}", params, env
311
+ expect(last_response.status).to eq(200)
312
+ actual = JSON.parse(last_response.body)
313
+ expected = follower.as_json(:only => [:id, :groups, :entity, :licenses, :type, :mac_key_id, :mac_key, :mac_algorithm, :mac_timestamp_delta])
314
+ expected.each_pair do |key, val|
315
+ expect(actual[key.to_s].to_json).to eq(val.to_json)
316
+ end
317
+ end
318
+ end
319
+
320
+ context 'without secrets param', &authorized
321
+ end
322
+
323
+ context 'when no follower exists with :id' do
324
+ it 'should respond with 404' do
325
+ json_get "/followers/invalid-id", params, env
326
+ expect(last_response.status).to eq(404)
327
+ end
328
+ end
329
+ end
330
+
331
+ context 'when authorized via identity' do
332
+ before { env['current_auth'] = follower }
333
+ context &authorized
334
+
335
+ context 'when follower private' do
336
+ before { follower.update(:public => false) }
337
+ context &authorized
338
+ end
339
+
340
+ context 'with secrets param' do
341
+ before { params['secrets'] = true }
342
+ context &authorized
343
+ end
344
+
345
+ context 'when no follower exists with :id' do
346
+ it 'should respond 403' do
347
+ json_get '/followers/invalid-id', params, env
348
+ expect(last_response.status).to eq(403)
349
+ end
350
+ end
351
+ end
352
+
353
+ context 'when not authorized' do
354
+ context 'when follower public' do
355
+ it 'should respond with follower json' do
356
+ json_get "/followers/#{follower.public_id}", params, env
357
+ expect(last_response.status).to eq(200)
358
+ expect(last_response.body).to eq(follower.as_json(:only => [:id, :groups, :entity, :licenses, :type]).to_json)
359
+ end
360
+ end
361
+
362
+ context 'when follower private' do
363
+ before { follower.update(:public => false) }
364
+ it 'should respond 403' do
365
+ json_get "/followers/#{follower.id}", params, env
366
+ expect(last_response.status).to eq(403)
367
+ end
368
+ end
369
+
370
+ context 'when no follower exists with :id' do
371
+ it 'should respond 403' do
372
+ json_get "/followers/invalid-id", params, env
373
+ expect(last_response.status).to eq(403)
374
+ end
375
+ end
376
+ end
377
+ end
378
+
379
+ describe 'PUT /followers/:id' do
380
+ blacklist = whitelist = []
381
+ authorized = proc do |*args|
382
+ it 'should update follower licenses' do
383
+ data = {
384
+ :licenses => ["http://creativecommons.org/licenses/by/3.0/"]
385
+ }
386
+ json_put "/followers/#{follower.public_id}", data, env
387
+ follower.reload
388
+ expect(follower.licenses).to eq(data[:licenses])
389
+ end
390
+
391
+ context '' do
392
+ before(:all) do
393
+ @data = {
394
+ :entity => "https://chunky-bacon.example.com",
395
+ :profile => { :entity => "https:://chunky-bacon.example.com" },
396
+ :type => :following,
397
+ :public => true,
398
+ :groups => ['group-id', 'group-id-2'],
399
+ :mac_key_id => '12345',
400
+ :mac_key => '12312',
401
+ :mac_algorithm => 'sdfjhsd',
402
+ :mac_timestamp_delta => 124123
403
+ }
404
+ end
405
+ (blacklist || []).each do |property|
406
+ it "should not update #{property}" do
407
+ original_value = follower.send(property)
408
+ data = { property => @data[property] }
409
+ json_put "/followers/#{follower.public_id}", data, env
410
+ follower.reload
411
+ expect(follower.send(property)).to eq(original_value)
412
+ end
413
+ end
414
+ (whitelist || []).each do |property|
415
+ it "should update #{property}" do
416
+ original_value = follower.send(property)
417
+ data = { property => @data[property] }
418
+ json_put "/followers/#{follower.public_id}", data, env
419
+ follower.reload
420
+ actual_value = follower.send(property)
421
+ expect(actual_value.to_json).to eq(@data[property].to_json)
422
+ end
423
+ end
424
+ end
425
+
426
+ it 'should update follower type notifications' do
427
+ data = {
428
+ :types => follower.notification_subscriptions.map {|ns| ns.type.to_s}.concat(["https://tent.io/types/post/video/v0.1.x#meta"])
429
+ }
430
+ expect( lambda { json_put "/followers/#{follower.public_id}", data, env } ).
431
+ to change(TentD::Model::NotificationSubscription, :count).by (1)
432
+
433
+ follower.reload
434
+ data = {
435
+ :types => follower.notification_subscriptions.map {|ns| ns.type.to_s}[0..-2]
436
+ }
437
+ expect( lambda { json_put "/followers/#{follower.public_id}", data, env } ).
438
+ to change(TentD::Model::NotificationSubscription, :count).by (-1)
439
+ end
440
+ end
441
+
442
+ context 'when authorized via scope' do
443
+ before { authorize!(:write_followers) }
444
+ blacklist = [:mac_key_id, :mac_key, :mac_algorithm, :mac_timestamp_delta]
445
+ whitelist = [:entity, :profile, :public, :groups]
446
+ context &authorized
447
+
448
+ context 'when no follower exists with :id' do
449
+ it 'should respond 404' do
450
+ json_put '/followers/invalid-id', params, env
451
+ expect(last_response.status).to eq(404)
452
+ end
453
+ end
454
+
455
+ context 'when write_secrets scope authorized' do
456
+ before { authorize!(:write_followers, :write_secrets) }
457
+ blacklist = []
458
+ whitelist = [:entity, :profile, :public, :groups, :mac_key_id, :mac_key, :mac_algorithm, :mac_timestamp_delta]
459
+ context &authorized
460
+ end
461
+ end
462
+
463
+ context 'when authorized via identity' do
464
+ before { env['current_auth'] = follower }
465
+ blacklist = [:entity, :profile, :public, :groups, :mac_key_id, :mac_key, :mac_algorithm, :mac_timestamp_delta]
466
+ whitelist = []
467
+ context &authorized
468
+
469
+ context 'when no follower exists with :id' do
470
+ it 'should respond 403' do
471
+ json_put '/followers/invalid-id', params, env
472
+ expect(last_response.status).to eq(403)
473
+ end
474
+ end
475
+ end
476
+ end
477
+
478
+ describe 'DELETE /followers/:id' do
479
+
480
+ authorized = proc do
481
+ it 'should delete follower' do
482
+ follower # create follower
483
+ expect(lambda {
484
+ delete "/followers/#{follower.public_id}", params, env
485
+ expect(last_response.status).to eq(200)
486
+ }).to change(TentD::Model::Follower, :count).by(-1)
487
+ end
488
+
489
+ it 'should create post (notification)' do
490
+ follower # create follower
491
+
492
+ expect(lambda {
493
+ delete "/followers/#{follower.public_id}", params, env
494
+ expect(last_response.status).to eq(200)
495
+ }).to change(TentD::Model::Post, :count).by(1)
496
+
497
+ post = TentD::Model::Post.last
498
+ expect(post.type.base).to eq('https://tent.io/types/post/follower')
499
+ expect(post.type.version).to eq('0.1.0')
500
+ expect(post.content['action']).to eq('delete')
501
+ end
502
+ end
503
+
504
+ not_authorized = proc do
505
+ it 'should respond 403' do
506
+ delete "/followers/invalid-id", params, env
507
+ expect(last_response.status).to eq(403)
508
+ end
509
+ end
510
+
511
+ context 'when authorized via scope' do
512
+ before { authorize!(:write_followers) }
513
+
514
+ context &authorized
515
+
516
+ it 'should respond with 404 if no follower exists with :id' do
517
+ delete "/followers/invalid-id", params, env
518
+ expect(last_response.status).to eq(404)
519
+ end
520
+ end
521
+
522
+ context 'when authorized via identity' do
523
+ before { env['current_auth'] = follower }
524
+
525
+ context &authorized
526
+
527
+ it 'should respond with 403 if no follower exists with :id' do
528
+ delete "/followers/invalid-id", params, env
529
+ expect(last_response.status).to eq(403)
530
+ end
531
+ end
532
+
533
+ context 'when not authorized', &not_authorized
534
+ end
535
+ end