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,207 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::Groups do
4
+ def app
5
+ TentD::API.new
6
+ end
7
+
8
+ def authorize!(*scopes)
9
+ env['current_auth'] = stub(
10
+ :kind_of? => true,
11
+ :id => nil,
12
+ :scopes => scopes
13
+ )
14
+ end
15
+
16
+ let(:env) { Hash.new }
17
+ let(:params) { Hash.new }
18
+
19
+ describe 'GET /groups/count' do
20
+ before { authorize!(:read_groups) }
21
+ it 'should return number of groups' do
22
+ TentD::Model::Group.all.destroy
23
+ Fabricate(:group)
24
+ json_get '/groups/count', params, env
25
+ expect(last_response.body).to eq(1.to_json)
26
+ end
27
+ end
28
+
29
+ describe 'GET /groups' do
30
+ context 'when read_groups scope authorized' do
31
+ before { authorize!(:read_groups) }
32
+
33
+ it 'should return all groups' do
34
+ Fabricate(:group, :name => 'chunky-bacon')
35
+
36
+ with_constants "TentD::API::PER_PAGE" => TentD::Model::Group.count + 1 do
37
+ json_get '/groups', params, env
38
+ expect(JSON.parse(last_response.body).size).to eq(TentD::Model::Group.count)
39
+ end
40
+ end
41
+
42
+ it 'should order by id desc' do
43
+ TentD::Model::Group.all.destroy
44
+ first_group = Fabricate(:group)
45
+ last_group = Fabricate(:group)
46
+
47
+ json_get '/groups', params, env
48
+ body = JSON.parse(last_response.body)
49
+ body_ids = body.map { |i| i['id'] }
50
+ expect(body_ids).to eq([last_group.public_id, first_group.public_id])
51
+ end
52
+
53
+ context 'with params' do
54
+ it 'should filter by before_id' do
55
+ TentD::Model::Group.all.destroy
56
+ group = Fabricate(:group)
57
+ before_group = Fabricate(:group)
58
+
59
+ params[:before_id] = before_group.public_id
60
+ json_get '/groups', params, env
61
+ expect(last_response.status).to eq(200)
62
+
63
+ body = JSON.parse(last_response.body)
64
+ body_ids = body.map { |i| i['id'] }
65
+ expect(body_ids).to eq([group.public_id])
66
+ end
67
+
68
+ it 'should filter by since_id' do
69
+ since_group = Fabricate(:group)
70
+ group = Fabricate(:group)
71
+
72
+ params[:since_id] = since_group.public_id
73
+ json_get '/groups', params, env
74
+ expect(last_response.status).to eq(200)
75
+
76
+ body = JSON.parse(last_response.body)
77
+ body_ids = body.map { |i| i['id'] }
78
+ expect(body_ids).to eq([group.public_id])
79
+ end
80
+
81
+ it 'should support limit' do
82
+ 2.times { Fabricate(:group) }
83
+ params[:limit] = 1
84
+
85
+ json_get '/groups', params, env
86
+ expect(last_response.status).to eq(200)
87
+
88
+ body = JSON.parse(last_response.body)
89
+ expect(body.size).to eq(1)
90
+ end
91
+
92
+ it 'should never return more than TentD::API::MAX_PER_PAGE groups' do
93
+ Fabricate(:group)
94
+ with_constants "TentD::API::MAX_PER_PAGE" => 0 do
95
+ params[:limit] = 1
96
+ json_get '/groups', params, env
97
+ expect(last_response.status).to eq(200)
98
+ expect(JSON.parse(last_response.body).size).to eq(0)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ context 'when read_groups scope unauthorized' do
105
+ it 'should return 403' do
106
+ get '/groups', params, env
107
+ expect(last_response.status).to eq(403)
108
+ end
109
+ end
110
+ end
111
+
112
+ describe 'GET /groups/:id' do
113
+ context 'when read_groups scope is authorized' do
114
+ before { authorize!(:read_groups) }
115
+
116
+ it 'should find group with :id' do
117
+ group = Fabricate(:group)
118
+ get "/groups/#{group.public_id}", params, env
119
+ expect(JSON.parse(last_response.body)['id']).to eq(group.public_id)
120
+ end
121
+
122
+ it "should render 404 if :id doesn't exist" do
123
+ get "/groups/invalid-id", params, env
124
+ expect(last_response.status).to eq(404)
125
+ end
126
+ end
127
+
128
+ context 'when read_groups scope is unauthorized' do
129
+ it 'should return 403' do
130
+ get '/groups/group-id', params, env
131
+ expect(last_response.status).to eq(403)
132
+ end
133
+ end
134
+ end
135
+
136
+ describe 'PUT /groups/:id' do
137
+ context 'when write_groups scope is authorized' do
138
+ before { authorize!(:write_groups) }
139
+
140
+ it 'should update group with :id' do
141
+ group = Fabricate(:group, :name => 'foo-bar')
142
+ group.name = 'bar-baz'
143
+ expect(group.save).to be_true
144
+ json_put "/groups/#{group.public_id}", group, env
145
+ actual_group = TentD::Model::Group.first(:id => group.id)
146
+ expect(actual_group.name).to eq(group.name)
147
+ expect(JSON.parse(last_response.body)['id']).to eq(actual_group.public_id)
148
+ end
149
+
150
+ it 'should return 404 unless group with :id exists' do
151
+ json_put '/groups/invalid-id', params, env
152
+ expect(last_response.status).to eq(404)
153
+ end
154
+ end
155
+
156
+ context 'when write_groups scope is not authorized' do
157
+ it 'should return 403' do
158
+ json_put '/groups/group-id', params, env
159
+ expect(last_response.status).to eq(403)
160
+ end
161
+ end
162
+ end
163
+
164
+ describe 'POST /groups' do
165
+ context 'when write_groups scope is authorized' do
166
+ before { authorize!(:write_groups) }
167
+
168
+ it 'should create group' do
169
+ expect(lambda { json_post "/groups", { :name => 'bacon-bacon' }, env }).
170
+ to change(TentD::Model::Group, :count).by(1)
171
+ end
172
+ end
173
+
174
+ context 'when write_groups scope is not authorized' do
175
+ it 'should return 403' do
176
+ json_post '/groups', params, env
177
+ expect(last_response.status).to eq(403)
178
+ end
179
+ end
180
+ end
181
+
182
+ describe 'DELETE /groups' do
183
+ context 'when write_groups scope is authorized' do
184
+ before { authorize!(:write_groups) }
185
+
186
+ it 'should destroy group' do
187
+ group = Fabricate(:group, :name => 'foo-bar-baz')
188
+ expect(lambda { delete "/groups/#{group.public_id}", params, env }).
189
+ to change(TentD::Model::Group, :count).by(-1)
190
+ end
191
+
192
+ it 'should returh 404 if group does not exist' do
193
+ Fabricate(:group, :name => 'baz')
194
+ expect(lambda { delete "/groups/invalid-id", params, env }).
195
+ to change(TentD::Model::Group, :count).by(0)
196
+ expect(last_response.status).to eq(404)
197
+ end
198
+ end
199
+
200
+ context 'when write_groups scope is not authorized' do
201
+ it 'should return 403' do
202
+ delete '/groups/group-id', params, env
203
+ expect(last_response.status).to eq(403)
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,874 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::Posts do
4
+ def app
5
+ TentD::API.new
6
+ end
7
+
8
+ let(:authorized_post_types) { [] }
9
+
10
+ def authorize!(*scopes)
11
+ options = scopes.last if scopes.last.kind_of?(Hash)
12
+ scopes.delete(options)
13
+ methods = {
14
+ :kind_of? => true,
15
+ :id => nil,
16
+ :scopes => scopes,
17
+ :post_types => authorized_post_types,
18
+ }
19
+ if options
20
+ methods[:app] = options[:app] if options[:app]
21
+ methods[:entity] = options[:entity] if options[:entity]
22
+ end
23
+ env['current_auth'] = stub(methods)
24
+ end
25
+
26
+ let(:env) { Hash.new }
27
+ let(:params) { Hash.new }
28
+
29
+ describe 'GET /posts/count' do
30
+ it_should_get_count = proc do
31
+ it 'should return count of posts' do
32
+ TentD::Model::Post.all.destroy
33
+ post = Fabricate(:post, :public => true)
34
+ json_get '/posts/count', params, env
35
+ expect(last_response.body).to eq(1.to_json)
36
+ end
37
+
38
+ it 'should return count of posts with type' do
39
+ TentD::Model::Post.all.destroy
40
+ type = TentD::TentType.new("https://tent.io/types/post/example/v0.1.0")
41
+ type2 = TentD::TentType.new("https://tent.io/types/post/blog/v0.1.0")
42
+ post = Fabricate(:post, :public => true, :type_base => type.base, :type_version => type.version)
43
+ post2 = Fabricate(:post, :public => true, :type_base => type.base, :type_version => type.version, :original => false)
44
+ post3 = Fabricate(:post, :public => true, :type_base => type2.base, :type_version => type2.version)
45
+
46
+ params[:post_types] = type.uri
47
+ json_get '/posts/count', params, env
48
+ expect(last_response.body).to eq(1.to_json)
49
+
50
+ end
51
+ end
52
+
53
+ context &it_should_get_count
54
+
55
+ context 'when read_posts scope authorized' do
56
+ before { authorize!(:read_posts) }
57
+
58
+ context &it_should_get_count
59
+
60
+ context 'when specific types authorized' do
61
+ let(:authorized_post_types) { %w(https://tent.io/types/post/example/v0.1.0 https://tent.io/types/post/blog/v0.1.0) }
62
+ context &it_should_get_count
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'GET /posts/:post_id' do
68
+ let(:env) { Hashie::Mash.new }
69
+ let(:params) { Hashie::Mash.new }
70
+ with_version = proc do
71
+ context 'with params[:version] specified' do
72
+ context 'when version exists' do
73
+ it 'should return specified post version' do
74
+ first_version = post.latest_version(:fields => [:version]).version
75
+ post.update(:content => { 'text' => 'foo bar baz' })
76
+ latest_version = post.latest_version(:fields => [:version]).version
77
+ expect(first_version).to_not eq(latest_version)
78
+
79
+ json_get "/posts/#{post.public_id}?version=#{first_version}", params, env
80
+ expect(last_response.status).to eq(200)
81
+ body = JSON.parse(last_response.body)
82
+ expect(body['id']).to eq(post.public_id)
83
+ expect(body['version']).to eq(first_version)
84
+
85
+ json_get "/posts/#{post.public_id}?version=#{latest_version}", params, env
86
+ expect(last_response.status).to eq(200)
87
+ body = JSON.parse(last_response.body)
88
+ expect(body['id']).to eq(post.public_id)
89
+ expect(body['version']).to eq(latest_version)
90
+
91
+ json_get "/posts/#{post.public_id}", params, env
92
+ expect(last_response.status).to eq(200)
93
+ body = JSON.parse(last_response.body)
94
+ expect(body['id']).to eq(post.public_id)
95
+ expect(body['version']).to eq(latest_version)
96
+ end
97
+ end
98
+
99
+ context 'when version does not exist' do
100
+ it 'should return 404' do
101
+ json_get "/posts/#{post.public_id}?version=12", params, env
102
+ expect(last_response.status).to eq(404)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ with_view = proc do
109
+ context 'with params[:view] specified' do
110
+ it 'should return post using specified view' do
111
+ post.update(
112
+ :views => {
113
+ 'mini' => {
114
+ 'content' => ['mini_text', 'title']
115
+ }
116
+ },
117
+ :content => {
118
+ 'text' => 'The quick brown fox jumps over the lazy dog',
119
+ 'mini_text' => 'The quick brown fox...',
120
+ 'title' => 'Quick Fox'
121
+ }
122
+ )
123
+
124
+ json_get "/posts/#{post.public_id}?view=mini", params, env
125
+ expect(last_response.status).to eq(200)
126
+
127
+ body = JSON.parse(last_response.body)
128
+ expect(body['id']).to eq(post.public_id)
129
+ expect(body['content']).to eq({
130
+ 'mini_text' => 'The quick brown fox...',
131
+ 'title' => 'Quick Fox'
132
+ })
133
+ end
134
+ end
135
+ end
136
+
137
+ using_permissions = proc do
138
+ not_authenticated = proc do
139
+ it "should find existing post by public_id" do
140
+ post = Fabricate(:post, :public => true)
141
+ json_get "/posts/#{post.public_id}"
142
+ expect(last_response.status).to eq(200)
143
+ expect(JSON.parse(last_response.body)['id']).to eq(post.public_id)
144
+ end
145
+
146
+ it "should not find existing post by actual id" do
147
+ post = Fabricate(:post, :public => true)
148
+ json_get "/posts/#{post.id}"
149
+ expect(last_response.status).to eq(404)
150
+ end
151
+
152
+ it "should be 404 if post_id doesn't exist" do
153
+ TentD::Model::Post.all.destroy!
154
+ json_get "/posts/1"
155
+ expect(last_response.status).to eq(404)
156
+ end
157
+ end
158
+
159
+ context &not_authenticated
160
+
161
+ with_permissions = proc do
162
+ it 'should return post' do
163
+ json_get "/posts/#{post.public_id}", params, env
164
+ expect(last_response.status).to eq(200)
165
+ expect(JSON.parse(last_response.body)['id']).to eq(post.public_id)
166
+ end
167
+
168
+ context &with_version
169
+ context &with_view
170
+ end
171
+
172
+ current_auth_examples = proc do
173
+ context 'when post is not public' do
174
+ let(:group) { Fabricate(:group, :name => 'friends') }
175
+ let(:post) { Fabricate(:post, :public => false) }
176
+
177
+ context 'when has explicit permission' do
178
+ before do
179
+ case current_auth
180
+ when TentD::Model::Follower
181
+ current_auth.access_permissions.create(:post_id => post.id)
182
+ else
183
+ current_auth.permissions.create(:post_id => post.id)
184
+ end
185
+ env.current_auth = current_auth
186
+ end
187
+
188
+ context &with_permissions
189
+ end
190
+
191
+ context 'when has permission via groups' do
192
+ before do
193
+ post.permissions.create(:group_public_id => group.public_id)
194
+ current_auth.groups = [group.public_id]
195
+ current_auth.save
196
+ env.current_auth = current_auth
197
+ end
198
+
199
+ context &with_permissions
200
+ end
201
+
202
+ context 'when does not have permission' do
203
+ it 'should return 404' do
204
+ post # create post
205
+ json_get "/posts/#{post.public_id}", params, env
206
+ expect(last_response.status).to eq(404)
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ context 'when Follower' do
213
+ let(:current_auth) { Fabricate(:follower) }
214
+
215
+ context &current_auth_examples
216
+ end
217
+
218
+ context 'when AppAuthorization' do
219
+ let(:current_auth) { Fabricate(:app_authorization, :app => Fabricate(:app)) }
220
+
221
+ context &not_authenticated
222
+ end
223
+ end
224
+
225
+ context 'without authorization', &using_permissions
226
+
227
+ context 'with read_posts scope authorized' do
228
+ before { authorize!(:read_posts) }
229
+ let(:post_type) { 'https://tent.io/types/post/status' }
230
+
231
+ post_type_authorized = proc do
232
+ context 'when post exists' do
233
+ it 'should return post' do
234
+ post = Fabricate(:post, :public => false, :type_base => post_type)
235
+ json_get "/posts/#{post.public_id}", params, env
236
+ expect(last_response.status).to eq(200)
237
+ expect(JSON.parse(last_response.body)['id']).to eq(post.public_id)
238
+ end
239
+ end
240
+
241
+ context 'when no post exists with :id' do
242
+ it 'should respond 404' do
243
+ json_get "/posts/invalid-id", params, env
244
+ expect(last_response.status).to eq(404)
245
+ end
246
+ end
247
+ end
248
+
249
+ context 'when post type is authorized' do
250
+ let(:authorized_post_types) { [post_type] }
251
+ context &post_type_authorized
252
+ end
253
+
254
+ context 'when all post types authorized' do
255
+ let(:authorized_post_types) { ['all'] }
256
+ let(:post) { Fabricate(:post, :public => false) }
257
+
258
+ context &post_type_authorized
259
+
260
+ context &with_version
261
+ context &with_view
262
+ end
263
+
264
+ context 'when post type is not authorized' do
265
+ it 'should return 404' do
266
+ post = Fabricate(:post, :public => false, :type_base => post_type)
267
+ json_get "/posts/#{post.public_id}", params, env
268
+ expect(last_response.status).to eq(404)
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ describe 'GET /posts' do
275
+ let(:post_public?) { true }
276
+ with_params = proc do
277
+ it "should respond with first TentD::API::PER_PAGE posts if no params given" do
278
+ with_constants "TentD::API::PER_PAGE" => 1 do
279
+ 0.upto(TentD::API::PER_PAGE+1).each { Fabricate(:post, :public => post_public?) }
280
+ json_get '/posts', params, env
281
+ expect(JSON.parse(last_response.body).size).to eq(1)
282
+ end
283
+ end
284
+
285
+ it "should filter by params[:post_types]" do
286
+ picture_type = TentD::TentType.new("https://tent.io/types/post/picture/v0.1.0")
287
+ blog_type = TentD::TentType.new("https://tent.io/types/post/blog/v0.1.0")
288
+
289
+ picture_post = Fabricate(:post, :public => post_public?, :type_base => picture_type.base)
290
+ non_picture_post = Fabricate(:post, :public => post_public?)
291
+ blog_post = Fabricate(:post, :public => post_public?, :type_base => blog_type.base)
292
+
293
+ posts = TentD::Model::Post.all(:type_base => [picture_type.base, blog_type.base])
294
+ post_types = [picture_post, blog_post].map { |p| URI.encode_www_form_component(p.type.uri) }
295
+
296
+ json_get "/posts?post_types=#{post_types.join(',')}", params, env
297
+ body = JSON.parse(last_response.body)
298
+ expect(body.size).to eq(posts.size)
299
+ body_ids = body.map { |i| i['id'] }
300
+ posts.each { |post|
301
+ expect(body_ids).to include(post.public_id)
302
+ }
303
+ end
304
+
305
+ it "should filter by params[:mentioned_post]" do
306
+ mentioned_post = Fabricate(:post, :public => post_public?)
307
+ post = Fabricate(:post, :public => post_public?, :mentions => [
308
+ Fabricate(:mention, :mentioned_post_id => mentioned_post.public_id, :entity => mentioned_post.entity)
309
+ ])
310
+
311
+ json_get "/posts?mentioned_post=#{mentioned_post.public_id}&mentioned_entity=#{URI.encode_www_form_component(mentioned_post.entity)}", params, env
312
+ body = JSON.parse(last_response.body)
313
+ expect(body.size).to eq(1)
314
+ body_ids = body.map { |i| i['id'] }
315
+ expect(body_ids).to include(post.public_id)
316
+ end
317
+
318
+ it "should filter by params[:entity]" do
319
+ other_post = Fabricate(:post, :public => post_public?)
320
+ first_post = Fabricate(:post, :public => post_public?, :entity => 'https://412doe.example.org')
321
+ last_post = Fabricate(:post, :public => post_public?, :entity => 'https://124alex.example.com')
322
+
323
+ params[:entity] = [first_post.entity, last_post.entity]
324
+ json_get "/posts", params, env
325
+ body = JSON.parse(last_response.body)
326
+ expect(body.size).to eq(2)
327
+ body_ids = body.map { |i| i['id'] }
328
+ expect(body_ids).to include(first_post.public_id)
329
+ expect(body_ids).to include(last_post.public_id)
330
+ end
331
+
332
+ it "should order by published_at desc" do
333
+ TentD::Model::Post.all.destroy
334
+ first_post = Fabricate(:post, :public => post_public?, :published_at => Time.at(Time.now.to_i-86400)) # 1.day.ago
335
+ latest_post = Fabricate(:post, :public => post_public?, :published_at => Time.at(Time.now.to_i+86400)) # 1.day.from_now
336
+
337
+ json_get "/posts", params, env
338
+ body = JSON.parse(last_response.body)
339
+ expect(body.size).to eq(2)
340
+ expect(body.first['id']).to eq(latest_post.public_id)
341
+ expect(body.last['id']).to eq(first_post.public_id)
342
+ end
343
+
344
+ it "should filter by params[:since_id]" do
345
+ since_post = Fabricate(:post, :public => post_public?)
346
+ post = Fabricate(:post, :public => post_public?)
347
+
348
+ json_get "/posts?since_id=#{since_post.public_id}", params, env
349
+ body = JSON.parse(last_response.body)
350
+ expect(body.size).to eq(1)
351
+ body_ids = body.map { |i| i['id'] }
352
+ expect(body_ids.first).to eq(post.public_id)
353
+ end
354
+
355
+ it "should filter by params[:before_id]" do
356
+ TentD::Model::Post.all.destroy
357
+ post = Fabricate(:post, :public => post_public?)
358
+ before_post = Fabricate(:post, :public => post_public?)
359
+
360
+ json_get "/posts?before_id=#{before_post.public_id}", params, env
361
+ body = JSON.parse(last_response.body)
362
+ expect(body.size).to eq(1)
363
+ body_ids = body.map { |i| i['id'] }
364
+ expect(body_ids.first).to eq(post.public_id)
365
+ end
366
+
367
+ it "should filter by both params[:since_id] and params[:before_id]" do
368
+ since_post = Fabricate(:post, :public => post_public?)
369
+ post = Fabricate(:post, :public => post_public?)
370
+ before_post = Fabricate(:post, :public => post_public?)
371
+
372
+ json_get "/posts?before_id=#{before_post.public_id}&since_id=#{since_post.public_id}", params, env
373
+ body = JSON.parse(last_response.body)
374
+ expect(body.size).to eq(1)
375
+ body_ids = body.map { |i| i['id'] }
376
+ expect(body_ids.first).to eq(post.public_id)
377
+ end
378
+
379
+ it "should filter by params[:since_time]" do
380
+ since_post = Fabricate(:post, :public => post_public?)
381
+ since_post.published_at = Time.at(Time.now.to_i + 86400) # 1.day.from_now
382
+ post = Fabricate(:post, :public => post_public?)
383
+ post.published_at = Time.at(Time.now.to_i + (86400 * 2)) # 2.days.from_now
384
+ post.save
385
+
386
+ json_get "/posts?since_time=#{since_post.published_at.to_time.to_i}", params, env
387
+ body = JSON.parse(last_response.body)
388
+ expect(body.size).to eq(1)
389
+ body_ids = body.map { |i| i['id'] }
390
+ expect(body_ids.first).to eq(post.public_id)
391
+ end
392
+
393
+ it "should filter by params[:before_time]" do
394
+ post = Fabricate(:post, :public => post_public?)
395
+ post.published_at = Time.at(Time.now.to_i - (86400 * 2)) # 2.days.ago
396
+ post.save
397
+ before_post = Fabricate(:post, :public => post_public?)
398
+ before_post.published_at = Time.at(Time.now.to_i - 86400) # 1.day.ago
399
+ before_post.save
400
+
401
+ json_get "/posts?before_time=#{before_post.published_at.to_time.to_i}", params, env
402
+ body = JSON.parse(last_response.body)
403
+ expect(body.size).to eq(1)
404
+ body_ids = body.map { |i| i['id'] }
405
+ expect(body_ids.first).to eq(post.public_id)
406
+ end
407
+
408
+ it "should filter by both params[:before_time] and params[:since_time]" do
409
+ now = Time.at(Time.now.to_i - (86400 * 6)) # 6.days.ago
410
+ since_post = Fabricate(:post, :public => post_public?)
411
+ since_post.published_at = Time.at(now.to_i - (86400 * 3)) # 3.days.ago
412
+ since_post.save
413
+ post = Fabricate(:post, :public => post_public?)
414
+ post.published_at = Time.at(now.to_i - (86400 * 2)) # 2.days.ago
415
+ post.save
416
+ before_post = Fabricate(:post, :public => post_public?)
417
+ before_post.published_at = Time.at(now.to_i - 86400) # 1.day.ago
418
+ before_post.save
419
+
420
+ json_get "/posts?before_time=#{before_post.published_at.to_time.to_i}&since_time=#{since_post.published_at.to_time.to_i}", params, env
421
+ body = JSON.parse(last_response.body)
422
+ expect(body.size).to eq(1)
423
+ body_ids = body.map { |i| i['id'] }
424
+ expect(body_ids.first).to eq(post.public_id)
425
+ end
426
+
427
+ it "should set feed length with params[:limit]" do
428
+ 0.upto(2).each { Fabricate(:post, :public => post_public?) }
429
+ json_get '/posts?limit=1', params, env
430
+ expect(JSON.parse(last_response.body).size).to eq(1)
431
+ end
432
+
433
+ it "limit should never exceed TentD::API::MAX_PER_PAGE" do
434
+ with_constants "TentD::API::MAX_PER_PAGE" => 0 do
435
+ 0.upto(2).each { Fabricate(:post, :public => post_public?) }
436
+ json_get '/posts?limit=1', params, env
437
+ expect(last_response.body).to eq([].to_json)
438
+ end
439
+ end
440
+ end
441
+
442
+ context 'without authorization', &with_params
443
+
444
+ context 'with read_posts scope authorized' do
445
+ before { authorize!(:read_posts) }
446
+ let(:post_public?) { false }
447
+
448
+ context 'when post type authorized' do
449
+ let(:authorized_post_types) { ["https://tent.io/types/post/status/v0.1.0", "https://tent.io/types/post/picture/v0.1.0", "https://tent.io/types/post/blog/v0.1.0"] }
450
+
451
+ context &with_params
452
+ end
453
+
454
+ context 'when all post types authorized' do
455
+ let(:authorized_post_types) { ['all'] }
456
+
457
+ context &with_params
458
+ end
459
+
460
+ context 'when post type not authorized' do
461
+ let(:authorized_post_types) { %w(https://tent.io/types/post/status/v0.1.0) }
462
+ it 'should return empty array' do
463
+ TentD::Model::Post.all.destroy
464
+ post = Fabricate(:post, :public => false, :type_base => 'https://tent.io/types/post/repost', :type_version => '0.1.0')
465
+ json_get "/posts", params, env
466
+ expect(last_response.body).to eq([].to_json)
467
+ end
468
+ end
469
+ end
470
+ end
471
+
472
+ describe 'POST /posts' do
473
+ let(:p) { Fabricate.build(:post) }
474
+
475
+ context 'as app with write_posts scope authorized' do
476
+ let(:application) { Fabricate.build(:app) }
477
+ before { authorize!(:write_posts, :app => application) }
478
+
479
+ it "should create post" do
480
+ post_attributes = p.attributes
481
+ post_attributes[:type] = p.type.uri
482
+ expect(lambda {
483
+ expect(lambda {
484
+ json_post "/posts", post_attributes, env
485
+ expect(last_response.status).to eq(200)
486
+ }).to change(TentD::Model::Post, :count).by(1)
487
+ }).to change(TentD::Model::PostVersion, :count).by(1)
488
+ post = TentD::Model::Post.last
489
+ expect(post.app_name).to eq(application.name)
490
+ expect(post.app_url).to eq(application.url)
491
+ body = JSON.parse(last_response.body)
492
+ expect(body['id']).to eq(post.public_id)
493
+ expect(body['app']).to eq('url' => application.url, 'name' => application.name)
494
+ end
495
+
496
+ it 'should create post with views' do
497
+ post_attributes = p.attributes
498
+ post_attributes.delete(:id)
499
+ post_attributes[:type] = p.type.uri
500
+ post_attributes[:views] = {
501
+ 'mini' => {
502
+ 'content' => ['mini_text', 'title']
503
+ }
504
+ }
505
+
506
+ expect(lambda {
507
+ expect(lambda {
508
+ json_post "/posts", post_attributes, env
509
+ expect(last_response.status).to eq(200)
510
+ }).to change(TentD::Model::Post, :count).by(1)
511
+ }).to change(TentD::Model::PostVersion, :count).by(1)
512
+ post = TentD::Model::Post.last
513
+
514
+ expect(post.views).to eq(post_attributes[:views])
515
+ end
516
+
517
+ it 'should create post with mentions' do
518
+ post_attributes = Hashie::Mash.new(p.attributes)
519
+ post_attributes.delete(:id)
520
+ post_attributes[:type] = p.type.uri
521
+ mentions = [
522
+ { :entity => "https://johndoe.example.com" },
523
+ { :entity => "https://alexsmith.example.org", :post => "post-uid" }
524
+ ]
525
+ post_attributes.merge!(
526
+ :mentions => mentions
527
+ )
528
+
529
+ expect(lambda {
530
+ json_post "/posts", post_attributes, env
531
+ expect(last_response.status).to eq(200)
532
+ }).to change(TentD::Model::Post, :count).by(1)
533
+
534
+ post = TentD::Model::Post.last
535
+ expect(post.as_json[:mentions]).to eq(mentions)
536
+ expect(post.mentions.map(&:id)).to eq(post.latest_version.mentions.map(&:id))
537
+ end
538
+
539
+ it 'should create post with permissions' do
540
+ TentD::Model::Group.all.destroy
541
+ TentD::Model::Follower.all.destroy
542
+ TentD::Model::Following.all.destroy
543
+ group = Fabricate(:group)
544
+ follower = Fabricate(:follower, :entity => 'https://john321.example.org')
545
+ following = Fabricate(:following, :entity => 'https://smith123.example.com')
546
+
547
+ post_attributes = p.attributes
548
+ post_attributes.delete(:id)
549
+ post_attributes[:type] = p.type.uri
550
+ post_attributes.merge!(
551
+ :permissions => {
552
+ :public => false,
553
+ :groups => [{ id: group.public_id }],
554
+ :entities => {
555
+ follower.entity => true,
556
+ following.entity => true
557
+ }
558
+ }
559
+ )
560
+
561
+ expect(lambda {
562
+ expect(lambda {
563
+ json_post "/posts", post_attributes, env
564
+ expect(last_response.status).to eq(200)
565
+ }).to change(TentD::Model::Post, :count).by(1)
566
+ }).to change(TentD::Model::Permission, :count).by(3)
567
+
568
+ post = TentD::Model::Post.last
569
+ expect(post.public).to be_false
570
+ end
571
+
572
+ it 'should create post with multipart attachments' do
573
+ post_attributes = p.attributes
574
+ post_attributes.delete(:id)
575
+ post_attributes[:type] = p.type.uri
576
+ attachments = { :foo => [{ :filename => 'a', :content_type => 'text/plain', :content => 'asdf' },
577
+ { :filename => 'a', :content_type => 'application/json', :content => 'asdf123' },
578
+ { :filename => 'b', :content_type => 'text/plain', :content => '1234' }],
579
+ :bar => { :filename => 'bar.html', :content_type => 'text/html', :content => '54321' } }
580
+ expect(lambda {
581
+ expect(lambda {
582
+ expect(lambda {
583
+ multipart_post('/posts', post_attributes, attachments, env)
584
+ }).to change(TentD::Model::Post, :count).by(1)
585
+ }).to change(TentD::Model::PostVersion, :count).by(1)
586
+ }).to change(TentD::Model::PostAttachment, :count).by(4)
587
+ body = JSON.parse(last_response.body)
588
+ expect(body['id']).to eq(TentD::Model::Post.last.public_id)
589
+
590
+ post = TentD::Model::Post.last
591
+ expect(post.attachments.map(&:id)).to eq(post.latest_version.attachments.map(&:id))
592
+ end
593
+ end
594
+
595
+ context 'without app write_posts scope authorized' do
596
+ it 'should respond 403' do
597
+ expect(lambda { json_post "/posts", {}, env }).to_not change(TentD::Model::Post, :count)
598
+ expect(last_response.status).to eq(403)
599
+ end
600
+ end
601
+
602
+ context 'as follower' do
603
+ before { authorize!(:entity => 'https://smith.example.com') }
604
+
605
+ it 'should allow a post from the follower' do
606
+ post_attributes = p.attributes
607
+ post_attributes[:id] = rand(36 ** 6).to_s(36)
608
+ post_attributes[:type] = p.type.uri
609
+ json_post "/posts", post_attributes, env
610
+ body = JSON.parse(last_response.body)
611
+ post = TentD::Model::Post.last
612
+ expect(body['id']).to eq(post.public_id)
613
+ expect(post.public_id).to eq(post_attributes[:id])
614
+ end
615
+
616
+ it "should not allow a post that isn't from the follower" do
617
+ post_attributes = p.attributes
618
+ post_attributes.delete(:id)
619
+ post_attributes[:type] = p.type.uri
620
+ json_post "/posts", post_attributes.merge(:entity => 'example.org'), env
621
+ expect(last_response.status).to eq(403)
622
+ end
623
+
624
+ describe 'profile update post' do
625
+ let(:following) { Fabricate(:following) }
626
+ let(:post_attributes) {
627
+ {
628
+ :type => 'https://tent.io/types/post/profile/v0.1.0',
629
+ :entity => following.entity,
630
+ :content => {
631
+ :action => 'update',
632
+ :types => ['https://tent.io/types/info/core/v0.1.0'],
633
+ }
634
+ }
635
+ }
636
+
637
+ it "should trigger a profile update" do
638
+ env['current_auth'] = following
639
+ TentD::Notifications.expects(:update_following_profile).with(:following_id => following.id)
640
+ json_post "/notifications/#{following.public_id}", post_attributes, env
641
+ expect(last_response.status).to eq(200)
642
+ end
643
+ end
644
+
645
+ describe 'delete post' do
646
+ let(:following) { Fabricate(:following) }
647
+ let(:p) { Fabricate(:post, :entity => following.entity, :following => following, :original => false) }
648
+ let(:post_attributes) {
649
+ {
650
+ :type => 'https://tent.io/types/post/delete/v0.1.0',
651
+ :entity => following.entity,
652
+ :content => {
653
+ :id => p.public_id
654
+ }
655
+ }
656
+ }
657
+
658
+ it "should trigger a post deletion" do
659
+ env['current_auth'] = following
660
+ json_post "/notifications/#{following.public_id}", post_attributes, env
661
+ expect(last_response.status).to eq(200)
662
+ expect(TentD::Model::Post.first(:id => p.id)).to be_nil
663
+ end
664
+ end
665
+ end
666
+
667
+ context 'as anonymous' do
668
+ before { Fabricate(:following) }
669
+
670
+ it 'should not allow a post by an entity that is a following' do
671
+ post_attributes = p.attributes
672
+ post_attributes.delete(:id)
673
+ post_attributes[:type] = p.type.uri
674
+ json_post "/posts", post_attributes, env
675
+ expect(last_response.status).to eq(403)
676
+ end
677
+
678
+ it 'should allow a post by an entity that is not a following' do
679
+ post_attributes = p.attributes
680
+ post_attributes[:id] = rand(36 ** 6).to_s(36)
681
+ post_attributes[:type] = p.type.uri
682
+ json_post "/posts", post_attributes.merge(:entity => 'example.org'), env
683
+ body = JSON.parse(last_response.body)
684
+ post = TentD::Model::Post.last
685
+ expect(body['id']).to eq(post.public_id)
686
+ expect(post.public_id).to eq(post_attributes[:id])
687
+ end
688
+ end
689
+ end
690
+
691
+ describe 'DELETE /posts/:post_id' do
692
+ let(:post) { Fabricate(:post, :original => true) }
693
+
694
+ context 'when authorized' do
695
+ before { authorize!(:write_posts) }
696
+
697
+ context 'when post exists' do
698
+ it 'should delete post and create post deleted notification' do
699
+ delete "/posts/#{post.public_id}", params, env
700
+ expect(last_response.status).to eq(200)
701
+ expect(TentD::Model::Post.first(:id => post.id)).to be_nil
702
+
703
+ deleted_post = post
704
+ post = TentD::Model::Post.last
705
+ expect(post.content['id']).to eq(deleted_post.public_id)
706
+ expect(post.type.base).to eq('https://tent.io/types/post/delete')
707
+ expect(post.type_version).to eq('0.1.0')
708
+ end
709
+ end
710
+
711
+ context 'when post is not original' do
712
+ let(:post) { Fabricate(:post, :original => false) }
713
+
714
+ it 'should return 403' do
715
+ delete "/posts/#{post.public_id}", params, env
716
+ expect(last_response.status).to eq(403)
717
+ end
718
+ end
719
+
720
+ context 'when post does not exist' do
721
+ it 'should return 404' do
722
+ delete "/posts/post-id", params, env
723
+ expect(last_response.status).to eq(404)
724
+ end
725
+ end
726
+ end
727
+
728
+ context 'when not authorized' do
729
+ it 'should return 403' do
730
+ delete "/posts/#{post.public_id}", params, env
731
+ expect(last_response.status).to eq(403)
732
+ end
733
+ end
734
+ end
735
+
736
+ describe 'GET /posts/:post_id/attachments/:attachment_name' do
737
+ let(:post) { Fabricate(:post) }
738
+ let(:attachment) { Fabricate(:post_attachment, :post => post) }
739
+
740
+ it 'should get an attachment' do
741
+ get "/posts/#{post.public_id}/attachments/#{attachment.name}", {}, 'HTTP_ACCEPT' => attachment.type
742
+ expect(last_response.status).to eq(200)
743
+ expect(last_response.headers['Content-Type']).to eq(attachment.type)
744
+ expect(last_response.body).to eq('54321')
745
+ end
746
+
747
+ context 'with params[:version]' do
748
+ it 'should get specified version of attachment' do
749
+ post_version = Fabricate(:post_version, :post => post, :public_id => post.public_id, :version => 12)
750
+ new_attachment = Fabricate(:post_attachment, :post => nil, :post_version => post_version, :data => Base64.encode64('ChunkyBacon'))
751
+
752
+ expect(post.latest_version(:fields => [:id]).id).to eq(post_version.id)
753
+ expect(new_attachment.name).to eq(attachment.name)
754
+
755
+ get "/posts/#{post.public_id}/attachments/#{attachment.name}", { :version => post_version.version}, 'HTTP_ACCEPT' => attachment.type
756
+
757
+ expect(last_response.status).to eq(200)
758
+ expect(last_response.headers['Content-Type']).to eq(attachment.type)
759
+ expect(last_response.body).to eq('ChunkyBacon')
760
+ end
761
+
762
+ it "should return 404 if specified version doesn't exist" do
763
+ get "/posts/#{post.public_id}/attachments/#{attachment.name}", { :version => 20}, 'HTTP_ACCEPT' => attachment.type
764
+ expect(last_response.status).to eq(404)
765
+ end
766
+ end
767
+
768
+ it "should 404 if the attachment doesn't exist" do
769
+ get "/posts/#{post.public_id}/attachments/asdf"
770
+ expect(last_response.status).to eq(404)
771
+ end
772
+
773
+ it "should 404 if the post doesn't exist" do
774
+ get "/posts/asdf/attachments/asdf"
775
+ expect(last_response.status).to eq(404)
776
+ end
777
+ end
778
+
779
+ describe 'PUT /posts/:post_id' do
780
+ let(:post) { Fabricate(:post) }
781
+
782
+ context 'when authorized' do
783
+ before { authorize!(:write_posts) }
784
+
785
+ it 'should update post' do
786
+ Fabricate(:post_attachment, :post => post)
787
+ Fabricate(:mention, :post => post)
788
+
789
+ post_attributes = {
790
+ :content => {
791
+ "text" => "Foo Bar Baz"
792
+ },
793
+ :views => {
794
+ 'mini' => { 'content' => ['mini_text'] }
795
+ },
796
+ :entity => "#{post.entity}/foo/bar",
797
+ :public => !post.public,
798
+ :licenses => post.licenses.to_a + ['https://license.example.org']
799
+ }
800
+
801
+ expect(lambda {
802
+ expect(lambda {
803
+ expect(lambda {
804
+ json_put "/posts/#{post.public_id}", post_attributes, env
805
+ expect(last_response.status).to eq(200)
806
+
807
+ post.reload
808
+ expect(post.content).to eq(post_attributes[:content])
809
+ expect(post.licenses).to eq(post_attributes[:licenses])
810
+ expect(post.views).to eq(post_attributes[:views])
811
+ expect(post.public).to_not eq(post_attributes[:public])
812
+ expect(post.entity).to_not eq(post_attributes[:entity])
813
+ }).to change(post.versions, :count).by(1)
814
+ }).to_not change(post.mentions, :count)
815
+ }).to_not change(post.attachments, :count)
816
+ end
817
+
818
+ it 'should update mentions' do
819
+ existing_mentions = 2.times.map { Fabricate(:mention, :post => post) }
820
+ post_attributes = {
821
+ :mentions => [
822
+ { :entity => "https://johndoe.example.com" },
823
+ { :entity => "https://alexsmith.example.org", :post => "post-uid" }
824
+ ]
825
+ }
826
+
827
+ expect(lambda {
828
+ expect(lambda {
829
+ json_put "/posts/#{post.public_id}", post_attributes, env
830
+ expect(last_response.status).to eq(200)
831
+
832
+ existing_mentions.each do |m|
833
+ m.reload
834
+ expect(m.post_version_id).to_not be_nil
835
+ expect(m.post_id).to be_nil
836
+ end
837
+ }).to change(TentD::Model::Mention, :count).by(2)
838
+ }).to change(post.versions, :count).by(1)
839
+ end
840
+
841
+ it 'should update attachments' do
842
+ existing_attachments = 2.times.map { Fabricate(:post_attachment, :post => post) }
843
+ attachments = { :foo => [{ :filename => 'a', :content_type => 'text/plain', :content => 'asdf' },
844
+ { :filename => 'a', :content_type => 'application/json', :content => 'asdf123' },
845
+ { :filename => 'b', :content_type => 'text/plain', :content => '1234' }],
846
+ :bar => { :filename => 'bar.html', :content_type => 'text/html', :content => '54321' } }
847
+ expect(lambda {
848
+ expect(lambda {
849
+ expect(lambda {
850
+ multipart_put("/posts/#{post.public_id}", {}, attachments, env)
851
+ expect(last_response.status).to eq(200)
852
+
853
+ existing_attachments.each do |a|
854
+ a.reload
855
+ expect(a.post_version_id).to_not be_nil
856
+ expect(a.post_id).to be_nil
857
+ end
858
+ }).to change(TentD::Model::Post, :count).by(0)
859
+ }).to change(TentD::Model::PostVersion, :count).by(1)
860
+ }).to change(TentD::Model::PostAttachment, :count).by(4)
861
+
862
+ post.reload
863
+ expect(post.attachments.map(&:id)).to eq(post.latest_version.attachments.map(&:id))
864
+ end
865
+ end
866
+
867
+ context 'when not authorized' do
868
+ it 'should return 403' do
869
+ json_put "/posts/#{post.public_id}", params, env
870
+ expect(last_response.status).to eq(403)
871
+ end
872
+ end
873
+ end
874
+ end