s3 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,110 @@
1
+ module Stree
2
+ class Service
3
+ include Parser
4
+ extend Roxy::Moxie
5
+
6
+ attr_reader :access_key_id, :secret_access_key, :use_ssl
7
+
8
+ # Compares service to other, by access_key_id and secret_access_key
9
+ def ==(other)
10
+ self.access_key_id == other.access_key_id and self.secret_access_key == other.secret_access_key
11
+ end
12
+
13
+ # ==== Parameters:
14
+ # +options+:: a hash of options described below
15
+ #
16
+ # ==== Options:
17
+ # +access_key_id+:: Amazon access key id, required
18
+ # +secret_access_key+:: Amazon secret access key, required
19
+ # +use_ssl+:: true if use ssl in connection, otherwise false
20
+ # +timeout+:: parameter for Net::HTTP module
21
+ # +debug+:: prints the raw requests to STDOUT
22
+ def initialize(options)
23
+ @access_key_id = options[:access_key_id] or raise ArgumentError, "No access key id given"
24
+ @secret_access_key = options[:secret_access_key] or raise ArgumentError, "No secret access key given"
25
+ @use_ssl = options[:use_ssl]
26
+ @timeout = options[:timeout]
27
+ @debug = options[:debug]
28
+ end
29
+
30
+ # Returns all buckets in the service and caches the result (see reload)
31
+ def buckets(reload = false)
32
+ if reload or @buckets.nil?
33
+ @buckets = list_all_my_buckets
34
+ else
35
+ @buckets
36
+ end
37
+ end
38
+
39
+ # Returns "http://" or "https://", depends on use_ssl value from initializer
40
+ def protocol
41
+ use_ssl ? "https://" : "http://"
42
+ end
43
+
44
+ # Return 443 or 80, depends on use_ssl value from initializer
45
+ def port
46
+ use_ssl ? 443 : 80
47
+ end
48
+
49
+ proxy :buckets do
50
+ # Builds new bucket with given name
51
+ def build(name)
52
+ Bucket.send(:new, proxy_owner, name)
53
+ end
54
+
55
+ # Finds the bucket with given name
56
+ def find_first(name)
57
+ bucket = build(name)
58
+ bucket.retrieve
59
+ end
60
+ alias :find :find_first
61
+
62
+ # Find all buckets in the service
63
+ def find_all
64
+ proxy_target
65
+ end
66
+
67
+ # Reloads the bucket list (clears the cache)
68
+ def reload
69
+ proxy_owner.buckets(true)
70
+ end
71
+
72
+ # Destroy all buckets in the service. Doesn't destroy non-empty
73
+ # buckets by default, pass true to force destroy (USE WITH
74
+ # CARE!).
75
+ def destroy_all(force = false)
76
+ proxy_target.each do |bucket|
77
+ bucket.destroy(force)
78
+ end
79
+ end
80
+ end
81
+
82
+ def inspect #:nodoc:
83
+ "#<#{self.class}:#@access_key_id>"
84
+ end
85
+
86
+ private
87
+
88
+ def list_all_my_buckets
89
+ response = service_request(:get)
90
+ names = parse_list_all_my_buckets_result(response.body)
91
+ names.map { |name| Bucket.send(:new, self, name) }
92
+ end
93
+
94
+ def service_request(method, options = {})
95
+ connection.request(method, options.merge(:path => "/#{options[:path]}"))
96
+ end
97
+
98
+ def connection
99
+ if @connection.nil?
100
+ @connection = Connection.new
101
+ @connection.access_key_id = @access_key_id
102
+ @connection.secret_access_key = @secret_access_key
103
+ @connection.use_ssl = @use_ssl
104
+ @connection.timeout = @timeout
105
+ @connection.debug = @debug
106
+ end
107
+ @connection
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,157 @@
1
+ module Stree
2
+
3
+ # Class responsible for generating signatures to requests.
4
+ #
5
+ # Implements algorithm defined by Amazon Web Services to sign
6
+ # request with secret private credentials
7
+ #
8
+ # === See:
9
+ # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?RESTAuthentication.html
10
+
11
+ class Signature
12
+
13
+ # Generates signature for given parameters
14
+ #
15
+ # ==== Parameters:
16
+ # +options+: a hash that contains options listed below
17
+ #
18
+ # ==== Options:
19
+ # +host+: hostname
20
+ # +request+: Net::HTTPRequest object with correct headers
21
+ # +access_key_id+: access key id
22
+ # +secret_access_key+: secret access key
23
+ #
24
+ # ==== Returns:
25
+ # Generated signature for given hostname and request
26
+ def self.generate(options)
27
+ request = options[:request]
28
+ host = options[:host]
29
+ access_key_id = options[:access_key_id]
30
+ secret_access_key = options[:secret_access_key]
31
+
32
+ http_verb = request.method
33
+ content_md5 = request["content-md5"] || ""
34
+ content_type = request["content-type"] || ""
35
+ date = request["x-amz-date"].nil? ? request["date"] : ""
36
+ canonicalized_resource = canonicalized_resource(host, request)
37
+ canonicalized_amz_headers = canonicalized_amz_headers(request)
38
+
39
+ string_to_sign = ""
40
+ string_to_sign << http_verb
41
+ string_to_sign << "\n"
42
+ string_to_sign << content_md5
43
+ string_to_sign << "\n"
44
+ string_to_sign << content_type
45
+ string_to_sign << "\n"
46
+ string_to_sign << date
47
+ string_to_sign << "\n"
48
+ string_to_sign << canonicalized_amz_headers
49
+ string_to_sign << canonicalized_resource
50
+
51
+ digest = OpenSSL::Digest::Digest.new('sha1')
52
+ hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
53
+ base64 = Base64.encode64(hmac)
54
+ signature = base64.chomp
55
+
56
+ "AWS #{access_key_id}:#{signature}"
57
+ end
58
+
59
+ private
60
+
61
+ # Helper method for extracting header fields from Net::HTTPRequest and
62
+ # preparing them for singing in #generate method
63
+ #
64
+ # ==== Parameters:
65
+ # +request+: Net::HTTPRequest object with header fields filled in
66
+ #
67
+ # ==== Returns:
68
+ # String containing interesting header fields in suitable order and form
69
+ def self.canonicalized_amz_headers(request)
70
+ headers = []
71
+
72
+ # 1. Convert each HTTP header name to lower-case. For example,
73
+ # 'X-Amz-Date' becomes 'x-amz-date'.
74
+ request.each { |key, value| headers << [key.downcase, value] if key =~ /\Ax-amz-/io }
75
+ #=> [["c", 0], ["a", 1], ["a", 2], ["b", 3]]
76
+
77
+ # 2. Sort the collection of headers lexicographically by header
78
+ # name.
79
+ headers.sort!
80
+ #=> [["a", 1], ["a", 2], ["b", 3], ["c", 0]]
81
+
82
+ # 3. Combine header fields with the same name into one
83
+ # "header-name:comma-separated-value-list" pair as prescribed by
84
+ # RFC 2616, section 4.2, without any white-space between
85
+ # values. For example, the two metadata headers
86
+ # 'x-amz-meta-username: fred' and 'x-amz-meta-username: barney'
87
+ # would be combined into the single header 'x-amz-meta-username:
88
+ # fred,barney'.
89
+ groupped_headers = headers.group_by { |i| i.first }
90
+ #=> {"a"=>[["a", 1], ["a", 2]], "b"=>[["b", 3]], "c"=>[["c", 0]]}
91
+ combined_headers = groupped_headers.map do |key, value|
92
+ values = value.map { |e| e.last }
93
+ [key, values.join(",")]
94
+ end
95
+ #=> [["a", "1,2"], ["b", "3"], ["c", "0"]]
96
+
97
+ # 4. "Un-fold" long headers that span multiple lines (as allowed
98
+ # by RFC 2616, section 4.2) by replacing the folding white-space
99
+ # (including new-line) by a single space.
100
+ unfolded_headers = combined_headers.map do |header|
101
+ key = header.first
102
+ value = header.last
103
+ value.gsub!(/\s+/, " ")
104
+ [key, value]
105
+ end
106
+
107
+ # 5. Trim any white-space around the colon in the header. For
108
+ # example, the header 'x-amz-meta-username: fred,barney' would
109
+ # become 'x-amz-meta-username:fred,barney'
110
+ joined_headers = unfolded_headers.map do |header|
111
+ key = header.first.strip
112
+ value = header.last.strip
113
+ "#{key}:#{value}"
114
+ end
115
+
116
+ # 6. Finally, append a new-line (U+000A) to each canonicalized
117
+ # header in the resulting list. Construct the
118
+ # CanonicalizedResource element by concatenating all headers in
119
+ # this list into a single string.
120
+ joined_headers << "" unless joined_headers.empty?
121
+ joined_headers.join("\n")
122
+ end
123
+
124
+ # Helper methods for extracting caninocalized resource address
125
+ #
126
+ # ==== Parameters:
127
+ # +host+: hostname
128
+ # +request+: Net::HTTPRequest object with headers filealds filled in
129
+ #
130
+ # ==== Returns:
131
+ # String containing extracted canonicalized resource
132
+ def self.canonicalized_resource(host, request)
133
+ # 1. Start with the empty string ("").
134
+ string = ""
135
+
136
+ # 2. If the request specifies a bucket using the HTTP Host
137
+ # header (virtual hosted-style), append the bucket name preceded
138
+ # by a "/" (e.g., "/bucketname"). For path-style requests and
139
+ # requests that don't address a bucket, do nothing. For more
140
+ # information on virtual hosted-style requests, see Virtual
141
+ # Hosting of Buckets.
142
+ bucket_name = host.sub(/\.?s3\.amazonaws\.com\Z/, "")
143
+ string << "/#{bucket_name}" unless bucket_name.empty?
144
+
145
+ # 3. Append the path part of the un-decoded HTTP Request-URI,
146
+ # up-to but not including the query string.
147
+ uri = URI.parse(request.path)
148
+ string << uri.path
149
+
150
+ # 4. If the request addresses a sub-resource, like ?location,
151
+ # ?acl, or ?torrent, append the sub-resource including question
152
+ # mark.
153
+ string << "?#{$1}" if uri.query =~ /&?(acl|torrent|logging|location)(?:&|=|\Z)/
154
+ string
155
+ end
156
+ end
157
+ end
data/lib/stree.rb ADDED
@@ -0,0 +1,24 @@
1
+ require "base64"
2
+ require "digest/md5"
3
+ require "forwardable"
4
+ require "net/http"
5
+ require "net/https"
6
+ require "openssl"
7
+ require "rexml/document"
8
+ require "time"
9
+
10
+ require "stree/roxy/moxie"
11
+ require "stree/roxy/proxy"
12
+
13
+ require "stree/parser"
14
+ require "stree/bucket"
15
+ require "stree/connection"
16
+ require "stree/exceptions"
17
+ require "stree/object"
18
+ require "stree/service"
19
+ require "stree/signature"
20
+
21
+ module Stree
22
+ # Default (and only) host serving S3 stuff
23
+ HOST = "s3.amazonaws.com"
24
+ end
@@ -0,0 +1,231 @@
1
+ require 'test_helper'
2
+
3
+ class BucketTest < Test::Unit::TestCase
4
+ def setup
5
+ @bucket_vhost = Stree::Bucket.new(nil, "data-bucket")
6
+ @bucket_path = Stree::Bucket.new(nil, "data_bucket")
7
+ @bucket = @bucket_vhost
8
+
9
+ @response_location = Net::HTTPOK.new("1.1", "200", "OK")
10
+ stub(@response_location).body { @bucket_location_body }
11
+ @bucket_location = "EU"
12
+ @bucket_location_body = <<-EOLocation
13
+ <?xml version="1.0" encoding="UTF-8"?>\n<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">EU</LocationConstraint>
14
+ EOLocation
15
+
16
+ @reponse_owned_by_you = Net::HTTPConflict.new("1.1", "409", "Conflict")
17
+ stub(@reponse_owned_by_you).body { @bucket_owned_by_you_body }
18
+ @bucket_owned_by_you_body = <<-EOOwnedByYou
19
+ <?xml version="1.0" encoding="UTF-8"?>\n<Error> <Code>BucketAlreadyOwnedByYou</Code> <Message>Your previous request to create the named bucket succeeded and you already own it.</Message> <BucketName>bucket</BucketName> <RequestId>117D08EA0EC6E860</RequestId> <HostId>4VpMSvmJ+G5+DLtVox6O5cZNgdPlYcjCu3l0n4HjDe01vPxxuk5eTAtcAkUynRyV</HostId> </Error>
20
+ EOOwnedByYou
21
+
22
+ @reponse_already_exists = Net::HTTPConflict.new("1.1", "409", "Conflict")
23
+ stub(@response_already_exists).body { @bucket_already_exists_body }
24
+ @bucket_already_exists_body = <<-EOAlreadyExists
25
+ <?xml version="1.0" encoding="UTF-8"?>\n<Error> <Code>BucketAlreadyExists</Code> <Message>The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.</Message> <BucketName>bucket</BucketName> <RequestId>4C154D32807C92BD</RequestId> <HostId>/xyHQgXcUXTZQhoO+NUBzbaxbFrIhKlyuaRHFnmcId0bMePvY9Zwg+dyk2LYE4g5</HostId> </Error>
26
+ EOAlreadyExists
27
+
28
+ @objects_list_empty = []
29
+ @objects_list = [
30
+ Stree::Object.new(@bucket, "obj1"),
31
+ Stree::Object.new(@bucket, "obj2")
32
+ ]
33
+
34
+ @response_objects_list_empty = Net::HTTPOK.new("1.1", "200", "OK")
35
+ stub(@response_objects_list_empty).body { @response_objects_list_empty_body }
36
+ @response_objects_list_empty_body = <<-EOEmpty
37
+ <?xml version="1.0" encoding="UTF-8"?>\n<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <Name>bucket</Name> <Prefix></Prefix> <Marker></Marker> <MaxKeys>1000</MaxKeys> <IsTruncated>false</IsTruncated> </ListBucketResult>
38
+ EOEmpty
39
+
40
+ @response_objects_list = Net::HTTPOK.new("1.1", "200", "OK")
41
+ stub(@response_objects_list).body { @response_objects_list_body }
42
+ @response_objects_list_body = <<-EOObjects
43
+ <?xml version="1.0" encoding="UTF-8"?>\n<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <Name>bucket</Name> <Prefix></Prefix> <Marker></Marker> <MaxKeys>1000</MaxKeys> <IsTruncated>false</IsTruncated> <Contents> <Key>obj1</Key> <LastModified>2009-07-03T10:17:33.000Z</LastModified> <ETag>&quot;99519cdf14c255e580e1b7bca85a458c&quot;</ETag> <Size>1729</Size> <Owner> <ID>df864aeb6f42be43f1d9e60aaabe3f15e245b035a4b79d1cfe36c4deaec67205</ID> <DisplayName>owner</DisplayName> </Owner> <StorageClass>STANDARD</StorageClass> </Contents> <Contents> <Key>obj2</Key> <LastModified>2009-07-03T11:17:33.000Z</LastModified> <ETag>&quot;99519cdf14c255e586e1b12bca85a458c&quot;</ETag> <Size>179</Size> <Owner> <ID>df864aeb6f42be43f1d9e60aaabe3f17e247b037a4b79d1cfe36c4deaec67205</ID> <DisplayName>owner</DisplayName> </Owner> <StorageClass>STANDARD</StorageClass> </Contents> </ListBucketResult>
44
+ EOObjects
45
+ end
46
+
47
+ def test_name_valid
48
+ assert_raise ArgumentError do Stree::Bucket.new(nil, "") end # should not be valid with empty name
49
+ assert_raise ArgumentError do Stree::Bucket.new(nil, "10.0.0.1") end # should not be valid with IP as name
50
+ assert_raise ArgumentError do Stree::Bucket.new(nil, "as") end # should not be valid with name shorter than 3 characters
51
+ assert_raise ArgumentError do Stree::Bucket.new(nil, "a"*256) end # should not be valid with name longer than 255 characters
52
+ assert_raise ArgumentError do Stree::Bucket.new(nil, ".asdf") end # should not allow special characters as first character
53
+ assert_raise ArgumentError do Stree::Bucket.new(nil, "-asdf") end # should not allow special characters as first character
54
+ assert_raise ArgumentError do Stree::Bucket.new(nil, "_asdf") end # should not allow special characters as first character
55
+
56
+ assert_nothing_raised do
57
+ Stree::Bucket.new(nil, "a-a-")
58
+ Stree::Bucket.new(nil, "a.a.")
59
+ Stree::Bucket.new(nil, "a_a_")
60
+ end
61
+ end
62
+
63
+ def test_path_prefix
64
+ expected = ""
65
+ actual = @bucket_vhost.path_prefix
66
+ assert_equal expected, actual
67
+
68
+ expected = "data_bucket/"
69
+ actual = @bucket_path.path_prefix
70
+ assert_equal expected, actual
71
+ end
72
+
73
+ def test_host
74
+ expected = "data-bucket.s3.amazonaws.com"
75
+ actual = @bucket_vhost.host
76
+ assert_equal expected, actual
77
+
78
+ expected = "s3.amazonaws.com"
79
+ actual = @bucket_path.host
80
+ assert_equal expected, actual
81
+ end
82
+
83
+ def test_vhost
84
+ assert @bucket_vhost.vhost?
85
+ assert ! @bucket_path.vhost?
86
+ end
87
+
88
+ def test_exists
89
+ mock(@bucket).retrieve { @bucket_vhost }
90
+ assert @bucket.exists?
91
+
92
+ mock(@bucket).retrieve { raise Stree::Error::NoSuchBucket.new(nil, nil) }
93
+ assert ! @bucket.exists?
94
+ end
95
+
96
+ def test_location_and_parse_location
97
+ mock(@bucket).bucket_request(:get, {:params=>{:location=>nil}}) { @response_location }
98
+
99
+ expected = @bucket_location
100
+ actual = @bucket.location
101
+ assert_equal expected, actual
102
+
103
+ stub(@bucket).bucket_request(:get, {:params=>{:location=>nil}}) { flunk "should deliver from cached result" }
104
+ actual = @bucket.location
105
+ assert_equal expected, actual
106
+ end
107
+
108
+ def test_save
109
+ mock(@bucket).bucket_request(:put, {:headers=>{}}) { }
110
+ assert @bucket.save
111
+ # mock ensures that bucket_request was called
112
+ end
113
+
114
+ def test_save_failure_owned_by_you
115
+ mock(@bucket).bucket_request(:put, {:headers=>{}}) { raise Stree::Error::BucketAlreadyOwnedByYou.new(409, @response_owned_by_you) }
116
+ assert_raise Stree::Error::BucketAlreadyOwnedByYou do
117
+ @bucket.save
118
+ end
119
+
120
+ mock(@bucket).bucket_request(:put, {:headers=>{}}) { raise Stree::Error::BucketAlreadyExists.new(409, @response_already_exists) }
121
+ assert_raise Stree::Error::BucketAlreadyExists do
122
+ @bucket.save
123
+ end
124
+ end
125
+
126
+ def test_objects
127
+ mock(@bucket).fetch_objects { @objects_list_empty }
128
+ expected = @objects_list_empty
129
+ actual = @bucket.objects
130
+ assert_equal expected, actual
131
+
132
+ stub(@bucket).fetch_objects { flunk "should load objects from cache" }
133
+ actual = @bucket.objects
134
+ assert_equal expected, actual
135
+
136
+ stub(@bucket).fetch_objects { @objects_list }
137
+
138
+ expected = @objects_list
139
+ actual = @bucket.objects(true)
140
+ assert_equal expected, actual
141
+ end
142
+
143
+ def test_fetch_objects_and_parse_objects
144
+ mock(@bucket).bucket_request(:get, :test=>true) { @response_objects_list_empty }
145
+ expected = @objects_list_empty
146
+ actual = @bucket.objects.find_all(:test => true)
147
+ assert_equal expected, actual
148
+
149
+ mock(@bucket).bucket_request(:get, :test=>true) { @response_objects_list }
150
+ expected = @objects_list
151
+ actual = @bucket.objects.find_all(:test => true)
152
+ assert_equal expected, actual
153
+ end
154
+
155
+ def test_destroy
156
+ mock(@bucket).bucket_request(:delete) { }
157
+ assert @bucket.destroy
158
+ end
159
+
160
+ def test_objects_build
161
+ stub(@bucket).bucket_request { flunk "should not connect to server" }
162
+
163
+ expected = "object_name"
164
+ actual = @bucket.objects.build("object_name")
165
+ assert_kind_of Stree::Object, actual
166
+ assert_equal expected, actual.key
167
+ end
168
+
169
+ def test_objects_find_first
170
+ assert_nothing_raised do
171
+ stub.instance_of(Stree::Object).retrieve { Stree::Object.new(nil, "obj2") }
172
+ expected = "obj2"
173
+ actual = @bucket.objects.find_first("obj2")
174
+ assert_equal "obj2", actual.key
175
+ end
176
+ end
177
+
178
+ def test_objects_find_first_fail
179
+ assert_raise Stree::Error::NoSuchKey do
180
+ stub.instance_of(Stree::Object).retrieve { raise Stree::Error::NoSuchKey.new(404, nil) }
181
+ @bucket.objects.find_first("obj3")
182
+ end
183
+ end
184
+
185
+ def test_objects_find_all_on_empty_list
186
+ stub(@bucket).fetch_objects { @objects_list_empty }
187
+ assert_nothing_raised do
188
+ expected = @objects_list_empty
189
+ actual = @bucket.objects.find_all
190
+ assert_equal expected, actual
191
+ end
192
+ end
193
+
194
+ def test_objects_find_all
195
+ stub(@bucket).fetch_objects { @objects_list }
196
+ assert_nothing_raised do
197
+ expected = @objects_list
198
+ actual = @bucket.objects.find_all
199
+ assert_equal expected, actual
200
+ end
201
+ end
202
+
203
+ def test_objects_reload
204
+ stub(@bucket).fetch_objects { @objects_list_empty }
205
+ expected = @objects_list_empty
206
+ actual = @bucket.objects
207
+ assert_equal expected, actual
208
+
209
+ stub(@bucket).fetch_objects { @objects_list }
210
+ expected = @objects_list_empty
211
+ actual = @bucket.objects
212
+ assert_equal expected, actual
213
+
214
+ assert @bucket.objects.reload
215
+
216
+ expected = @objects_list
217
+ actual = @bucket.objects
218
+ assert_equal expected, actual
219
+ end
220
+
221
+ def test_objects_destroy_all
222
+ @counter = 0
223
+ stub(@bucket).fetch_objects { @objects_list }
224
+ @bucket.objects.each do |obj|
225
+ mock(obj).destroy { @counter += 1 }
226
+ end
227
+
228
+ @bucket.objects.destroy_all
229
+ assert_equal @objects_list.length, @counter
230
+ end
231
+ end
@@ -0,0 +1,164 @@
1
+ require 'test_helper'
2
+
3
+ class ConnectionTest < Test::Unit::TestCase
4
+ def setup
5
+ @connection = Stree::Connection.new(
6
+ :access_key_id => "12345678901234567890",
7
+ :secret_access_key => "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDF"
8
+ )
9
+ @http_request = Net::HTTP.new("")
10
+ @response_ok = Net::HTTPOK.new("1.1", "200", "OK")
11
+ @response_not_found = Net::HTTPNotFound.new("1.1", "404", "Not Found")
12
+ stub(@connection).http { @http_request }
13
+ stub(@http_request).start { @response_ok }
14
+ end
15
+
16
+ def test_handle_response_not_modify_response_when_ok
17
+ assert_nothing_raised do
18
+ response = @connection.request(
19
+ :get,
20
+ :host => "s3.amazonaws.com",
21
+ :path => "/"
22
+ )
23
+ assert_equal @response_ok, response
24
+ end
25
+ end
26
+
27
+ def test_handle_response_throws_exception_when_not_ok
28
+ response_body = <<-EOFakeBody
29
+ <?xml version=\"1.0\" encoding=\"UTF-8\"?>
30
+ <SomeResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">
31
+ <Code>NoSuchBucket</Code>
32
+ <Message>The specified bucket does not exist</Message>
33
+ </SomeResult>
34
+ EOFakeBody
35
+ stub(@http_request).start { @response_not_found }
36
+ stub(@response_not_found).body { response_body }
37
+
38
+ assert_raise Stree::Error::NoSuchBucket do
39
+ response = @connection.request(
40
+ :get,
41
+ :host => "data.example.com.s3.amazonaws.com",
42
+ :path => "/"
43
+ )
44
+ end
45
+ end
46
+
47
+ def test_handle_response_throws_standard_exception_when_not_ok
48
+ stub(@http_request).start { @response_not_found }
49
+ stub(@response_not_found).body { nil }
50
+ assert_raise Stree::Error::ResponseError do
51
+ response = @connection.request(
52
+ :get,
53
+ :host => "data.example.com.s3.amazonaws.com",
54
+ :path => "/"
55
+ )
56
+ end
57
+
58
+ stub(@response_not_found).body { "" }
59
+ assert_raise Stree::Error::ResponseError do
60
+ response = @connection.request(
61
+ :get,
62
+ :host => "data.example.com.s3.amazonaws.com",
63
+ :path => "/"
64
+ )
65
+ end
66
+ end
67
+
68
+ def test_parse_params_empty
69
+ expected = ""
70
+ actual = Stree::Connection.parse_params({})
71
+ assert_equal expected, actual
72
+ end
73
+
74
+ def test_parse_params_only_interesting_params
75
+ expected = ""
76
+ actual = Stree::Connection.parse_params(:param1 => "1", :maxkeys => "2")
77
+ assert_equal expected, actual
78
+ end
79
+
80
+ def test_parse_params_remove_underscore
81
+ expected = "max-keys=100"
82
+ actual = Stree::Connection.parse_params(:max_keys => 100)
83
+ assert_equal expected, actual
84
+ end
85
+
86
+ def test_parse_params_with_and_without_values
87
+ expected = "max-keys=100&prefix"
88
+ actual = Stree::Connection.parse_params(:max_keys => 100, :prefix => nil)
89
+ assert_equal expected, actual
90
+ end
91
+
92
+ def test_headers_headers_empty
93
+ expected = {}
94
+ actual = Stree::Connection.parse_headers({})
95
+ assert_equal expected, actual
96
+ end
97
+
98
+ def test_parse_headers_only_interesting_headers
99
+ expected = {}
100
+ actual = Stree::Connection.parse_headers(
101
+ :accept => "text/*, text/html, text/html;level=1, */*",
102
+ :accept_charset => "iso-8859-2, unicode-1-1;q=0.8"
103
+ )
104
+ assert_equal expected, actual
105
+ end
106
+
107
+ def test_parse_headers_remove_underscore
108
+ expected = {
109
+ "content-type" => nil,
110
+ "x-amz-acl" => nil,
111
+ "if-modified-since" => nil,
112
+ "if-unmodified-since" => nil,
113
+ "if-match" => nil,
114
+ "if-none-match" => nil,
115
+ "content-disposition" => nil,
116
+ "content-encoding" => nil
117
+ }
118
+ actual = Stree::Connection.parse_headers(
119
+ :content_type => nil,
120
+ :x_amz_acl => nil,
121
+ :if_modified_since => nil,
122
+ :if_unmodified_since => nil,
123
+ :if_match => nil,
124
+ :if_none_match => nil,
125
+ :content_disposition => nil,
126
+ :content_encoding => nil
127
+ )
128
+ assert_equal expected, actual
129
+ end
130
+
131
+ def test_parse_headers_with_values
132
+ expected = {
133
+ "content-type" => "text/html",
134
+ "x-amz-acl" => "public-read",
135
+ "if-modified-since" => "today",
136
+ "if-unmodified-since" => "tomorrow",
137
+ "if-match" => "1234",
138
+ "if-none-match" => "1243",
139
+ "content-disposition" => "inline",
140
+ "content-encoding" => "gzip"
141
+ }
142
+ actual = Stree::Connection.parse_headers(
143
+ :content_type => "text/html",
144
+ :x_amz_acl => "public-read",
145
+ :if_modified_since => "today",
146
+ :if_unmodified_since => "tomorrow",
147
+ :if_match => "1234",
148
+ :if_none_match => "1243",
149
+ :content_disposition => "inline",
150
+ :content_encoding => "gzip"
151
+ )
152
+ assert_equal expected, actual
153
+ end
154
+
155
+ def test_parse_headers_with_range
156
+ expected = {
157
+ "range" => "bytes=0-100"
158
+ }
159
+ actual = Stree::Connection.parse_headers(
160
+ :range => 0..100
161
+ )
162
+ assert_equal expected, actual
163
+ end
164
+ end