aws-s3 0.1.0

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 (55) hide show
  1. data/COPYING +19 -0
  2. data/INSTALL +35 -0
  3. data/README +529 -0
  4. data/Rakefile +284 -0
  5. data/bin/s3sh +4 -0
  6. data/bin/setup.rb +10 -0
  7. data/lib/aws/s3.rb +64 -0
  8. data/lib/aws/s3/acl.rb +631 -0
  9. data/lib/aws/s3/authentication.rb +218 -0
  10. data/lib/aws/s3/base.rb +232 -0
  11. data/lib/aws/s3/bittorrent.rb +58 -0
  12. data/lib/aws/s3/bucket.rb +323 -0
  13. data/lib/aws/s3/connection.rb +212 -0
  14. data/lib/aws/s3/error.rb +69 -0
  15. data/lib/aws/s3/exceptions.rb +130 -0
  16. data/lib/aws/s3/extensions.rb +186 -0
  17. data/lib/aws/s3/logging.rb +163 -0
  18. data/lib/aws/s3/object.rb +565 -0
  19. data/lib/aws/s3/owner.rb +44 -0
  20. data/lib/aws/s3/parsing.rb +138 -0
  21. data/lib/aws/s3/response.rb +180 -0
  22. data/lib/aws/s3/service.rb +43 -0
  23. data/lib/aws/s3/version.rb +12 -0
  24. data/support/faster-xml-simple/lib/faster_xml_simple.rb +115 -0
  25. data/support/faster-xml-simple/test/regression_test.rb +16 -0
  26. data/support/faster-xml-simple/test/xml_simple_comparison_test.rb +22 -0
  27. data/support/rdoc/code_info.rb +211 -0
  28. data/test/acl_test.rb +243 -0
  29. data/test/authentication_test.rb +96 -0
  30. data/test/base_test.rb +143 -0
  31. data/test/bucket_test.rb +48 -0
  32. data/test/connection_test.rb +120 -0
  33. data/test/error_test.rb +75 -0
  34. data/test/extensions_test.rb +282 -0
  35. data/test/fixtures.rb +89 -0
  36. data/test/fixtures/buckets.yml +102 -0
  37. data/test/fixtures/errors.yml +34 -0
  38. data/test/fixtures/headers.yml +3 -0
  39. data/test/fixtures/logging.yml +15 -0
  40. data/test/fixtures/policies.yml +16 -0
  41. data/test/logging_test.rb +36 -0
  42. data/test/mocks/base.rb +89 -0
  43. data/test/object_test.rb +177 -0
  44. data/test/parsing_test.rb +82 -0
  45. data/test/remote/acl_test.rb +117 -0
  46. data/test/remote/bittorrent_test.rb +45 -0
  47. data/test/remote/bucket_test.rb +127 -0
  48. data/test/remote/logging_test.rb +82 -0
  49. data/test/remote/object_test.rb +267 -0
  50. data/test/remote/test_file.data +0 -0
  51. data/test/remote/test_helper.rb +30 -0
  52. data/test/response_test.rb +70 -0
  53. data/test/service_test.rb +26 -0
  54. data/test/test_helper.rb +82 -0
  55. metadata +125 -0
