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