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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +2 -0
  4. data/README.md +24 -16
  5. data/fake_aws.gemspec +4 -1
  6. data/lib/fake_aws.rb +10 -2
  7. data/lib/fake_aws/error.rb +16 -0
  8. data/lib/fake_aws/s3/bucket_on_disk.rb +26 -0
  9. data/lib/fake_aws/s3/error_index.rb +37 -0
  10. data/lib/fake_aws/s3/object_on_disk.rb +50 -0
  11. data/lib/fake_aws/s3/operations/get_object.rb +30 -23
  12. data/lib/fake_aws/s3/operations/put_bucket.rb +38 -0
  13. data/lib/fake_aws/s3/operations/put_object.rb +22 -36
  14. data/lib/fake_aws/s3/rack_app.rb +19 -6
  15. data/lib/fake_aws/s3/request.rb +100 -0
  16. data/lib/fake_aws/s3/responses/common.rb +37 -0
  17. data/lib/fake_aws/s3/responses/empty.rb +25 -0
  18. data/lib/fake_aws/s3/responses/error.rb +50 -0
  19. data/lib/fake_aws/s3/responses/success.rb +31 -0
  20. data/lib/fake_aws/version.rb +1 -1
  21. data/spec/integration/s3/get_object_spec.rb +35 -12
  22. data/spec/integration/s3/put_bucket_spec.rb +40 -0
  23. data/spec/integration/s3/put_object_spec.rb +12 -4
  24. data/spec/integration/s3/request_styles_spec.rb +54 -0
  25. data/spec/spec_helper.rb +1 -16
  26. data/spec/support/common_header_examples.rb +16 -0
  27. data/spec/support/s3_integration_helpers.rb +18 -0
  28. data/spec/support/xml_parsing_helper.rb +7 -0
  29. data/spec/unit/s3/bucket_on_disk_spec.rb +39 -0
  30. data/spec/unit/s3/error_index_spec.rb +17 -0
  31. data/spec/unit/s3/object_on_disk_spec.rb +82 -0
  32. data/spec/unit/s3/request_spec.rb +92 -0
  33. data/spec/unit/s3/responses/empty_spec.rb +14 -0
  34. data/spec/unit/s3/responses/error_spec.rb +49 -0
  35. data/spec/unit/s3/responses/success_spec.rb +25 -0
  36. metadata +82 -26
  37. data/lib/fake_aws/s3/object_store.rb +0 -66
  38. data/lib/fake_aws/s3/xml_error_response.rb +0 -29
  39. data/spec/unit/s3/object_store_spec.rb +0 -75
  40. data/spec/unit/s3/xml_error_response_spec.rb +0 -9
@@ -7,18 +7,31 @@ module FakeAWS
7
7
  end
8
8
 
9
9
  def call(env)
10
- operation_class = case env["REQUEST_METHOD"]
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
- Operations::PutObject
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 "Unhandled request method" # TODO: Make a proper exception for this.
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,25 @@
1
+ module FakeAWS
2
+ module S3
3
+ module Responses
4
+
5
+ class Empty
6
+ include Common
7
+
8
+ def status_code
9
+ 200
10
+ end
11
+
12
+ def headers
13
+ common_headers
14
+ end
15
+
16
+ def body
17
+ ""
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
25
+
@@ -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
+
@@ -1,3 +1,3 @@
1
1
  module FakeAWS
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -1,12 +1,17 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "S3 GET Object operation" do
4
- include S3IntegrationSpecHelpers
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 200" do
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
- file_metadata = {
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 404" do
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 correct XML response"
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 the right sort of error" do
58
- pending "Need to figure out what error AWS actually returns for this case"
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 S3IntegrationSpecHelpers
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 correctly constructed response"
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 404" do
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 correct XML response"
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