@@ -0,0 +1,96 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class HeaderAuthenticationTest < Test::Unit::TestCase
4
+ def test_encoded_canonical
5
+ signature = Authentication::Signature.new(request, key_id, secret)
6
+ assert_equal AmazonDocExampleData::Example1.canonical_string, signature.send(:canonical_string)
7
+ assert_equal AmazonDocExampleData::Example1.signature, signature.send(:encoded_canonical)
8
+ end
9
+
10
+ def test_authorization_header
11
+ header = Authentication::Header.new(request, key_id, secret)
12
+ assert_equal AmazonDocExampleData::Example1.canonical_string, header.send(:canonical_string)
13
+ assert_equal AmazonDocExampleData::Example1.authorization_header, header
14
+ end
15
+
16
+ private
17
+ def request; AmazonDocExampleData::Example1.request end
18
+ def key_id ; AmazonDocExampleData::Example1.access_key_id end
19
+ def secret ; AmazonDocExampleData::Example1.secret_access_key end
20
+ end
21
+
22
+ class QueryStringAuthenticationTest < Test::Unit::TestCase
23
+ def test_query_string
24
+ query_string = Authentication::QueryString.new(request, key_id, secret, :expires_in => 60)
25
+ assert_equal AmazonDocExampleData::Example3.canonical_string, query_string.send(:canonical_string)
26
+ assert_equal AmazonDocExampleData::Example3.query_string, query_string
27
+ end
28
+
29
+ def test_query_string_with_explicit_expiry
30
+ query_string = Authentication::QueryString.new(request, key_id, secret, :expires => expires)
31
+ assert_equal expires, query_string.send(:canonical_string).instance_variable_get(:@options)[:expires]
32
+ assert_equal AmazonDocExampleData::Example3.query_string, query_string
33
+ end
34
+
35
+ private
36
+ def request; AmazonDocExampleData::Example3.request end
37
+ def key_id ; AmazonDocExampleData::Example3.access_key_id end
38
+ def secret ; AmazonDocExampleData::Example3.secret_access_key end
39
+ def expires; AmazonDocExampleData::Example3.expires end
40
+ end
41
+
42
+ class CanonicalStringTest < Test::Unit::TestCase
43
+ def setup
44
+ @request = Net::HTTP::Post.new('/test')
45
+ @canonical_string = Authentication::CanonicalString.new(@request)
46
+ end
47
+
48
+ def test_path_does_not_include_query_string
49
+ request = Net::HTTP::Get.new('/test/query/string?foo=bar&baz=quux')
50
+ assert_equal '/test/query/string', Authentication::CanonicalString.new(request).send(:path)
51
+
52
+ # Make sure things still work when there is *no* query string
53
+ request = Net::HTTP::Get.new('/')
54
+ assert_equal '/', Authentication::CanonicalString.new(request).send(:path)
55
+ request = Net::HTTP::Get.new('/foo/bar')
56
+ assert_equal '/foo/bar', Authentication::CanonicalString.new(request).send(:path)
57
+ end
58
+
59
+ def test_path_includes_significant_query_strings
60
+ significant_query_strings = [
61
+ ['/test/query/string?acl', '/test/query/string?acl'],
62
+ ['/test/query/string?acl&foo=bar', '/test/query/string?acl'],
63
+ ['/test/query/string?foo=bar&acl', '/test/query/string?acl'],
64
+ ['/test/query/string?acl=foo', '/test/query/string?acl'],
65
+ ['/test/query/string?torrent=foo', '/test/query/string?torrent'],
66
+ ['/test/query/string?logging=foo', '/test/query/string?logging'],
67
+ ['/test/query/string?bar=baz&acl=foo', '/test/query/string?acl']
68
+ ]
69
+
70
+ significant_query_strings.each do |uncleaned_path, expected_cleaned_path|
71
+ assert_equal expected_cleaned_path, Authentication::CanonicalString.new(Net::HTTP::Get.new(uncleaned_path)).send(:path)
72
+ end
73
+ end
74
+
75
+ def test_default_headers_set
76
+ Authentication::CanonicalString.default_headers.each do |header|
77
+ assert @canonical_string.headers.include?(header)
78
+ end
79
+ end
80
+
81
+ def test_interesting_headers_are_copied_over
82
+ an_interesting_header = 'content-md5'
83
+ string_without_interesting_header = Authentication::CanonicalString.new(@request)
84
+ assert string_without_interesting_header.headers[an_interesting_header].empty?
85
+
86
+ # Add an interesting header
87
+ @request[an_interesting_header] = 'foo'
88
+ string_with_interesting_header = Authentication::CanonicalString.new(@request)
89
+ assert_equal 'foo', string_with_interesting_header.headers[an_interesting_header]
90
+ end
91
+
92
+ def test_canonical_string
93
+ request = AmazonDocExampleData::Example1.request
94
+ assert_equal AmazonDocExampleData::Example1.canonical_string, Authentication::CanonicalString.new(request)
95
+ end
96
+ end
@@ -0,0 +1,143 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+ def test_connection_established
5
+ assert_raises(NoConnectionEstablished) do
6
+ Base.connection
7
+ end
8
+
9
+ Base.establish_connection!(:access_key_id => '123', :secret_access_key => 'abc')
10
+ assert_kind_of Connection, Base.connection
11
+
12
+ instance = Base.new
13
+ assert_equal instance.send(:connection), Base.connection
14
+ assert_equal instance.send(:http), Base.connection.http
15
+ end
16
+
17
+ def test_respond_with
18
+ assert_equal Base::Response, Base.send(:response_class)
19
+ Base.send(:respond_with, Bucket::Response) do
20
+ assert_equal Bucket::Response, Base.send(:response_class)
21
+ end
22
+ assert_equal Base::Response, Base.send(:response_class)
23
+ end
24
+
25
+ def test_request_tries_again_when_encountering_an_internal_error
26
+ Bucket.in_test_mode do
27
+ Bucket.request_returns [
28
+ # First request is an internal error
29
+ {:body => Fixtures::Errors.internal_error, :code => 500, :error => true},
30
+ # Second request is a success
31
+ {:body => Fixtures::Buckets.empty_bucket, :code => 200}
32
+ ]
33
+ bucket = nil # Block scope hack
34
+ assert_nothing_raised do
35
+ bucket = Bucket.find('marcel')
36
+ end
37
+ # Don't call objects 'cause we don't want to make another request
38
+ assert bucket.object_cache.empty?
39
+ end
40
+ end
41
+
42
+ def test_request_tries_up_to_three_times
43
+ Bucket.in_test_mode do
44
+ Bucket.request_returns [
45
+ # First request is an internal error
46
+ {:body => Fixtures::Errors.internal_error, :code => 500, :error => true},
47
+ # Second request is also an internal error
48
+ {:body => Fixtures::Errors.internal_error, :code => 500, :error => true},
49
+ # Ditto third
50
+ {:body => Fixtures::Errors.internal_error, :code => 500, :error => true},
51
+ # Fourth works
52
+ {:body => Fixtures::Buckets.empty_bucket, :code => 200}
53
+ ]
54
+ bucket = nil # Block scope hack
55
+ assert_nothing_raised do
56
+ bucket = Bucket.find('marcel')
57
+ end
58
+ # Don't call objects 'cause we don't want to make another request
59
+ assert bucket.object_cache.empty?
60
+ end
61
+ end
62
+
63
+ def test_request_tries_again_three_times_and_gives_up
64
+ Bucket.in_test_mode do
65
+ Bucket.request_returns [
66
+ # First request is an internal error
67
+ {:body => Fixtures::Errors.internal_error, :code => 500, :error => true},
68
+ # Second request is also an internal error
69
+ {:body => Fixtures::Errors.internal_error, :code => 500, :error => true},
70
+ # Ditto third
71
+ {:body => Fixtures::Errors.internal_error, :code => 500, :error => true},
72
+ # Ditto fourth
73
+ {:body => Fixtures::Errors.internal_error, :code => 500, :error => true},
74
+ ]
75
+ assert_raises(InternalError) do
76
+ Bucket.find('marcel')
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ class MultiConnectionsTest < Test::Unit::TestCase
83
+ class ClassToTestSettingCurrentBucket < Base
84
+ set_current_bucket_to 'foo'
85
+ end
86
+
87
+ def setup
88
+ Base.send(:class_variable_get, :@@connections).clear
89
+ end
90
+ alias_method :teardown, :setup
91
+
92
+ def test_default_connection_options_are_used_for_subsequent_connections
93
+ assert !Base.connected?
94
+
95
+ assert_raises(MissingAccessKey) do
96
+ Base.establish_connection!
97
+ end
98
+
99
+ assert !Base.connected?
100
+
101
+ assert_raises(NoConnectionEstablished) do
102
+ Base.connection
103
+ end
104
+
105
+ assert_nothing_raised do
106
+ Base.establish_connection!(:access_key_id => '123', :secret_access_key => 'abc')
107
+ end
108
+
109
+ assert Base.connected?
110
+
111
+ assert_nothing_raised do
112
+ Base.connection
113
+ end
114
+
115
+ # All subclasses are currently using the default connection
116
+ assert Base.connection == Bucket.connection
117
+
118
+ # No need to pass in the required options. The default connection will supply them
119
+ assert_nothing_raised do
120
+ Bucket.establish_connection!(:server => 'foo.s3.amazonaws.com')
121
+ end
122
+
123
+ assert Base.connection != Bucket.connection
124
+ assert_equal '123', Bucket.connection.access_key_id
125
+ assert_equal 'foo', Bucket.connection.subdomain
126
+ end
127
+
128
+ def test_current_bucket
129
+ Base.establish_connection!(:access_key_id => '123', :secret_access_key => 'abc')
130
+ assert_raises(CurrentBucketNotSpecified) do
131
+ Base.current_bucket
132
+ end
133
+
134
+ S3Object.establish_connection!(:server => 'foo-bucket.s3.amazonaws.com')
135
+ assert_nothing_raised do
136
+ assert_equal 'foo-bucket', S3Object.current_bucket
137
+ end
138
+ end
139
+
140
+ def test_setting_the_current_bucket
141
+ assert_equal 'foo', ClassToTestSettingCurrentBucket.current_bucket
142
+ end
143
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class BucketTest < Test::Unit::TestCase
4
+ def test_bucket_name_validation
5
+ valid_names = %w(123 joe step-one step_two step3 step_4 step-5)
6
+ invalid_names = ['12', 'jo', 'kevin spacey', 'larry@wall', '', 'a' * 256]
7
+ validate_name = Proc.new {|name| Bucket.send(:validate_name!, name)}
8
+ valid_names.each do |valid_name|
9
+ assert_nothing_raised { validate_name[valid_name] }
10
+ end
11
+
12
+ invalid_names.each do |invalid_name|
13
+ assert_raises(InvalidBucketName) { validate_name[invalid_name] }
14
+ end
15
+ end
16
+
17
+ def test_empty_bucket
18
+ Bucket.request_always_returns :body => Fixtures::Buckets.empty_bucket, :code => 200 do
19
+ bucket = Bucket.find('marcel_molina')
20
+ assert bucket.empty?
21
+ end
22
+ end
23
+
24
+ def test_bucket_with_one_file
25
+ Bucket.request_always_returns :body => Fixtures::Buckets.bucket_with_one_key, :code => 200 do
26
+ bucket = Bucket.find('marcel_molina')
27
+ assert !bucket.empty?
28
+ assert_equal 1, bucket.size
29
+ assert_equal %w(tongue_overload.jpg), bucket.objects.map {|object| object.key}
30
+ assert bucket['tongue_overload.jpg']
31
+ end
32
+ end
33
+
34
+ def test_bucket_with_more_than_one_file
35
+ Bucket.request_always_returns :body => Fixtures::Buckets.bucket_with_more_than_one_key, :code => 200 do
36
+ bucket = Bucket.find('marcel_molina')
37
+ assert !bucket.empty?
38
+ assert_equal 2, bucket.size
39
+ assert_equal %w(beluga_baby.jpg tongue_overload.jpg), bucket.objects.map {|object| object.key}.sort
40
+ assert bucket['tongue_overload.jpg']
41
+ end
42
+ end
43
+
44
+ def test_bucket_path
45
+ assert_equal '/bucket_name?max-keys=2', Bucket.send(:path, 'bucket_name', :max_keys => 2)
46
+ assert_equal '/bucket_name', Bucket.send(:path, 'bucket_name', {})
47
+ end
48
+ end
@@ -0,0 +1,120 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ConnectionTest < Test::Unit::TestCase
4
+ def setup
5
+ @keys = {:access_key_id => '123', :secret_access_key => 'abc'}
6
+ end
7
+
8
+ def test_creating_a_connection
9
+ connection = Connection.new(@keys)
10
+ assert_kind_of Net::HTTP, connection.http
11
+ end
12
+
13
+ def test_use_ssl_option_is_set_in_connection
14
+ connection = Connection.new(@keys.merge(:use_ssl => true))
15
+ assert connection.http.use_ssl?
16
+ end
17
+
18
+ def test_setting_port_to_443_implies_use_ssl
19
+ connection = Connection.new(@keys.merge(:port => 443))
20
+ assert connection.http.use_ssl?
21
+ end
22
+
23
+ def test_protocol
24
+ connection = Connection.new(@keys)
25
+ assert_equal 'http://', connection.protocol
26
+ connection = Connection.new(@keys.merge(:use_ssl => true))
27
+ assert_equal 'https://', connection.protocol
28
+ end
29
+
30
+ def test_server_and_port_are_passed_onto_connection
31
+ connection = Connection.new(@keys)
32
+ options = connection.instance_variable_get('@options')
33
+ assert_equal connection.http.address, options[:server]
34
+ assert_equal connection.http.port, options[:port]
35
+ end
36
+
37
+ def test_not_including_required_access_keys_raises
38
+ assert_raises(MissingAccessKey) do
39
+ Connection.new
40
+ end
41
+
42
+ assert_raises(MissingAccessKey) do
43
+ Connection.new(:access_key_id => '123')
44
+ end
45
+
46
+ assert_nothing_raised do
47
+ Connection.new(@keys)
48
+ end
49
+ end
50
+
51
+ def test_access_keys_extracted
52
+ connection = Connection.new(@keys)
53
+ assert_equal '123', connection.access_key_id
54
+ assert_equal 'abc', connection.secret_access_key
55
+ end
56
+
57
+ def test_request_method_class_lookup
58
+ c = Connection.new(@keys)
59
+ expectations = {
60
+ :get => Net::HTTP::Get, :post => Net::HTTP::Post,
61
+ :put => Net::HTTP::Put, :delete => Net::HTTP::Delete,
62
+ :head => Net::HTTP::Head
63
+ }
64
+
65
+ expectations.each do |verb, klass|
66
+ assert_equal klass, c.send(:request_method, verb)
67
+ end
68
+ end
69
+ end
70
+
71
+ class ConnectionOptionsTest < Test::Unit::TestCase
72
+
73
+ def setup
74
+ @options = generate_options(:server => 'example.org', :port => 555)
75
+ @default_options = generate_options
76
+ end
77
+
78
+ def test_server_extracted
79
+ assert_key_transfered(:server, 'example.org', @options)
80
+ end
81
+
82
+ def test_port_extracted
83
+ assert_key_transfered(:port, 555, @options)
84
+ end
85
+
86
+ def test_server_defaults_to_default_host
87
+ assert_equal DEFAULT_HOST, @default_options[:server]
88
+ end
89
+
90
+ def test_port_defaults_to_80_if_use_ssl_is_false
91
+ assert_equal 80, @default_options[:port]
92
+ end
93
+
94
+ def test_port_is_set_to_443_if_use_ssl_is_true
95
+ options = generate_options(:use_ssl => true)
96
+ assert_equal 443, options[:port]
97
+ end
98
+
99
+ def test_explicit_port_trumps_use_ssl
100
+ options = generate_options(:port => 555, :use_ssl => true)
101
+ assert_equal 555, options[:port]
102
+ end
103
+
104
+ def test_invalid_options_raise
105
+ assert_raises(InvalidConnectionOption) do
106
+ generate_options(:host => 'campfire.s3.amazonaws.com')
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def assert_key_transfered(key, value, options)
113
+ assert_equal value, options[key]
114
+ assert !options.instance_variable_get('@options').has_key?(key)
115
+ end
116
+
117
+ def generate_options(options = {})
118
+ Connection::Options.new(options)
119
+ end
120
+ end
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ErrorTest < Test::Unit::TestCase
4
+ def setup
5
+ @container = AWS::S3
6
+ @error = Error.new(Parsing::XmlParser.new(Fixtures::Errors.access_denied))
7
+ end
8
+
9
+ def teardown
10
+ @container.send(:remove_const, :NotImplemented) if @container.const_defined?(:NotImplemented)
11
+ end
12
+
13
+ def test_error_class_is_automatically_generated
14
+ assert !@container.const_defined?('NotImplemented')
15
+ error = Error.new(Parsing::XmlParser.new(Fixtures::Errors.not_implemented))
16
+ assert @container.const_defined?('NotImplemented')
17
+ end
18
+
19
+ def test_error_contains_attributes
20
+ assert_equal 'Access Denied', @error.message
21
+ end
22
+
23
+ def test_error_is_raisable_as_exception
24
+ assert_raises(@container::AccessDenied) do
25
+ @error.raise
26
+ end
27
+ end
28
+
29
+ def test_error_message_is_passed_along_to_exception
30
+ @error.raise
31
+ rescue @container::AccessDenied => e
32
+ assert_equal 'Access Denied', e.message
33
+ end
34
+
35
+ def test_response_is_passed_along_to_exception
36
+ response = Error::Response.new(FakeResponse.new(:code => 409, :body => Fixtures::Errors.access_denied))
37
+ response.error.raise
38
+ rescue @container::ResponseError => e
39
+ assert e.response
40
+ assert_kind_of Error::Response, e.response
41
+ assert_equal response.error, e.response.error
42
+ end
43
+
44
+ def test_exception_class_clash
45
+ assert !@container.const_defined?(:NotImplemented)
46
+ # Create a class that does not inherit from exception that has the same name as the class
47
+ # the Error instance is about to attempt to find or create
48
+ @container.const_set(:NotImplemented, Class.new)
49
+ assert @container.const_defined?(:NotImplemented)
50
+
51
+ assert_raises(ExceptionClassClash) do
52
+ Error.new(Parsing::XmlParser.new(Fixtures::Errors.not_implemented))
53
+ end
54
+ end
55
+
56
+ def test_error_response_handles_attributes_with_no_value
57
+ Bucket.in_test_mode do
58
+ Bucket.request_returns :body => Fixtures::Errors.error_with_no_message, :code => 500
59
+
60
+ begin
61
+ Bucket.create('foo', 'invalid-argument' => 'bad juju')
62
+ rescue ResponseError => error
63
+ end
64
+
65
+ assert_nothing_raised do
66
+ error.response.error.message
67
+ end
68
+ assert_nil error.response.error.message
69
+
70
+ assert_raises(NoMethodError) do
71
+ error.response.error.non_existant_method
72
+ end
73
+ end
74
+ end
75
+ end