fake_aws 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +2 -0
- data/README.md +24 -16
- data/fake_aws.gemspec +4 -1
- data/lib/fake_aws.rb +10 -2
- data/lib/fake_aws/error.rb +16 -0
- data/lib/fake_aws/s3/bucket_on_disk.rb +26 -0
- data/lib/fake_aws/s3/error_index.rb +37 -0
- data/lib/fake_aws/s3/object_on_disk.rb +50 -0
- data/lib/fake_aws/s3/operations/get_object.rb +30 -23
- data/lib/fake_aws/s3/operations/put_bucket.rb +38 -0
- data/lib/fake_aws/s3/operations/put_object.rb +22 -36
- data/lib/fake_aws/s3/rack_app.rb +19 -6
- data/lib/fake_aws/s3/request.rb +100 -0
- data/lib/fake_aws/s3/responses/common.rb +37 -0
- data/lib/fake_aws/s3/responses/empty.rb +25 -0
- data/lib/fake_aws/s3/responses/error.rb +50 -0
- data/lib/fake_aws/s3/responses/success.rb +31 -0
- data/lib/fake_aws/version.rb +1 -1
- data/spec/integration/s3/get_object_spec.rb +35 -12
- data/spec/integration/s3/put_bucket_spec.rb +40 -0
- data/spec/integration/s3/put_object_spec.rb +12 -4
- data/spec/integration/s3/request_styles_spec.rb +54 -0
- data/spec/spec_helper.rb +1 -16
- data/spec/support/common_header_examples.rb +16 -0
- data/spec/support/s3_integration_helpers.rb +18 -0
- data/spec/support/xml_parsing_helper.rb +7 -0
- data/spec/unit/s3/bucket_on_disk_spec.rb +39 -0
- data/spec/unit/s3/error_index_spec.rb +17 -0
- data/spec/unit/s3/object_on_disk_spec.rb +82 -0
- data/spec/unit/s3/request_spec.rb +92 -0
- data/spec/unit/s3/responses/empty_spec.rb +14 -0
- data/spec/unit/s3/responses/error_spec.rb +49 -0
- data/spec/unit/s3/responses/success_spec.rb +25 -0
- metadata +82 -26
- data/lib/fake_aws/s3/object_store.rb +0 -66
- data/lib/fake_aws/s3/xml_error_response.rb +0 -29
- data/spec/unit/s3/object_store_spec.rb +0 -75
- data/spec/unit/s3/xml_error_response_spec.rb +0 -9
data/lib/fake_aws/s3/rack_app.rb
CHANGED
@@ -7,18 +7,31 @@ module FakeAWS
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def call(env)
|
10
|
-
|
10
|
+
request = Request.new(env)
|
11
|
+
operation_for(request).call.to_rack_response
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def operation_for(request)
|
17
|
+
operation_class(request).new(@directory, request)
|
18
|
+
end
|
19
|
+
|
20
|
+
def operation_class(request)
|
21
|
+
case request.method
|
11
22
|
when "PUT"
|
12
|
-
|
23
|
+
if request.has_key?
|
24
|
+
Operations::PutObject
|
25
|
+
else
|
26
|
+
Operations::PutBucket
|
27
|
+
end
|
13
28
|
when "GET"
|
14
29
|
Operations::GetObject
|
15
30
|
else
|
16
|
-
raise
|
31
|
+
raise FakeAWS::UnsupportedRequestError # TODO: This needs a spec.
|
17
32
|
end
|
18
|
-
|
19
|
-
operation = operation_class.new(@directory, env)
|
20
|
-
operation.call
|
21
33
|
end
|
34
|
+
|
22
35
|
end
|
23
36
|
|
24
37
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module FakeAWS
|
2
|
+
module S3
|
3
|
+
|
4
|
+
# Extract the bucket and key from the various different styles of S3 request.
|
5
|
+
#
|
6
|
+
# See http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html
|
7
|
+
class Request
|
8
|
+
def initialize(env)
|
9
|
+
@env = env
|
10
|
+
@server_name = env["SERVER_NAME"]
|
11
|
+
@path_info = env["PATH_INFO"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def method
|
15
|
+
@env["REQUEST_METHOD"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def content_type
|
19
|
+
@env["CONTENT_TYPE"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def content
|
23
|
+
@env["rack.input"].read
|
24
|
+
end
|
25
|
+
|
26
|
+
def http_headers
|
27
|
+
http_headers = env_headers.map {|key, value| [header_key_for_env_key(key), value] }
|
28
|
+
Hash[http_headers]
|
29
|
+
end
|
30
|
+
|
31
|
+
def bucket
|
32
|
+
@bucket ||= case request_style
|
33
|
+
when :path
|
34
|
+
path_components.first
|
35
|
+
when :virtual_hosted
|
36
|
+
server_name_components[0..-4].join(".")
|
37
|
+
when :cname
|
38
|
+
@server_name
|
39
|
+
else
|
40
|
+
raise "Uh oh"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def key
|
45
|
+
@key ||= "/" + case request_style
|
46
|
+
when :path
|
47
|
+
path_components.drop(1).join("/")
|
48
|
+
when :virtual_hosted, :cname
|
49
|
+
path_components.join("/")
|
50
|
+
else
|
51
|
+
raise "Uh oh"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_key?
|
56
|
+
key != "/"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def server_name
|
62
|
+
@server_name ||= @env["SERVER_NAME"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def path_info
|
66
|
+
@path_info ||= @env["PATH_INFO"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def server_name_components
|
70
|
+
@server_name_components ||= server_name.split(".")
|
71
|
+
end
|
72
|
+
|
73
|
+
def path_components
|
74
|
+
@path_components ||= path_info.split("/").drop(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
def request_style
|
78
|
+
@request_style ||= if server_name =~ %r{s3[-\w\d]*\.amazonaws\.com$}
|
79
|
+
if server_name_components.length > 3
|
80
|
+
:virtual_hosted
|
81
|
+
else
|
82
|
+
:path
|
83
|
+
end
|
84
|
+
else
|
85
|
+
:cname
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def env_headers
|
90
|
+
@env.select {|key, _| key.start_with?("HTTP_") }
|
91
|
+
end
|
92
|
+
|
93
|
+
def header_key_for_env_key(env_key)
|
94
|
+
env_key.sub(/^HTTP_/, "").gsub("_", "-").downcase
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module FakeAWS
|
4
|
+
module S3
|
5
|
+
module Responses
|
6
|
+
|
7
|
+
# Useful things for generating responses to S3 requests.
|
8
|
+
module Common
|
9
|
+
|
10
|
+
# Headers which should be included in all S3 responses.
|
11
|
+
def common_headers
|
12
|
+
{
|
13
|
+
"Date" => Time.now.httpdate,
|
14
|
+
"Server" => "AmazonS3",
|
15
|
+
"x-amz-request-id" => request_id
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_rack_response
|
20
|
+
[
|
21
|
+
status_code,
|
22
|
+
headers,
|
23
|
+
body
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def request_id
|
30
|
+
@request_id ||= SecureRandom.hex(8)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module FakeAWS
|
2
|
+
module S3
|
3
|
+
module Responses
|
4
|
+
|
5
|
+
class Error
|
6
|
+
include Common
|
7
|
+
|
8
|
+
def initialize(error_code, fields = {})
|
9
|
+
@error_code = error_code
|
10
|
+
@fields = fields
|
11
|
+
end
|
12
|
+
|
13
|
+
def status_code
|
14
|
+
error.status_code
|
15
|
+
end
|
16
|
+
|
17
|
+
def headers
|
18
|
+
@headers ||= common_headers.merge("Content-Type" => "application/xml")
|
19
|
+
end
|
20
|
+
|
21
|
+
def body
|
22
|
+
"".tap do |xml|
|
23
|
+
xml << %q{<?xml version="1.0" encoding="UTF-8"?>\n}
|
24
|
+
xml << %q{<Error>}
|
25
|
+
|
26
|
+
xml << " <Code>#{@error_code}</Code>"
|
27
|
+
xml << " <Message>#{error.description}</Message>"
|
28
|
+
|
29
|
+
@fields.each_pair do |key, value|
|
30
|
+
xml << " <#{key}>#{value}</#{key}>"
|
31
|
+
end
|
32
|
+
|
33
|
+
xml << " <RequestId>#{request_id}</RequestId>"
|
34
|
+
|
35
|
+
xml << %q{</Error>}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def error
|
42
|
+
@error ||= FakeAWS::S3::ErrorIndex.error_for_code(@error_code)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module FakeAWS
|
2
|
+
module S3
|
3
|
+
module Responses
|
4
|
+
|
5
|
+
class Success
|
6
|
+
include Common
|
7
|
+
|
8
|
+
def initialize(headers, body)
|
9
|
+
@headers = headers
|
10
|
+
@body = body
|
11
|
+
end
|
12
|
+
|
13
|
+
def status_code
|
14
|
+
200
|
15
|
+
end
|
16
|
+
|
17
|
+
def headers
|
18
|
+
common_headers.merge(@headers)
|
19
|
+
end
|
20
|
+
|
21
|
+
def body
|
22
|
+
@body
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
data/lib/fake_aws/version.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "S3 GET Object operation" do
|
4
|
-
include
|
4
|
+
include S3IntegrationHelpers
|
5
|
+
include XMLParsingHelper
|
5
6
|
|
6
7
|
let(:bucket) { "mah-bucket" }
|
7
8
|
let(:file_name) { "mah-file.txt"}
|
8
9
|
let(:file_contents) { "Hello, world!" }
|
9
10
|
|
11
|
+
def set_metadata(metadata)
|
12
|
+
File.write(File.join(s3_path, bucket, "#{file_name}.metadata.json"), metadata.to_json)
|
13
|
+
end
|
14
|
+
|
10
15
|
def get_example_file(key)
|
11
16
|
connection.get(File.join(bucket, key))
|
12
17
|
end
|
@@ -17,27 +22,34 @@ describe "S3 GET Object operation" do
|
|
17
22
|
File.write(File.join(s3_path, bucket, file_name), file_contents)
|
18
23
|
end
|
19
24
|
|
20
|
-
it "returns a
|
25
|
+
it "returns a successful response" do
|
21
26
|
response = get_example_file(file_name)
|
22
27
|
expect(response.status).to eq(200)
|
23
28
|
end
|
24
29
|
|
25
|
-
it "returns a correctly constructed response"
|
26
|
-
|
27
30
|
it "returns the contents of the file" do
|
28
31
|
response = get_example_file(file_name)
|
29
32
|
expect(response.body).to eq(file_contents)
|
30
33
|
end
|
31
34
|
|
32
35
|
it "returns the right content type" do
|
33
|
-
|
34
|
-
"Content-Type" => "text/plain"
|
35
|
-
}.to_json
|
36
|
-
File.write(File.join(s3_path, bucket, "#{file_name}.metadata.json"), file_metadata)
|
36
|
+
set_metadata("Content-Type" => "text/plain")
|
37
37
|
|
38
38
|
response = get_example_file(file_name)
|
39
39
|
expect(response.headers["Content-Type"]).to eq("text/plain")
|
40
40
|
end
|
41
|
+
|
42
|
+
it "returns a content type of application/octet-stream if none is set" do
|
43
|
+
response = get_example_file(file_name)
|
44
|
+
expect(response.headers["Content-Type"]).to eq("application/octet-stream")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns x-amz-meta-* headers from the metadata" do
|
48
|
+
set_metadata("x-amz-meta-foo" => "bar")
|
49
|
+
|
50
|
+
response = get_example_file(file_name)
|
51
|
+
expect(response.headers["x-amz-meta-foo"]).to eq("bar")
|
52
|
+
end
|
41
53
|
end
|
42
54
|
|
43
55
|
context "with a file that doesn't exist" do
|
@@ -45,17 +57,28 @@ describe "S3 GET Object operation" do
|
|
45
57
|
FileUtils.mkdir(File.join(s3_path, bucket))
|
46
58
|
end
|
47
59
|
|
48
|
-
it "returns a
|
60
|
+
it "returns a NoSuchKey error" do
|
49
61
|
response = get_example_file(file_name)
|
50
62
|
expect(response.status).to eq(404)
|
63
|
+
expect(parse_xml(response.body)["Error"]["Code"]).to eq("NoSuchKey")
|
51
64
|
end
|
52
65
|
|
53
|
-
it "returns the
|
66
|
+
it "returns the key as the resource" do
|
67
|
+
response = get_example_file(file_name)
|
68
|
+
expect(parse_xml(response.body)["Error"]["Resource"]).to eq("/#{file_name}")
|
69
|
+
end
|
54
70
|
end
|
55
71
|
|
56
72
|
context "with a bucket that doesn't exist" do
|
57
|
-
it "returns
|
58
|
-
|
73
|
+
it "returns a NoSuchBucket error" do
|
74
|
+
response = get_example_file(file_name)
|
75
|
+
expect(response.status).to eq(404)
|
76
|
+
expect(parse_xml(response.body)["Error"]["Code"]).to eq("NoSuchBucket")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns the bucket name" do
|
80
|
+
response = get_example_file(file_name)
|
81
|
+
expect(parse_xml(response.body)["Error"]["BucketName"]).to eq(bucket)
|
59
82
|
end
|
60
83
|
end
|
61
84
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "S3 PUT Bucket operation" do
|
4
|
+
include S3IntegrationHelpers
|
5
|
+
include XMLParsingHelper
|
6
|
+
|
7
|
+
let(:bucket) { "mah-bucket" }
|
8
|
+
let(:bucket_path) { File.join(s3_path, bucket) }
|
9
|
+
|
10
|
+
def put_bucket
|
11
|
+
connection.put do |request|
|
12
|
+
request.url("http://#{bucket}.s3.amazonaws.com")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "without an existing bucket" do
|
17
|
+
it "returns a 200" do
|
18
|
+
response = put_bucket
|
19
|
+
expect(response.status).to eq(200)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "creates the bucket directory on disk" do
|
23
|
+
response = put_bucket
|
24
|
+
expect(Dir.exist?(bucket_path)).to be_truthy
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with an existing bucket" do
|
29
|
+
before do
|
30
|
+
Dir.mkdir(bucket_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns a Bucket Already Exists error" do
|
34
|
+
response = put_bucket
|
35
|
+
expect(response.status).to eq(409)
|
36
|
+
expect(parse_xml(response.body)["Error"]["Code"]).to eq("BucketAlreadyExists")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "S3 PUT Object operation" do
|
4
|
-
include
|
4
|
+
include S3IntegrationHelpers
|
5
|
+
include XMLParsingHelper
|
5
6
|
|
6
7
|
let(:bucket) { "mah-bucket" }
|
7
8
|
let(:file_name) { "mah-file.txt"}
|
@@ -31,7 +32,10 @@ describe "S3 PUT Object operation" do
|
|
31
32
|
expect(response.status).to eq(200)
|
32
33
|
end
|
33
34
|
|
34
|
-
it "returns a
|
35
|
+
it "returns a response with an empty body" do
|
36
|
+
response = put_example_file(file_name)
|
37
|
+
expect(response.body).to be_empty
|
38
|
+
end
|
35
39
|
|
36
40
|
it "creates a file" do
|
37
41
|
put_example_file(file_name)
|
@@ -65,12 +69,16 @@ describe "S3 PUT Object operation" do
|
|
65
69
|
end
|
66
70
|
|
67
71
|
context "without an existing bucket" do
|
68
|
-
it "returns a
|
72
|
+
it "returns a No Such Bucket error" do
|
69
73
|
response = put_example_file(file_name)
|
70
74
|
expect(response.status).to eq(404)
|
75
|
+
expect(parse_xml(response.body)["Error"]["Code"]).to eq("NoSuchBucket")
|
71
76
|
end
|
72
77
|
|
73
|
-
it "returns the
|
78
|
+
it "returns the bucket name" do
|
79
|
+
response = put_example_file(file_name)
|
80
|
+
expect(parse_xml(response.body)["Error"]["BucketName"]).to eq(bucket)
|
81
|
+
end
|
74
82
|
end
|
75
83
|
|
76
84
|
end
|