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,145 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::Model::NotificationSubscription do
4
+ it 'should parse view from type URI before save' do
5
+ instance = described_class.new(:type => "https://tent.io/types/post/photo/v0.1.x#meta")
6
+ expect(instance.save).to be_true
7
+ expect(instance.reload.type_view).to eq('meta')
8
+ end
9
+
10
+ it 'should parse version from type URI' do
11
+ instance = described_class.new(:type => "https://tent.io/types/post/photo/v0.1.x#meta")
12
+ expect(instance.type_version).to eq("0.1.x")
13
+ end
14
+
15
+ it 'should parse view from type URI' do
16
+ instance = described_class.new(:type => "https://tent.io/types/post/photo/v0.1.x#meta")
17
+ expect(instance.type_view).to eq('meta')
18
+ end
19
+
20
+ it 'should remove version and view from type' do
21
+ instance = described_class.create(:type => "https://tent.io/types/post/photo/v0.1.x#meta")
22
+ expect(instance.type.base).to eq('https://tent.io/types/post/photo')
23
+ end
24
+
25
+ it 'should create if type is all' do
26
+ expect(lambda {
27
+ described_class.create(:type => 'all')
28
+ }).to change(described_class, :count).by(1)
29
+ end
30
+
31
+ it 'should require type_version unless type_base set to all' do
32
+ expect(lambda {
33
+ described_class.create(:type => 'https://tent.io/types/post/photo')
34
+ }).to_not change(described_class, :count)
35
+ end
36
+
37
+ context "notifications" do
38
+ let(:http_stubs) { Faraday::Adapter::Test::Stubs.new }
39
+ let(:post) { Fabricate(:post) }
40
+ before { TentD::Model::NotificationSubscription.all.destroy! }
41
+
42
+ context "to everyone" do
43
+ let!(:subscription) { Fabricate(:notification_subscription, :follower => Fabricate(:follower)) }
44
+
45
+ it 'should notify about a post' do
46
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
47
+ http_stubs.post('/notifications/asdf') { [200, {}, nil] }
48
+
49
+ described_class.notify_all(post.type.uri, post.id)
50
+ http_stubs.verify_stubbed_calls
51
+ end
52
+ end
53
+
54
+ context "to a follower" do
55
+ let(:subscription) { Fabricate(:notification_subscription, :follower => Fabricate(:follower)) }
56
+
57
+ it 'should notify about a post' do
58
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
59
+ http_stubs.post('/notifications/asdf') { [200, {}, nil] }
60
+ expect(subscription.notify_about(post.id)).to be_true
61
+ end
62
+ end
63
+
64
+ context "to an app" do
65
+ let(:subscription) { Fabricate(:notification_subscription, :app_authorization => Fabricate(:app_authorization, :app => Fabricate(:app))) }
66
+
67
+ it 'should notify about a post' do
68
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
69
+ http_stubs.post('/notifications') { [200, {}, nil] }
70
+ expect(subscription.notify_about(post.id)).to be_true
71
+ end
72
+ end
73
+
74
+ context ".notify_entity(entity, post_id)" do
75
+ before {
76
+ TentClient.any_instance.stubs(:faraday_adapter).returns([:test, http_stubs])
77
+ }
78
+
79
+ let(:post) { Fabricate(:post) }
80
+ let(:path_prefix) { URI(server_url).path }
81
+
82
+ notification_examples = proc do
83
+ it "should send notification" do
84
+ http_stubs.post("#{path_prefix}/posts") { |env|
85
+ expect_server(env, server_url)
86
+ [200, {}, '']
87
+ }
88
+
89
+ described_class.notify_entity(entity, post.id)
90
+
91
+ http_stubs.verify_stubbed_calls
92
+ end
93
+ end
94
+
95
+ context "entity exists as follower" do
96
+ let(:entity) { 'https://example.com/johndoe' }
97
+ let(:server_url) { 'https://example.org/johndoe/tent' }
98
+ before { Fabricate(:follower, :entity => entity, :server_urls => server_url) }
99
+
100
+ it "should send notification" do
101
+ http_stubs.post("#{path_prefix}/notifications/asdf") { |env|
102
+ expect_server(env, server_url)
103
+ [200, {}, '']
104
+ }
105
+
106
+ described_class.notify_entity(entity, post.id)
107
+
108
+ http_stubs.verify_stubbed_calls
109
+ end
110
+ end
111
+
112
+ context "entity exists as following" do
113
+ let(:entity) { 'https://example.com/alexsmith' }
114
+ let(:server_url) { 'https://alex.example.org/tent' }
115
+ before { Fabricate(:following, :entity => entity, :server_urls => server_url) }
116
+
117
+ context &notification_examples
118
+ end
119
+
120
+ context "entity is not a follower or following" do
121
+ let(:entity) { 'https://bob.example.com' }
122
+ let(:server_url) { 'https://bob.example.org/tent' }
123
+
124
+ let(:link_header) { %(<#{server_url}/profile>; rel="%s") % TentClient::PROFILE_REL }
125
+ let(:tent_profile) { %({"https://tent.io/types/info/core/v0.1.0":{"licenses":["http://creativecommons.org/licenses/by/3.0/"],"entity":"#{entity}","servers":["#{server_url}"]}}) }
126
+
127
+ let(:http_stubs) { Faraday::Adapter::Test::Stubs.new }
128
+ let(:client) { TentClient.new(nil, :faraday_adapter => [:test, http_stubs]) }
129
+
130
+ before do
131
+ http_stubs.head("/") { |env|
132
+ expect_server(env, entity)
133
+ [200, { 'Link' => link_header }, '']
134
+ }
135
+ http_stubs.get("#{path_prefix}/profile") { |env|
136
+ expect_server(env, server_url)
137
+ [200, { 'Content-Type' => TentClient::MEDIA_TYPE }, tent_profile]
138
+ }
139
+ end
140
+
141
+ context 'with discovery', &notification_examples
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,658 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::Model::Post do
4
+ let(:http_stubs) { Faraday::Adapter::Test::Stubs.new }
5
+
6
+ describe '.create(data)' do
7
+ context 'when posted on behalf of this server (original)' do
8
+ class TestNotificationQueue
9
+ attr_accessor :items
10
+ def initialize
11
+ @items = []
12
+ end
13
+
14
+ def push(item)
15
+ (@items ||= []) << item
16
+ end
17
+ end
18
+
19
+ let(:subscribed_follower) { Fabricate(:follower, :entity => 'https://johnsmith.example.com') }
20
+ let(:other_follower) { Fabricate(:follower, :entity => 'https://marks.example.com') }
21
+ let(:entity_url) { 'https://alexdoe.example.org' }
22
+
23
+ it 'should send notification to all mentioned entities not already subscribed' do
24
+ post_type = 'https://tent.io/types/post/status/v0.1.0'
25
+ post_attrs = Fabricate.build(:post).attributes.merge(
26
+ :type => post_type,
27
+ :mentions => [
28
+ { :entity => subscribed_follower.entity },
29
+ { :entity => other_follower.entity },
30
+ { :entity => entity_url },
31
+ ]
32
+ )
33
+
34
+ notification_subscription = Fabricate(:notification_subscription, :follower => subscribed_follower, :type => post_type)
35
+ queue = TestNotificationQueue.new
36
+ with_constants "TentD::Notifications::NOTIFY_ENTITY_QUEUE" => queue do
37
+ expect(lambda {
38
+ described_class.create(post_attrs)
39
+ }).to change(queue.items, :size).by(2)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ describe 'find_with_permissions(id, current_auth)' do
46
+ shared_examples 'current_auth param' do
47
+ let(:group) { Fabricate(:group, :name => 'family') }
48
+ let(:post) { Fabricate(:post, :public => false) }
49
+
50
+ context 'when has permission via explicit' do
51
+ before do
52
+ TentD::Model::Permission.create(
53
+ :post_id => post.id,
54
+ current_auth.permissible_foreign_key => current_auth.id
55
+ )
56
+ end
57
+
58
+ it 'should return post' do
59
+ returned_post = described_class.find_with_permissions(post.id, current_auth)
60
+ expect(returned_post).to eq(post)
61
+ end
62
+
63
+ end
64
+
65
+ context 'when has permission via group' do
66
+ before do
67
+ group.permissions.create(:post_id => post.id)
68
+ current_auth.groups = [group.public_id]
69
+ current_auth.save
70
+ end
71
+
72
+ it 'should return post' do
73
+ returned_post = described_class.find_with_permissions(post.id, current_auth)
74
+ expect(returned_post).to eq(post)
75
+ end
76
+ end
77
+
78
+ context 'when does not have permission' do
79
+ it 'should return nil' do
80
+ returned_post = described_class.find_with_permissions(post.id, current_auth)
81
+ expect(returned_post).to be_nil
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'when Follower' do
87
+ let(:current_auth) { Fabricate(:follower, :groups => []) }
88
+
89
+ it_behaves_like 'current_auth param'
90
+ end
91
+ end
92
+
93
+ describe 'changing type' do
94
+ it 'updates the version and view' do
95
+ p = Fabricate(:post)
96
+ p.type = 'http://me.io/sometype/v0.1.0'
97
+ p.save
98
+ expect(p.type_version).to eq('0.1.0')
99
+
100
+ p.update type_version: '0.1.0'
101
+
102
+ p = TentD::Model::Post.first(:id => p.id)
103
+
104
+
105
+ p.type = 'http://mytype.io/v0.3.0'
106
+ p.save
107
+ expect(p.type_version).to eq('0.3.0')
108
+ end
109
+ end
110
+
111
+ describe 'fetch_with_permissions(params, current_auth)' do
112
+ let(:group) { Fabricate(:group, :name => 'friends') }
113
+ let(:params) { Hash.new }
114
+
115
+ with_params = proc do
116
+ before do
117
+ if current_auth && create_permissions == true
118
+ @authorize_post = lambda { |post| TentD::Model::Permission.create(:post_id => post.id, current_auth.permissible_foreign_key => current_auth.id) }
119
+ end
120
+ end
121
+
122
+ it 'should order by published_at desc' do
123
+ TentD::Model::Post.all.destroy
124
+ latest_post = Fabricate(:post, :public => !create_permissions, :published_at => Time.at(Time.now.to_i+86400)) # 1.day.from_now
125
+ first_post = Fabricate(:post, :public => !create_permissions, :published_at => Time.at(Time.now.to_i-86400)) # 1.day.ago
126
+
127
+ if create_permissions
128
+ [first_post, latest_post].each { |post| @authorize_post.call(post) }
129
+ end
130
+
131
+ returned_post = described_class.fetch_with_permissions(params, current_auth)
132
+ expect(returned_post.map(&:public_id)).to eq([latest_post.public_id, first_post.public_id])
133
+ end
134
+
135
+ context '[:since_id]' do
136
+ it 'should only return posts with ids > :since_id' do
137
+ TentD::Model::Post.all.destroy!
138
+ since_post = Fabricate(:post, :public => !create_permissions)
139
+ post = Fabricate(:post, :public => !create_permissions)
140
+
141
+ if create_permissions
142
+ [post, since_post].each { |post| @authorize_post.call(post) }
143
+ end
144
+
145
+ params['since_id'] = since_post.id
146
+
147
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
148
+ expect(returned_posts).to eq([post])
149
+ end
150
+ end
151
+
152
+ context '[:before_id]' do
153
+ it 'should only return posts with ids < :before_id' do
154
+ TentD::Model::Post.all.destroy!
155
+ post = Fabricate(:post, :public => !create_permissions)
156
+ before_post = Fabricate(:post, :public => !create_permissions)
157
+
158
+ if create_permissions
159
+ [post, before_post].each { |post| @authorize_post.call(post) }
160
+ end
161
+
162
+ params['before_id'] = before_post.id
163
+
164
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
165
+ expect(returned_posts).to eq([post])
166
+ end
167
+ end
168
+
169
+ context '[:since_time]' do
170
+ it 'should only return posts with published_at > :since_time' do
171
+ TentD::Model::Post.all.destroy!
172
+ since_post = Fabricate(:post, :public => !create_permissions,
173
+ :published_at => Time.at(Time.now.to_i + (86400 * 10))) # 10.days.from_now
174
+ post = Fabricate(:post, :public => !create_permissions,
175
+ :published_at => Time.at(Time.now.to_i + (86400 * 11))) # 11.days.from_now
176
+
177
+ if create_permissions
178
+ [post, since_post].each { |post| @authorize_post.call(post) }
179
+ end
180
+
181
+ params['since_time'] = since_post.published_at.to_time.to_i.to_s
182
+
183
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
184
+ expect(returned_posts).to eq([post])
185
+ end
186
+ end
187
+
188
+ context '[:before_time]' do
189
+ it 'should only return posts with published_at < :before_time' do
190
+ TentD::Model::Post.all.destroy!
191
+ post = Fabricate(:post, :public => !create_permissions,
192
+ :published_at => Time.at(Time.now.to_i - (86400 * 10))) # 10.days.ago
193
+ before_post = Fabricate(:post, :public => !create_permissions,
194
+ :published_at => Time.at(Time.now.to_i - (86400 * 9))) # 9.days.ago
195
+
196
+ if create_permissions
197
+ [post, before_post].each { |post| @authorize_post.call(post) }
198
+ end
199
+
200
+ params['before_time'] = before_post.published_at.to_time.to_i.to_s
201
+
202
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
203
+ expect(returned_posts).to eq([post])
204
+ end
205
+ end
206
+
207
+ context '[:post_types]' do
208
+ it 'should only return posts type in :post_types' do
209
+ TentD::Model::Post.all.destroy!
210
+ photo_post = Fabricate(:post, :public => !create_permissions, :type_base => "https://tent.io/types/post/photo")
211
+ blog_post = Fabricate(:post, :public => !create_permissions, :type_base => "https://tent.io/types/post/blog")
212
+ status_post = Fabricate(:post, :public => !create_permissions, :type_base => "https://tent.io/types/post/status")
213
+
214
+ if create_permissions
215
+ [photo_post, blog_post, status_post].each { |post| @authorize_post.call(post) }
216
+ end
217
+
218
+ params['post_types'] = [blog_post, photo_post].map { |p| URI.escape(p.type.uri, "://") }.join(',')
219
+
220
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
221
+ expect(returned_posts.size).to eq(2)
222
+ expect(returned_posts).to include(photo_post)
223
+ expect(returned_posts).to include(blog_post)
224
+ end
225
+ end
226
+
227
+ context '[:limit]' do
228
+ it 'should return at most :limit number of posts' do
229
+ limit = 1
230
+ posts = 0.upto(limit).map { Fabricate(:post, :public => !create_permissions) }
231
+
232
+ if create_permissions
233
+ posts.each { |post| @authorize_post.call(post) }
234
+ end
235
+
236
+ params['limit'] = limit.to_s
237
+
238
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
239
+ expect(returned_posts.size).to eq(limit)
240
+ end
241
+
242
+ it 'should never return more than TentD::API::MAX_PER_PAGE' do
243
+ limit = 1
244
+ posts = 0.upto(limit).map { Fabricate(:post, :public => !create_permissions) }
245
+
246
+ if create_permissions
247
+ posts.each { |post| @authorize_post.call(post) }
248
+ end
249
+
250
+ params['limit'] = limit.to_s
251
+
252
+ with_constants "TentD::API::MAX_PER_PAGE" => 0 do
253
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
254
+ expect(returned_posts.size).to eq(0)
255
+ end
256
+ end
257
+ end
258
+
259
+ context 'no [:limit]' do
260
+ it 'should return TentD::API::PER_PAGE number of posts' do
261
+ with_constants "TentD::API::PER_PAGE" => 1, "TentD::API::MAX_PER_PAGE" => 2 do
262
+ limit = TentD::API::PER_PAGE
263
+ posts = 0.upto(limit+1).map { Fabricate(:post, :public => !create_permissions) }
264
+
265
+ if create_permissions
266
+ posts.each { |post| @authorize_post.call(post) }
267
+ end
268
+
269
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
270
+ expect(returned_posts.size).to eq(limit)
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ context 'without current_auth' do
277
+ let(:current_auth) { nil }
278
+ let(:create_permissions) { false }
279
+
280
+ it 'should only fetch public posts' do
281
+ private_post = Fabricate(:post, :public => false)
282
+ public_post = Fabricate(:post, :public => true)
283
+ returned_posts = described_class.fetch_with_permissions(params, nil)
284
+ expect(returned_posts).to include(public_post)
285
+ expect(returned_posts).to_not include(private_post)
286
+ end
287
+
288
+ context 'with params', &with_params
289
+ end
290
+
291
+ context 'with current_auth' do
292
+ let(:create_permissions) { false }
293
+ current_auth_stuff = proc do
294
+ context 'with permission' do
295
+ it 'should return private posts' do
296
+ private_post = Fabricate(:post, :public => false)
297
+ public_post = Fabricate(:post, :public => true)
298
+ TentD::Model::Permission.create(:post_id => private_post.id, current_auth.permissible_foreign_key => current_auth.id)
299
+
300
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
301
+ expect(returned_posts).to include(private_post)
302
+ expect(returned_posts).to include(public_post)
303
+ end
304
+
305
+ context 'with params' do
306
+ context 'private posts' do
307
+ let(:create_permissions) { true }
308
+ context '', &with_params
309
+ end
310
+
311
+ context 'public posts', &with_params
312
+ end
313
+ end
314
+
315
+ context 'without permission' do
316
+ it 'should only return public posts' do
317
+ private_post = Fabricate(:post, :public => false)
318
+ public_post = Fabricate(:post, :public => true)
319
+
320
+ returned_posts = described_class.fetch_with_permissions(params, current_auth)
321
+ expect(returned_posts).to_not include(private_post)
322
+ expect(returned_posts).to include(public_post)
323
+ end
324
+
325
+ context 'with params' do
326
+ context '', &with_params
327
+ end
328
+ end
329
+ end
330
+
331
+ context 'when Follower' do
332
+ let(:current_auth) { Fabricate(:follower) }
333
+ context '', &current_auth_stuff
334
+ end
335
+ end
336
+ end
337
+
338
+ it "should persist with proper serialization" do
339
+ attributes = {
340
+ :entity => "https://example.org",
341
+ :type_base => "https://tent.io/types/post/status",
342
+ :type_version => "0.1.0",
343
+ :licenses => ["http://creativecommons.org/licenses/by-nc-sa/3.0/", "http://www.gnu.org/copyleft/gpl.html"],
344
+ :content => {
345
+ "text" => "Voluptate nulla et similique sed dignissimos ea. Dignissimos sint reiciendis voluptas. Aliquid id qui nihil illum omnis. Explicabo ipsum non blanditiis aut aperiam enim ab."
346
+ }
347
+ }
348
+
349
+ post = described_class.create(attributes)
350
+ post = described_class.first(:id => post.id)
351
+ attributes.each_pair do |k,v|
352
+ if k == :type
353
+ actual_value = post.type.uri
354
+ else
355
+ actual_value = post.send(k)
356
+ end
357
+ expect(actual_value).to eq(v)
358
+ end
359
+ end
360
+
361
+ describe "#as_json" do
362
+ let(:post) { Fabricate(:post) }
363
+
364
+ let(:public_attributes) do
365
+ {
366
+ :id => post.public_id,
367
+ :version => post.is_a?(TentD::Model::Post) ? post.latest_version(:fields => [:version]).version : post.version,
368
+ :entity => post.entity,
369
+ :type => post.type.uri,
370
+ :licenses => post.licenses,
371
+ :content => post.content,
372
+ :mentions => [],
373
+ :app => { :url => post.app_url, :name => post.app_name },
374
+ :attachments => [],
375
+ :permissions => { :public => post.public },
376
+ :published_at => post.published_at.to_time.to_i
377
+ }
378
+ end
379
+
380
+ examples = proc do
381
+ it "should replace id with public_id" do
382
+ expect(post.as_json[:id]).to eq(post.public_id)
383
+ expect(post.as_json).to_not have_key(:public_id)
384
+ end
385
+
386
+ it "should not add id to returned object if excluded" do
387
+ expect(post.as_json(:exclude => :id)).to_not have_key(:id)
388
+ end
389
+
390
+ context 'without options' do
391
+ it 'should only return public attributes' do
392
+ expect(post.as_json).to eq(public_attributes)
393
+ end
394
+ end
395
+
396
+ context 'with options[:permissions] = true' do
397
+ let(:follower) { Fabricate(:follower) }
398
+ let(:group) { Fabricate(:group) }
399
+ let(:entity_permission) { Fabricate(:permission, :follower_access => follower) }
400
+ let(:group_permission) { Fabricate(:permission, :group => group) }
401
+ let(:post) { Fabricate(:post, :permissions => [entity_permission, group_permission]) }
402
+
403
+ it 'should return detailed permissions' do
404
+ expect(post.as_json(:permissions => true)).to eq(public_attributes.merge(
405
+ :permissions => {
406
+ :public => post.public,
407
+ :groups => [group.public_id],
408
+ :entities => {
409
+ follower.entity => true
410
+ }
411
+ }
412
+ ))
413
+ end
414
+ end
415
+
416
+ context 'with options[:app] = true' do
417
+ it 'should return app relevant data' do
418
+ post.following = Fabricate(:following)
419
+ expect(post.as_json(:app => true)).to eq(public_attributes.merge(
420
+ :received_at => post.received_at.to_time.to_i,
421
+ :updated_at => post.updated_at.to_time.to_i,
422
+ :published_at => post.published_at.to_time.to_i,
423
+ :following_id => post.following.public_id
424
+ ))
425
+ end
426
+ end
427
+
428
+ context 'with options[:exclude]' do
429
+ it 'should return public attributes excluding specified keys' do
430
+ expected_attributes = public_attributes.dup
431
+ expected_attributes.delete(:published_at)
432
+ expect(post.as_json(:exclude => [:published_at])).to eq(expected_attributes)
433
+ end
434
+ end
435
+
436
+ context 'with options[:view]' do
437
+ it 'should return content keys specified by view' do
438
+ post.update(:views => {
439
+ 'foo' => {
440
+ 'content' => [
441
+ 'foo/bar'
442
+ ]
443
+ },
444
+ 'bar' => {
445
+ 'content' => ['baz', 'foo']
446
+ }
447
+ }, :content => {
448
+ 'foo' => { 'bar' => { 'baz' => 'ChunkyBacon' } },
449
+ 'baz' => 'FooBar'
450
+ })
451
+
452
+ expect(post.as_json(:view => 'foo')).to eq(public_attributes.merge(
453
+ :content => {
454
+ 'bar' => { 'baz' => 'ChunkyBacon' }
455
+ }
456
+ ))
457
+
458
+ expect(post.as_json(:view => 'bar')).to eq(public_attributes.merge(
459
+ :content => {
460
+ 'foo' => { 'bar' => { 'baz' => 'ChunkyBacon' } },
461
+ 'baz' => 'FooBar'
462
+ }
463
+ ))
464
+
465
+ expect(post.as_json(:view => 'full')).to eq(public_attributes)
466
+
467
+ expected_attributes = public_attributes.dup
468
+ expected_attributes.delete(:content)
469
+ expected_attributes.delete(:attachments)
470
+ expect(post.as_json(:view => 'meta')).to eq(expected_attributes)
471
+ end
472
+
473
+ it 'should filter attachments' do
474
+ first_attachment = Fabricate(:post_attachment,
475
+ :category => 'foo',
476
+ :type => 'text/plain',
477
+ :name => 'foobar.txt',
478
+ :data => 'Chunky Bacon',
479
+ :size => 4)
480
+ other_attachment = Fabricate(:post_attachment,
481
+ :category => 'bar',
482
+ :type => 'application/javascript',
483
+ :name => 'barbaz.js',
484
+ :data => 'alert("Chunky Bacon")',
485
+ :size => 8)
486
+ post.attachments << first_attachment
487
+ post.attachments << other_attachment
488
+ post.save
489
+
490
+ post.update(:views => {
491
+ 'foo' => {
492
+ 'attachments' => [ { 'category' => 'foo' } ]
493
+ },
494
+ 'text' => {
495
+ 'attachments' => [{ 'type' => 'text/plain' }]
496
+ },
497
+ 'foobar' => {
498
+ 'attachments' => [{ 'name' => 'foobar.txt' }]
499
+ },
500
+ 'foojs' => {
501
+ 'attachments' => [{ 'type' => 'application/javascript' }, { 'category' => 'foo' }]
502
+ },
503
+ 'nothing' => {
504
+ 'attachments' => [{ 'type' => 'text/plain', 'category' => 'bar' }]
505
+ },
506
+ 'invalid' => {
507
+ 'attachments' => [{ 'id' => first_attachment.id }, { 'category' => 'bar' }]
508
+ }
509
+ }, :content => {
510
+ 'foo' => { 'bar' => { 'baz' => 'ChunkyBacon' } },
511
+ 'baz' => 'FooBar'
512
+ })
513
+
514
+ expect(post.as_json(:view => 'foo')).to eq(public_attributes.merge(
515
+ :attachments => [first_attachment],
516
+ :content => {}
517
+ ))
518
+
519
+ expect(post.as_json(:view => 'text')).to eq(public_attributes.merge(
520
+ :attachments => [first_attachment],
521
+ :content => {}
522
+ ))
523
+
524
+ expect(post.as_json(:view => 'foobar')).to eq(public_attributes.merge(
525
+ :attachments => [first_attachment],
526
+ :content => {}
527
+ ))
528
+
529
+ expect(post.as_json(:view => 'foojs')).to eq(public_attributes.merge(
530
+ :attachments => [first_attachment, other_attachment],
531
+ :content => {}
532
+ ))
533
+
534
+ expect(post.as_json(:view => 'nothing')).to eq(public_attributes.merge(
535
+ :attachments => [],
536
+ :content => {}
537
+ ))
538
+
539
+ expect(post.as_json(:view => 'invalid')).to eq(public_attributes.merge(
540
+ :attachments => [other_attachment],
541
+ :content => {}
542
+ ))
543
+
544
+ expect(post.as_json(:view => 'full')).to eq(public_attributes.merge(
545
+ :attachments => [first_attachment.as_json, other_attachment.as_json]
546
+ ))
547
+
548
+ expected_attributes = public_attributes.dup
549
+ expected_attributes.delete(:content)
550
+ expected_attributes.delete(:attachments)
551
+ expect(post.as_json(:view => 'meta')).to eq(expected_attributes)
552
+ end
553
+ end
554
+ end
555
+
556
+ context &examples
557
+
558
+ context 'PostVersion' do
559
+ let(:post) { Fabricate(:post).latest_version }
560
+
561
+ context &examples
562
+ end
563
+ end
564
+
565
+ it "should generate public_id on create" do
566
+ post = Fabricate.build(:post)
567
+ expect(post.save).to be_true
568
+ expect(post.public_id).to_not be_nil
569
+ end
570
+
571
+ xit "should ensure public_id is unique" do
572
+ first_post = Fabricate(:post)
573
+ post = Fabricate.build(:post, :public_id => first_post.public_id)
574
+ post.save
575
+ expect(post).to be_saved
576
+ expect(post.public_id).to_not eq(first_post.public_id)
577
+ end
578
+
579
+ describe "can_notify?" do
580
+ let(:post) { Fabricate.build(:post) }
581
+
582
+ context "with app authorization" do
583
+ it "should be true for a public post" do
584
+ expect(post.can_notify?(Fabricate(:app_authorization, :app => Fabricate(:app)))).to be_true
585
+ end
586
+
587
+ describe "with private post" do
588
+ let(:post) { Fabricate.build(:post, :public => false) }
589
+
590
+ it "should be true with read_posts scope" do
591
+ auth = Fabricate.build(:app_authorization, :scopes => [:read_posts])
592
+ expect(post.can_notify?(auth)).to be_true
593
+ end
594
+
595
+ it "should be true with authorized type" do
596
+ auth = Fabricate.build(:app_authorization, :post_types => [post.type.base])
597
+ expect(post.can_notify?(auth)).to be_true
598
+ end
599
+
600
+ it "should be false if unauthorized" do
601
+ auth = Fabricate.build(:app_authorization)
602
+ expect(post.can_notify?(auth)).to be_false
603
+ end
604
+ end
605
+ end
606
+
607
+ context "with follower" do
608
+ it "should be true for a public post" do
609
+ expect(post.can_notify?(Fabricate(:follower))).to be_true
610
+ end
611
+
612
+ describe "with private post" do
613
+ let(:post) { Fabricate(:post, :public => false) }
614
+ let(:follower) { Fabricate(:follower, :groups => [Fabricate(:group).public_id]) }
615
+
616
+ it "should be true for permission group" do
617
+ TentD::Model::Permission.create(:group_public_id => follower.groups.first, :post_id => post.id)
618
+ expect(post.can_notify?(follower)).to be_true
619
+ end
620
+
621
+ it "should be true for explicit permission" do
622
+ TentD::Model::Permission.create(:follower_access_id => follower.id, :post_id => post.id)
623
+ expect(post.can_notify?(follower)).to be_true
624
+ end
625
+
626
+ it "should be false if unauthorized" do
627
+ expect(post.can_notify?(follower)).to be_false
628
+ end
629
+ end
630
+ end
631
+
632
+ context "with following" do
633
+ it "should be true for a public post" do
634
+ expect(post.can_notify?(Fabricate(:following))).to be_true
635
+ end
636
+
637
+ context "with private post" do
638
+ let(:post) { Fabricate(:post, :public => false) }
639
+ let(:following) { Fabricate(:following, :groups => [Fabricate(:group).public_id]) }
640
+
641
+ it "should be true for permission group" do
642
+ Fabricate(:permission, :group_public_id => following.groups.first, :post_id => post.id)
643
+ expect(post.can_notify?(following)).to be_true
644
+ end
645
+
646
+ it "should be true for explicit permission" do
647
+ Fabricate(:permission, :following => following, :post_id => post.id)
648
+ expect(post.can_notify?(following)).to be_true
649
+ end
650
+
651
+ it "should be false if unauthorized" do
652
+ expect(post.can_notify?(following)).to be_false
653
+ end
654
+ end
655
+ end
656
+ end
657
+ end
658
+