aws-s3 0.4.0 → 0.5.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.
- data/COPYING +2 -2
- data/README +2 -2
- data/Rakefile +18 -12
- data/lib/aws/s3/authentication.rb +8 -5
- data/lib/aws/s3/base.rb +5 -1
- data/lib/aws/s3/bucket.rb +3 -4
- data/lib/aws/s3/connection.rb +18 -9
- data/lib/aws/s3/extensions.rb +5 -4
- data/lib/aws/s3/logging.rb +8 -3
- data/lib/aws/s3/object.rb +11 -9
- data/lib/aws/s3/version.rb +1 -1
- data/test/authentication_test.rb +19 -1
- data/test/base_test.rb +41 -48
- data/test/bucket_test.rb +44 -18
- data/test/connection_test.rb +42 -16
- data/test/error_test.rb +13 -18
- data/test/fixtures/buckets.yml +31 -0
- data/test/mocks/fake_response.rb +26 -0
- data/test/object_test.rb +69 -81
- data/test/remote/object_test.rb +2 -2
- data/test/response_test.rb +3 -5
- data/test/service_test.rb +11 -14
- data/test/test_helper.rb +23 -2
- metadata +87 -80
- data/test/mocks/base.rb +0 -89
data/test/bucket_test.rb
CHANGED
@@ -15,34 +15,60 @@ class BucketTest < Test::Unit::TestCase
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def test_empty_bucket
|
18
|
-
Bucket
|
19
|
-
|
20
|
-
|
21
|
-
end
|
18
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.empty_bucket, :code => 200})
|
19
|
+
bucket = Bucket.find('marcel_molina')
|
20
|
+
assert bucket.empty?
|
22
21
|
end
|
23
22
|
|
24
23
|
def test_bucket_with_one_file
|
25
|
-
Bucket
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
24
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.bucket_with_one_key, :code => 200})
|
25
|
+
bucket = Bucket.find('marcel_molina')
|
26
|
+
assert !bucket.empty?
|
27
|
+
assert_equal 1, bucket.size
|
28
|
+
assert_equal %w(tongue_overload.jpg), bucket.objects.map {|object| object.key}
|
29
|
+
assert bucket['tongue_overload.jpg']
|
32
30
|
end
|
33
31
|
|
34
32
|
def test_bucket_with_more_than_one_file
|
35
|
-
Bucket
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
33
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.bucket_with_more_than_one_key, :code => 200})
|
34
|
+
bucket = Bucket.find('marcel_molina')
|
35
|
+
assert !bucket.empty?
|
36
|
+
assert_equal 2, bucket.size
|
37
|
+
assert_equal %w(beluga_baby.jpg tongue_overload.jpg), bucket.objects.map {|object| object.key}.sort
|
38
|
+
assert bucket['tongue_overload.jpg']
|
42
39
|
end
|
43
40
|
|
44
41
|
def test_bucket_path
|
45
42
|
assert_equal '/bucket_name?max-keys=2', Bucket.send(:path, 'bucket_name', :max_keys => 2)
|
46
43
|
assert_equal '/bucket_name', Bucket.send(:path, 'bucket_name', {})
|
47
44
|
end
|
45
|
+
|
46
|
+
def test_should_not_be_truncated
|
47
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.bucket_with_more_than_one_key, :code => 200})
|
48
|
+
bucket = Bucket.find('marcel_molina')
|
49
|
+
assert !bucket.is_truncated
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_should_be_truncated
|
53
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.truncated_bucket_with_more_than_one_key, :code => 200})
|
54
|
+
bucket = Bucket.find('marcel_molina')
|
55
|
+
assert bucket.is_truncated
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_bucket_name_should_have_leading_slash_prepended_only_once_when_forcing_a_delete
|
59
|
+
# References bug: http://rubyforge.org/tracker/index.php?func=detail&aid=19158&group_id=2409&atid=9356
|
60
|
+
bucket_name = 'foo'
|
61
|
+
expected_bucket_path = "/#{bucket_name}"
|
62
|
+
|
63
|
+
mock_bucket = flexmock('Mock bucket') do |mock|
|
64
|
+
mock.should_receive(:delete_all).once
|
65
|
+
end
|
66
|
+
mock_response = flexmock('Mock delete response') do |mock|
|
67
|
+
mock.should_receive(:success?).once
|
68
|
+
end
|
69
|
+
|
70
|
+
flexmock(Bucket).should_receive(:find).with(bucket_name).once.and_return(mock_bucket)
|
71
|
+
flexmock(Base).should_receive(:delete).with(expected_bucket_path).once.and_return(mock_response)
|
72
|
+
Bucket.delete(bucket_name, :force => true)
|
73
|
+
end
|
48
74
|
end
|
data/test/connection_test.rb
CHANGED
@@ -1,42 +1,49 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/test_helper'
|
2
2
|
|
3
3
|
class ConnectionTest < Test::Unit::TestCase
|
4
|
+
attr_reader :keys
|
4
5
|
def setup
|
5
6
|
@keys = {:access_key_id => '123', :secret_access_key => 'abc'}
|
6
7
|
end
|
7
8
|
|
8
9
|
def test_creating_a_connection
|
9
|
-
connection = Connection.new(
|
10
|
+
connection = Connection.new(keys)
|
10
11
|
assert_kind_of Net::HTTP, connection.http
|
11
12
|
end
|
12
13
|
|
13
14
|
def test_use_ssl_option_is_set_in_connection
|
14
|
-
connection = Connection.new(
|
15
|
+
connection = Connection.new(keys.merge(:use_ssl => true))
|
15
16
|
assert connection.http.use_ssl?
|
16
17
|
end
|
17
18
|
|
18
19
|
def test_setting_port_to_443_implies_use_ssl
|
19
|
-
connection = Connection.new(
|
20
|
+
connection = Connection.new(keys.merge(:port => 443))
|
20
21
|
assert connection.http.use_ssl?
|
21
22
|
end
|
22
23
|
|
23
24
|
def test_protocol
|
24
|
-
connection = Connection.new(
|
25
|
+
connection = Connection.new(keys)
|
25
26
|
assert_equal 'http://', connection.protocol
|
26
|
-
connection = Connection.new(
|
27
|
+
connection = Connection.new(keys.merge(:use_ssl => true))
|
27
28
|
assert_equal 'https://', connection.protocol
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def test_url_for_honors_use_ssl_option_if_it_is_false_even_if_connection_has_use_ssl_option_set
|
32
|
+
# References bug: http://rubyforge.org/tracker/index.php?func=detail&aid=17628&group_id=2409&atid=9356
|
33
|
+
connection = Connection.new(keys.merge(:use_ssl => true))
|
34
|
+
assert_match %r(^http://), connection.url_for('/pathdoesnotmatter', :authenticated => false, :use_ssl => false)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_connection_is_not_persistent_by_default
|
38
|
+
connection = Connection.new(keys)
|
35
39
|
assert !connection.persistent?
|
40
|
+
|
41
|
+
connection = Connection.new(keys.merge(:persistent => true))
|
42
|
+
assert connection.persistent?
|
36
43
|
end
|
37
44
|
|
38
45
|
def test_server_and_port_are_passed_onto_connection
|
39
|
-
connection = Connection.new(
|
46
|
+
connection = Connection.new(keys)
|
40
47
|
options = connection.instance_variable_get('@options')
|
41
48
|
assert_equal connection.http.address, options[:server]
|
42
49
|
assert_equal connection.http.port, options[:port]
|
@@ -52,18 +59,18 @@ class ConnectionTest < Test::Unit::TestCase
|
|
52
59
|
end
|
53
60
|
|
54
61
|
assert_nothing_raised do
|
55
|
-
Connection.new(
|
62
|
+
Connection.new(keys)
|
56
63
|
end
|
57
64
|
end
|
58
65
|
|
59
66
|
def test_access_keys_extracted
|
60
|
-
connection = Connection.new(
|
67
|
+
connection = Connection.new(keys)
|
61
68
|
assert_equal '123', connection.access_key_id
|
62
69
|
assert_equal 'abc', connection.secret_access_key
|
63
70
|
end
|
64
71
|
|
65
72
|
def test_request_method_class_lookup
|
66
|
-
|
73
|
+
connection = Connection.new(keys)
|
67
74
|
expectations = {
|
68
75
|
:get => Net::HTTP::Get, :post => Net::HTTP::Post,
|
69
76
|
:put => Net::HTTP::Put, :delete => Net::HTTP::Delete,
|
@@ -71,7 +78,7 @@ class ConnectionTest < Test::Unit::TestCase
|
|
71
78
|
}
|
72
79
|
|
73
80
|
expectations.each do |verb, klass|
|
74
|
-
assert_equal klass,
|
81
|
+
assert_equal klass, connection.send(:request_method, verb)
|
75
82
|
end
|
76
83
|
end
|
77
84
|
|
@@ -99,10 +106,29 @@ class ConnectionTest < Test::Unit::TestCase
|
|
99
106
|
def test_connecting_through_a_proxy
|
100
107
|
connection = nil
|
101
108
|
assert_nothing_raised do
|
102
|
-
connection = Connection.new(
|
109
|
+
connection = Connection.new(keys.merge(:proxy => sample_proxy_settings))
|
103
110
|
end
|
104
111
|
assert connection.http.proxy?
|
105
112
|
end
|
113
|
+
|
114
|
+
def test_request_only_escapes_the_path_the_first_time_it_runs_and_not_subsequent_times
|
115
|
+
connection = Connection.new(@keys)
|
116
|
+
unescaped_path = 'path with spaces'
|
117
|
+
escaped_path = 'path%20with%20spaces'
|
118
|
+
|
119
|
+
flexmock(Connection).should_receive(:prepare_path).with(unescaped_path).once.and_return(escaped_path).ordered
|
120
|
+
flexmock(connection.http).should_receive(:request).and_raise(Errno::EPIPE).ordered
|
121
|
+
flexmock(connection.http).should_receive(:request).ordered
|
122
|
+
connection.request :put, unescaped_path
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_if_request_has_no_body_then_the_content_length_is_set_to_zero
|
126
|
+
# References bug: http://rubyforge.org/tracker/index.php?func=detail&aid=13052&group_id=2409&atid=9356
|
127
|
+
connection = Connection.new(@keys)
|
128
|
+
flexmock(Net::HTTP::Put).new_instances.should_receive(:content_length=).once.with(0).ordered
|
129
|
+
flexmock(connection.http).should_receive(:request).once.ordered
|
130
|
+
connection.request :put, 'path does not matter'
|
131
|
+
end
|
106
132
|
end
|
107
133
|
|
108
134
|
class ConnectionOptionsTest < Test::Unit::TestCase
|
data/test/error_test.rb
CHANGED
@@ -4,9 +4,6 @@ class ErrorTest < Test::Unit::TestCase
|
|
4
4
|
def setup
|
5
5
|
@container = AWS::S3
|
6
6
|
@error = Error.new(Parsing::XmlParser.new(Fixtures::Errors.access_denied))
|
7
|
-
end
|
8
|
-
|
9
|
-
def teardown
|
10
7
|
@container.send(:remove_const, :NotImplemented) if @container.const_defined?(:NotImplemented)
|
11
8
|
end
|
12
9
|
|
@@ -54,22 +51,20 @@ class ErrorTest < Test::Unit::TestCase
|
|
54
51
|
end
|
55
52
|
|
56
53
|
def test_error_response_handles_attributes_with_no_value
|
57
|
-
Bucket.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
54
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Errors.error_with_no_message, :code => 500})
|
55
|
+
|
56
|
+
begin
|
57
|
+
Bucket.create('foo', 'invalid-argument' => 'bad juju')
|
58
|
+
rescue ResponseError => error
|
59
|
+
end
|
60
|
+
|
61
|
+
assert_nothing_raised do
|
62
|
+
error.response.error.message
|
63
|
+
end
|
64
|
+
assert_nil error.response.error.message
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
assert_nil error.response.error.message
|
69
|
-
|
70
|
-
assert_raises(NoMethodError) do
|
71
|
-
error.response.error.non_existant_method
|
72
|
-
end
|
66
|
+
assert_raises(NoMethodError) do
|
67
|
+
error.response.error.non_existant_method
|
73
68
|
end
|
74
69
|
end
|
75
70
|
end
|
data/test/fixtures/buckets.yml
CHANGED
@@ -100,3 +100,34 @@ bucket_with_more_than_one_key: >
|
|
100
100
|
<StorageClass>STANDARD</StorageClass>
|
101
101
|
</Contents>
|
102
102
|
</ListBucketResult>
|
103
|
+
|
104
|
+
truncated_bucket_with_more_than_one_key: >
|
105
|
+
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
106
|
+
<Name>marcel_molina</Name>
|
107
|
+
<Prefix></Prefix>
|
108
|
+
<Marker></Marker>
|
109
|
+
<MaxKeys>2</MaxKeys>
|
110
|
+
<IsTruncated>true</IsTruncated>
|
111
|
+
<Contents>
|
112
|
+
<Key>beluga_baby.jpg</Key>
|
113
|
+
<LastModified>2006-10-05T02:55:10.000Z</LastModified>
|
114
|
+
<ETag>"b2453d4a39a7387674a8c505112a2f0b"</ETag>
|
115
|
+
<Size>35807</Size>
|
116
|
+
<Owner>
|
117
|
+
<ID>bb2041a25975c3d4ce9775fe9e93e5b77a6a9fad97dc7e00686191f3790b13f1</ID>
|
118
|
+
<DisplayName>mmolina@onramp.net</DisplayName>
|
119
|
+
</Owner>
|
120
|
+
<StorageClass>STANDARD</StorageClass>
|
121
|
+
</Contents>
|
122
|
+
<Contents>
|
123
|
+
<Key>tongue_overload.jpg</Key>
|
124
|
+
<LastModified>2006-10-05T02:42:22.000Z</LastModified>
|
125
|
+
<ETag>"f21f7c4e8ea6e34b268887b07d6da745"</ETag>
|
126
|
+
<Size>60673</Size>
|
127
|
+
<Owner>
|
128
|
+
<ID>bb2041a25975c3d4ce9775fe9e93e5b77a6a9fad97dc7e00686191f3790b13f1</ID>
|
129
|
+
<DisplayName>mmolina@onramp.net</DisplayName>
|
130
|
+
</Owner>
|
131
|
+
<StorageClass>STANDARD</StorageClass>
|
132
|
+
</Contents>
|
133
|
+
</ListBucketResult>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module AWS
|
2
|
+
module S3
|
3
|
+
class FakeResponse
|
4
|
+
attr_reader :code, :body, :headers
|
5
|
+
def initialize(options = {})
|
6
|
+
@code = options.delete(:code) || 200
|
7
|
+
@body = options.delete(:body) || ''
|
8
|
+
@headers = {'content-type' => 'application/xml'}.merge(options.delete(:headers) || {})
|
9
|
+
end
|
10
|
+
|
11
|
+
# For ErrorResponse
|
12
|
+
def response
|
13
|
+
body
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](header)
|
17
|
+
headers[header]
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
headers.each(&block)
|
22
|
+
end
|
23
|
+
alias_method :each_header, :each
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/test/object_test.rb
CHANGED
@@ -7,25 +7,23 @@ class ObjectTest < Test::Unit::TestCase
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def test_header_settings_reader_and_writer
|
10
|
-
|
11
|
-
|
12
|
-
S3Object.request_returns :headers => headers
|
13
|
-
|
14
|
-
assert_nothing_raised do
|
15
|
-
@object.content_type
|
16
|
-
end
|
10
|
+
headers = {'content-type' => 'text/plain'}
|
11
|
+
mock_connection_for(S3Object, :returns => {:headers => headers})
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
13
|
+
assert_nothing_raised do
|
14
|
+
@object.content_type
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_equal 'text/plain', @object.content_type
|
18
|
+
|
19
|
+
assert_nothing_raised do
|
20
|
+
@object.content_type = 'image/jpg'
|
21
|
+
end
|
22
|
+
|
23
|
+
assert_equal 'image/jpg', @object.content_type
|
24
|
+
|
25
|
+
assert_raises(NoMethodError) do
|
26
|
+
@object.non_existant_header_setting
|
29
27
|
end
|
30
28
|
end
|
31
29
|
|
@@ -75,91 +73,81 @@ class ObjectTest < Test::Unit::TestCase
|
|
75
73
|
end
|
76
74
|
|
77
75
|
def test_fetching_object_value_generates_value_object
|
78
|
-
S3Object
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
assert_equal 'hello!', value
|
83
|
-
end
|
76
|
+
mock_connection_for(S3Object, :returns => {:body => 'hello!'})
|
77
|
+
value = S3Object.value('foo', 'bar')
|
78
|
+
assert_kind_of S3Object::Value, value
|
79
|
+
assert_equal 'hello!', value
|
84
80
|
end
|
85
81
|
|
86
82
|
def test_fetching_file_by_name_raises_when_heuristic_fails
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
object = nil # Block scoping
|
93
|
-
assert_nothing_raised do
|
94
|
-
object = S3Object.find('tongue_overload.jpg', 'marcel_molina')
|
95
|
-
end
|
96
|
-
assert_kind_of S3Object, object
|
97
|
-
assert_equal 'tongue_overload.jpg', object.key
|
83
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.bucket_with_one_key})
|
84
|
+
assert_raises(NoSuchKey) do
|
85
|
+
S3Object.find('not_tongue_overload.jpg', 'marcel_molina')
|
98
86
|
end
|
87
|
+
|
88
|
+
object = nil # Block scoping
|
89
|
+
assert_nothing_raised do
|
90
|
+
object = S3Object.find('tongue_overload.jpg', 'marcel_molina')
|
91
|
+
end
|
92
|
+
assert_kind_of S3Object, object
|
93
|
+
assert_equal 'tongue_overload.jpg', object.key
|
99
94
|
end
|
100
95
|
|
101
96
|
def test_about
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
97
|
+
headers = {'content-size' => '12345', 'date' => Time.now.httpdate, 'content-type' => 'application/xml'}
|
98
|
+
mock_connection_for(S3Object, :returns => [
|
99
|
+
{:headers => headers},
|
100
|
+
{:code => 404}
|
101
|
+
]
|
102
|
+
)
|
103
|
+
about = S3Object.about('foo', 'bar')
|
104
|
+
assert_kind_of S3Object::About, about
|
105
|
+
assert_equal headers, about
|
106
|
+
|
107
|
+
assert_raises(NoSuchKey) do
|
108
|
+
S3Object.about('foo', 'bar')
|
113
109
|
end
|
114
110
|
end
|
115
111
|
|
116
|
-
def
|
117
|
-
S3Object
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
112
|
+
def test_can_tell_that_an_s3object_does_not_exist
|
113
|
+
mock_connection_for(S3Object, :returns => {:code => 404})
|
114
|
+
assert_equal false, S3Object.exists?('foo', 'bar')
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_can_tell_that_an_s3object_exists
|
118
|
+
mock_connection_for(S3Object, :returns => {:code => 200})
|
119
|
+
assert_equal true, S3Object.exists?('foo', 'bar')
|
124
120
|
end
|
125
121
|
|
126
122
|
def test_s3object_equality
|
127
|
-
Bucket.
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
assert !(file1 == file2) # /!\ Parens required /!\
|
133
|
-
end
|
123
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.bucket_with_more_than_one_key})
|
124
|
+
file1, file2 = Bucket.objects('does not matter')
|
125
|
+
assert file1 == file1
|
126
|
+
assert file2 == file2
|
127
|
+
assert !(file1 == file2) # /!\ Parens required /!\
|
134
128
|
end
|
135
129
|
|
136
130
|
def test_inspect
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
144
|
-
end
|
131
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.bucket_with_one_key})
|
132
|
+
object = S3Object.find('tongue_overload.jpg', 'bucket does not matter')
|
133
|
+
assert object.path
|
134
|
+
assert_nothing_raised { object.inspect }
|
135
|
+
assert object.inspect[object.path]
|
136
|
+
end
|
145
137
|
|
146
|
-
|
147
|
-
|
148
|
-
S3Object.request_returns :body => Fixtures::Buckets.bucket_with_one_key
|
138
|
+
def test_etag
|
139
|
+
mock_connection_for(Bucket, :returns => {:body => Fixtures::Buckets.bucket_with_one_key})
|
149
140
|
file = S3Object.find('tongue_overload.jpg', 'bucket does not matter')
|
150
141
|
assert file.etag
|
151
142
|
assert_equal 'f21f7c4e8ea6e34b268887b07d6da745', file.etag
|
152
143
|
end
|
153
|
-
end
|
154
144
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
end
|
145
|
+
def test_fetching_information_about_an_object_that_does_not_exist_raises_no_such_key
|
146
|
+
mock_connection_for(S3Object, :returns => {:body => '', :code => 404})
|
147
|
+
assert_raises(NoSuchKey) do
|
148
|
+
S3Object.about('asdfasdfasdfas-this-does-not-exist', 'bucket does not matter')
|
149
|
+
end
|
150
|
+
end
|
163
151
|
end
|
164
152
|
|
165
153
|
class MetadataTest < Test::Unit::TestCase
|