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,37 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'bundler/setup'
5
+ require 'mocha_standalone'
6
+ require 'rack/test'
7
+ require 'tentd'
8
+ require 'fabrication'
9
+ require 'tentd/core_ext/hash/slice'
10
+ require 'girl_friday'
11
+ require 'tentd/notifications/girl_friday'
12
+
13
+ Dir["#{File.dirname(__FILE__)}/support/*.rb"].each { |f| require f }
14
+
15
+ ENV['RACK_ENV'] ||= 'test'
16
+
17
+ require 'data_mapper'
18
+
19
+ RSpec.configure do |config|
20
+ config.include Rack::Test::Methods
21
+ config.include JsonRequest
22
+ config.mock_with :mocha
23
+
24
+ config.around do |suite|
25
+ with_constants "TentD::Notifications::NOTIFY_ENTITY_QUEUE" => [], "TentD::Notifications::TRIGGER_QUEUE" => [] do
26
+ suite.run
27
+ end
28
+ end
29
+
30
+ config.before(:suite) do
31
+ GirlFriday::WorkQueue.immediate!
32
+ # DataMapper::Logger.new(STDOUT, :debug)
33
+ DataMapper.setup(:default, ENV['TEST_DATABASE_URL'] || 'postgres://localhost/tent_server_test')
34
+ DataMapper.auto_migrate!
35
+ TentD::Model::User.current = TentD::Model::User.first_or_create
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ def expect_server(env, url)
2
+ expect(env[:url].to_s).to match(url)
3
+ end
@@ -0,0 +1,54 @@
1
+ require 'json'
2
+
3
+ module JsonRequest
4
+ def json_patch(path, data = {}, rack_env = {})
5
+ patch path, data.to_json, { 'CONTENT_TYPE' => TentD::API::MEDIA_TYPE }.merge(rack_env)
6
+ end
7
+
8
+ def json_put(path, data = {}, rack_env= {})
9
+ put path, data.to_json, { 'CONTENT_TYPE' => TentD::API::MEDIA_TYPE }.merge(rack_env)
10
+ end
11
+
12
+ def json_post(path, data = {}, rack_env = {})
13
+ post path, data.to_json, { 'CONTENT_TYPE' => TentD::API::MEDIA_TYPE }.merge(rack_env)
14
+ end
15
+
16
+ def json_get(path, data = {}, rack_env = {})
17
+ get path, data, { 'HTTP_ACCEPT' => TentD::API::MEDIA_TYPE }.merge(rack_env)
18
+ end
19
+
20
+ def multipart_post(path, json, parts, rack_env = {})
21
+ body = build_json_part(json) + build_parts(parts) + "--#{Rack::Multipart::MULTIPART_BOUNDARY}--\r"
22
+ post path, body, { 'CONTENT_TYPE' => "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}",
23
+ 'HTTP_ACCEPT' => TentD::API::MEDIA_TYPE }.merge(rack_env)
24
+ end
25
+
26
+ def multipart_put(path, json, parts, rack_env = {})
27
+ body = build_json_part(json) + build_parts(parts) + "--#{Rack::Multipart::MULTIPART_BOUNDARY}--\r"
28
+ put path, body, { 'CONTENT_TYPE' => "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}",
29
+ 'HTTP_ACCEPT' => TentD::API::MEDIA_TYPE }.merge(rack_env)
30
+ end
31
+
32
+ private
33
+
34
+ def build_json_part(json)
35
+ build_part('post', :filename => 'post.json', :content_type => TentD::API::MEDIA_TYPE, :content => json.to_json)
36
+ end
37
+
38
+ def build_parts(parts)
39
+ parts.map do |k,v|
40
+ v.kind_of?(Array) ? v.each_with_index.map { |part,i| build_part("#{k}[#{i}]", part) } : build_part(k, v)
41
+ end.join
42
+ end
43
+
44
+ def build_part(name, data)
45
+ <<-EOF
46
+ --#{Rack::Multipart::MULTIPART_BOUNDARY}\r
47
+ Content-Disposition: form-data; name="#{name}"; filename="#{Rack::Utils.escape(data[:filename])}"\r
48
+ Content-Type: #{data[:content_type]}\r
49
+ Content-Length: #{data[:content].size}\r
50
+ \r
51
+ #{data[:content]}\r
52
+ EOF
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ def split_constant_string(const_string)
2
+ parent_constant = const_string.to_s.split("::")[0..-2].inject(Object) { |o, c| o.const_get(c) }
3
+ child_constant = const_string.to_s.split("::").last
4
+ [parent_constant, child_constant]
5
+ end
6
+
7
+ def with_constants(constants, &block)
8
+ saved_constants = {}
9
+ constants.each_pair do |constant, val|
10
+ parent, child = split_constant_string(constant)
11
+ saved_constants[ constant ] = parent.const_get( child )
12
+ with_warnings(nil) { parent.const_set( child, val ) }
13
+ end
14
+
15
+ begin
16
+ block.call
17
+ ensure
18
+ constants.each_pair do |constant, val|
19
+ parent, child = split_constant_string(constant)
20
+ with_warnings(nil) { parent.const_set( child, saved_constants[ constant ] ) }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ def with_warnings(flag)
2
+ old_verbose, $VERBOSE = $VERBOSE, flag
3
+ yield
4
+ ensure
5
+ $VERBOSE = old_verbose
6
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::AuthenticationFinalize do
4
+ def app
5
+ TentD::API.new
6
+ end
7
+
8
+ it "should set current_auth" do
9
+ instance = stub(:mac_timestamp_delta => 1346171050)
10
+ env = Hashie::Mash.new({
11
+ "potential_auth" => instance,
12
+ 'hmac' => { 'ts' => "1336363200", 'verified' => true }
13
+ })
14
+ described_class.new(app).call(env)
15
+ expect(env["current_auth"]).to eq(instance)
16
+ end
17
+
18
+ it "should set mac_timestamp_delta on current_auth" do
19
+ now = Time.now; Time.stubs(:now).returns(now)
20
+ delta = now.to_i - 1336363200
21
+ instance = stub(:mac_timestamp_delta => nil)
22
+ env = Hashie::Mash.new({
23
+ "potential_auth" => instance,
24
+ 'hmac' => { 'ts' => "1336363200", 'verified' => true }
25
+ })
26
+ instance.expects(:update).with(:mac_timestamp_delta => delta).returns(true)
27
+ described_class.new(app).call(env)
28
+ end
29
+
30
+ it "should not set mac_timestamp_delta on current_auth if already set" do
31
+ instance = stub(:mac_timestamp_delta => 1346171050)
32
+ env = Hashie::Mash.new({
33
+ "potential_auth" => instance,
34
+ 'hmac' => { 'ts' => "1336363200", 'verified' => true }
35
+ })
36
+ instance.expects(:update).never
37
+ described_class.new(app).call(env)
38
+ end
39
+
40
+ it 'should do nothing unless env.hmac.verified present' do
41
+ env = Hashie::Mash.new({ "potential_auth" => stub(:mac_timestamp_delta => 1346171050) })
42
+ described_class.new(app).call(env)
43
+ expect(env.current_auth).to be_nil
44
+ end
45
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::AuthenticationLookup do
4
+ def app
5
+ TentD::API.new
6
+ end
7
+
8
+ let(:env) { Hashie::Mash.new({}) }
9
+ let(:auth_header) { 'MAC id="%s:h480djs93hd8", ts="1336363200", nonce="dj83hs9s", mac="hqpo01mLJLSYDbxmfRgNMEw38Wg="' }
10
+
11
+ it 'should parse hmac authorization header' do
12
+ TentD::Model::Follower.all.destroy!
13
+ follow = Fabricate(:follower, :mac_key_id => "s:h480djs93hd8")
14
+ env['HTTP_AUTHORIZATION'] = auth_header % 's'
15
+ described_class.new(app).call(env)
16
+ expect(env['hmac']).to eq({
17
+ "algorithm" => "hmac-sha-256",
18
+ "id" => "s:h480djs93hd8",
19
+ "ts" => "1336363200",
20
+ "nonce" => "dj83hs9s",
21
+ "secret" => follow.mac_key,
22
+ "mac" => "hqpo01mLJLSYDbxmfRgNMEw38Wg="
23
+ })
24
+ end
25
+
26
+ it 'should lookup server authentication model' do
27
+ TentD::Model::Follower.all(:mac_key_id => "s:h480djs93hd8").destroy!
28
+ follow = Fabricate(:follower, :mac_key_id => "s:h480djs93hd8")
29
+ expect(follow.saved?).to be_true
30
+ env['HTTP_AUTHORIZATION'] = auth_header % 's'
31
+ described_class.new(app).call(env)
32
+ expect(env.potential_auth).to eq(follow.reload)
33
+ expect(env.hmac.secret).to eq(follow.mac_key)
34
+ expect(env.hmac.algorithm).to eq(follow.mac_algorithm)
35
+ end
36
+
37
+ it 'should lookup app authentication model' do
38
+ TentD::Model::App.all.destroy
39
+ authed_app = Fabricate(:app, :mac_key_id => "a:h480djs93hd8")
40
+ expect(authed_app.saved?).to be_true
41
+ env['HTTP_AUTHORIZATION'] = auth_header % 'a'
42
+ described_class.new(app).call(env)
43
+ expect(env.potential_auth).to eq(authed_app)
44
+ expect(env.hmac.secret).to eq(authed_app.mac_key)
45
+ expect(env.hmac.algorithm).to eq(authed_app.mac_algorithm)
46
+ end
47
+
48
+ it 'should lookup user authentication model' do
49
+ TentD::Model::AppAuthorization.all.destroy!
50
+ authed_user = TentD::Model::AppAuthorization.create(:mac_key_id => "u:h480djs93hd8",
51
+ :app => Fabricate(:app))
52
+ expect(authed_user.saved?).to be_true
53
+ env['HTTP_AUTHORIZATION'] = auth_header % 'u'
54
+ described_class.new(app).call(env)
55
+ expect(env.potential_auth).to eq(authed_user)
56
+ expect(env.hmac.secret).to eq(authed_user.mac_key)
57
+ expect(env.hmac.algorithm).to eq(authed_user.mac_algorithm)
58
+ end
59
+
60
+ it 'should do nothing unless HTTP_AUTHORIZATION header' do
61
+ env = {}
62
+ described_class.new(app).call(env)
63
+ expect(env['hmac']).to be_nil
64
+ end
65
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::AuthenticationVerification do
4
+ def app
5
+ TentD::API.new
6
+ end
7
+
8
+ it 'should verify mac signature' do
9
+ env = Hashie::Mash.new({
10
+ 'hmac' => {
11
+ "id" => "s:h480djs93hd8", "ts" => "1336363200", "nonce" => "dj83hs9s", "mac" => "hqpo01mLJLSYDbxmfRgNMEw38Wg=",
12
+ "secret" => '489dks293j39',
13
+ 'algorithm' => 'hmac-sha-1',
14
+ },
15
+ 'rack.input' => StringIO.new("asdf\nasdf"),
16
+ 'REQUEST_METHOD' => 'POST',
17
+ 'SCRIPT_NAME' => "/resource/1",
18
+ 'QUERY_STRING' => "b=1&a=2",
19
+ 'HTTP_HOST' => "example.com",
20
+ 'SERVER_PORT' => "80"
21
+ })
22
+ described_class.new(app).call(env)
23
+ expect(env.hmac.verified).to be_true
24
+ end
25
+
26
+ it 'should respond 403 Unauthorized if signature fails verification' do
27
+ env = Hashie::Mash.new({
28
+ 'hmac' => {
29
+ "id" => "s:h480djs93hd8", "ts" => "1336363200", "nonce" => "dj83hs9s", "mac" => "hqpo01mLJLSYDbxmfRgNMEw38Wg=",
30
+ "secret" => 'WRONG-KEY',
31
+ 'algorithm' => 'hmac-sha-1',
32
+ },
33
+ 'rack.input' => StringIO.new("asdf\nasdf"),
34
+ 'REQUEST_METHOD' => 'POST',
35
+ 'SCRIPT_NAME' => "/resource/1",
36
+ 'QUERY_STRING' => "b=1&a=2",
37
+ 'HTTP_HOST' => "example.com",
38
+ 'SERVER_PORT' => "80"
39
+ })
40
+ env = described_class.new(app).call(env)
41
+ expect(env).to be_an(Array)
42
+ expect(env.first).to eq(403)
43
+ end
44
+
45
+ it 'should not do anything if no signature' do
46
+ env = Hashie::Mash.new({})
47
+ res = described_class.new(app).call(env)
48
+ expect(env.hmac).to be_nil
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'hashie'
3
+
4
+ describe TentD::API::Authorizable do
5
+ class TestMiddleware2
6
+ include TentD::API::Authorizable
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ authorize_env!(env, :read_posts)
14
+ @app.call(env)
15
+ end
16
+ end
17
+
18
+ class OtherTestMiddleware < TentD::API::Middleware
19
+ def action(env)
20
+ authorize_env!(env, :read_posts)
21
+ env
22
+ end
23
+ end
24
+
25
+ def app
26
+ TentD::API.new
27
+ end
28
+
29
+ let(:env) { Hashie::Mash.new }
30
+ let(:middleware) { TestMiddleware2.new(app) }
31
+
32
+ describe '#authorize_env!(env, scope)' do
33
+ it 'should raise Unauthorized unless env.authorized_scopes includes scope' do
34
+ expect( lambda { middleware.call(env) } ).to raise_error(described_class::Unauthorized)
35
+ end
36
+
37
+ it 'should do nothing if env.authorized_scopes includes scope' do
38
+ env.authorized_scopes = [:read_posts]
39
+ expect( lambda { middleware.call(env) } ).to_not raise_error
40
+ end
41
+
42
+ context 'when TentD::API::Middleware' do
43
+ it 'should respond 403 unless env.authorized_scopes includes scope' do
44
+ response = OtherTestMiddleware.new(app).call(env)
45
+ expect(response).to be_an(Array)
46
+ expect(response.first).to be(403)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require 'hashie'
3
+
4
+ describe TentD::API::Authorization do
5
+ def app
6
+ TentD::API.new
7
+ end
8
+
9
+ let(:env) { Hashie::Mash.new }
10
+
11
+ not_authorized = proc do
12
+ it 'should not authorize scopes' do
13
+ described_class.new(app).call(env)
14
+ expect(env.authorized_scopes).to eq([])
15
+ end
16
+ end
17
+
18
+ context 'without current_auth', &not_authorized
19
+
20
+ context 'with current_auth' do
21
+ context 'when Follower' do
22
+ before do
23
+ env.current_auth = Fabricate(:follower)
24
+ end
25
+
26
+ context '', &not_authorized
27
+ end
28
+
29
+ context 'when AppAuthorization' do
30
+ before do
31
+ env.current_auth = Fabricate(
32
+ :app_authorization,
33
+ :scopes => ['read_posts', 'write_posts'],
34
+ :app => Fabricate(:app)
35
+ )
36
+ end
37
+
38
+ it 'should lookup authorized scopes' do
39
+ described_class.new(app).call(env)
40
+ expect(env.authorized_scopes).to eq([:read_posts, :write_posts])
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ describe TentD::API::Router::CachingHeaders do
4
+ let(:app) { lambda { |env| [200, {}, ''] } }
5
+ let(:middleware) { TentD::API::Router::CachingHeaders.new(app) }
6
+ let(:env) { Hashie::Mash.new('REQUEST_METHOD' => 'GET') }
7
+
8
+ shared_examples 'conditional get' do
9
+ it 'should respond with a 304 when cached' do
10
+ status, headers, body = middleware.call env.merge('response' => response,
11
+ 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate)
12
+ expect(status).to eq(304)
13
+ expect(body).to be_nil
14
+ end
15
+
16
+ it 'should not 304 when not cached' do
17
+ status, headers, body = middleware.call env.merge('response' => response,
18
+ 'HTTP_IF_MODIFIED_SINCE' => (Time.now - 60).httpdate)
19
+ expect(status).to eq(200)
20
+ expect(headers['Last-Modified']).to_not be_nil
21
+ end
22
+ end
23
+
24
+ shared_examples 'public response' do
25
+ it 'should set Cache-Control to public' do
26
+ expect(middleware.call(env.merge('response' => response))[1]['Cache-Control']).to eq('public')
27
+ end
28
+ end
29
+
30
+ shared_examples 'private response' do
31
+ it 'should set Cache-Control to private' do
32
+ expect(middleware.call(env.merge('response' => response))[1]['Cache-Control']).to eq('private')
33
+ end
34
+ end
35
+
36
+ context 'object instance with #updated_at' do
37
+ let(:response) { stub(:updated_at => Time.now-1) }
38
+ it_behaves_like 'conditional get'
39
+ end
40
+
41
+ context 'hash with "updated_at"' do
42
+ let(:response) { { 'updated_at' => Time.now-1 } }
43
+ it_behaves_like 'conditional get'
44
+ end
45
+
46
+ context 'object array with #updated_at' do
47
+ let(:response) { [stub(:updated_at => Time.now-1), stub(:updated_at => Time.now-90)] }
48
+ it_behaves_like 'conditional get'
49
+ end
50
+
51
+ context 'hash array with "updated_at"' do
52
+ let(:response) { [{ "updated_at" => Time.now-1 }, { "updated_at" => Time.now-90 }] }
53
+ it_behaves_like 'conditional get'
54
+ end
55
+
56
+ context "object that doesn't respond to #updated_at" do
57
+ it "should return without changes" do
58
+ expect(middleware.call(env.merge('response' => stub)).first).to eq(200)
59
+ end
60
+ end
61
+
62
+ context "object that is #public" do
63
+ let(:response) { stub(:public => true) }
64
+ it_behaves_like 'public response'
65
+ end
66
+
67
+ context "object that is not #public" do
68
+ let(:response) { stub(:public => false) }
69
+ it_behaves_like 'private response'
70
+ end
71
+
72
+ context "array of #public objects" do
73
+ let(:response) { [stub(:public => true)] * 2 }
74
+ it_behaves_like 'public response'
75
+ end
76
+
77
+ context "array of public and private objects" do
78
+ let(:response) { [stub(:public => true), stub(:public => false)] }
79
+ it_behaves_like 'private response'
80
+ end
81
+
82
+ context 'hash that is "public"' do
83
+ let(:response) { { "public" => true } }
84
+ it_behaves_like 'public response'
85
+ end
86
+
87
+ context 'hash that is not "public"' do
88
+ let(:response) { { "public" => false } }
89
+ it_behaves_like 'private response'
90
+ end
91
+
92
+ context 'array of hashes that are "public"' do
93
+ let(:response) { [{ "public" => true }]*2 }
94
+ it_behaves_like 'public response'
95
+ end
96
+
97
+ context 'array of hashes that are not "public"' do
98
+ let(:response) { [{ "public" => false }]*2 }
99
+ it_behaves_like 'private response'
100
+ end
101
+
102
+ context 'hash that has public set on permissions' do
103
+ let(:response) { { "permissions" => { "public" => true } } }
104
+ it_behaves_like 'public response'
105
+ end
106
+
107
+ context 'hash that has public set to false on permissions' do
108
+ let(:response) { { "permissions" => { "public" => false } } }
109
+ it_behaves_like 'private response'
110
+ end
111
+
112
+ context 'array of hashes that have public set on permissions' do
113
+ let(:response) { [{ "permissions" => { "public" => true } }]*2 }
114
+ it_behaves_like 'public response'
115
+ end
116
+
117
+ context 'array of hashes that have public set to false on permissions' do
118
+ let(:response) { [{ "permissions" => { "public" => false } }]*2 }
119
+ it_behaves_like 'private response'
120
+ end
121
+ end