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,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
+