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.
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