Merge pull request #38 from 5apps/rs_spec_02
Support for remoteStorage -02 spec
This commit is contained in:
commit
74a2c21c85
@ -23,28 +23,35 @@ module RemoteStorage
|
|||||||
request_method = server.env["REQUEST_METHOD"]
|
request_method = server.env["REQUEST_METHOD"]
|
||||||
|
|
||||||
if directory.split("/").first == "public"
|
if directory.split("/").first == "public"
|
||||||
return true if request_method == "GET" && !listing
|
return true if ["GET", "HEAD"].include?(request_method) && !listing
|
||||||
end
|
end
|
||||||
|
|
||||||
authorizations = auth_bucket.get("#{user}:#{token}").data
|
authorizations = auth_bucket.get("#{user}:#{token}").data
|
||||||
permission = directory_permission(authorizations, directory)
|
permission = directory_permission(authorizations, directory)
|
||||||
|
|
||||||
server.halt 403 unless permission
|
server.halt 401 unless permission
|
||||||
if ["PUT", "DELETE"].include? request_method
|
if ["PUT", "DELETE"].include? request_method
|
||||||
server.halt 403 unless permission == "rw"
|
server.halt 401 unless permission == "rw"
|
||||||
end
|
end
|
||||||
rescue ::Riak::HTTPFailedRequest
|
rescue ::Riak::HTTPFailedRequest
|
||||||
server.halt 403
|
server.halt 401
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_head(user, directory, key)
|
||||||
|
object = data_bucket.get("#{user}:#{directory}:#{key}")
|
||||||
|
set_object_response_headers(object)
|
||||||
|
server.halt 200
|
||||||
|
rescue ::Riak::HTTPFailedRequest
|
||||||
|
server.halt 404
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_data(user, directory, key)
|
def get_data(user, directory, key)
|
||||||
object = data_bucket.get("#{user}:#{directory}:#{key}")
|
object = data_bucket.get("#{user}:#{directory}:#{key}")
|
||||||
|
|
||||||
server.headers["Content-Type"] = object.content_type
|
set_object_response_headers(object)
|
||||||
server.headers["Last-Modified"] = last_modified_date_for(object)
|
|
||||||
server.headers["ETag"] = object.etag
|
|
||||||
|
|
||||||
server.halt 304 if server.env["HTTP_IF_NONE_MATCH"] == object.etag
|
none_match = (server.env["HTTP_IF_NONE_MATCH"] || "").split(",").map(&:strip)
|
||||||
|
server.halt 304 if none_match.include? object.etag
|
||||||
|
|
||||||
if binary_key = object.meta["binary_key"]
|
if binary_key = object.meta["binary_key"]
|
||||||
object = cs_binary_bucket.files.get(binary_key[0])
|
object = cs_binary_bucket.files.get(binary_key[0])
|
||||||
@ -67,16 +74,21 @@ module RemoteStorage
|
|||||||
server.halt 404
|
server.halt 404
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_head_directory_listing(user, directory)
|
||||||
|
directory_object = directory_bucket.get("#{user}:#{directory}")
|
||||||
|
set_directory_response_headers(directory_object)
|
||||||
|
server.halt 200
|
||||||
|
rescue ::Riak::HTTPFailedRequest
|
||||||
|
server.halt 404
|
||||||
|
end
|
||||||
|
|
||||||
def get_directory_listing(user, directory)
|
def get_directory_listing(user, directory)
|
||||||
directory_object = directory_bucket.get("#{user}:#{directory}")
|
directory_object = directory_bucket.get("#{user}:#{directory}")
|
||||||
|
|
||||||
timestamp = directory_object.data.to_i
|
set_directory_response_headers(directory_object)
|
||||||
timestamp /= 1000 if timestamp.to_s.length == 13
|
|
||||||
server.headers["Content-Type"] = "application/json"
|
|
||||||
server.headers["Last-Modified"] = Time.at(timestamp).to_s(:rfc822)
|
|
||||||
server.headers["ETag"] = directory_object.etag
|
|
||||||
|
|
||||||
server.halt 304 if server.env["HTTP_IF_NONE_MATCH"] == directory_object.etag
|
none_match = (server.env["HTTP_IF_NONE_MATCH"] || "").split(",").map(&:strip)
|
||||||
|
server.halt 304 if none_match.include? directory_object.etag
|
||||||
|
|
||||||
listing = directory_listing(user, directory)
|
listing = directory_listing(user, directory)
|
||||||
|
|
||||||
@ -86,6 +98,8 @@ module RemoteStorage
|
|||||||
end
|
end
|
||||||
|
|
||||||
def put_data(user, directory, key, data, content_type=nil)
|
def put_data(user, directory, key, data, content_type=nil)
|
||||||
|
server.halt 409 if has_name_collision?(user, directory, key)
|
||||||
|
|
||||||
object = build_data_object(user, directory, key, data, content_type)
|
object = build_data_object(user, directory, key, data, content_type)
|
||||||
|
|
||||||
if required_match = server.env["HTTP_IF_MATCH"]
|
if required_match = server.env["HTTP_IF_MATCH"]
|
||||||
@ -116,7 +130,7 @@ module RemoteStorage
|
|||||||
update_all_directory_objects(user, directory, timestamp)
|
update_all_directory_objects(user, directory, timestamp)
|
||||||
|
|
||||||
server.headers["ETag"] = object.etag
|
server.headers["ETag"] = object.etag
|
||||||
server.halt 200
|
server.halt object_exists ? 200 : 201
|
||||||
rescue ::Riak::HTTPFailedRequest
|
rescue ::Riak::HTTPFailedRequest
|
||||||
server.halt 422
|
server.halt 422
|
||||||
end
|
end
|
||||||
@ -144,14 +158,24 @@ module RemoteStorage
|
|||||||
timestamp = (Time.now.to_f * 1000).to_i
|
timestamp = (Time.now.to_f * 1000).to_i
|
||||||
delete_or_update_directory_objects(user, directory, timestamp)
|
delete_or_update_directory_objects(user, directory, timestamp)
|
||||||
|
|
||||||
server.headers["ETag"] = etag
|
server.halt 200
|
||||||
server.halt riak_response[:code]
|
|
||||||
rescue ::Riak::HTTPFailedRequest
|
rescue ::Riak::HTTPFailedRequest
|
||||||
server.halt 404
|
server.halt 404
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def set_object_response_headers(object)
|
||||||
|
server.headers["Content-Type"] = object.content_type
|
||||||
|
server.headers["ETag"] = object.etag
|
||||||
|
server.headers["Content-Length"] = object_size(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_directory_response_headers(directory_object)
|
||||||
|
server.headers["Content-Type"] = "application/json"
|
||||||
|
server.headers["ETag"] = directory_object.etag
|
||||||
|
end
|
||||||
|
|
||||||
def extract_category(directory)
|
def extract_category(directory)
|
||||||
if directory.match(/^public\//)
|
if directory.match(/^public\//)
|
||||||
"public/#{directory.split('/')[1]}"
|
"public/#{directory.split('/')[1]}"
|
||||||
@ -166,7 +190,7 @@ module RemoteStorage
|
|||||||
object.content_type = content_type || "text/plain; charset=utf-8"
|
object.content_type = content_type || "text/plain; charset=utf-8"
|
||||||
|
|
||||||
directory_index = directory == "" ? "/" : directory
|
directory_index = directory == "" ? "/" : directory
|
||||||
object.indexes.merge!({:user_id_bin => [user],
|
object.indexes.merge!({:user_id_bin => [user],
|
||||||
:directory_bin => [directory_index]})
|
:directory_bin => [directory_index]})
|
||||||
|
|
||||||
object
|
object
|
||||||
@ -179,8 +203,8 @@ module RemoteStorage
|
|||||||
log_entry = opslog_bucket.new
|
log_entry = opslog_bucket.new
|
||||||
log_entry.content_type = "application/json"
|
log_entry.content_type = "application/json"
|
||||||
log_entry.data = {
|
log_entry.data = {
|
||||||
"count" => count,
|
"count" => count,
|
||||||
"size" => size,
|
"size" => size,
|
||||||
"category" => extract_category(directory)
|
"category" => extract_category(directory)
|
||||||
}
|
}
|
||||||
log_entry.indexes.merge!({:user_id_bin => [user]})
|
log_entry.indexes.merge!({:user_id_bin => [user]})
|
||||||
@ -210,14 +234,6 @@ module RemoteStorage
|
|||||||
::Riak::Serializers[content_type[/^[^;\s]+/]]
|
::Riak::Serializers[content_type[/^[^;\s]+/]]
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_modified_date_for(object)
|
|
||||||
timestamp = object.meta["timestamp"]
|
|
||||||
timestamp = (timestamp[0].to_i / 1000) if timestamp
|
|
||||||
last_modified = timestamp ? Time.at(timestamp) : object.last_modified
|
|
||||||
|
|
||||||
last_modified.to_s(:rfc822)
|
|
||||||
end
|
|
||||||
|
|
||||||
def directory_permission(authorizations, directory)
|
def directory_permission(authorizations, directory)
|
||||||
authorizations = authorizations.map do |auth|
|
authorizations = authorizations.map do |auth|
|
||||||
auth.index(":") ? auth.split(":") : [auth, "rw"]
|
auth.index(":") ? auth.split(":") : [auth, "rw"]
|
||||||
@ -239,26 +255,31 @@ module RemoteStorage
|
|||||||
end
|
end
|
||||||
|
|
||||||
def directory_listing(user, directory)
|
def directory_listing(user, directory)
|
||||||
listing = {}
|
listing = {
|
||||||
|
"@context" => "http://remotestorage.io/spec/folder-description",
|
||||||
|
"items" => {}
|
||||||
|
}
|
||||||
|
|
||||||
sub_directories(user, directory).each do |entry|
|
sub_directories(user, directory).each do |entry|
|
||||||
directory_name = entry["name"].split("/").last
|
directory_name = entry["name"].split("/").last
|
||||||
timestamp = entry["timestamp"].to_i
|
etag = entry["etag"]
|
||||||
etag = entry["etag"]
|
|
||||||
|
|
||||||
listing.merge!({ "#{directory_name}/" => etag })
|
listing["items"].merge!({ "#{directory_name}/" => { "ETag" => etag }})
|
||||||
end
|
end
|
||||||
|
|
||||||
directory_entries(user, directory).each do |entry|
|
directory_entries(user, directory).each do |entry|
|
||||||
entry_name = entry["name"]
|
entry_name = entry["name"]
|
||||||
timestamp = if entry["timestamp"]
|
etag = entry["etag"]
|
||||||
entry["timestamp"].to_i
|
content_type = entry["contentType"]
|
||||||
else
|
content_length = entry["contentLength"].to_i
|
||||||
DateTime.rfc2822(entry["last_modified"]).to_time.to_i
|
|
||||||
end
|
|
||||||
etag = entry["etag"]
|
|
||||||
|
|
||||||
listing.merge!({ entry_name => etag })
|
listing["items"].merge!({
|
||||||
|
entry_name => {
|
||||||
|
"ETag" => etag,
|
||||||
|
"Content-Type" => content_type,
|
||||||
|
"Content-Length" => content_length
|
||||||
|
}
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
listing
|
listing
|
||||||
@ -277,15 +298,15 @@ module RemoteStorage
|
|||||||
}
|
}
|
||||||
var name = v.key.match(/^[^:]*:(.*)/)[1]; // strip username from key
|
var name = v.key.match(/^[^:]*:(.*)/)[1]; // strip username from key
|
||||||
name = name.replace(dir_name + ':', ''); // strip directory from key
|
name = name.replace(dir_name + ':', ''); // strip directory from key
|
||||||
var last_modified_date = metadata['X-Riak-Last-Modified'];
|
|
||||||
var timestamp = metadata['X-Riak-Meta']['X-Riak-Meta-Timestamp'];
|
|
||||||
var etag = metadata['X-Riak-VTag'];
|
var etag = metadata['X-Riak-VTag'];
|
||||||
|
var contentType = metadata['content-type'];
|
||||||
|
var contentLength = metadata['X-Riak-Meta']['X-Riak-Meta-Content_length'] || 0;
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
name: name,
|
name: name,
|
||||||
last_modified: last_modified_date,
|
etag: etag,
|
||||||
timestamp: timestamp,
|
contentType: contentType,
|
||||||
etag: etag
|
contentLength: contentLength
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
EOH
|
EOH
|
||||||
@ -300,12 +321,10 @@ module RemoteStorage
|
|||||||
map_query = <<-EOH
|
map_query = <<-EOH
|
||||||
function(v){
|
function(v){
|
||||||
var name = v.key.match(/^[^:]*:(.*)/)[1]; // strip username from key
|
var name = v.key.match(/^[^:]*:(.*)/)[1]; // strip username from key
|
||||||
var timestamp = v.values[0]['data'];
|
|
||||||
var etag = v.values[0]['metadata']['X-Riak-VTag'];
|
var etag = v.values[0]['metadata']['X-Riak-VTag'];
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
name: name,
|
name: name,
|
||||||
timestamp: timestamp,
|
|
||||||
etag: etag
|
etag: etag
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@ -376,6 +395,8 @@ module RemoteStorage
|
|||||||
data = JSON.parse(data)
|
data = JSON.parse(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
object.meta["content_length"] = data.size
|
||||||
|
|
||||||
if serializer_for(object.content_type)
|
if serializer_for(object.content_type)
|
||||||
object.data = data
|
object.data = data
|
||||||
else
|
else
|
||||||
@ -392,7 +413,8 @@ module RemoteStorage
|
|||||||
:content_type => object.content_type
|
:content_type => object.content_type
|
||||||
)
|
)
|
||||||
|
|
||||||
object.meta["binary_key"] = cs_binary_object.key
|
object.meta["binary_key"] = cs_binary_object.key
|
||||||
|
object.meta["content_length"] = cs_binary_object.content_length
|
||||||
object.raw_data = ""
|
object.raw_data = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -419,6 +441,32 @@ module RemoteStorage
|
|||||||
parent_directories << ""
|
parent_directories << ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_name_collision?(user, directory, key)
|
||||||
|
parent_directories = parent_directories_for(directory).reverse
|
||||||
|
parent_directories.shift # remove root dir entry
|
||||||
|
|
||||||
|
# check for existing documents with the same name as one of the parent directories
|
||||||
|
parent_directories.each do |dir|
|
||||||
|
begin
|
||||||
|
parts = dir.split("/")
|
||||||
|
document_key = parts.pop
|
||||||
|
directory_name = parts.join("/")
|
||||||
|
data_bucket.get("#{user}:#{directory_name}:#{document_key}")
|
||||||
|
return true
|
||||||
|
rescue ::Riak::HTTPFailedRequest
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# check for an existing directory with same name as document
|
||||||
|
begin
|
||||||
|
directory_bucket.get("#{user}:#{directory}/#{key}")
|
||||||
|
return true
|
||||||
|
rescue ::Riak::HTTPFailedRequest
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
def client
|
def client
|
||||||
@client ||= ::Riak::Client.new(:host => settings['host'],
|
@client ||= ::Riak::Client.new(:host => settings['host'],
|
||||||
:http_port => settings['http_port'])
|
:http_port => settings['http_port'])
|
||||||
|
@ -46,6 +46,7 @@ class LiquorCabinet < Sinatra::Base
|
|||||||
'Access-Control-Expose-Headers' => 'ETag'
|
'Access-Control-Expose-Headers' => 'ETag'
|
||||||
headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"]
|
headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"]
|
||||||
headers['Cache-Control'] = 'no-cache'
|
headers['Cache-Control'] = 'no-cache'
|
||||||
|
headers['Expires'] = '0'
|
||||||
|
|
||||||
@user, @key = params[:user], params[:key]
|
@user, @key = params[:user], params[:key]
|
||||||
@directory = params[:splat] && params[:splat].first || ""
|
@directory = params[:splat] && params[:splat].first || ""
|
||||||
@ -65,6 +66,10 @@ class LiquorCabinet < Sinatra::Base
|
|||||||
storage.get_data(@user, @directory, @key)
|
storage.get_data(@user, @directory, @key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
head path do
|
||||||
|
storage.get_head(@user, @directory, @key)
|
||||||
|
end
|
||||||
|
|
||||||
put path do
|
put path do
|
||||||
data = request.body.read
|
data = request.body.read
|
||||||
|
|
||||||
@ -86,6 +91,10 @@ class LiquorCabinet < Sinatra::Base
|
|||||||
get path do
|
get path do
|
||||||
storage.get_directory_listing(@user, @directory)
|
storage.get_directory_listing(@user, @directory)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
head path do
|
||||||
|
storage.get_head_directory_listing(@user, @directory)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -13,6 +13,46 @@ describe "Directories" do
|
|||||||
header "Authorization", "Bearer 123"
|
header "Authorization", "Bearer 123"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "HEAD listing" do
|
||||||
|
before do
|
||||||
|
put "/jimmy/tasks/foo", "do the laundry"
|
||||||
|
put "/jimmy/tasks/http%3A%2F%2F5apps.com", "prettify design"
|
||||||
|
|
||||||
|
head "/jimmy/tasks/"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has an empty body" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.body.must_equal ""
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has an ETag header set" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["ETag"].wont_be_nil
|
||||||
|
|
||||||
|
# check that ETag stays the same
|
||||||
|
etag = last_response.headers["ETag"]
|
||||||
|
get "/jimmy/tasks/"
|
||||||
|
last_response.headers["ETag"].must_equal etag
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has CORS headers set" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["Access-Control-Allow-Origin"].must_equal "*"
|
||||||
|
last_response.headers["Access-Control-Allow-Methods"].must_equal "GET, PUT, DELETE"
|
||||||
|
last_response.headers["Access-Control-Allow-Headers"].must_equal "Authorization, Content-Type, Origin, If-Match, If-None-Match"
|
||||||
|
last_response.headers["Access-Control-Expose-Headers"].must_equal "ETag"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for an empty or absent directory" do
|
||||||
|
it "responds with 404" do
|
||||||
|
head "/jimmy/documents/"
|
||||||
|
|
||||||
|
last_response.status.must_equal 404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "GET listing" do
|
describe "GET listing" do
|
||||||
before do
|
before do
|
||||||
put "/jimmy/tasks/foo", "do the laundry"
|
put "/jimmy/tasks/foo", "do the laundry"
|
||||||
@ -20,7 +60,7 @@ describe "Directories" do
|
|||||||
put "/jimmy/tasks/%3A/foo%3Abar%40foo.org", "hello world"
|
put "/jimmy/tasks/%3A/foo%3Abar%40foo.org", "hello world"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "lists the objects with their version" do
|
it "lists the objects with version, length and content-type" do
|
||||||
get "/jimmy/tasks/"
|
get "/jimmy/tasks/"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
@ -29,32 +69,12 @@ describe "Directories" do
|
|||||||
foo = data_bucket.get("jimmy:tasks:foo")
|
foo = data_bucket.get("jimmy:tasks:foo")
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "http://5apps.com"
|
content["items"]["http://5apps.com"].wont_be_nil
|
||||||
content.must_include ":/"
|
content["items"][":/"].wont_be_nil
|
||||||
content.must_include "foo"
|
content["items"]["foo"].wont_be_nil
|
||||||
content["foo"].must_equal foo.etag.gsub(/"/, "")
|
content["items"]["foo"]["ETag"].must_equal foo.etag.gsub(/"/, "")
|
||||||
end
|
content["items"]["foo"]["Content-Type"].must_equal "text/plain"
|
||||||
|
content["items"]["foo"]["Content-Length"].must_equal 14
|
||||||
it "doesn't choke on colons in the directory name" do
|
|
||||||
get "/jimmy/tasks/%3A/"
|
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
|
||||||
last_response.content_type.must_equal "application/json"
|
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
|
||||||
content.must_include "foo:bar@foo.org"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has a Last-Modifier header set" do
|
|
||||||
get "/jimmy/tasks/"
|
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
|
||||||
last_response.headers["Last-Modified"].wont_be_nil
|
|
||||||
|
|
||||||
now = Time.now
|
|
||||||
last_modified = DateTime.parse(last_response.headers["Last-Modified"])
|
|
||||||
last_modified.year.must_equal now.year
|
|
||||||
last_modified.day.must_equal now.day
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "has an ETag header set" do
|
it "has an ETag header set" do
|
||||||
@ -79,6 +99,23 @@ describe "Directories" do
|
|||||||
last_response.headers["Access-Control-Expose-Headers"].must_equal "ETag"
|
last_response.headers["Access-Control-Expose-Headers"].must_equal "ETag"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "has caching headers set" do
|
||||||
|
get "/jimmy/tasks/"
|
||||||
|
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["Expires"].must_equal "0"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't choke on colons in the directory name" do
|
||||||
|
get "/jimmy/tasks/%3A/"
|
||||||
|
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.content_type.must_equal "application/json"
|
||||||
|
|
||||||
|
content = JSON.parse(last_response.body)
|
||||||
|
content["items"]["foo:bar@foo.org"].wont_be_nil
|
||||||
|
end
|
||||||
|
|
||||||
context "when If-None-Match header is set" do
|
context "when If-None-Match header is set" do
|
||||||
before do
|
before do
|
||||||
get "/jimmy/tasks/"
|
get "/jimmy/tasks/"
|
||||||
@ -104,6 +141,31 @@ describe "Directories" do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "when If-None-Match header is set with multiple revisions" do
|
||||||
|
before do
|
||||||
|
get "/jimmy/tasks/"
|
||||||
|
|
||||||
|
@etag = last_response.headers["ETag"]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with 'not modified' when it contains the current ETag" do
|
||||||
|
header "If-None-Match", "DEADBEEF,#{@etag} ,F00BA4"
|
||||||
|
get "/jimmy/tasks/"
|
||||||
|
|
||||||
|
last_response.status.must_equal 304
|
||||||
|
last_response.body.must_be_empty
|
||||||
|
last_response.headers["ETag"].must_equal @etag
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds normally when it does not contain the current ETag" do
|
||||||
|
header "If-None-Match", "FOO,BAR"
|
||||||
|
get "/jimmy/tasks/"
|
||||||
|
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.body.wont_be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "with sub-directories" do
|
context "with sub-directories" do
|
||||||
before do
|
before do
|
||||||
get "/jimmy/tasks/"
|
get "/jimmy/tasks/"
|
||||||
@ -120,10 +182,10 @@ describe "Directories" do
|
|||||||
home = directory_bucket.get("jimmy:tasks/home")
|
home = directory_bucket.get("jimmy:tasks/home")
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "foo"
|
content["items"]["foo"].wont_be_nil
|
||||||
content.must_include "http://5apps.com"
|
content["items"]["http://5apps.com"].wont_be_nil
|
||||||
content.must_include "home/"
|
content["items"]["home/"].wont_be_nil
|
||||||
content["home/"].must_equal home.etag.gsub(/"/, "")
|
content["items"]["home/"]["ETag"].must_equal home.etag.gsub(/"/, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "updates the ETag of the parent directory" do
|
it "updates the ETag of the parent directory" do
|
||||||
@ -150,10 +212,10 @@ describe "Directories" do
|
|||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.wont_include "/"
|
content["items"]["/"].must_be_nil
|
||||||
content.wont_include "tasks/"
|
content["items"]["tasks/"].must_be_nil
|
||||||
content.wont_include "home/"
|
content["items"]["home/"].must_be_nil
|
||||||
content.must_include "homework"
|
content["items"]["homework"].wont_be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -167,8 +229,7 @@ describe "Directories" do
|
|||||||
projects = directory_bucket.get("jimmy:tasks/private/projects")
|
projects = directory_bucket.get("jimmy:tasks/private/projects")
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "projects/"
|
content["items"]["projects/"]["ETag"].must_equal projects.etag.gsub(/"/, "")
|
||||||
content["projects/"].must_equal projects.etag.gsub(/"/, "")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "updates the timestamps of the existing directory objects" do
|
it "updates the timestamps of the existing directory objects" do
|
||||||
@ -203,8 +264,9 @@ describe "Directories" do
|
|||||||
jaypeg = data_bucket.get("jimmy:tasks:jaypeg.jpg")
|
jaypeg = data_bucket.get("jimmy:tasks:jaypeg.jpg")
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "jaypeg.jpg"
|
content["items"]["jaypeg.jpg"]["ETag"].must_equal jaypeg.etag.gsub(/"/, "")
|
||||||
content["jaypeg.jpg"].must_equal jaypeg.etag.gsub(/"/, "")
|
content["items"]["jaypeg.jpg"]["Content-Type"].must_equal "image/jpeg"
|
||||||
|
content["items"]["jaypeg.jpg"]["Content-Length"].must_equal 16044
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -224,8 +286,9 @@ describe "Directories" do
|
|||||||
jaypeg = data_bucket.get("jimmy:tasks:jaypeg.jpg")
|
jaypeg = data_bucket.get("jimmy:tasks:jaypeg.jpg")
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "jaypeg.jpg"
|
content["items"]["jaypeg.jpg"]["ETag"].must_equal jaypeg.etag.gsub(/"/, "")
|
||||||
content["jaypeg.jpg"].must_equal jaypeg.etag.gsub(/"/, "")
|
content["items"]["jaypeg.jpg"]["Content-Type"].must_equal "image/jpeg"
|
||||||
|
content["items"]["jaypeg.jpg"]["Content-Length"].must_equal 16044
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -244,8 +307,7 @@ describe "Directories" do
|
|||||||
laundry = data_bucket.get("jimmy:tasks/home:laundry")
|
laundry = data_bucket.get("jimmy:tasks/home:laundry")
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "laundry"
|
content["items"]["laundry"]["ETag"].must_equal laundry.etag.gsub(/"/, "")
|
||||||
content["laundry"].must_equal laundry.etag.gsub(/"/, "")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -268,7 +330,7 @@ describe "Directories" do
|
|||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "foo~bar/"
|
content["items"]["foo~bar/"].wont_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "lists the containing objects" do
|
it "lists the containing objects" do
|
||||||
@ -277,7 +339,7 @@ describe "Directories" do
|
|||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "task1"
|
content["items"]["task1"].wont_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns the requested object" do
|
it "returns the requested object" do
|
||||||
@ -300,7 +362,7 @@ describe "Directories" do
|
|||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "bla~blub"
|
content["items"]["bla~blub"].wont_be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -322,10 +384,10 @@ describe "Directories" do
|
|||||||
tasks = directory_bucket.get("jimmy:tasks")
|
tasks = directory_bucket.get("jimmy:tasks")
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "root-1"
|
content["items"]["root-1"].wont_be_nil
|
||||||
content.must_include "root-2"
|
content["items"]["root-2"].wont_be_nil
|
||||||
content.must_include "tasks/"
|
content["items"]["tasks/"].wont_be_nil
|
||||||
content["tasks/"].must_equal tasks.etag.gsub(/"/, "")
|
content["items"]["tasks/"]["ETag"].must_equal tasks.etag.gsub(/"/, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "has an ETag header set" do
|
it "has an ETag header set" do
|
||||||
@ -352,7 +414,7 @@ describe "Directories" do
|
|||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "5apps"
|
content["items"]["5apps"].wont_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "has an ETag header set" do
|
it "has an ETag header set" do
|
||||||
@ -376,7 +438,7 @@ describe "Directories" do
|
|||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "5apps"
|
content["items"]["5apps"].wont_be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -388,13 +450,13 @@ describe "Directories" do
|
|||||||
it "does not allow a directory listing of the public root" do
|
it "does not allow a directory listing of the public root" do
|
||||||
get "/jimmy/public/"
|
get "/jimmy/public/"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not allow a directory listing of a sub-directory" do
|
it "does not allow a directory listing of a sub-directory" do
|
||||||
get "/jimmy/public/bookmarks/"
|
get "/jimmy/public/bookmarks/"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -498,7 +560,7 @@ describe "Directories" do
|
|||||||
it "deletes the directory objects for all empty parent directories" do
|
it "deletes the directory objects for all empty parent directories" do
|
||||||
delete "/jimmy/tasks/home/trash"
|
delete "/jimmy/tasks/home/trash"
|
||||||
|
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
lambda {
|
lambda {
|
||||||
directory_bucket.get("jimmy:tasks/home")
|
directory_bucket.get("jimmy:tasks/home")
|
||||||
|
@ -26,8 +26,6 @@ describe "Permissions" do
|
|||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
last_response.body.must_equal "some text data"
|
last_response.body.must_equal "some text data"
|
||||||
|
|
||||||
last_response.headers["Last-Modified"].wont_be_nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns the value from a sub-directory" do
|
it "returns the value from a sub-directory" do
|
||||||
@ -79,10 +77,10 @@ describe "Permissions" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when not authorized" do
|
context "when not authorized" do
|
||||||
it "returns a 403 for a key in a top-level directory" do
|
it "returns a 401 for a key in a top-level directory" do
|
||||||
get "/jimmy/confidential/bar"
|
get "/jimmy/confidential/bar"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -101,14 +99,14 @@ describe "Permissions" do
|
|||||||
it "saves the value when there are write permissions" do
|
it "saves the value when there are write permissions" do
|
||||||
put "/jimmy/contacts/1", "John Doe"
|
put "/jimmy/contacts/1", "John Doe"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:contacts:1").data.must_equal "John Doe"
|
data_bucket.get("jimmy:contacts:1").data.must_equal "John Doe"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 403 when there are read permissions only" do
|
it "returns a 401 when there are read permissions only" do
|
||||||
put "/jimmy/documents/foo", "some text"
|
put "/jimmy/documents/foo", "some text"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -116,21 +114,21 @@ describe "Permissions" do
|
|||||||
it "saves the value when there are direct write permissions" do
|
it "saves the value when there are direct write permissions" do
|
||||||
put "/jimmy/tasks/home/1", "take out the trash"
|
put "/jimmy/tasks/home/1", "take out the trash"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:tasks/home:1").data.must_equal "take out the trash"
|
data_bucket.get("jimmy:tasks/home:1").data.must_equal "take out the trash"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "saves the value when there are write permissions for a parent directory" do
|
it "saves the value when there are write permissions for a parent directory" do
|
||||||
put "/jimmy/contacts/family/1", "Bobby Brother"
|
put "/jimmy/contacts/family/1", "Bobby Brother"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:contacts/family:1").data.must_equal "Bobby Brother"
|
data_bucket.get("jimmy:contacts/family:1").data.must_equal "Bobby Brother"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 403 when there are read permissions only" do
|
it "returns a 401 when there are read permissions only" do
|
||||||
put "/jimmy/documents/business/1", "some text"
|
put "/jimmy/documents/business/1", "some text"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -139,23 +137,23 @@ describe "Permissions" do
|
|||||||
it "saves the value" do
|
it "saves the value" do
|
||||||
put "/jimmy/public/contacts/foo", "Foo Bar"
|
put "/jimmy/public/contacts/foo", "Foo Bar"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:public/contacts:foo").data.must_equal "Foo Bar"
|
data_bucket.get("jimmy:public/contacts:foo").data.must_equal "Foo Bar"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "saves the value to a sub-directory" do
|
it "saves the value to a sub-directory" do
|
||||||
put "/jimmy/public/contacts/family/foo", "Foo Bar"
|
put "/jimmy/public/contacts/family/foo", "Foo Bar"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:public/contacts/family:foo").data.must_equal "Foo Bar"
|
data_bucket.get("jimmy:public/contacts/family:foo").data.must_equal "Foo Bar"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when not authorized for the corresponding category" do
|
context "when not authorized for the corresponding category" do
|
||||||
it "returns a 403" do
|
it "returns a 401" do
|
||||||
put "/jimmy/public/documents/foo", "Foo Bar"
|
put "/jimmy/public/documents/foo", "Foo Bar"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -186,7 +184,7 @@ describe "Permissions" do
|
|||||||
it "removes the key from a top-level directory" do
|
it "removes the key from a top-level directory" do
|
||||||
delete "/jimmy/tasks/1"
|
delete "/jimmy/tasks/1"
|
||||||
|
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
lambda {
|
lambda {
|
||||||
data_bucket.get("jimmy:tasks:1")
|
data_bucket.get("jimmy:tasks:1")
|
||||||
}.must_raise Riak::HTTPFailedRequest
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
@ -195,7 +193,7 @@ describe "Permissions" do
|
|||||||
it "removes the key from a top-level directory" do
|
it "removes the key from a top-level directory" do
|
||||||
delete "/jimmy/tasks/home/1"
|
delete "/jimmy/tasks/home/1"
|
||||||
|
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
lambda {
|
lambda {
|
||||||
data_bucket.get("jimmy:tasks/home:1")
|
data_bucket.get("jimmy:tasks/home:1")
|
||||||
}.must_raise Riak::HTTPFailedRequest
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
@ -212,7 +210,7 @@ describe "Permissions" do
|
|||||||
it "removes the key" do
|
it "removes the key" do
|
||||||
delete "/jimmy/public/tasks/open"
|
delete "/jimmy/public/tasks/open"
|
||||||
|
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
lambda {
|
lambda {
|
||||||
data_bucket.get("jimmy:public/tasks:open")
|
data_bucket.get("jimmy:public/tasks:open")
|
||||||
}.must_raise Riak::HTTPFailedRequest
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
@ -233,16 +231,16 @@ describe "Permissions" do
|
|||||||
object.store
|
object.store
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 403 for a key in a top-level directory" do
|
it "returns a 401 for a key in a top-level directory" do
|
||||||
delete "/jimmy/documents/private"
|
delete "/jimmy/documents/private"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 403 for a key in a sub-directory" do
|
it "returns a 401 for a key in a sub-directory" do
|
||||||
delete "/jimmy/documents/business/foo"
|
delete "/jimmy/documents/business/foo"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
|
|
||||||
context "public directory" do
|
context "public directory" do
|
||||||
@ -253,10 +251,10 @@ describe "Permissions" do
|
|||||||
object.store
|
object.store
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 403" do
|
it "returns a 401" do
|
||||||
delete "/jimmy/public/documents/foo"
|
delete "/jimmy/public/documents/foo"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -289,14 +287,14 @@ describe "Permissions" do
|
|||||||
it "allows PUT requests" do
|
it "allows PUT requests" do
|
||||||
put "/jimmy/contacts/1", "John Doe"
|
put "/jimmy/contacts/1", "John Doe"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:contacts:1").data.must_equal "John Doe"
|
data_bucket.get("jimmy:contacts:1").data.must_equal "John Doe"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "allows DELETE requests" do
|
it "allows DELETE requests" do
|
||||||
delete "/jimmy/documents/very/interesting/text"
|
delete "/jimmy/documents/very/interesting/text"
|
||||||
|
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
lambda {
|
lambda {
|
||||||
data_bucket.get("jimmy:documents/very/interesting:text")
|
data_bucket.get("jimmy:documents/very/interesting:text")
|
||||||
}.must_raise Riak::HTTPFailedRequest
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
@ -320,14 +318,14 @@ describe "Permissions" do
|
|||||||
it "allows PUT requests" do
|
it "allows PUT requests" do
|
||||||
put "/jimmy/1", "Gonna kick it root down"
|
put "/jimmy/1", "Gonna kick it root down"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy::1").data.must_equal "Gonna kick it root down"
|
data_bucket.get("jimmy::1").data.must_equal "Gonna kick it root down"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "allows DELETE requests" do
|
it "allows DELETE requests" do
|
||||||
delete "/jimmy/root"
|
delete "/jimmy/root"
|
||||||
|
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
lambda {
|
lambda {
|
||||||
data_bucket.get("jimmy::root")
|
data_bucket.get("jimmy::root")
|
||||||
}.must_raise Riak::HTTPFailedRequest
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
@ -351,14 +349,14 @@ describe "Permissions" do
|
|||||||
it "allows PUT requests" do
|
it "allows PUT requests" do
|
||||||
put "/jimmy/public/1", "Hello World"
|
put "/jimmy/public/1", "Hello World"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:public:1").data.must_equal "Hello World"
|
data_bucket.get("jimmy:public:1").data.must_equal "Hello World"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "allows DELETE requests" do
|
it "allows DELETE requests" do
|
||||||
delete "/jimmy/public/tasks/hello"
|
delete "/jimmy/public/tasks/hello"
|
||||||
|
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
lambda {
|
lambda {
|
||||||
data_bucket.get("jimmy:public/tasks:hello")
|
data_bucket.get("jimmy:public/tasks:hello")
|
||||||
}.must_raise Riak::HTTPFailedRequest
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
@ -385,13 +383,13 @@ describe "Permissions" do
|
|||||||
it "disallows PUT requests" do
|
it "disallows PUT requests" do
|
||||||
put "/jimmy/documents/foo", "some text"
|
put "/jimmy/documents/foo", "some text"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
|
|
||||||
it "disallows DELETE requests" do
|
it "disallows DELETE requests" do
|
||||||
delete "/jimmy/documents/very/interesting/text"
|
delete "/jimmy/documents/very/interesting/text"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
|
|
||||||
context "public directory" do
|
context "public directory" do
|
||||||
@ -411,13 +409,13 @@ describe "Permissions" do
|
|||||||
it "disallows PUT requests" do
|
it "disallows PUT requests" do
|
||||||
put "/jimmy/public/tasks/foo", "some text"
|
put "/jimmy/public/tasks/foo", "some text"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
|
|
||||||
it "disallows DELETE requests" do
|
it "disallows DELETE requests" do
|
||||||
delete "/jimmy/public/tasks/hello"
|
delete "/jimmy/public/tasks/hello"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -7,6 +7,32 @@ describe "App with Riak backend" do
|
|||||||
purge_all_buckets
|
purge_all_buckets
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "HEAD public data" do
|
||||||
|
before do
|
||||||
|
object = data_bucket.new("jimmy:public:foo")
|
||||||
|
object.content_type = "text/plain"
|
||||||
|
object.data = "some text data"
|
||||||
|
object.store
|
||||||
|
|
||||||
|
head "/jimmy/public/foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an empty body" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.body.must_equal ""
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has an ETag header set" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["ETag"].wont_be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has a Content-Length header set" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["Content-Length"].must_equal 14
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "GET public data" do
|
describe "GET public data" do
|
||||||
before do
|
before do
|
||||||
object = data_bucket.new("jimmy:public:foo")
|
object = data_bucket.new("jimmy:public:foo")
|
||||||
@ -22,21 +48,20 @@ describe "App with Riak backend" do
|
|||||||
last_response.body.must_equal "some text data"
|
last_response.body.must_equal "some text data"
|
||||||
end
|
end
|
||||||
|
|
||||||
# If this one fails, try restarting Riak
|
|
||||||
it "has a Last-Modified header set" do
|
|
||||||
last_response.status.must_equal 200
|
|
||||||
last_response.headers["Last-Modified"].wont_be_nil
|
|
||||||
|
|
||||||
now = Time.now
|
|
||||||
last_modified = DateTime.parse(last_response.headers["Last-Modified"])
|
|
||||||
last_modified.year.must_equal now.year
|
|
||||||
last_modified.day.must_equal now.day
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has an ETag header set" do
|
it "has an ETag header set" do
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
last_response.headers["ETag"].wont_be_nil
|
last_response.headers["ETag"].wont_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "has a Content-Length header set" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["Content-Length"].must_equal "14"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has caching headers set" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["Expires"].must_equal "0"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET data with custom content type" do
|
describe "GET data with custom content type" do
|
||||||
@ -70,6 +95,37 @@ describe "App with Riak backend" do
|
|||||||
auth.store
|
auth.store
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "HEAD" do
|
||||||
|
before do
|
||||||
|
header "Authorization", "Bearer 123"
|
||||||
|
head "/jimmy/documents/foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an empty body" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.body.must_equal ""
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has an ETag header set" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["ETag"].wont_be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has a Content-Length header set" do
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.headers["Content-Length"].must_equal 22
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "HEAD nonexisting key" do
|
||||||
|
it "returns a 404" do
|
||||||
|
header "Authorization", "Bearer 123"
|
||||||
|
head "/jimmy/documents/somestupidkey"
|
||||||
|
|
||||||
|
last_response.status.must_equal 404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "GET" do
|
describe "GET" do
|
||||||
before do
|
before do
|
||||||
header "Authorization", "Bearer 123"
|
header "Authorization", "Bearer 123"
|
||||||
@ -100,6 +156,25 @@ describe "App with Riak backend" do
|
|||||||
last_response.body.must_equal "some private text data"
|
last_response.body.must_equal "some private text data"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "when If-None-Match header is set with multiple revisions" do
|
||||||
|
it "responds with 'not modified' when it contains the current ETag" do
|
||||||
|
header "If-None-Match", "DEADBEEF,#{@etag},F00BA4"
|
||||||
|
get "/jimmy/documents/foo"
|
||||||
|
|
||||||
|
last_response.status.must_equal 304
|
||||||
|
last_response.body.must_be_empty
|
||||||
|
last_response.headers["ETag"].must_equal @etag
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds normally when it does not contain the current ETag" do
|
||||||
|
header "If-None-Match", "FOO,BAR"
|
||||||
|
get "/jimmy/documents/foo"
|
||||||
|
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.body.must_equal "some private text data"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET nonexisting key" do
|
describe "GET nonexisting key" do
|
||||||
@ -122,7 +197,7 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "saves the value" do
|
it "saves the value" do
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
last_response.body.must_equal ""
|
last_response.body.must_equal ""
|
||||||
data_bucket.get("jimmy:documents:bar").data.must_equal "another text"
|
data_bucket.get("jimmy:documents:bar").data.must_equal "another text"
|
||||||
end
|
end
|
||||||
@ -161,7 +236,7 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "saves the value (as JSON)" do
|
it "saves the value (as JSON)" do
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:documents:jason").data.must_be_kind_of Hash
|
data_bucket.get("jimmy:documents:jason").data.must_be_kind_of Hash
|
||||||
data_bucket.get("jimmy:documents:jason").data.must_equal({"foo" => "bar", "unhosted" => 1})
|
data_bucket.get("jimmy:documents:jason").data.must_equal({"foo" => "bar", "unhosted" => 1})
|
||||||
end
|
end
|
||||||
@ -186,7 +261,7 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "saves the value" do
|
it "saves the value" do
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:documents:magic").raw_data.must_equal "pure magic"
|
data_bucket.get("jimmy:documents:magic").raw_data.must_equal "pure magic"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -210,7 +285,7 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "saves the value (as JSON)" do
|
it "saves the value (as JSON)" do
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:documents:jason").data.must_be_kind_of Hash
|
data_bucket.get("jimmy:documents:jason").data.must_be_kind_of Hash
|
||||||
data_bucket.get("jimmy:documents:jason").data.must_equal({"foo" => "bar", "unhosted" => 1})
|
data_bucket.get("jimmy:documents:jason").data.must_equal({"foo" => "bar", "unhosted" => 1})
|
||||||
end
|
end
|
||||||
@ -227,6 +302,32 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "naming collissions between documents and directories" do
|
||||||
|
before do
|
||||||
|
put "/jimmy/documents/archive/document", "lorem ipsum"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with 409 when directory with same name already exists" do
|
||||||
|
put "/jimmy/documents/archive", "some awesome content"
|
||||||
|
|
||||||
|
last_response.status.must_equal 409
|
||||||
|
|
||||||
|
lambda {
|
||||||
|
data_bucket.get("jimmy:documents/archive")
|
||||||
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds with 409 when there is an existing document with same name as one of the directories" do
|
||||||
|
put "/jimmy/documents/archive/document/subdir/doc", "some awesome content"
|
||||||
|
|
||||||
|
last_response.status.must_equal 409
|
||||||
|
|
||||||
|
lambda {
|
||||||
|
data_bucket.get("jimmy:documents/archive/document/subdir/doc")
|
||||||
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "with existing content" do
|
describe "with existing content" do
|
||||||
before do
|
before do
|
||||||
put "/jimmy/documents/archive/foo", "lorem ipsum"
|
put "/jimmy/documents/archive/foo", "lorem ipsum"
|
||||||
@ -304,7 +405,7 @@ describe "App with Riak backend" do
|
|||||||
it "succeeds when the document does not exist" do
|
it "succeeds when the document does not exist" do
|
||||||
put "/jimmy/documents/archive/bar", "my little content"
|
put "/jimmy/documents/archive/bar", "my little content"
|
||||||
|
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -332,7 +433,7 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "saves the value" do
|
it "saves the value" do
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:public/documents/notes:foo").data.must_equal "note to self"
|
data_bucket.get("jimmy:public/documents/notes:foo").data.must_equal "note to self"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -380,6 +481,12 @@ describe "App with Riak backend" do
|
|||||||
last_response.headers["ETag"].must_equal etag
|
last_response.headers["ETag"].must_equal etag
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "responds with a Content-Length header" do
|
||||||
|
get "/jimmy/documents/jaypeg"
|
||||||
|
|
||||||
|
last_response.headers["Content-Length"].must_equal "16044"
|
||||||
|
end
|
||||||
|
|
||||||
it "changes the ETag when updating the file" do
|
it "changes the ETag when updating the file" do
|
||||||
old_etag = last_response.headers["ETag"]
|
old_etag = last_response.headers["ETag"]
|
||||||
put "/jimmy/documents/jaypeg", @image
|
put "/jimmy/documents/jaypeg", @image
|
||||||
@ -482,7 +589,7 @@ describe "App with Riak backend" do
|
|||||||
get "/jimmy/documents/bar:baz/"
|
get "/jimmy/documents/bar:baz/"
|
||||||
|
|
||||||
content = JSON.parse(last_response.body)
|
content = JSON.parse(last_response.body)
|
||||||
content.must_include "john@doe.com"
|
content["items"]["john@doe.com"].wont_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "delivers the data correctly" do
|
it "delivers the data correctly" do
|
||||||
@ -513,7 +620,7 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "saves an empty JSON object" do
|
it "saves an empty JSON object" do
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 201
|
||||||
data_bucket.get("jimmy:documents:jason").data.must_be_kind_of Hash
|
data_bucket.get("jimmy:documents:jason").data.must_be_kind_of Hash
|
||||||
data_bucket.get("jimmy:documents:jason").data.must_equal({})
|
data_bucket.get("jimmy:documents:jason").data.must_equal({})
|
||||||
end
|
end
|
||||||
@ -543,7 +650,7 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "removes the key" do
|
it "removes the key" do
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
lambda {
|
lambda {
|
||||||
data_bucket.get("jimmy:documents:foo")
|
data_bucket.get("jimmy:documents:foo")
|
||||||
}.must_raise Riak::HTTPFailedRequest
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
@ -558,10 +665,6 @@ describe "App with Riak backend" do
|
|||||||
log_entry.data["category"].must_equal "documents"
|
log_entry.data["category"].must_equal "documents"
|
||||||
log_entry.indexes["user_id_bin"].must_include "jimmy"
|
log_entry.indexes["user_id_bin"].must_include "jimmy"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets the ETag header" do
|
|
||||||
last_response.headers["ETag"].wont_be_nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "non-existing object" do
|
context "non-existing object" do
|
||||||
@ -587,7 +690,7 @@ describe "App with Riak backend" do
|
|||||||
header "If-Match", old_etag
|
header "If-Match", old_etag
|
||||||
|
|
||||||
delete "/jimmy/documents/foo"
|
delete "/jimmy/documents/foo"
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
get "/jimmy/documents/foo"
|
get "/jimmy/documents/foo"
|
||||||
last_response.status.must_equal 404
|
last_response.status.must_equal 404
|
||||||
@ -616,14 +719,14 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "removes the main object" do
|
it "removes the main object" do
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
lambda {
|
lambda {
|
||||||
data_bucket.get("jimmy:documents:jaypeg")
|
data_bucket.get("jimmy:documents:jaypeg")
|
||||||
}.must_raise Riak::HTTPFailedRequest
|
}.must_raise Riak::HTTPFailedRequest
|
||||||
end
|
end
|
||||||
|
|
||||||
it "removes the binary object" do
|
it "removes the binary object" do
|
||||||
last_response.status.must_equal 204
|
last_response.status.must_equal 200
|
||||||
|
|
||||||
binary = cs_binary_bucket.files.get("jimmy:documents:jaypeg")
|
binary = cs_binary_bucket.files.get("jimmy:documents:jaypeg")
|
||||||
binary.must_be_nil
|
binary.must_be_nil
|
||||||
@ -651,26 +754,26 @@ describe "App with Riak backend" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "GET" do
|
describe "GET" do
|
||||||
it "returns a 403" do
|
it "returns a 401" do
|
||||||
get "/jimmy/documents/foo"
|
get "/jimmy/documents/foo"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "PUT" do
|
describe "PUT" do
|
||||||
it "returns a 403" do
|
it "returns a 401" do
|
||||||
put "/jimmy/documents/foo", "some text"
|
put "/jimmy/documents/foo", "some text"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "DELETE" do
|
describe "DELETE" do
|
||||||
it "returns a 403" do
|
it "returns a 401" do
|
||||||
delete "/jimmy/documents/foo"
|
delete "/jimmy/documents/foo"
|
||||||
|
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user