Remove all duplication the specs

S3 and Swift now run the same specs. The only difference is the before
block that defines the stubbed HTTP requests and the responses from the
Swift and S3 servers
This commit is contained in:
Greg Karékinian 2018-04-19 18:18:05 +02:00
parent 21dad2aba7
commit 21f3a9f60f
6 changed files with 762 additions and 1712 deletions

View File

@ -15,6 +15,7 @@ group :test do
gem 'purdytest', :require => false gem 'purdytest', :require => false
gem 'm' gem 'm'
gem 'minitest-stub_any_instance' gem 'minitest-stub_any_instance'
gem 'webmock'
end end
group :staging, :production do group :staging, :production do

View File

@ -6,12 +6,17 @@ GEM
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
backports (3.11.2) backports (3.11.2)
concurrent-ruby (1.0.5) concurrent-ruby (1.0.5)
crack (0.4.3)
safe_yaml (~> 1.0.0)
domain_name (0.5.20170404) domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
faraday (0.14.0) faraday (0.14.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
hashdiff (0.3.7)
http-accept (1.7.0) http-accept (1.7.0)
http-cookie (1.0.3) http-cookie (1.0.3)
domain_name (~> 0.5) domain_name (~> 0.5)
@ -31,6 +36,7 @@ GEM
multipart-post (2.0.0) multipart-post (2.0.0)
mustermann (1.0.2) mustermann (1.0.2)
netrc (0.11.0) netrc (0.11.0)
public_suffix (3.0.2)
purdytest (2.0.0) purdytest (2.0.0)
minitest (~> 5.5) minitest (~> 5.5)
rack (2.0.4) rack (2.0.4)
@ -50,6 +56,7 @@ GEM
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
safe_yaml (1.0.4)
sentry-raven (2.7.2) sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0) faraday (>= 0.7.6, < 1.0)
sinatra (2.0.1) sinatra (2.0.1)
@ -74,6 +81,10 @@ GEM
unicorn (5.4.0) unicorn (5.4.0)
kgio (~> 2.6) kgio (~> 2.6)
raindrops (~> 0.7) raindrops (~> 0.7)
webmock (3.3.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
PLATFORMS PLATFORMS
ruby ruby
@ -92,6 +103,7 @@ DEPENDENCIES
sentry-raven sentry-raven
sinatra sinatra
sinatra-contrib sinatra-contrib
webmock
BUNDLED WITH BUNDLED WITH
1.16.0 1.16.0

View File

@ -1,882 +1,56 @@
require_relative "../spec_helper" require_relative "../spec_helper"
describe "App" do describe "App" do
include Rack::Test::Methods def container_url_for(user)
"#{app.settings.s3["endpoint"]}#{app.settings.s3["bucket"]}/#{user}"
def app
LiquorCabinet
end end
it "returns 404 on non-existing routes" do def storage_class
get "/virginmargarita" RemoteStorage::S3Rest
last_response.status.must_equal 404
end end
describe "PUT requests" do before do
stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
before do to_return(status: 200, headers: { etag: '"0815etag"' })
purge_redis stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
end with(body: "si").
to_return(status: 200, headers: { etag: '"0815etag"' })
context "authorized" do stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
before do with(body: "aye").
redis.sadd "authorizations:phil:amarillo", [":rw"] to_return(status: 200, headers: { etag: '"0915etag"' })
header "Authorization", "Bearer amarillo" stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
end with(body: "deliciosa").
to_return(status: 200, headers: { etag: '"0815etag"' })
it "creates the metadata object in redis" do stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
put_stub = OpenStruct.new(headers: { with(body: "muy deliciosa").
etag: '"bla"' to_return(status: 200, headers: { etag: '"0815etag"' })
}) stub_request(:head, "#{container_url_for("phil")}/food/aguacate").
to_return(status: 200, headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
RestClient.stub :put, put_stub do stub_request(:delete, "#{container_url_for("phil")}/food/aguacate").
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do to_return(status: 200, headers: { etag: '"0815etag"' })
put "/phil/food/aguacate", "si" stub_request(:put, "#{container_url_for("phil")}/food/camaron").
end to_return(status: 200, headers: { etag: '"0816etag"' })
end stub_request(:head, "#{container_url_for("phil")}/food/camaron").
to_return(status: 200, headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
metadata = redis.hgetall "rs:m:phil:food/aguacate" stub_request(:delete, "#{container_url_for("phil")}/food/camaron").
metadata["s"].must_equal "2" to_return(status: 200, headers: { etag: '"0816etag"' })
metadata["t"].must_equal "text/plain; charset=utf-8" stub_request(:put, "#{container_url_for("phil")}/food/desayunos/bolon").
metadata["e"].must_equal "bla" to_return(status: 200, headers: { etag: '"0817etag"' })
metadata["m"].length.must_equal 13 stub_request(:head, "#{container_url_for("phil")}/food/desayunos/bolon").
end to_return(status: 200, headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
stub_request(:delete, "#{container_url_for("phil")}/food/desayunos/bolon").
it "creates the directory objects metadata in redis" do to_return(status: 200, headers: { etag: '"0817etag"' })
put_stub = OpenStruct.new(headers: { stub_request(:get, "#{container_url_for("phil")}/food/aguacate").
etag: '"bla"' to_return(status: 200, body: "rootbody", headers: { etag: '"0817etag"', content_type: "text/plain; charset=utf-8" })
}) stub_request(:put, "#{container_url_for("phil")}/bamboo.txt").
get_stub = OpenStruct.new(body: "rootbody") to_return(status: 200, headers: { etag: '"0818etag"' })
stub_request(:head, "#{container_url_for("phil")}/bamboo.txt").
RestClient.stub :put, put_stub do to_return(status: 200, headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do stub_request(:head, "#{container_url_for("phil")}/food/steak").
RestClient.stub :get, get_stub do to_return(status: 404)
RemoteStorage::S3Rest.stub_any_instance :etag_for, "newetag" do stub_request(:get, "#{container_url_for("phil")}/food/steak").
put "/phil/food/aguacate", "si" to_return(status: 404)
put "/phil/food/camaron", "yummi"
end
end
end
end
metadata = redis.hgetall "rs:m:phil:/"
metadata["e"].must_equal "newetag"
metadata["m"].length.must_equal 13
metadata = redis.hgetall "rs:m:phil:food/"
metadata["e"].must_equal "newetag"
metadata["m"].length.must_equal 13
food_items = redis.smembers "rs:m:phil:food/:items"
food_items.each do |food_item|
["camaron", "aguacate"].must_include food_item
end
root_items = redis.smembers "rs:m:phil:/:items"
root_items.must_equal ["food/"]
end
context "response code" do
before do
@put_stub = OpenStruct.new(headers: {
etag: '"bla"'
})
end
it "is 201 for newly created objects" do
RestClient.stub :put, @put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "muy deliciosa"
end
end
last_response.status.must_equal 201
end
it "is 200 for updated objects" do
RestClient.stub :put, @put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "deliciosa"
put "/phil/food/aguacate", "muy deliciosa"
end
end
last_response.status.must_equal 200
end
end
context "logging usage size" do
before do
@put_stub = OpenStruct.new(headers: {
etag: '"bla"'
})
@head_stub = OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
end
it "logs the complete size when creating new objects" do
RestClient.stub :put, @put_stub do
RestClient.stub :head, @head_stub do
put "/phil/food/aguacate", "1234567890"
end
end
size_log = redis.get "rs:s:phil"
size_log.must_equal "10"
end
it "logs the size difference when updating existing objects" do
RestClient.stub :put, @put_stub do
RestClient.stub :head, @head_stub do
put "/phil/food/camaron", "1234567890"
put "/phil/food/aguacate", "1234567890"
put "/phil/food/aguacate", "123"
end
end
size_log = redis.get "rs:s:phil"
size_log.must_equal "13"
end
end
describe "objects in root dir" do
before do
put_stub = OpenStruct.new(headers: {
etag: '"bla"',
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/bamboo.txt", "shir kan"
end
end
end
it "are listed in the directory listing with all metadata" do
get "phil/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"]["bamboo.txt"].wont_be_nil
content["items"]["bamboo.txt"]["ETag"].must_equal "bla"
content["items"]["bamboo.txt"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["bamboo.txt"]["Content-Length"].must_equal 8
content["items"]["bamboo.txt"]["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
describe "name collision checks" do
it "is successful when there is no name collision" do
put_stub = OpenStruct.new(headers: {
etag: '"bla"',
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
get_stub = OpenStruct.new(body: "rootbody")
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
RestClient.stub :get, get_stub do
RemoteStorage::S3Rest.stub_any_instance :etag_for, "rootetag" do
put "/phil/food/aguacate", "si"
end
end
end
end
last_response.status.must_equal 201
metadata = redis.hgetall "rs:m:phil:food/aguacate"
metadata["s"].must_equal "2"
end
it "conflicts when there is a directory with same name as document" do
put_stub = OpenStruct.new(headers: {
etag: '"bla"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
put "/phil/food", "wontwork"
end
end
last_response.status.must_equal 409
last_response.body.must_equal "Conflict"
metadata = redis.hgetall "rs:m:phil:food"
metadata.must_be_empty
end
it "conflicts when there is a document with same name as directory" do
put_stub = OpenStruct.new(headers: {
etag: '"bla"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
put "/phil/food/aguacate/empanado", "wontwork"
end
end
last_response.status.must_equal 409
metadata = redis.hgetall "rs:m:phil:food/aguacate/empanado"
metadata.must_be_empty
end
it "returns 400 when a Content-Range header is sent" do
header "Content-Range", "bytes 0-3/3"
put "/phil/food/aguacate", "si"
last_response.status.must_equal 400
end
end
describe "If-Match header" do
before do
put_stub = OpenStruct.new(headers: {
etag: '"oldetag"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
end
end
end
it "allows the request if the header matches the current ETag" do
header "If-Match", "\"oldetag\""
put_stub = OpenStruct.new(headers: {
etag: '"newetag"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "aye"
end
end
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"newetag\""
end
it "allows the request if the header contains a weak ETAG matching the current ETag" do
header "If-Match", "W/\"oldetag\""
put_stub = OpenStruct.new(headers: {
etag: '"newetag"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "aye"
end
end
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"newetag\""
end
it "allows the request if the header contains a weak ETAG with leading quote matching the current ETag" do
header "If-Match", "\"W/\"oldetag\""
put_stub = OpenStruct.new(headers: {
etag: '"newetag"',
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "aye"
end
end
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"newetag\""
end
it "fails the request if the header does not match the current ETag" do
header "If-Match", "someotheretag"
head_stub = OpenStruct.new(headers: {
etag: '"oldetag"',
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT",
content_type: "text/plain",
content_length: 23
})
RestClient.stub :head, head_stub do
put "/phil/food/aguacate", "aye"
end
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
it "allows the request if redis metadata became out of sync" do
header "If-Match", "\"existingetag\""
head_stub = OpenStruct.new(headers: {
etag: '"existingetag"',
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT",
content_type: "text/plain",
content_length: 23
})
put_stub = OpenStruct.new(headers: {
etag: '"newetag"'
})
RestClient.stub :head, head_stub do
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "aye"
end
end
last_response.status.must_equal 200
end
end
describe "If-None-Match header set to '*'" do
it "succeeds when the document doesn't exist yet" do
put_stub = OpenStruct.new(headers: {
etag: '"someetag"'
})
header "If-None-Match", "*"
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
end
end
last_response.status.must_equal 201
end
it "fails the request if the document already exists" do
put_stub = OpenStruct.new(headers: {
etag: '"someetag"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
end
end
header "If-None-Match", "*"
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
end
end
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
end
end
end end
describe "DELETE requests" do it_behaves_like 'a REST adapter'
before do
purge_redis
end
context "not authorized" do
describe "with no token" do
it "says it's not authorized" do
delete "/phil/food/aguacate"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
describe "with empty token" do
it "says it's not authorized" do
header "Authorization", "Bearer "
delete "/phil/food/aguacate"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
describe "with wrong token" do
it "says it's not authorized" do
header "Authorization", "Bearer wrongtoken"
delete "/phil/food/aguacate"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put_stub = OpenStruct.new(headers: {
etag: '"bla"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
end
end
it "decreases the size log by size of deleted object" do
RestClient.stub :delete, "" do
RemoteStorage::S3Rest.stub_any_instance :etag_for, "rootetag" do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
delete "/phil/food/aguacate"
end
end
end
size_log = redis.get "rs:s:phil"
size_log.must_equal "8"
end
it "deletes the metadata object in redis" do
RestClient.stub :delete, "" do
RemoteStorage::S3Rest.stub_any_instance :etag_for, "rootetag" do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
delete "/phil/food/aguacate"
end
end
end
metadata = redis.hgetall "rs:m:phil:food/aguacate"
metadata.must_be_empty
end
it "deletes the directory objects metadata in redis" do
old_metadata = redis.hgetall "rs:m:phil:food/"
RestClient.stub :delete, "" do
RemoteStorage::S3Rest.stub_any_instance :etag_for, "newetag" do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
delete "/phil/food/aguacate"
end
end
end
metadata = redis.hgetall "rs:m:phil:food/"
metadata["e"].must_equal "newetag"
metadata["m"].length.must_equal 13
metadata["m"].wont_equal old_metadata["m"]
food_items = redis.smembers "rs:m:phil:food/:items"
food_items.sort.must_equal ["camaron", "desayunos/"]
root_items = redis.smembers "rs:m:phil:/:items"
root_items.must_equal ["food/"]
end
it "deletes the parent directory objects metadata when deleting all items" do
RestClient.stub :delete, "" do
RemoteStorage::S3Rest.stub_any_instance :etag_for, "rootetag" do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
delete "/phil/food/aguacate"
delete "/phil/food/camaron"
delete "/phil/food/desayunos/bolon"
end
end
end
redis.smembers("rs:m:phil:food/desayunos:items").must_be_empty
redis.hgetall("rs:m:phil:food/desayunos/").must_be_empty
redis.smembers("rs:m:phil:food/:items").must_be_empty
redis.hgetall("rs:m:phil:food/").must_be_empty
redis.smembers("rs:m:phil:/:items").must_be_empty
end
it "responds with the ETag of the deleted item in the header" do
RestClient.stub :delete, "" do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
delete "/phil/food/aguacate"
end
end
last_response.headers["ETag"].must_equal "\"bla\""
end
context "when item doesn't exist" do
before do
purge_redis
put_stub = OpenStruct.new(headers: {
etag: '"bla"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/steak", "si"
end
end
raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new }
RestClient.stub :head, raises_exception do
delete "/phil/food/steak"
end
end
it "returns a 404" do
last_response.status.must_equal 404
last_response.body.must_equal "Not Found"
end
it "deletes any metadata that might still exist" do
raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new }
RestClient.stub :head, raises_exception do
delete "/phil/food/steak"
end
metadata = redis.hgetall "rs:m:phil:food/steak"
metadata.must_be_empty
redis.smembers("rs:m:phil:food/:items").must_be_empty
redis.hgetall("rs:m:phil:food/").must_be_empty
redis.smembers("rs:m:phil:/:items").must_be_empty
end
end
describe "If-Match header" do
it "succeeds when the header matches the current ETag" do
header "If-Match", "\"bla\""
RestClient.stub :delete, "" do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
delete "/phil/food/aguacate"
end
end
last_response.status.must_equal 200
end
it "succeeds when the header contains a weak ETAG matching the current ETag" do
header "If-Match", "W/\"bla\""
RestClient.stub :delete, "" do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
delete "/phil/food/aguacate"
end
end
last_response.status.must_equal 200
end
it "fails the request if it does not match the current ETag" do
header "If-Match", "someotheretag"
RestClient.stub :delete, "" do
RestClient.stub :head, OpenStruct.new(headers: { etag: '"someetag"' }) do
delete "/phil/food/aguacate"
end
end
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
end
end
end
describe "GET requests" do
before do
purge_redis
end
context "not authorized" do
describe "without token" do
it "says it's not authorized" do
get "/phil/food/"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
describe "with wrong token" do
it "says it's not authorized" do
header "Authorization", "Bearer wrongtoken"
get "/phil/food/"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put_stub = OpenStruct.new(headers: {
etag: '"0815etag"'
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
end
end
describe "documents" do
it "returns the required response headers" do
get_stub = OpenStruct.new(body: "si", headers: {
etag: '"0815etag"',
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT",
content_type: "text/plain; charset=utf-8",
content_length: 2
})
RestClient.stub :get, get_stub do
get "/phil/food/aguacate"
end
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Cache-Control"].must_equal "no-cache"
last_response.headers["Content-Type"].must_equal "text/plain; charset=utf-8"
end
it "returns a 404 when data doesn't exist" do
raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new }
RestClient.stub :get, raises_exception do
get "/phil/food/steak"
end
last_response.status.must_equal 404
last_response.body.must_equal "Not Found"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"0815etag\""
get "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the current ETag" do
header "If-None-Match", "W/\"0815etag\""
get "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
describe "directory listings" do
it "returns the correct ETag header" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
end
it "returns a Cache-Control header with value 'no-cache'" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.headers["Cache-Control"].must_equal "no-cache"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
get "/phil/food/"
last_response.status.must_equal 304
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the ETag" do
header "If-None-Match", "W/\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
get "/phil/food/"
last_response.status.must_equal 304
end
it "contains all items in the directory" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["@context"].must_equal "http://remotestorage.io/spec/folder-description"
content["items"]["aguacate"].wont_be_nil
content["items"]["aguacate"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["aguacate"]["Content-Length"].must_equal 2
content["items"]["aguacate"]["ETag"].must_equal "0815etag"
content["items"]["camaron"].wont_be_nil
content["items"]["camaron"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["camaron"]["Content-Length"].must_equal 5
content["items"]["camaron"]["ETag"].must_equal "0815etag"
content["items"]["desayunos/"].wont_be_nil
content["items"]["desayunos/"]["ETag"].must_equal "dd36e3cfe52b5f33421150b289a7d48d"
end
it "contains all items in the root directory" do
get "phil/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"]["food/"].wont_be_nil
content["items"]["food/"]["ETag"].must_equal "f9f85fbf5aa1fa378fd79ac8aa0a457d"
end
it "responds with an empty directory liting when directory doesn't exist" do
get "phil/some-non-existing-dir/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"].must_equal({})
end
end
end
end
describe "HEAD requests" do
before do
purge_redis
end
context "not authorized" do
describe "without token" do
it "says it's not authorized" do
head "/phil/food/camarones"
last_response.status.must_equal 401
last_response.body.must_be_empty
end
end
describe "with wrong token" do
it "says it's not authorized" do
header "Authorization", "Bearer wrongtoken"
head "/phil/food/camarones"
last_response.status.must_equal 401
last_response.body.must_be_empty
end
end
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put_stub = OpenStruct.new(headers: {
etag: "0815etag"
})
RestClient.stub :put, put_stub do
RestClient.stub :head, OpenStruct.new(headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" }) do
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
end
end
describe "directory listings" do
it "returns the correct header information" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
last_response.headers["ETag"].must_equal "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
end
end
describe "documents" do
context "when the document doesn't exist" do
it "returns a 404" do
head "/phil/food/steak"
last_response.status.must_equal 404
last_response.body.must_be_empty
end
end
context "when the document exists" do
it "returns the required response headers" do
head "/phil/food/aguacate"
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Cache-Control"].must_equal "no-cache"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"0815etag\""
head "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the current ETag" do
header "If-None-Match", "W/\"0815etag\""
head "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
end
end
end
end end

635
spec/shared_examples.rb Normal file
View File

@ -0,0 +1,635 @@
require_relative "./spec_helper"
shared_examples_for 'a REST adapter' do
include Rack::Test::Methods
def container_url_for(user)
raise NotImplementedError
end
def storage_class
raise NotImplementedError
end
def config_file
raise NotImplementedError
end
it "returns 404 on non-existing routes" do
get "/virginmargarita"
last_response.status.must_equal 404
end
describe "PUT requests" do
before do
purge_redis
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
end
it "creates the metadata object in redis" do
put "/phil/food/aguacate", "si"
metadata = redis.hgetall "rs:m:phil:food/aguacate"
metadata["s"].must_equal "2"
metadata["t"].must_equal "text/plain; charset=utf-8"
metadata["e"].must_equal "0815etag"
metadata["m"].length.must_equal 13
end
it "creates the directory objects metadata in redis" do
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
metadata = redis.hgetall "rs:m:phil:/"
metadata["e"].must_equal "fe2976909daaf074660981ab563fe65d"
metadata["m"].length.must_equal 13
metadata = redis.hgetall "rs:m:phil:food/"
metadata["e"].must_equal "926f98ff820f2f9764fd3c60a22865ad"
metadata["m"].length.must_equal 13
food_items = redis.smembers "rs:m:phil:food/:items"
food_items.each do |food_item|
["camaron", "aguacate"].must_include food_item
end
root_items = redis.smembers "rs:m:phil:/:items"
root_items.must_equal ["food/"]
end
context "response code" do
it "is 201 for newly created objects" do
put "/phil/food/aguacate", "ci"
last_response.status.must_equal 201
end
it "is 200 for updated objects" do
put "/phil/food/aguacate", "deliciosa"
put "/phil/food/aguacate", "muy deliciosa"
last_response.status.must_equal 200
end
end
context "logging usage size" do
it "logs the complete size when creating new objects" do
put "/phil/food/aguacate", "1234567890"
size_log = redis.get "rs:s:phil"
size_log.must_equal "10"
end
it "logs the size difference when updating existing objects" do
put "/phil/food/camaron", "1234567890"
put "/phil/food/aguacate", "1234567890"
put "/phil/food/aguacate", "123"
size_log = redis.get "rs:s:phil"
size_log.must_equal "13"
end
end
describe "objects in root dir" do
before do
put "/phil/bamboo.txt", "shir kan"
end
it "are listed in the directory listing with all metadata" do
get "phil/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"]["bamboo.txt"].wont_be_nil
content["items"]["bamboo.txt"]["ETag"].must_equal "0818etag"
content["items"]["bamboo.txt"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["bamboo.txt"]["Content-Length"].must_equal 8
content["items"]["bamboo.txt"]["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
describe "name collision checks" do
it "is successful when there is no name collision" do
put "/phil/food/aguacate", "si"
last_response.status.must_equal 201
metadata = redis.hgetall "rs:m:phil:food/aguacate"
metadata["s"].must_equal "2"
end
it "conflicts when there is a directory with same name as document" do
put "/phil/food/aguacate", "si"
put "/phil/food", "wontwork"
last_response.status.must_equal 409
last_response.body.must_equal "Conflict"
metadata = redis.hgetall "rs:m:phil:food"
metadata.must_be_empty
end
it "conflicts when there is a document with same name as directory" do
put "/phil/food/aguacate", "si"
put "/phil/food/aguacate/empanado", "wontwork"
last_response.status.must_equal 409
metadata = redis.hgetall "rs:m:phil:food/aguacate/empanado"
metadata.must_be_empty
end
it "returns 400 when a Content-Range header is sent" do
header "Content-Range", "bytes 0-3/3"
put "/phil/food/aguacate", "si"
last_response.status.must_equal 400
end
end
describe "If-Match header" do
before do
put "/phil/food/aguacate", "si"
end
it "allows the request if the header matches the current ETag" do
header "If-Match", "\"0815etag\""
put "/phil/food/aguacate", "aye"
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"0915etag\""
end
it "allows the request if the header contains a weak ETAG matching the current ETag" do
header "If-Match", "W/\"0815etag\""
put "/phil/food/aguacate", "aye"
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"0915etag\""
end
it "allows the request if the header contains a weak ETAG with leading quote matching the current ETag" do
header "If-Match", "\"W/\"0815etag\""
put "/phil/food/aguacate", "aye"
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"0915etag\""
end
it "fails the request if the header does not match the current ETag" do
header "If-Match", "someotheretag"
put "/phil/food/aguacate", "aye"
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
it "allows the request if redis metadata became out of sync" do
header "If-Match", "\"0815etag\""
put "/phil/food/aguacate", "aye"
last_response.status.must_equal 200
end
end
describe "If-None-Match header set to '*'" do
it "succeeds when the document doesn't exist yet" do
header "If-None-Match", "*"
put "/phil/food/aguacate", "si"
last_response.status.must_equal 201
end
it "fails the request if the document already exists" do
put "/phil/food/aguacate", "si"
header "If-None-Match", "*"
put "/phil/food/aguacate", "si"
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
end
end
end
describe "DELETE requests" do
before do
purge_redis
end
context "not authorized" do
describe "with no token" do
it "says it's not authorized" do
delete "/phil/food/aguacate"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
describe "with empty token" do
it "says it's not authorized" do
header "Authorization", "Bearer "
delete "/phil/food/aguacate"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
describe "with wrong token" do
it "says it's not authorized" do
header "Authorization", "Bearer wrongtoken"
delete "/phil/food/aguacate"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
it "decreases the size log by size of deleted object" do
delete "/phil/food/aguacate"
size_log = redis.get "rs:s:phil"
size_log.must_equal "8"
end
it "deletes the metadata object in redis" do
delete "/phil/food/aguacate"
metadata = redis.hgetall "rs:m:phil:food/aguacate"
metadata.must_be_empty
end
it "deletes the directory objects metadata in redis" do
old_metadata = redis.hgetall "rs:m:phil:food/"
storage_class.stub_any_instance :etag_for, "newetag" do
delete "/phil/food/aguacate"
end
metadata = redis.hgetall "rs:m:phil:food/"
metadata["e"].must_equal "newetag"
metadata["m"].length.must_equal 13
metadata["m"].wont_equal old_metadata["m"]
food_items = redis.smembers "rs:m:phil:food/:items"
food_items.sort.must_equal ["camaron", "desayunos/"]
root_items = redis.smembers "rs:m:phil:/:items"
root_items.must_equal ["food/"]
end
it "deletes the parent directory objects metadata when deleting all items" do
delete "/phil/food/aguacate"
delete "/phil/food/camaron"
delete "/phil/food/desayunos/bolon"
redis.smembers("rs:m:phil:food/desayunos:items").must_be_empty
redis.hgetall("rs:m:phil:food/desayunos/").must_be_empty
redis.smembers("rs:m:phil:food/:items").must_be_empty
redis.hgetall("rs:m:phil:food/").must_be_empty
redis.smembers("rs:m:phil:/:items").must_be_empty
end
it "responds with the ETag of the deleted item in the header" do
delete "/phil/food/aguacate"
last_response.headers["ETag"].must_equal "\"0815etag\""
end
context "when item doesn't exist" do
before do
purge_redis
delete "/phil/food/steak"
end
it "returns a 404" do
last_response.status.must_equal 404
last_response.body.must_equal "Not Found"
end
it "deletes any metadata that might still exist" do
delete "/phil/food/steak"
metadata = redis.hgetall "rs:m:phil:food/steak"
metadata.must_be_empty
redis.smembers("rs:m:phil:food/:items").must_be_empty
redis.hgetall("rs:m:phil:food/").must_be_empty
redis.smembers("rs:m:phil:/:items").must_be_empty
end
end
describe "If-Match header" do
it "succeeds when the header matches the current ETag" do
header "If-Match", "\"0815etag\""
delete "/phil/food/aguacate"
last_response.status.must_equal 200
end
it "succeeds when the header contains a weak ETAG matching the current ETag" do
header "If-Match", "W/\"0815etag\""
delete "/phil/food/aguacate"
last_response.status.must_equal 200
end
it "fails the request if it does not match the current ETag" do
header "If-Match", "someotheretag"
delete "/phil/food/aguacate"
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
end
end
end
describe "GET requests" do
before do
purge_redis
end
context "not authorized" do
describe "without token" do
it "says it's not authorized" do
get "/phil/food/"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
describe "with wrong token" do
it "says it's not authorized" do
header "Authorization", "Bearer wrongtoken"
get "/phil/food/"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
describe "documents" do
it "returns the required response headers" do
get "/phil/food/aguacate"
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"0817etag\""
last_response.headers["Cache-Control"].must_equal "no-cache"
last_response.headers["Content-Type"].must_equal "text/plain; charset=utf-8"
end
it "returns a 404 when data doesn't exist" do
get "/phil/food/steak"
last_response.status.must_equal 404
last_response.body.must_equal "Not Found"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"0815etag\""
get "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the current ETag" do
header "If-None-Match", "W/\"0815etag\""
get "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
describe "directory listings" do
it "returns the correct ETag header" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
end
it "returns a Cache-Control header with value 'no-cache'" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.headers["Cache-Control"].must_equal "no-cache"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
get "/phil/food/"
last_response.status.must_equal 304
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the ETag" do
header "If-None-Match", "W/\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
get "/phil/food/"
last_response.status.must_equal 304
end
it "contains all items in the directory" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["@context"].must_equal "http://remotestorage.io/spec/folder-description"
content["items"]["aguacate"].wont_be_nil
content["items"]["aguacate"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["aguacate"]["Content-Length"].must_equal 2
content["items"]["aguacate"]["ETag"].must_equal "0815etag"
content["items"]["camaron"].wont_be_nil
content["items"]["camaron"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["camaron"]["Content-Length"].must_equal 5
content["items"]["camaron"]["ETag"].must_equal "0816etag"
content["items"]["desayunos/"].wont_be_nil
content["items"]["desayunos/"]["ETag"].must_equal "dd36e3cfe52b5f33421150b289a7d48d"
end
it "contains all items in the root directory" do
get "phil/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"]["food/"].wont_be_nil
content["items"]["food/"]["ETag"].must_equal "f9f85fbf5aa1fa378fd79ac8aa0a457d"
end
it "responds with an empty directory liting when directory doesn't exist" do
get "phil/some-non-existing-dir/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"].must_equal({})
end
end
end
end
describe "HEAD requests" do
before do
purge_redis
end
context "not authorized" do
describe "without token" do
it "says it's not authorized" do
head "/phil/food/camarones"
last_response.status.must_equal 401
last_response.body.must_be_empty
end
end
describe "with wrong token" do
it "says it's not authorized" do
header "Authorization", "Bearer wrongtoken"
head "/phil/food/camarones"
last_response.status.must_equal 401
last_response.body.must_be_empty
end
end
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
describe "directory listings" do
it "returns the correct header information" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
last_response.headers["ETag"].must_equal "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
end
end
describe "documents" do
context "when the document doesn't exist" do
it "returns a 404" do
head "/phil/food/steak"
last_response.status.must_equal 404
last_response.body.must_be_empty
end
end
context "when the document exists" do
it "returns the required response headers" do
head "/phil/food/aguacate"
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Cache-Control"].must_equal "no-cache"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"0815etag\""
head "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the current ETag" do
header "If-None-Match", "W/\"0815etag\""
head "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
end
end
end
end

View File

@ -6,12 +6,15 @@ Bundler.require
require_relative '../liquor-cabinet' require_relative '../liquor-cabinet'
require 'minitest/autorun' require 'minitest/autorun'
require "minitest/stub_any_instance"
require 'rack/test' require 'rack/test'
require 'purdytest' require 'purdytest'
require "redis" require "redis"
require "rest_client" require "rest_client"
require "minitest/stub_any_instance"
require "ostruct" require "ostruct"
require 'webmock/minitest'
WebMock.disable_net_connect!
def app def app
LiquorCabinet LiquorCabinet
@ -19,17 +22,6 @@ end
app.set :environment, :test app.set :environment, :test
def wait_a_second
now = Time.now.to_i
while Time.now.to_i == now; end
end
def write_last_response_to_file(filename = "last_response.html")
File.open(filename, "w") do |f|
f.write last_response.body
end
end
alias context describe alias context describe
if app.settings.respond_to? :redis if app.settings.respond_to? :redis
@ -43,3 +35,23 @@ if app.settings.respond_to? :redis
end end
end end
end end
MiniTest::Spec.class_eval do
def self.shared_examples
@shared_examples ||= {}
end
end
module MiniTest::Spec::SharedExamples
def shared_examples_for(desc, &block)
MiniTest::Spec.shared_examples[desc] = block
end
def it_behaves_like(desc)
self.instance_eval(&MiniTest::Spec.shared_examples[desc])
end
end
Object.class_eval { include(MiniTest::Spec::SharedExamples) }
require_relative 'shared_examples'

View File

@ -1,840 +1,56 @@
require_relative "../spec_helper" require_relative "../spec_helper"
describe "App" do describe "App" do
include Rack::Test::Methods def container_url_for(user)
"#{app.settings.swift["host"]}/rs:documents:test/#{user}"
def app
LiquorCabinet
end end
it "returns 404 on non-existing routes" do def storage_class
get "/virginmargarita" RemoteStorage::Swift
last_response.status.must_equal 404
end end
describe "PUT requests" do def config_file
"config.yml.example.swift"
before do
purge_redis
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
end
it "creates the metadata object in redis" do
put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
end
metadata = redis.hgetall "rs:m:phil:food/aguacate"
metadata["s"].must_equal "2"
metadata["t"].must_equal "text/plain; charset=utf-8"
metadata["e"].must_equal "bla"
metadata["m"].length.must_equal 13
end
it "creates the directory objects metadata in redis" do
put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
get_stub = OpenStruct.new(body: "rootbody")
RestClient.stub :put, put_stub do
RestClient.stub :get, get_stub do
RemoteStorage::Swift.stub_any_instance :etag_for, "newetag" do
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
end
end
end
metadata = redis.hgetall "rs:m:phil:/"
metadata["e"].must_equal "newetag"
metadata["m"].length.must_equal 13
metadata = redis.hgetall "rs:m:phil:food/"
metadata["e"].must_equal "newetag"
metadata["m"].length.must_equal 13
food_items = redis.smembers "rs:m:phil:food/:items"
food_items.each do |food_item|
["camaron", "aguacate"].must_include food_item
end
root_items = redis.smembers "rs:m:phil:/:items"
root_items.must_equal ["food/"]
end
context "response code" do
before do
@put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
end
it "is 201 for newly created objects" do
RestClient.stub :put, @put_stub do
put "/phil/food/aguacate", "muy deliciosa"
end
last_response.status.must_equal 201
end
it "is 200 for updated objects" do
RestClient.stub :put, @put_stub do
put "/phil/food/aguacate", "deliciosa"
put "/phil/food/aguacate", "muy deliciosa"
end
last_response.status.must_equal 200
end
end
context "logging usage size" do
before do
@put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
end
it "logs the complete size when creating new objects" do
RestClient.stub :put, @put_stub do
put "/phil/food/aguacate", "1234567890"
end
size_log = redis.get "rs:s:phil"
size_log.must_equal "10"
end
it "logs the size difference when updating existing objects" do
RestClient.stub :put, @put_stub do
put "/phil/food/camaron", "1234567890"
put "/phil/food/aguacate", "1234567890"
put "/phil/food/aguacate", "123"
end
size_log = redis.get "rs:s:phil"
size_log.must_equal "13"
end
end
describe "objects in root dir" do
before do
put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/bamboo.txt", "shir kan"
end
end
it "are listed in the directory listing with all metadata" do
get "phil/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"]["bamboo.txt"].wont_be_nil
content["items"]["bamboo.txt"]["ETag"].must_equal "bla"
content["items"]["bamboo.txt"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["bamboo.txt"]["Content-Length"].must_equal 8
content["items"]["bamboo.txt"]["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
describe "name collision checks" do
it "is successful when there is no name collision" do
put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
get_stub = OpenStruct.new(body: "rootbody")
RestClient.stub :put, put_stub do
RestClient.stub :get, get_stub do
RemoteStorage::Swift.stub_any_instance :etag_for, "rootetag" do
put "/phil/food/aguacate", "si"
end
end
end
last_response.status.must_equal 201
metadata = redis.hgetall "rs:m:phil:food/aguacate"
metadata["s"].must_equal "2"
end
it "conflicts when there is a directory with same name as document" do
put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
put "/phil/food", "wontwork"
end
last_response.status.must_equal 409
last_response.body.must_equal "Conflict"
metadata = redis.hgetall "rs:m:phil:food"
metadata.must_be_empty
end
it "conflicts when there is a document with same name as directory" do
put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
put "/phil/food/aguacate/empanado", "wontwork"
end
last_response.status.must_equal 409
metadata = redis.hgetall "rs:m:phil:food/aguacate/empanado"
metadata.must_be_empty
end
it "returns 400 when a Content-Range header is sent" do
header "Content-Range", "bytes 0-3/3"
put "/phil/food/aguacate", "si"
last_response.status.must_equal 400
end
end
describe "If-Match header" do
before do
put_stub = OpenStruct.new(headers: {
etag: "oldetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
end
end
it "allows the request if the header matches the current ETag" do
header "If-Match", "\"oldetag\""
put_stub = OpenStruct.new(headers: {
etag: "newetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "aye"
end
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"newetag\""
end
it "allows the request if the header contains a weak ETAG matching the current ETag" do
header "If-Match", "W/\"oldetag\""
put_stub = OpenStruct.new(headers: {
etag: "newetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "aye"
end
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"newetag\""
end
it "allows the request if the header contains a weak ETAG with leading quote matching the current ETag" do
header "If-Match", "\"W/\"oldetag\""
put_stub = OpenStruct.new(headers: {
etag: "newetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "aye"
end
last_response.status.must_equal 200
last_response.headers["Etag"].must_equal "\"newetag\""
end
it "fails the request if the header does not match the current ETag" do
header "If-Match", "someotheretag"
head_stub = OpenStruct.new(headers: {
etag: "oldetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT",
content_type: "text/plain",
content_length: 23
})
RestClient.stub :head, head_stub do
put "/phil/food/aguacate", "aye"
end
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
it "allows the request if redis metadata became out of sync" do
header "If-Match", "\"existingetag\""
head_stub = OpenStruct.new(headers: {
etag: "existingetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT",
content_type: "text/plain",
content_length: 23
})
put_stub = OpenStruct.new(headers: {
etag: "newetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :head, head_stub do
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "aye"
end
end
last_response.status.must_equal 200
end
end
describe "If-None-Match header set to '*'" do
it "succeeds when the document doesn't exist yet" do
put_stub = OpenStruct.new(headers: {
etag: "someetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
header "If-None-Match", "*"
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
end
last_response.status.must_equal 201
end
it "fails the request if the document already exists" do
put_stub = OpenStruct.new(headers: {
etag: "someetag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
end
header "If-None-Match", "*"
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
end
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
end
end
end end
describe "DELETE requests" do before do
stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
before do to_return(status: 200, headers: { etag: "0815etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
purge_redis stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
end with(body: "si").
to_return(status: 200, headers: { etag: "0815etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
context "not authorized" do stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
describe "with no token" do with(body: "aye").
it "says it's not authorized" do to_return(status: 200, headers: { etag: "0915etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
delete "/phil/food/aguacate" stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
with(body: "deliciosa").
last_response.status.must_equal 401 to_return(status: 200, headers: { etag: "0815etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
last_response.body.must_equal "Unauthorized" stub_request(:put, "#{container_url_for("phil")}/food/aguacate").
end with(body: "muy deliciosa").
end to_return(status: 200, headers: { etag: "0815etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
stub_request(:head, "#{container_url_for("phil")}/food/aguacate").
describe "with empty token" do to_return(status: 200, headers: { last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
it "says it's not authorized" do stub_request(:delete, "#{container_url_for("phil")}/food/aguacate").
header "Authorization", "Bearer " to_return(status: 200, headers: { etag: "0815etag" })
delete "/phil/food/aguacate" stub_request(:put, "#{container_url_for("phil")}/food/camaron").
to_return(status: 200, headers: { etag: "0816etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
last_response.status.must_equal 401 stub_request(:delete, "#{container_url_for("phil")}/food/camaron").
last_response.body.must_equal "Unauthorized" to_return(status: 200, headers: { etag: "0816etag" })
end stub_request(:put, "#{container_url_for("phil")}/food/desayunos/bolon").
end to_return(status: 200, headers: { etag: "0817etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
stub_request(:delete, "#{container_url_for("phil")}/food/desayunos/bolon").
describe "with wrong token" do to_return(status: 200, headers: { etag: "0817etag" })
it "says it's not authorized" do stub_request(:get, "#{container_url_for("phil")}/food/aguacate").
header "Authorization", "Bearer wrongtoken" to_return(status: 200, body: "rootbody", headers: { etag: "0817etag", content_type: "text/plain; charset=utf-8" })
delete "/phil/food/aguacate" stub_request(:put, "#{container_url_for("phil")}/bamboo.txt").
to_return(status: 200, headers: { etag: "0818etag", last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" })
last_response.status.must_equal 401 stub_request(:head, "#{container_url_for("phil")}/food/steak").
last_response.body.must_equal "Unauthorized" to_return(status: 404)
end stub_request(:get, "#{container_url_for("phil")}/food/steak").
end to_return(status: 404)
stub_request(:delete, "#{container_url_for("phil")}/food/steak").
end to_return(status: 404)
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
end
it "decreases the size log by size of deleted object" do
RestClient.stub :delete, "" do
RemoteStorage::Swift.stub_any_instance :etag_for, "rootetag" do
delete "/phil/food/aguacate"
end
end
size_log = redis.get "rs:s:phil"
size_log.must_equal "8"
end
it "deletes the metadata object in redis" do
RestClient.stub :delete, "" do
RemoteStorage::Swift.stub_any_instance :etag_for, "rootetag" do
delete "/phil/food/aguacate"
end
end
metadata = redis.hgetall "rs:m:phil:food/aguacate"
metadata.must_be_empty
end
it "deletes the directory objects metadata in redis" do
old_metadata = redis.hgetall "rs:m:phil:food/"
RestClient.stub :delete, "" do
RemoteStorage::Swift.stub_any_instance :etag_for, "newetag" do
delete "/phil/food/aguacate"
end
end
metadata = redis.hgetall "rs:m:phil:food/"
metadata["e"].must_equal "newetag"
metadata["m"].length.must_equal 13
metadata["m"].wont_equal old_metadata["m"]
food_items = redis.smembers "rs:m:phil:food/:items"
food_items.sort.must_equal ["camaron", "desayunos/"]
root_items = redis.smembers "rs:m:phil:/:items"
root_items.must_equal ["food/"]
end
it "deletes the parent directory objects metadata when deleting all items" do
RestClient.stub :delete, "" do
RemoteStorage::Swift.stub_any_instance :etag_for, "rootetag" do
delete "/phil/food/aguacate"
delete "/phil/food/camaron"
delete "/phil/food/desayunos/bolon"
end
end
redis.smembers("rs:m:phil:food/desayunos:items").must_be_empty
redis.hgetall("rs:m:phil:food/desayunos/").must_be_empty
redis.smembers("rs:m:phil:food/:items").must_be_empty
redis.hgetall("rs:m:phil:food/").must_be_empty
redis.smembers("rs:m:phil:/:items").must_be_empty
end
it "responds with the ETag of the deleted item in the header" do
RestClient.stub :delete, "" do
delete "/phil/food/aguacate"
end
last_response.headers["ETag"].must_equal "\"bla\""
end
context "when item doesn't exist" do
before do
purge_redis
put_stub = OpenStruct.new(headers: {
etag: "bla",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/steak", "si"
end
raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new }
RestClient.stub :delete, raises_exception do
delete "/phil/food/steak"
end
end
it "returns a 404" do
last_response.status.must_equal 404
last_response.body.must_equal "Not Found"
end
it "deletes any metadata that might still exist" do
raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new }
RestClient.stub :delete, raises_exception do
delete "/phil/food/steak"
end
metadata = redis.hgetall "rs:m:phil:food/steak"
metadata.must_be_empty
redis.smembers("rs:m:phil:food/:items").must_be_empty
redis.hgetall("rs:m:phil:food/").must_be_empty
redis.smembers("rs:m:phil:/:items").must_be_empty
end
end
describe "If-Match header" do
it "succeeds when the header matches the current ETag" do
header "If-Match", "\"bla\""
RestClient.stub :delete, "" do
delete "/phil/food/aguacate"
end
last_response.status.must_equal 200
end
it "succeeds when the header contains a weak ETAG matching the current ETag" do
header "If-Match", "W/\"bla\""
RestClient.stub :delete, "" do
delete "/phil/food/aguacate"
end
last_response.status.must_equal 200
end
it "fails the request if it does not match the current ETag" do
header "If-Match", "someotheretag"
delete "/phil/food/aguacate"
last_response.status.must_equal 412
last_response.body.must_equal "Precondition Failed"
end
end
end
end end
describe "GET requests" do it_behaves_like 'a REST adapter'
before do
purge_redis
end
context "not authorized" do
describe "without token" do
it "says it's not authorized" do
get "/phil/food/"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
describe "with wrong token" do
it "says it's not authorized" do
header "Authorization", "Bearer wrongtoken"
get "/phil/food/"
last_response.status.must_equal 401
last_response.body.must_equal "Unauthorized"
end
end
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put_stub = OpenStruct.new(headers: {
etag: "0815etag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
end
describe "documents" do
it "returns the required response headers" do
get_stub = OpenStruct.new(body: "si", headers: {
etag: "0815etag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT",
content_type: "text/plain; charset=utf-8",
content_length: 2
})
RestClient.stub :get, get_stub do
get "/phil/food/aguacate"
end
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Cache-Control"].must_equal "no-cache"
last_response.headers["Content-Type"].must_equal "text/plain; charset=utf-8"
end
it "returns a 404 when data doesn't exist" do
raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new }
RestClient.stub :get, raises_exception do
get "/phil/food/steak"
end
last_response.status.must_equal 404
last_response.body.must_equal "Not Found"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"0815etag\""
get "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the current ETag" do
header "If-None-Match", "W/\"0815etag\""
get "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
describe "directory listings" do
it "returns the correct ETag header" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
end
it "returns a Cache-Control header with value 'no-cache'" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.headers["Cache-Control"].must_equal "no-cache"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
get "/phil/food/"
last_response.status.must_equal 304
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the ETag" do
header "If-None-Match", "W/\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
get "/phil/food/"
last_response.status.must_equal 304
end
it "contains all items in the directory" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["@context"].must_equal "http://remotestorage.io/spec/folder-description"
content["items"]["aguacate"].wont_be_nil
content["items"]["aguacate"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["aguacate"]["Content-Length"].must_equal 2
content["items"]["aguacate"]["ETag"].must_equal "0815etag"
content["items"]["camaron"].wont_be_nil
content["items"]["camaron"]["Content-Type"].must_equal "text/plain; charset=utf-8"
content["items"]["camaron"]["Content-Length"].must_equal 5
content["items"]["camaron"]["ETag"].must_equal "0815etag"
content["items"]["desayunos/"].wont_be_nil
content["items"]["desayunos/"]["ETag"].must_equal "dd36e3cfe52b5f33421150b289a7d48d"
end
it "contains all items in the root directory" do
get "phil/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"]["food/"].wont_be_nil
content["items"]["food/"]["ETag"].must_equal "f9f85fbf5aa1fa378fd79ac8aa0a457d"
end
it "responds with an empty directory liting when directory doesn't exist" do
get "phil/some-non-existing-dir/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
content = JSON.parse(last_response.body)
content["items"].must_equal({})
end
end
end
end
describe "HEAD requests" do
before do
purge_redis
end
context "not authorized" do
describe "without token" do
it "says it's not authorized" do
head "/phil/food/camarones"
last_response.status.must_equal 401
last_response.body.must_be_empty
end
end
describe "with wrong token" do
it "says it's not authorized" do
header "Authorization", "Bearer wrongtoken"
head "/phil/food/camarones"
last_response.status.must_equal 401
last_response.body.must_be_empty
end
end
end
context "authorized" do
before do
redis.sadd "authorizations:phil:amarillo", [":rw"]
header "Authorization", "Bearer amarillo"
put_stub = OpenStruct.new(headers: {
etag: "0815etag",
last_modified: "Fri, 04 Mar 2016 12:20:18 GMT"
})
RestClient.stub :put, put_stub do
put "/phil/food/aguacate", "si"
put "/phil/food/camaron", "yummi"
put "/phil/food/desayunos/bolon", "wow"
end
end
describe "directory listings" do
it "returns the correct header information" do
get "/phil/food/"
last_response.status.must_equal 200
last_response.content_type.must_equal "application/ld+json"
last_response.headers["ETag"].must_equal "\"f9f85fbf5aa1fa378fd79ac8aa0a457d\""
end
end
describe "documents" do
context "when the document doesn't exist" do
it "returns a 404" do
head "/phil/food/steak"
last_response.status.must_equal 404
last_response.body.must_be_empty
end
end
context "when the document exists" do
it "returns the required response headers" do
head "/phil/food/aguacate"
last_response.status.must_equal 200
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Cache-Control"].must_equal "no-cache"
end
it "responds with 304 when IF_NONE_MATCH header contains the ETag" do
header "If-None-Match", "\"0815etag\""
head "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
it "responds with 304 when IF_NONE_MATCH header contains weak ETAG matching the current ETag" do
header "If-None-Match", "W/\"0815etag\""
head "/phil/food/aguacate"
last_response.status.must_equal 304
last_response.headers["ETag"].must_equal "\"0815etag\""
last_response.headers["Last-Modified"].must_equal "Fri, 04 Mar 2016 12:20:18 GMT"
end
end
end
end
end
end end