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,285 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::Profile do
4
+ def app
5
+ TentD::API.new
6
+ end
7
+
8
+ let(:env) { {} }
9
+ let(:params) { Hash.new }
10
+
11
+ let(:authorized_info_types) { [] }
12
+ let(:authorized_scopes) { [] }
13
+ let(:app_authorization) do
14
+ Fabricate(
15
+ :app_authorization,
16
+ :app => Fabricate(:app),
17
+ :profile_info_types => authorized_info_types,
18
+ :scopes => authorized_scopes
19
+ )
20
+ end
21
+
22
+ let(:basic_info_type) { "https://tent.io/types/info/basic/v0.1.0" }
23
+ let(:basic_info_content) do
24
+ {
25
+ "name" => "John Smith",
26
+ "age" => 21
27
+ }
28
+ end
29
+
30
+ let(:work_history_type) { "https://tent.io/types/info/work-history/v0.1.0" }
31
+ let(:work_history_content) do
32
+ {
33
+ "employers" => ["Foo Corp"]
34
+ }
35
+ end
36
+
37
+ def url_encode_type(type)
38
+ URI.encode(type, ":/")
39
+ end
40
+
41
+ def create_info(type, content, options = {})
42
+ Fabricate(:profile_info, :public => options[:public], :type => type, :content => content)
43
+ end
44
+
45
+ describe 'GET /profile' do
46
+ context 'when read_profile scope authorized' do
47
+ let(:authorized_scopes) { [:read_profile] }
48
+ before { env['current_auth'] = app_authorization }
49
+
50
+ context 'when authorized for all info types' do
51
+ let(:authorized_info_types) { ['all'] }
52
+
53
+ it 'should return all info types' do
54
+ TentD::Model::ProfileInfo.all.destroy
55
+
56
+ profile_infos = []
57
+ profile_infos << Fabricate(:profile_info, :public => false)
58
+ profile_infos << Fabricate(:basic_profile_info, :public => false)
59
+
60
+ json_get '/profile', params, env
61
+ expect(last_response.body).to eq({
62
+ "#{ profile_infos.first.type.uri }" => profile_infos.first.content.merge(:permissions => profile_infos.first.permissions_json),
63
+ "#{ profile_infos.last.type.uri }" => profile_infos.last.content.merge(:permissions => profile_infos.first.permissions_json)
64
+ }.to_json)
65
+ end
66
+ end
67
+
68
+ context 'when authorized for specific info types' do
69
+ let(:authorized_info_types) { ['https://tent.io/types/info/basic'] }
70
+
71
+ it 'should only return authorized info types' do
72
+ TentD::Model::ProfileInfo.all.destroy
73
+
74
+ profile_infos = []
75
+ profile_infos << Fabricate(:profile_info, :public => false, :type => "https://tent.io/types/info/basic/v0.1.0")
76
+ profile_infos << Fabricate(:profile_info, :public => false)
77
+
78
+ json_get '/profile', params, env
79
+ expect(last_response.body).to eq({
80
+ "#{ profile_infos.first.type.uri }" => profile_infos.first.content.merge(:permissions => profile_infos.first.permissions_json)
81
+ }.to_json)
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'when read_profile scope unauthorized' do
87
+ it 'should only return public profile into types' do
88
+ TentD::Model::ProfileInfo.all.destroy
89
+
90
+ profile_infos = []
91
+ profile_infos << Fabricate(:profile_info, :public => true)
92
+ profile_infos << Fabricate(:basic_profile_info, :public => false)
93
+
94
+ json_get '/profile', params, env
95
+ expect(last_response.body).to eq({
96
+ "#{ profile_infos.first.type.uri }" => profile_infos.first.content.merge(:permissions => profile_infos.first.permissions_json)
97
+ }.to_json)
98
+ end
99
+ end
100
+ end
101
+
102
+ describe 'PUT /profile/:type_url' do
103
+ before { env['current_auth'] = app_authorization }
104
+
105
+ context 'when authorized' do
106
+ let(:authorized_scopes) { [:write_profile] }
107
+
108
+ can_update_basic_info_type = proc do
109
+ it 'should update info type' do
110
+ info = create_info(basic_info_type, basic_info_content, :public => false)
111
+
112
+ expect(info.type.uri).to eq(basic_info_type)
113
+
114
+ data = {
115
+ "name" => "John Doe"
116
+ }
117
+
118
+ json_put "/profile/#{url_encode_type(basic_info_type)}", data, env
119
+
120
+ expect(last_response.status).to eq(200)
121
+ expect(info.reload.content).to eq(data)
122
+
123
+ expect(info.reload.type.uri).to eq(basic_info_type)
124
+
125
+ end
126
+
127
+ it 'should create unless exists' do
128
+ TentD::Model::ProfileInfo.all.destroy
129
+
130
+ data = {
131
+ "name" => "John Doe"
132
+ }
133
+
134
+ expect(lambda {
135
+ json_put "/profile/#{url_encode_type(basic_info_type)}", data, env
136
+ }).to change(TentD::Model::ProfileInfo, :count).by(1)
137
+
138
+ info = TentD::Model::ProfileInfo.last
139
+ expect(last_response.status).to eq(200)
140
+ expect(info.content).to eq(data)
141
+ expect(info.type.version).to eq('0.1.0')
142
+ end
143
+ end
144
+
145
+ context 'when all info types authorized' do
146
+ let(:authorized_info_types) { ['all'] }
147
+
148
+ context '', &can_update_basic_info_type
149
+ end
150
+
151
+ context 'when specific info types authorized' do
152
+ context 'when :type_url authorized info type' do
153
+ let(:authorized_info_types) { [basic_info_type] }
154
+
155
+ context '', &can_update_basic_info_type
156
+ end
157
+
158
+ context 'when :type_url not authoried info type' do
159
+ it 'should return 403' do
160
+ json_put "/profile/#{url_encode_type(basic_info_type)}", params, env
161
+ expect(last_response.status).to eq(403)
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ context 'when not authorized' do
168
+ it 'should return 403' do
169
+ json_put "/profile/#{url_encode_type(basic_info_type)}", params, env
170
+ expect(last_response.status).to eq(403)
171
+ end
172
+ end
173
+ end
174
+
175
+ describe 'PATCH /profile' do
176
+ before { env['current_auth'] = app_authorization }
177
+
178
+ def diff_encode_type(type)
179
+ type.gsub(/~/, '~0').gsub(/\//, '~1')
180
+ end
181
+
182
+ context 'when authorized' do
183
+ let(:authorized_scopes) { [:write_profile] }
184
+
185
+ can_update_basic_info_type = proc do
186
+ it 'should update basic info with diff' do
187
+ TentD::Model::ProfileInfo.all.destroy
188
+
189
+ info = create_info(basic_info_type, basic_info_content, :public => false)
190
+
191
+ diff = [
192
+ { "add" => "#{diff_encode_type(basic_info_type)}/city", "value" => "New York" },
193
+ { "remove" => "#{diff_encode_type(basic_info_type)}/age" },
194
+ { "replace" => "#{diff_encode_type(basic_info_type)}/name", "value" => "Alex Smith" }
195
+ ]
196
+
197
+ expected_data = {
198
+ "city" => "New York",
199
+ "name" => "Alex Smith",
200
+ }
201
+
202
+ json_patch "/profile", diff, env
203
+
204
+ expect(last_response.status).to eq(200)
205
+ expect(info.reload.content).to eq(expected_data)
206
+ end
207
+
208
+ it 'should return 422 if diff tests fail' do
209
+ TentD::Model::ProfileInfo.all.destroy
210
+
211
+ info = create_info(basic_info_type, basic_info_content, :public => false)
212
+
213
+ diff = [
214
+ { "add" => "#{diff_encode_type(basic_info_type)}/city", "value" => "New York" },
215
+ { "remove" => "#{diff_encode_type(basic_info_type)}/age" },
216
+ { "replace" => "#{diff_encode_type(basic_info_type)}/name", "value" => "Alex Smith" },
217
+ { "test" => "#{diff_encode_type(basic_info_type)}/age", "value" => 40 },
218
+ ]
219
+
220
+ expected_data = info.content
221
+
222
+ json_patch "/profile", diff, env
223
+
224
+ expect(last_response.status).to eq(422)
225
+ expect(info.reload.content).to eq(expected_data)
226
+ end
227
+ end
228
+
229
+ context 'when all info types authorized' do
230
+ let(:authorized_info_types) { ['all'] }
231
+
232
+ context '', &can_update_basic_info_type
233
+
234
+ it 'should update any info' do
235
+ TentD::Model::ProfileInfo.all.destroy
236
+
237
+ info = create_info(basic_info_type, basic_info_content, :public => false)
238
+
239
+ diff = [
240
+ { "add" => "#{diff_encode_type(basic_info_type)}/city", "value" => "New York" },
241
+ { "remove" => "#{diff_encode_type(basic_info_type)}/age" },
242
+ { "replace" => "#{diff_encode_type(basic_info_type)}/name", "value" => "Alex Smith" },
243
+ { "add" => "#{diff_encode_type(work_history_type)}/employers", "value" => ["Foo Corp"] },
244
+ { "move" => "#{diff_encode_type(basic_info_type)}/name", "to" => "#{diff_encode_type(work_history_type)}/name" },
245
+ { "add" => "#{diff_encode_type(work_history_type)}/employers/1", "value" => "Bar Corp" },
246
+ { "add" => "#{diff_encode_type(work_history_type)}/city", "value" => "London" }
247
+ ]
248
+
249
+ expected_basic_data = {
250
+ "city" => "New York",
251
+ }
252
+
253
+ expected_work_data = {
254
+ "name" => "Alex Smith",
255
+ "employers" => ["Foo Corp", "Bar Corp"],
256
+ "city" => "London",
257
+ }
258
+
259
+ expect(lambda {
260
+ json_patch "/profile", diff, env
261
+ }).to change(TentD::Model::ProfileInfo, :count).by(1)
262
+
263
+ expect(last_response.status).to eq(200)
264
+ expect(info.reload.content).to eq(expected_basic_data)
265
+
266
+ work_info = TentD::Model::ProfileInfo.last
267
+ expect(work_info.content).to eq(expected_work_data)
268
+ end
269
+ end
270
+
271
+ context 'when specific info types authorized' do
272
+ let(:authorized_info_types) { [basic_info_type] }
273
+
274
+ context '', &can_update_basic_info_type
275
+ end
276
+ end
277
+
278
+ context 'when not authorized' do
279
+ it 'should return 403' do
280
+ json_patch '/profile', params, env
281
+ expect(last_response.status).to eq(403)
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::Router do
4
+ class TestMiddleware < TentD::API::Middleware
5
+ def action(env)
6
+ env['response'] = { 'params' => env['params'] }
7
+ env
8
+ end
9
+ end
10
+
11
+ class TestMiddlewarePrematureResponse < TentD::API::Middleware
12
+ def action(env)
13
+ [200, { 'Content-Type' => 'text/plain' }, 'Premature-Response']
14
+ end
15
+ end
16
+
17
+ class TestMountedApp
18
+ include TentD::API::Router
19
+
20
+ get '/chunky/:bacon' do |b|
21
+ b.use TestMiddleware
22
+ end
23
+ end
24
+
25
+ class PrefixMountedApp
26
+ def initialize(app)
27
+ @app = app
28
+ end
29
+
30
+ def call(env)
31
+ myprefix = '/prefix'
32
+
33
+ if env['PATH_INFO'].start_with?(myprefix)
34
+ env['SCRIPT_NAME'] = env['SCRIPT_NAME'][0..-2] if env['SCRIPT_NAME'].end_with?('/') # strip trailing slash
35
+ env['SCRIPT_NAME'] += myprefix
36
+
37
+ env['PATH_INFO'].sub! myprefix, ''
38
+ @app.call(env)
39
+ end
40
+ end
41
+ end
42
+
43
+ class TestApp
44
+ include TentD::API::Router
45
+
46
+ get '/foo/:bar' do |b|
47
+ b.use TestMiddleware
48
+ end
49
+
50
+ get '/premature/response' do |b|
51
+ b.use TestMiddlewarePrematureResponse
52
+ b.use TestMiddleware
53
+ end
54
+
55
+ post %r{^/foo/([^/]+)/bar} do |b|
56
+ b.use TestMiddleware
57
+ end
58
+
59
+ mount TestMountedApp
60
+ end
61
+
62
+ def app
63
+ TestApp.new
64
+ end
65
+
66
+ let(:env) { { 'authorized_scopes' => [] } }
67
+
68
+ context "as a mounted app with a prefix" do
69
+ let(:app) { PrefixMountedApp.new(TestApp.new) }
70
+
71
+ it "still matches the path name" do
72
+ json_get '/prefix/foo/baz', nil, env
73
+ expect(last_response.status).to eq(200)
74
+ expect(JSON.parse(last_response.body)['params']['bar']).to eq('baz')
75
+ end
76
+ end
77
+
78
+ it "should extract params" do
79
+ json_get '/foo/baz', nil, env
80
+ expect(last_response.status).to eq(200)
81
+ expect(JSON.parse(last_response.body)['params']['bar']).to eq('baz')
82
+ end
83
+
84
+ it "should merge both sets of params" do
85
+ json_post '/foo/baz/bar?chunky=bacon', nil, env
86
+ expect(last_response.status).to eq(200)
87
+ actual_body = JSON.parse(last_response.body)
88
+ expect(actual_body['params']['chunky']).to eq('bacon')
89
+ expect(actual_body['params']['captures']).to include('baz')
90
+ end
91
+
92
+ it "should work with mount" do
93
+ json_get '/chunky/crunch', nil, env
94
+ expect(last_response.status).to eq(200)
95
+ expect(JSON.parse(last_response.body)['params']['bacon']).to eq('crunch')
96
+ end
97
+
98
+ it "should allow middleware to prematurely respond" do
99
+ json_get '/premature/response', nil, env
100
+ expect(last_response.body).to eq('Premature-Response')
101
+ end
102
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::Model::AppAuthorization do
4
+ let(:app_authorization) { Fabricate(:app_authorization, :app => Fabricate(:app)) }
5
+
6
+ describe '.follow_url' do
7
+ it 'should find app authorization with follow_ui scope and follow_url' do
8
+ Fabricate(:app_authorization, :app => Fabricate(:app), :scopes => %w{ follow_ui }, :follow_url => 'https://follow.example.org/awesome-ui')
9
+ app_auth = Fabricate(:app_authorization, :app => Fabricate(:app), :scopes => %w{ read_posts follow_ui write_posts }, :follow_url => 'https://follow.example.com')
10
+ entity = 'https://johndoe.example.org'
11
+
12
+ follow_url = described_class.follow_url(entity)
13
+ expect(follow_url).to eq("#{app_auth.follow_url}?entity=#{URI.encode_www_form_component(entity)}")
14
+ end
15
+ end
16
+
17
+ describe '#as_json' do
18
+ let(:app_attributes) do
19
+ {
20
+ :id => app_authorization.public_id,
21
+ :post_types => app_authorization.post_types,
22
+ :profile_info_types => app_authorization.profile_info_types,
23
+ :scopes => app_authorization.scopes,
24
+ :notification_url => app_authorization.notification_url,
25
+ :created_at => app_authorization.created_at.to_time.to_i,
26
+ :updated_at => app_authorization.updated_at.to_time.to_i
27
+ }
28
+ end
29
+
30
+ context 'with options[:app]' do
31
+ let(:options) { { :app => true } }
32
+
33
+ it 'should return everything except mac stuff' do
34
+ expect(app_authorization.as_json(options)).to eq(app_attributes)
35
+ end
36
+
37
+ context 'and options[:authorization_token]' do
38
+ before { options[:authorization_token] = true }
39
+ it 'should return token code' do
40
+ expect(app_authorization.as_json(options)).to eq(app_attributes.merge(
41
+ :token_code => app_authorization.token_code
42
+ ))
43
+ end
44
+ end
45
+
46
+ context 'and options[:mac]' do
47
+ before { options[:mac] = true }
48
+
49
+ it 'should return mac stuff' do
50
+ expect(app_authorization.as_json(options)).to eq(app_attributes.merge(
51
+ :mac_key_id => app_authorization.mac_key_id,
52
+ :mac_key => app_authorization.mac_key,
53
+ :mac_algorithm => app_authorization.mac_algorithm
54
+ ))
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::Model::App do
4
+ let(:app) { Fabricate(:app) }
5
+
6
+ describe '#as_json' do
7
+ let(:public_attributes) do
8
+ {
9
+ :id => app.public_id,
10
+ :name => app.name,
11
+ :description => app.description,
12
+ :url => app.url,
13
+ :icon => app.icon,
14
+ :redirect_uris => app.redirect_uris,
15
+ :scopes => app.scopes,
16
+ :authorizations => []
17
+ }
18
+ end
19
+
20
+ context 'without options' do
21
+ it 'should return public_attributes' do
22
+ expect(app.as_json).to eq(public_attributes)
23
+ end
24
+ end
25
+
26
+ with_mac_keys = proc do
27
+ it 'should return mac keys' do
28
+ expect(app.as_json(options)).to eq(public_attributes.merge(
29
+ :mac_key_id => app.mac_key_id,
30
+ :mac_key => app.mac_key,
31
+ :mac_algorithm => app.mac_algorithm
32
+ ))
33
+ end
34
+ end
35
+
36
+ context 'with options[:mac]' do
37
+ let(:options) { { :mac => true } }
38
+ context &with_mac_keys
39
+ end
40
+
41
+ context 'with options[:exclude]' do
42
+ it 'should excude specified keys' do
43
+ attribtues = public_attributes
44
+ attribtues.delete(:id)
45
+ expect(app.as_json(:exclude => [:id])).to eq(attribtues)
46
+ end
47
+ end
48
+
49
+ context 'with options[:app]' do
50
+ let!(:app) { Fabricate(:app) }
51
+ let!(:authorization) { Fabricate(:app_authorization, :app => app) }
52
+
53
+ it 'should return authorizations' do
54
+ options = { :app => true }
55
+ expect(app.as_json(options)).to eq(public_attributes.merge(
56
+ :authorizations => [authorization.as_json(options)],
57
+ :created_at => app.created_at.to_time.to_i,
58
+ :updated_at => app.updated_at.to_time.to_i
59
+ ))
60
+ end
61
+ end
62
+ end
63
+ end