commit
165a30bcca
@ -40,8 +40,11 @@ module RemoteStorage
|
||||
def get_data(user, directory, key)
|
||||
object = data_bucket.get("#{user}:#{directory}:#{key}")
|
||||
|
||||
server.halt 304 if server.env["HTTP_IF_NONE_MATCH"] == object.etag
|
||||
|
||||
server.headers["Content-Type"] = object.content_type
|
||||
server.headers["Last-Modified"] = last_modified_date_for(object)
|
||||
server.headers["ETag"] = object.etag
|
||||
|
||||
if binary_key = object.meta["binary_key"]
|
||||
object = cs_binary_bucket.files.get(binary_key[0])
|
||||
@ -66,25 +69,34 @@ module RemoteStorage
|
||||
|
||||
def get_directory_listing(user, directory)
|
||||
directory_object = directory_bucket.get("#{user}:#{directory}")
|
||||
|
||||
server.halt 304 if server.env["HTTP_IF_NONE_MATCH"] == directory_object.etag
|
||||
|
||||
timestamp = directory_object.data.to_i
|
||||
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
|
||||
|
||||
listing = directory_listing(user, directory)
|
||||
|
||||
return listing.to_json
|
||||
rescue ::Riak::HTTPFailedRequest
|
||||
server.headers["Content-Type"] = "application/json"
|
||||
return "{}"
|
||||
server.halt 404
|
||||
end
|
||||
|
||||
def put_data(user, directory, key, data, content_type=nil)
|
||||
object = build_data_object(user, directory, key, data, content_type)
|
||||
|
||||
if required_match = server.env["HTTP_IF_MATCH"]
|
||||
server.halt 412 unless required_match == object.etag
|
||||
end
|
||||
|
||||
object_exists = !object.raw_data.nil?
|
||||
existing_object_size = object_size(object)
|
||||
|
||||
server.halt 412 if object_exists && server.env["HTTP_IF_NONE_MATCH"] == "*"
|
||||
|
||||
timestamp = (Time.now.to_f * 1000).to_i
|
||||
object.meta["timestamp"] = timestamp
|
||||
|
||||
@ -96,13 +108,14 @@ module RemoteStorage
|
||||
new_object_size = object.raw_data.size
|
||||
end
|
||||
|
||||
response = object.store
|
||||
object.store
|
||||
|
||||
log_count = object_exists ? 0 : 1
|
||||
log_operation(user, directory, log_count, new_object_size, existing_object_size)
|
||||
|
||||
update_all_directory_objects(user, directory, timestamp)
|
||||
|
||||
server.headers["ETag"] = object.etag
|
||||
server.halt 200
|
||||
rescue ::Riak::HTTPFailedRequest
|
||||
server.halt 422
|
||||
@ -111,6 +124,11 @@ module RemoteStorage
|
||||
def delete_data(user, directory, key)
|
||||
object = data_bucket.get("#{user}:#{directory}:#{key}")
|
||||
existing_object_size = object_size(object)
|
||||
etag = object.etag
|
||||
|
||||
if required_match = server.env["HTTP_IF_MATCH"]
|
||||
server.halt 412 unless required_match == etag
|
||||
end
|
||||
|
||||
if binary_key = object.meta["binary_key"]
|
||||
object = cs_binary_bucket.files.get(binary_key[0])
|
||||
@ -126,6 +144,7 @@ module RemoteStorage
|
||||
timestamp = (Time.now.to_f * 1000).to_i
|
||||
delete_or_update_directory_objects(user, directory, timestamp)
|
||||
|
||||
server.headers["ETag"] = etag
|
||||
server.halt riak_response[:code]
|
||||
rescue ::Riak::HTTPFailedRequest
|
||||
server.halt 404
|
||||
@ -222,8 +241,9 @@ module RemoteStorage
|
||||
sub_directories(user, directory).each do |entry|
|
||||
directory_name = entry["name"].split("/").last
|
||||
timestamp = entry["timestamp"].to_i
|
||||
etag = entry["etag"]
|
||||
|
||||
listing.merge!({ "#{directory_name}/" => timestamp })
|
||||
listing.merge!({ "#{directory_name}/" => etag })
|
||||
end
|
||||
|
||||
directory_entries(user, directory).each do |entry|
|
||||
@ -233,20 +253,16 @@ module RemoteStorage
|
||||
else
|
||||
DateTime.rfc2822(entry["last_modified"]).to_time.to_i
|
||||
end
|
||||
etag = entry["etag"]
|
||||
|
||||
listing.merge!({ entry_name => timestamp })
|
||||
listing.merge!({ entry_name => etag })
|
||||
end
|
||||
|
||||
listing
|
||||
end
|
||||
|
||||
def directory_entries(user, directory)
|
||||
directory = "/" if directory == ""
|
||||
|
||||
user_keys = data_bucket.get_index("user_id_bin", user)
|
||||
directory_keys = data_bucket.get_index("directory_bin", directory)
|
||||
|
||||
all_keys = user_keys & directory_keys
|
||||
all_keys = user_directory_keys(user, directory, data_bucket)
|
||||
return [] if all_keys.empty?
|
||||
|
||||
map_query = <<-EOH
|
||||
@ -256,48 +272,53 @@ module RemoteStorage
|
||||
key_name = keys.join(':');
|
||||
last_modified_date = v.values[0]['metadata']['X-Riak-Last-Modified'];
|
||||
timestamp = v.values[0]['metadata']['X-Riak-Meta']['X-Riak-Meta-Timestamp'];
|
||||
etag = v.values[0]['metadata']['X-Riak-VTag'];
|
||||
return [{
|
||||
name: key_name,
|
||||
last_modified: last_modified_date,
|
||||
timestamp: timestamp,
|
||||
etag: etag
|
||||
}];
|
||||
}
|
||||
EOH
|
||||
|
||||
map_reduce = ::Riak::MapReduce.new(client)
|
||||
all_keys.each do |key|
|
||||
map_reduce.add(data_bucket.name, key)
|
||||
end
|
||||
|
||||
map_reduce.
|
||||
map(map_query, :keep => true).
|
||||
run
|
||||
run_map_reduce(data_bucket, all_keys, map_query)
|
||||
end
|
||||
|
||||
def sub_directories(user, directory)
|
||||
directory = "/" if directory == ""
|
||||
|
||||
user_keys = directory_bucket.get_index("user_id_bin", user)
|
||||
directory_keys = directory_bucket.get_index("directory_bin", directory)
|
||||
|
||||
all_keys = user_keys & directory_keys
|
||||
all_keys = user_directory_keys(user, directory, directory_bucket)
|
||||
return [] if all_keys.empty?
|
||||
|
||||
map_query = <<-EOH
|
||||
function(v){
|
||||
keys = v.key.split(':');
|
||||
key_name = keys[keys.length-1];
|
||||
timestamp = v.values[0]['data']
|
||||
timestamp = v.values[0]['data'];
|
||||
etag = v.values[0]['metadata']['X-Riak-VTag'];
|
||||
return [{
|
||||
name: key_name,
|
||||
timestamp: timestamp,
|
||||
etag: etag
|
||||
}];
|
||||
}
|
||||
EOH
|
||||
|
||||
run_map_reduce(directory_bucket, all_keys, map_query)
|
||||
end
|
||||
|
||||
def user_directory_keys(user, directory, bucket)
|
||||
directory = "/" if directory == ""
|
||||
|
||||
user_keys = bucket.get_index("user_id_bin", user)
|
||||
directory_keys = bucket.get_index("directory_bin", directory)
|
||||
|
||||
user_keys & directory_keys
|
||||
end
|
||||
|
||||
def run_map_reduce(bucket, keys, map_query)
|
||||
map_reduce = ::Riak::MapReduce.new(client)
|
||||
all_keys.each do |key|
|
||||
map_reduce.add(directory_bucket.name, key)
|
||||
keys.each do |key|
|
||||
map_reduce.add(bucket.name, key)
|
||||
end
|
||||
|
||||
map_reduce.
|
||||
|
||||
@ -42,7 +42,8 @@ class LiquorCabinet < Sinatra::Base
|
||||
before path do
|
||||
headers 'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'GET, PUT, DELETE',
|
||||
'Access-Control-Allow-Headers' => 'Authorization, Content-Type, Origin'
|
||||
'Access-Control-Allow-Headers' => 'Authorization, Content-Type, Origin, If-Match, If-None-Match',
|
||||
'Access-Control-Expose-Headers' => 'ETag'
|
||||
headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"]
|
||||
headers['Cache-Control'] = 'no-cache'
|
||||
|
||||
|
||||
@ -19,17 +19,18 @@ describe "Directories" do
|
||||
put "/jimmy/tasks/http%3A%2F%2F5apps.com", "prettify design"
|
||||
end
|
||||
|
||||
it "lists the objects with a timestamp of the last modification" do
|
||||
it "lists the objects with their version" do
|
||||
get "/jimmy/tasks/"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.content_type.must_equal "application/json"
|
||||
|
||||
foo = data_bucket.get("jimmy:tasks:foo")
|
||||
|
||||
content = JSON.parse(last_response.body)
|
||||
content.must_include "http://5apps.com"
|
||||
content.must_include "foo"
|
||||
content["foo"].must_be_kind_of Integer
|
||||
content["foo"].to_s.length.must_equal 13
|
||||
content["foo"].must_equal foo.etag.gsub(/"/, "")
|
||||
end
|
||||
|
||||
it "has a Last-Modifier header set" do
|
||||
@ -44,17 +45,57 @@ describe "Directories" do
|
||||
last_modified.day.must_equal now.day
|
||||
end
|
||||
|
||||
it "has an ETag header set" do
|
||||
get "/jimmy/tasks/"
|
||||
|
||||
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
|
||||
get "/jimmy/tasks/"
|
||||
|
||||
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"
|
||||
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 "when If-None-Match header is set" do
|
||||
before do
|
||||
get "/jimmy/tasks/"
|
||||
|
||||
@etag = last_response.headers["ETag"]
|
||||
end
|
||||
|
||||
it "responds with 'not modified' when it matches the current ETag" do
|
||||
header "If-None-Match", @etag
|
||||
get "/jimmy/tasks/"
|
||||
|
||||
last_response.status.must_equal 304
|
||||
last_response.body.must_be_empty
|
||||
end
|
||||
|
||||
it "responds normally when it does not match the current ETag" do
|
||||
header "If-None-Match", "FOO"
|
||||
get "/jimmy/tasks/"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.body.wont_be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "with sub-directories" do
|
||||
before do
|
||||
get "/jimmy/tasks/"
|
||||
@old_etag = last_response.headers["ETag"]
|
||||
|
||||
put "/jimmy/tasks/home/laundry", "do the laundry"
|
||||
end
|
||||
|
||||
@ -63,12 +104,20 @@ describe "Directories" do
|
||||
|
||||
last_response.status.must_equal 200
|
||||
|
||||
home = directory_bucket.get("jimmy:tasks/home")
|
||||
|
||||
content = JSON.parse(last_response.body)
|
||||
content.must_include "foo"
|
||||
content.must_include "http://5apps.com"
|
||||
content.must_include "home/"
|
||||
content["home/"].must_be_kind_of Integer
|
||||
content["home/"].to_s.length.must_equal 13
|
||||
content["home/"].must_equal home.etag.gsub(/"/, "")
|
||||
end
|
||||
|
||||
it "updates the ETag of the parent directory" do
|
||||
get "/jimmy/tasks/"
|
||||
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
last_response.headers["ETag"].wont_equal @old_etag
|
||||
end
|
||||
|
||||
context "for a different user" do
|
||||
@ -102,10 +151,11 @@ describe "Directories" do
|
||||
|
||||
last_response.status.must_equal 200
|
||||
|
||||
projects = directory_bucket.get("jimmy:tasks/private/projects")
|
||||
|
||||
content = JSON.parse(last_response.body)
|
||||
content.must_include "projects/"
|
||||
content["projects/"].must_be_kind_of Integer
|
||||
content["projects/"].to_s.length.must_equal 13
|
||||
content["projects/"].must_equal projects.etag.gsub(/"/, "")
|
||||
end
|
||||
|
||||
it "updates the timestamps of the existing directory objects" do
|
||||
@ -137,10 +187,11 @@ describe "Directories" do
|
||||
|
||||
last_response.status.must_equal 200
|
||||
|
||||
jaypeg = data_bucket.get("jimmy:tasks:jaypeg.jpg")
|
||||
|
||||
content = JSON.parse(last_response.body)
|
||||
content.must_include "jaypeg.jpg"
|
||||
content["jaypeg.jpg"].must_be_kind_of Integer
|
||||
content["jaypeg.jpg"].to_s.length.must_equal 13
|
||||
content["jaypeg.jpg"].must_equal jaypeg.etag.gsub(/"/, "")
|
||||
end
|
||||
end
|
||||
|
||||
@ -157,10 +208,11 @@ describe "Directories" do
|
||||
|
||||
last_response.status.must_equal 200
|
||||
|
||||
jaypeg = data_bucket.get("jimmy:tasks:jaypeg.jpg")
|
||||
|
||||
content = JSON.parse(last_response.body)
|
||||
content.must_include "jaypeg.jpg"
|
||||
content["jaypeg.jpg"].must_be_kind_of Integer
|
||||
content["jaypeg.jpg"].to_s.length.must_equal 13
|
||||
content["jaypeg.jpg"].must_equal jaypeg.etag.gsub(/"/, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -176,10 +228,11 @@ describe "Directories" do
|
||||
|
||||
last_response.status.must_equal 200
|
||||
|
||||
laundry = data_bucket.get("jimmy:tasks/home:laundry")
|
||||
|
||||
content = JSON.parse(last_response.body)
|
||||
content.must_include "laundry"
|
||||
content["laundry"].must_be_kind_of Integer
|
||||
content["laundry"].to_s.length.must_equal 13
|
||||
content["laundry"].must_equal laundry.etag.gsub(/"/, "")
|
||||
end
|
||||
end
|
||||
|
||||
@ -187,8 +240,7 @@ describe "Directories" do
|
||||
it "returns an empty listing" do
|
||||
get "/jimmy/documents/notfound/"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.body.must_equal "{}"
|
||||
last_response.status.must_equal 404
|
||||
end
|
||||
end
|
||||
|
||||
@ -254,12 +306,20 @@ describe "Directories" do
|
||||
|
||||
last_response.status.must_equal 200
|
||||
|
||||
tasks = directory_bucket.get("jimmy:tasks")
|
||||
|
||||
content = JSON.parse(last_response.body)
|
||||
content.must_include "root-1"
|
||||
content.must_include "root-2"
|
||||
content.must_include "tasks/"
|
||||
content["tasks/"].must_be_kind_of Integer
|
||||
content["tasks/"].to_s.length.must_equal 13
|
||||
content["tasks/"].must_equal tasks.etag.gsub(/"/, "")
|
||||
end
|
||||
|
||||
it "has an ETag header set" do
|
||||
get "/jimmy/"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -281,6 +341,13 @@ describe "Directories" do
|
||||
content = JSON.parse(last_response.body)
|
||||
content.must_include "5apps"
|
||||
end
|
||||
|
||||
it "has an ETag header set" do
|
||||
get "/jimmy/public/bookmarks/"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when directly authorized for the public directory" do
|
||||
@ -323,9 +390,11 @@ describe "Directories" do
|
||||
describe "directory object" do
|
||||
describe "PUT file" do
|
||||
context "no existing directory object" do
|
||||
it "creates a new directory object" do
|
||||
before do
|
||||
put "/jimmy/tasks/home/trash", "take out the trash"
|
||||
end
|
||||
|
||||
it "creates a new directory object" do
|
||||
object = data_bucket.get("jimmy:tasks/home:trash")
|
||||
directory = directory_bucket.get("jimmy:tasks/home")
|
||||
|
||||
@ -334,15 +403,11 @@ describe "Directories" do
|
||||
end
|
||||
|
||||
it "sets the correct index for the directory object" do
|
||||
put "/jimmy/tasks/home/trash", "take out the trash"
|
||||
|
||||
object = directory_bucket.get("jimmy:tasks/home")
|
||||
object.indexes["directory_bin"].must_include "tasks"
|
||||
end
|
||||
|
||||
it "creates directory objects for the parent directories" do
|
||||
put "/jimmy/tasks/home/trash", "take out the trash"
|
||||
|
||||
object = directory_bucket.get("jimmy:tasks")
|
||||
object.indexes["directory_bin"].must_include "/"
|
||||
object.data.wont_be_nil
|
||||
@ -355,10 +420,7 @@ describe "Directories" do
|
||||
|
||||
context "existing directory object" do
|
||||
before do
|
||||
directory = directory_bucket.new("jimmy:tasks/home")
|
||||
directory.content_type = "text/plain"
|
||||
directory.data = (2.seconds.ago.to_f * 1000).to_i
|
||||
directory.store
|
||||
put "/jimmy/tasks/home/trash", "collect some trash"
|
||||
end
|
||||
|
||||
it "updates the timestamp of the directory" do
|
||||
@ -383,7 +445,8 @@ describe "Directories" do
|
||||
|
||||
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"
|
||||
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 "sub-directories" do
|
||||
@ -394,7 +457,8 @@ describe "Directories" do
|
||||
|
||||
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"
|
||||
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
|
||||
end
|
||||
|
||||
@ -406,7 +470,8 @@ describe "Directories" do
|
||||
|
||||
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"
|
||||
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
|
||||
end
|
||||
end
|
||||
@ -450,6 +515,31 @@ describe "Directories" do
|
||||
directory_bucket.get("jimmy:").wont_be_nil
|
||||
end
|
||||
|
||||
it "updates the ETag headers of all parent directories" do
|
||||
get "/jimmy/tasks/home/"
|
||||
home_etag = last_response.headers["ETag"]
|
||||
|
||||
get "/jimmy/tasks/"
|
||||
tasks_etag = last_response.headers["ETag"]
|
||||
|
||||
get "/jimmy/"
|
||||
root_etag = last_response.headers["ETag"]
|
||||
|
||||
delete "/jimmy/tasks/home/trash"
|
||||
|
||||
get "/jimmy/tasks/home/"
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
last_response.headers["ETag"].wont_equal home_etag
|
||||
|
||||
get "/jimmy/tasks/"
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
last_response.headers["ETag"].wont_equal tasks_etag
|
||||
|
||||
get "/jimmy/"
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
last_response.headers["ETag"].wont_equal root_etag
|
||||
end
|
||||
|
||||
describe "timestamps" do
|
||||
before do
|
||||
@old_timestamp = (2.seconds.ago.to_f * 1000).to_i
|
||||
|
||||
@ -345,7 +345,7 @@ describe "Permissions" do
|
||||
it "allows GET requests" do
|
||||
get "/jimmy/public/tasks/"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.status.must_equal 404
|
||||
end
|
||||
|
||||
it "allows PUT requests" do
|
||||
@ -403,19 +403,19 @@ describe "Permissions" do
|
||||
end
|
||||
|
||||
it "allows GET requests" do
|
||||
get "/jimmy/tasks/"
|
||||
get "/jimmy/public/tasks/"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.status.must_equal 404
|
||||
end
|
||||
|
||||
it "disallows PUT requests" do
|
||||
put "/jimmy/tasks/foo", "some text"
|
||||
put "/jimmy/public/tasks/foo", "some text"
|
||||
|
||||
last_response.status.must_equal 403
|
||||
end
|
||||
|
||||
it "disallows DELETE requests" do
|
||||
delete "/jimmy/tasks/hello"
|
||||
delete "/jimmy/public/tasks/hello"
|
||||
|
||||
last_response.status.must_equal 403
|
||||
end
|
||||
|
||||
@ -32,6 +32,11 @@ describe "App with Riak backend" do
|
||||
last_modified.year.must_equal now.year
|
||||
last_modified.day.must_equal now.day
|
||||
end
|
||||
|
||||
it "has an ETag header set" do
|
||||
last_response.status.must_equal 200
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET data with custom content type" do
|
||||
@ -58,19 +63,42 @@ describe "App with Riak backend" do
|
||||
object.data = "some private text data"
|
||||
object.store
|
||||
|
||||
@etag = object.etag
|
||||
|
||||
auth = auth_bucket.new("jimmy:123")
|
||||
auth.data = ["documents", "public"]
|
||||
auth.store
|
||||
end
|
||||
|
||||
describe "GET" do
|
||||
it "returns the value" do
|
||||
before do
|
||||
header "Authorization", "Bearer 123"
|
||||
end
|
||||
|
||||
it "returns the value" do
|
||||
get "/jimmy/documents/foo"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.body.must_equal "some private text data"
|
||||
end
|
||||
|
||||
describe "when If-None-Match header is set" do
|
||||
it "responds with 'not modified' when it matches the current ETag" do
|
||||
header "If-None-Match", @etag
|
||||
get "/jimmy/documents/foo"
|
||||
|
||||
last_response.status.must_equal 304
|
||||
last_response.body.must_be_empty
|
||||
end
|
||||
|
||||
it "responds normally when it does not match the current ETag" do
|
||||
header "If-None-Match", "FOO"
|
||||
get "/jimmy/documents/foo"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
last_response.body.must_equal "some private text data"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET nonexisting key" do
|
||||
@ -102,6 +130,10 @@ describe "App with Riak backend" do
|
||||
data_bucket.get("jimmy:documents:bar").content_type.must_equal "text/plain; charset=utf-8"
|
||||
end
|
||||
|
||||
it "sets the ETag header" do
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
end
|
||||
|
||||
it "indexes the data set" do
|
||||
indexes = data_bucket.get("jimmy:documents:bar").indexes
|
||||
indexes["user_id_bin"].must_be_kind_of Set
|
||||
@ -197,15 +229,18 @@ describe "App with Riak backend" do
|
||||
describe "with existing content" do
|
||||
before do
|
||||
put "/jimmy/documents/archive/foo", "lorem ipsum"
|
||||
put "/jimmy/documents/archive/foo", "some awesome content"
|
||||
end
|
||||
|
||||
it "saves the value" do
|
||||
put "/jimmy/documents/archive/foo", "some awesome content"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
data_bucket.get("jimmy:documents/archive:foo").data.must_equal "some awesome content"
|
||||
end
|
||||
|
||||
it "logs the operations" do
|
||||
put "/jimmy/documents/archive/foo", "some awesome content"
|
||||
|
||||
objects = []
|
||||
opslog_bucket.keys.each { |k| objects << opslog_bucket.get(k) rescue nil }
|
||||
|
||||
@ -220,22 +255,74 @@ describe "App with Riak backend" do
|
||||
update_entry.indexes["user_id_bin"].must_include "jimmy"
|
||||
end
|
||||
|
||||
describe "when no serializer is registered for the given content-type" do
|
||||
before do
|
||||
header "Content-Type", "text/html; charset=UTF-8"
|
||||
put "/jimmy/documents/html", '<html></html>'
|
||||
put "/jimmy/documents/html", '<html><body></body></html>'
|
||||
end
|
||||
it "changes the ETag header" do
|
||||
old_etag = last_response.headers["ETag"]
|
||||
put "/jimmy/documents/archive/foo", "some awesome content"
|
||||
|
||||
it "saves the value" do
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
last_response.headers["ETag"].wont_equal old_etag
|
||||
end
|
||||
|
||||
describe "when If-Match header is set" do
|
||||
it "allows the request if the header matches the current ETag" do
|
||||
old_etag = last_response.headers["ETag"]
|
||||
header "If-Match", old_etag
|
||||
|
||||
put "/jimmy/documents/archive/foo", "some awesome content"
|
||||
last_response.status.must_equal 200
|
||||
data_bucket.get("jimmy:documents:html").raw_data.must_equal "<html><body></body></html>"
|
||||
|
||||
get "/jimmy/documents/archive/foo"
|
||||
last_response.body.must_equal "some awesome content"
|
||||
end
|
||||
|
||||
it "uses the requested content type" do
|
||||
data_bucket.get("jimmy:documents:html").content_type.must_equal "text/html; charset=UTF-8"
|
||||
it "fails the request if the header does not match the current ETag" do
|
||||
header "If-Match", "WONTMATCH"
|
||||
|
||||
put "/jimmy/documents/archive/foo", "some awesome content"
|
||||
last_response.status.must_equal 412
|
||||
|
||||
get "/jimmy/documents/archive/foo"
|
||||
last_response.body.must_equal "lorem ipsum"
|
||||
end
|
||||
end
|
||||
|
||||
describe "when If-None-Match header is set" do
|
||||
before do
|
||||
header "If-None-Match", "*"
|
||||
end
|
||||
|
||||
it "fails when the document already exists" do
|
||||
put "/jimmy/documents/archive/foo", "some awesome content"
|
||||
|
||||
last_response.status.must_equal 412
|
||||
|
||||
get "/jimmy/documents/archive/foo"
|
||||
last_response.body.must_equal "lorem ipsum"
|
||||
end
|
||||
|
||||
it "succeeds when the document does not exist" do
|
||||
put "/jimmy/documents/archive/bar", "my little content"
|
||||
|
||||
last_response.status.must_equal 200
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "exsting content without serializer registered for the given content-type" do
|
||||
before do
|
||||
header "Content-Type", "text/html; charset=UTF-8"
|
||||
put "/jimmy/documents/html", '<html></html>'
|
||||
put "/jimmy/documents/html", '<html><body></body></html>'
|
||||
end
|
||||
|
||||
it "saves the value" do
|
||||
last_response.status.must_equal 200
|
||||
data_bucket.get("jimmy:documents:html").raw_data.must_equal "<html><body></body></html>"
|
||||
end
|
||||
|
||||
it "uses the requested content type" do
|
||||
data_bucket.get("jimmy:documents:html").content_type.must_equal "text/html; charset=UTF-8"
|
||||
end
|
||||
end
|
||||
|
||||
describe "public data" do
|
||||
@ -282,13 +369,23 @@ describe "App with Riak backend" do
|
||||
last_response.body.must_equal @image
|
||||
end
|
||||
|
||||
# it "indexes the binary set" do
|
||||
# indexes = binary_bucket.get("jimmy:documents:jaypeg").indexes
|
||||
# indexes["user_id_bin"].must_be_kind_of Set
|
||||
# indexes["user_id_bin"].must_include "jimmy"
|
||||
it "responds with an ETag header" do
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
etag = last_response.headers["ETag"]
|
||||
|
||||
# indexes["directory_bin"].must_include "documents"
|
||||
# end
|
||||
get "/jimmy/documents/jaypeg"
|
||||
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
last_response.headers["ETag"].must_equal etag
|
||||
end
|
||||
|
||||
it "changes the ETag when updating the file" do
|
||||
old_etag = last_response.headers["ETag"]
|
||||
put "/jimmy/documents/jaypeg", @image
|
||||
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
last_response.headers["ETag"].wont_equal old_etag
|
||||
end
|
||||
|
||||
it "logs the operation" do
|
||||
objects = []
|
||||
@ -322,14 +419,6 @@ describe "App with Riak backend" do
|
||||
last_response.status.must_equal 200
|
||||
last_response.body.must_equal @image
|
||||
end
|
||||
|
||||
# it "indexes the binary set" do
|
||||
# indexes = binary_bucket.get("jimmy:documents:jaypeg").indexes
|
||||
# indexes["user_id_bin"].must_be_kind_of Set
|
||||
# indexes["user_id_bin"].must_include "jimmy"
|
||||
|
||||
# indexes["directory_bin"].must_include "documents"
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
||||
@ -376,24 +465,33 @@ describe "App with Riak backend" do
|
||||
describe "DELETE" do
|
||||
before do
|
||||
header "Authorization", "Bearer 123"
|
||||
delete "/jimmy/documents/foo"
|
||||
end
|
||||
|
||||
it "removes the key" do
|
||||
last_response.status.must_equal 204
|
||||
lambda {
|
||||
data_bucket.get("jimmy:documents:foo")
|
||||
}.must_raise Riak::HTTPFailedRequest
|
||||
end
|
||||
describe "basics" do
|
||||
before do
|
||||
delete "/jimmy/documents/foo"
|
||||
end
|
||||
|
||||
it "logs the operation" do
|
||||
objects = []
|
||||
opslog_bucket.keys.each { |k| objects << opslog_bucket.get(k) rescue nil }
|
||||
it "removes the key" do
|
||||
last_response.status.must_equal 204
|
||||
lambda {
|
||||
data_bucket.get("jimmy:documents:foo")
|
||||
}.must_raise Riak::HTTPFailedRequest
|
||||
end
|
||||
|
||||
log_entry = objects.select{|o| o.data["count"] == -1}.first
|
||||
log_entry.data["size"].must_equal(-22)
|
||||
log_entry.data["category"].must_equal "documents"
|
||||
log_entry.indexes["user_id_bin"].must_include "jimmy"
|
||||
it "logs the operation" do
|
||||
objects = []
|
||||
opslog_bucket.keys.each { |k| objects << opslog_bucket.get(k) rescue nil }
|
||||
|
||||
log_entry = objects.select{|o| o.data["count"] == -1}.first
|
||||
log_entry.data["size"].must_equal(-22)
|
||||
log_entry.data["category"].must_equal "documents"
|
||||
log_entry.indexes["user_id_bin"].must_include "jimmy"
|
||||
end
|
||||
|
||||
it "sets the ETag header" do
|
||||
last_response.headers["ETag"].wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "non-existing object" do
|
||||
@ -401,10 +499,39 @@ describe "App with Riak backend" do
|
||||
delete "/jimmy/documents/foozius"
|
||||
end
|
||||
|
||||
it "responds with 404" do
|
||||
last_response.status.must_equal 404
|
||||
end
|
||||
|
||||
it "doesn't log the operation" do
|
||||
objects = []
|
||||
opslog_bucket.keys.each { |k| objects << opslog_bucket.get(k) rescue nil }
|
||||
objects.select{|o| o.data["count"] == -1}.size.must_equal 1
|
||||
objects.select{|o| o.data["count"] == -1}.size.must_equal 0
|
||||
end
|
||||
end
|
||||
|
||||
context "when an If-Match header is given" do
|
||||
it "allows the request if it matches the current ETag" do
|
||||
get "/jimmy/documents/foo"
|
||||
old_etag = last_response.headers["ETag"]
|
||||
header "If-Match", old_etag
|
||||
|
||||
delete "/jimmy/documents/foo"
|
||||
last_response.status.must_equal 204
|
||||
|
||||
get "/jimmy/documents/foo"
|
||||
last_response.status.must_equal 404
|
||||
end
|
||||
|
||||
it "fails the request if it does not match the current ETag" do
|
||||
header "If-Match", "WONTMATCH"
|
||||
|
||||
delete "/jimmy/documents/foo"
|
||||
last_response.status.must_equal 412
|
||||
|
||||
get "/jimmy/documents/foo"
|
||||
last_response.status.must_equal 200
|
||||
last_response.body.must_equal "some private text data"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user