tentd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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