fake_aws 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|