sndacs 0.0.1
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/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +33 -0
- data/LICENSE +21 -0
- data/README.rdoc +80 -0
- data/Rakefile +6 -0
- data/lib/sndacs/bucket.rb +179 -0
- data/lib/sndacs/buckets_extension.rb +26 -0
- data/lib/sndacs/connection.rb +225 -0
- data/lib/sndacs/exceptions.rb +110 -0
- data/lib/sndacs/object.rb +253 -0
- data/lib/sndacs/objects_extension.rb +37 -0
- data/lib/sndacs/parser.rb +54 -0
- data/lib/sndacs/request.rb +31 -0
- data/lib/sndacs/service.rb +95 -0
- data/lib/sndacs/signature.rb +243 -0
- data/lib/sndacs/version.rb +3 -0
- data/lib/sndacs.rb +27 -0
- data/sndacs.gemspec +29 -0
- data/spec/sndacs/service_spec.rb +25 -0
- data/spec/spec_helper.rb +5 -0
- data/test/bucket_test.rb +215 -0
- data/test/connection_test.rb +214 -0
- data/test/object_test.rb +205 -0
- data/test/service_test.rb +111 -0
- data/test/signature_test.rb +205 -0
- data/test/test_helper.rb +3 -0
- metadata +128 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
module Sndacs
|
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
|
+
# ==== Options
|
16
|
+
# * <tt>:host</tt> - Hostname
|
17
|
+
# * <tt>:request</tt> - Net::HTTPRequest object with correct
|
18
|
+
# headers
|
19
|
+
# * <tt>:access_key_id</tt> - Access key id
|
20
|
+
# * <tt>:secret_access_key</tt> - Secret access key
|
21
|
+
#
|
22
|
+
# ==== Returns
|
23
|
+
# Generated signature string for given hostname and request
|
24
|
+
def self.generate(options)
|
25
|
+
request = options[:request]
|
26
|
+
access_key_id = options[:access_key_id]
|
27
|
+
|
28
|
+
options.merge!(:headers => request, :method => request.method, :resource => request.path)
|
29
|
+
|
30
|
+
signature = canonicalized_signature(options)
|
31
|
+
|
32
|
+
"SNDA #{access_key_id}:#{signature}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generates temporary URL signature for given resource
|
36
|
+
#
|
37
|
+
# ==== Options
|
38
|
+
# * <tt>:bucket</tt> - Bucket in which the resource resides
|
39
|
+
# * <tt>:resource</tt> - Path to the resouce you want to create
|
40
|
+
# a temporary link to
|
41
|
+
# * <tt>:secret_access_key</tt> - Secret access key
|
42
|
+
# * <tt>:expires_at</tt> - Unix time stamp of when the resouce
|
43
|
+
# link will expire
|
44
|
+
# * <tt>:method</tt> - HTTP request method you want to use on
|
45
|
+
# the resource, defaults to GET
|
46
|
+
# * <tt>:headers</tt> - Any additional HTTP headers you intend
|
47
|
+
# to use when requesting the resource
|
48
|
+
def self.generate_temporary_url_signature(options)
|
49
|
+
bucket = options[:bucket]
|
50
|
+
resource = options[:resource]
|
51
|
+
secret_access_key = options[:secret_access_key]
|
52
|
+
expires = options[:expires_at]
|
53
|
+
|
54
|
+
headers = options[:headers] || {}
|
55
|
+
headers.merge!("date" => expires.to_i.to_s)
|
56
|
+
|
57
|
+
options.merge!(:resource => "/#{bucket}/#{URI.escape(resource)}",
|
58
|
+
:method => options[:method] || :get,
|
59
|
+
:headers => headers)
|
60
|
+
signature = canonicalized_signature(options)
|
61
|
+
|
62
|
+
CGI.escape(signature)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Generates temporary URL for given resource
|
66
|
+
#
|
67
|
+
# ==== Options
|
68
|
+
# * <tt>:bucket</tt> - Bucket in which the resource resides
|
69
|
+
# * <tt>:resource</tt> - Path to the resouce you want to create
|
70
|
+
# a temporary link to
|
71
|
+
# * <tt>:access_key</tt> - Access key
|
72
|
+
# * <tt>:secret_access_key</tt> - Secret access key
|
73
|
+
# * <tt>:expires_at</tt> - Unix time stamp of when the resouce
|
74
|
+
# link will expire
|
75
|
+
# * <tt>:method</tt> - HTTP request method you want to use on
|
76
|
+
# the resource, defaults to GET
|
77
|
+
# * <tt>:headers</tt> - Any additional HTTP headers you intend
|
78
|
+
# to use when requesting the resource
|
79
|
+
def self.generate_temporary_url(options)
|
80
|
+
bucket = options[:bucket]
|
81
|
+
resource = options[:resource]
|
82
|
+
access_key = options[:access_key]
|
83
|
+
expires = options[:expires_at].to_i
|
84
|
+
signature = generate_temporary_url_signature(options)
|
85
|
+
|
86
|
+
url = "http://#{S3::HOST}/#{bucket}/#{resource}"
|
87
|
+
url << "?AWSAccessKeyId=#{access_key}"
|
88
|
+
url << "&Expires=#{expires}"
|
89
|
+
url << "&Signature=#{signature}"
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def self.canonicalized_signature(options)
|
95
|
+
headers = options[:headers] || {}
|
96
|
+
host = options[:host] || ""
|
97
|
+
resource = options[:resource]
|
98
|
+
access_key_id = options[:access_key_id]
|
99
|
+
secret_access_key = options[:secret_access_key]
|
100
|
+
|
101
|
+
http_verb = options[:method].to_s.upcase
|
102
|
+
content_md5 = headers["content-md5"] || ""
|
103
|
+
content_type = headers["content-type"] || ""
|
104
|
+
date = headers["x-snda-date"].nil? ? headers["date"] : ""
|
105
|
+
canonicalized_resource = canonicalized_resource(host, resource)
|
106
|
+
canonicalized_snda_headers = canonicalized_snda_headers(headers)
|
107
|
+
|
108
|
+
string_to_sign = ""
|
109
|
+
string_to_sign << http_verb
|
110
|
+
string_to_sign << "\n"
|
111
|
+
string_to_sign << content_md5
|
112
|
+
string_to_sign << "\n"
|
113
|
+
string_to_sign << content_type
|
114
|
+
string_to_sign << "\n"
|
115
|
+
string_to_sign << date
|
116
|
+
string_to_sign << "\n"
|
117
|
+
string_to_sign << canonicalized_snda_headers
|
118
|
+
string_to_sign << canonicalized_resource
|
119
|
+
|
120
|
+
digest = OpenSSL::Digest::Digest.new("sha1")
|
121
|
+
hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
|
122
|
+
base64 = Base64.encode64(hmac)
|
123
|
+
base64.chomp
|
124
|
+
end
|
125
|
+
|
126
|
+
# Helper method for extracting header fields from Net::HTTPRequest
|
127
|
+
# and preparing them for singing in #generate method
|
128
|
+
#
|
129
|
+
# ==== Parameters
|
130
|
+
# * <tt>request</tt> - Net::HTTPRequest object with header fields
|
131
|
+
# filled in
|
132
|
+
#
|
133
|
+
# ==== Returns
|
134
|
+
# String containing interesting header fields in suitable order
|
135
|
+
# and form
|
136
|
+
def self.canonicalized_snda_headers(request)
|
137
|
+
headers = []
|
138
|
+
|
139
|
+
# 1. Convert each HTTP header name to lower-case. For example,
|
140
|
+
# "X-Amz-Date" becomes "x-snda-date".
|
141
|
+
request.each { |key, value| headers << [key.downcase, value] if key =~ /\Ax-snda-/io }
|
142
|
+
#=> [["c", 0], ["a", 1], ["a", 2], ["b", 3]]
|
143
|
+
|
144
|
+
# 2. Sort the collection of headers lexicographically by header
|
145
|
+
# name.
|
146
|
+
headers.sort!
|
147
|
+
#=> [["a", 1], ["a", 2], ["b", 3], ["c", 0]]
|
148
|
+
|
149
|
+
# 3. Combine header fields with the same name into one
|
150
|
+
# "header-name:comma-separated-value-list" pair as prescribed by
|
151
|
+
# RFC 2616, section 4.2, without any white-space between
|
152
|
+
# values. For example, the two metadata headers
|
153
|
+
# "x-snda-meta-username: fred" and "x-snda-meta-username: barney"
|
154
|
+
# would be combined into the single header "x-snda-meta-username:
|
155
|
+
# fred,barney".
|
156
|
+
combined_headers = headers.inject([]) do |new_headers, header|
|
157
|
+
existing_header = new_headers.find { |h| h.first == header.first }
|
158
|
+
if existing_header
|
159
|
+
existing_header.last << ",#{header.last}"
|
160
|
+
else
|
161
|
+
new_headers << header
|
162
|
+
end
|
163
|
+
end
|
164
|
+
#=> [["a", "1,2"], ["b", "3"], ["c", "0"]]
|
165
|
+
|
166
|
+
# 4. "Un-fold" long headers that span multiple lines (as allowed
|
167
|
+
# by RFC 2616, section 4.2) by replacing the folding white-space
|
168
|
+
# (including new-line) by a single space.
|
169
|
+
unfolded_headers = combined_headers.map do |header|
|
170
|
+
key = header.first
|
171
|
+
value = header.last
|
172
|
+
value.gsub!(/\s+/, " ")
|
173
|
+
[key, value]
|
174
|
+
end
|
175
|
+
|
176
|
+
# 5. Trim any white-space around the colon in the header. For
|
177
|
+
# example, the header "x-snda-meta-username: fred,barney" would
|
178
|
+
# become "x-snda-meta-username:fred,barney"
|
179
|
+
joined_headers = unfolded_headers.map do |header|
|
180
|
+
key = header.first.strip
|
181
|
+
value = header.last.strip
|
182
|
+
"#{key}:#{value}"
|
183
|
+
end
|
184
|
+
|
185
|
+
# 6. Finally, append a new-line (U+000A) to each canonicalized
|
186
|
+
# header in the resulting list. Construct the
|
187
|
+
# CanonicalizedResource element by concatenating all headers in
|
188
|
+
# this list into a single string.
|
189
|
+
joined_headers << "" unless joined_headers.empty?
|
190
|
+
joined_headers.join("\n")
|
191
|
+
end
|
192
|
+
|
193
|
+
# Helper methods for extracting caninocalized resource address
|
194
|
+
#
|
195
|
+
# ==== Parameters
|
196
|
+
# * <tt>host</tt> - Hostname
|
197
|
+
# * <tt>request</tt> - Net::HTTPRequest object with header fields
|
198
|
+
# filled in
|
199
|
+
#
|
200
|
+
# ==== Returns
|
201
|
+
# String containing extracted canonicalized resource
|
202
|
+
def self.canonicalized_resource(host, resource)
|
203
|
+
# 1. Start with the empty string ("").
|
204
|
+
string = ""
|
205
|
+
|
206
|
+
# 2. If the request specifies a bucket using the HTTP Host
|
207
|
+
# header (virtual hosted-style), append the bucket name preceded
|
208
|
+
# by a "/" (e.g., "/bucketname"). For path-style requests and
|
209
|
+
# requests that don't address a bucket, do nothing. For more
|
210
|
+
# information on virtual hosted-style requests, see Virtual
|
211
|
+
# Hosting of Buckets.
|
212
|
+
bucket_name = host.sub(/\.?storage\.grandcloud\.cn\Z/, "")
|
213
|
+
string << "/#{bucket_name}" unless bucket_name.empty?
|
214
|
+
|
215
|
+
# 3. Append the path part of the un-decoded HTTP Request-URI,
|
216
|
+
# up-to but not including the query string.
|
217
|
+
uri = URI.parse(resource)
|
218
|
+
string << uri.path
|
219
|
+
|
220
|
+
# 4. If the request addresses a sub-resource, like ?location,
|
221
|
+
# ?acl, or ?torrent, append the sub-resource including question
|
222
|
+
# mark.
|
223
|
+
sub_resources = [
|
224
|
+
"acl",
|
225
|
+
"location",
|
226
|
+
"logging",
|
227
|
+
"notification",
|
228
|
+
"partNumber",
|
229
|
+
"policy",
|
230
|
+
"requestPayment",
|
231
|
+
"torrent",
|
232
|
+
"uploadId",
|
233
|
+
"uploads",
|
234
|
+
"versionId",
|
235
|
+
"versioning",
|
236
|
+
"versions",
|
237
|
+
"website"
|
238
|
+
]
|
239
|
+
string << "?#{$1}" if uri.query =~ /&?(#{sub_resources.join("|")})(?:&|=|\Z)/
|
240
|
+
string
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
data/lib/sndacs.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "cgi"
|
3
|
+
require "digest/md5"
|
4
|
+
require "forwardable"
|
5
|
+
require "net/http"
|
6
|
+
require "net/https"
|
7
|
+
require "openssl"
|
8
|
+
require "rexml/document"
|
9
|
+
require "time"
|
10
|
+
|
11
|
+
require "proxies"
|
12
|
+
require "sndacs/objects_extension"
|
13
|
+
require "sndacs/buckets_extension"
|
14
|
+
require "sndacs/parser"
|
15
|
+
require "sndacs/bucket"
|
16
|
+
require "sndacs/connection"
|
17
|
+
require "sndacs/exceptions"
|
18
|
+
require "sndacs/object"
|
19
|
+
require "sndacs/request"
|
20
|
+
require "sndacs/service"
|
21
|
+
require "sndacs/signature"
|
22
|
+
require "sndacs/version"
|
23
|
+
|
24
|
+
module Sndacs
|
25
|
+
# Default (and only) host serving S3 stuff
|
26
|
+
HOST = "storage.grandcloud.cn"
|
27
|
+
end
|
data/sndacs.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Load version requiring the canonical "s3/version", otherwise Ruby will think
|
4
|
+
# is a different file and complaint about a double declaration of S3::VERSION.
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
6
|
+
require "sndacs/version"
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = "sndacs"
|
10
|
+
s.version = Sndacs::VERSION
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.authors = ["LI Daobing"]
|
13
|
+
s.email = ["lidaobing@snda.com"]
|
14
|
+
s.homepage = "https://github.com/grandcloud/sndacs-ruby"
|
15
|
+
s.summary = "Library for accessing SNDA Cloud Storage objects and buckets"
|
16
|
+
s.description = "sndacs library provides access to SNDA Cloud Storage."
|
17
|
+
|
18
|
+
s.required_rubygems_version = ">= 1.3.6"
|
19
|
+
|
20
|
+
s.add_dependency "proxies", "~> 0.2.0"
|
21
|
+
s.add_development_dependency "test-unit", ">= 2.0"
|
22
|
+
s.add_development_dependency "mocha"
|
23
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
24
|
+
s.add_development_dependency "rspec", "~> 2.0"
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
28
|
+
s.require_path = "lib"
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'sndacs/service'
|
4
|
+
|
5
|
+
module Sndacs
|
6
|
+
describe Service do
|
7
|
+
context "#buckets" do
|
8
|
+
context "when buckets is empty" do
|
9
|
+
it "should works" do
|
10
|
+
@service_empty_buckets_list = Sndacs::Service.new(
|
11
|
+
:access_key_id => "12345678901234567890",
|
12
|
+
:secret_access_key => "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDF"
|
13
|
+
)
|
14
|
+
@response_empty_buckets_list = Net::HTTPOK.new("1.1", "200", "OK")
|
15
|
+
@service_empty_buckets_list.should_receive(:service_request).and_return(@response_empty_buckets_list)
|
16
|
+
@response_empty_buckets_list.should_receive(:body).and_return(@buckets_empty_list_body)
|
17
|
+
@buckets_empty_list_body = <<-EOEmptyBuckets
|
18
|
+
<?xml version="1.0" encoding="UTF-8"?>\n<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <Owner> <ID>123u1odhkhfoadf</ID> <DisplayName>JohnDoe</DisplayName> </Owner> <Buckets> </Buckets> </ListAllMyBucketsResult>
|
19
|
+
EOEmptyBuckets
|
20
|
+
@service_empty_buckets_list.buckets.should == []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/test/bucket_test.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class BucketTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@bucket_vhost = S3::Bucket.send(:new, nil, "Data-Bucket")
|
6
|
+
@bucket_path = S3::Bucket.send(:new, nil, "Data_Bucket")
|
7
|
+
@bucket = @bucket_vhost
|
8
|
+
|
9
|
+
@bucket_location = "EU"
|
10
|
+
@bucket_location_body = <<-EOLocation
|
11
|
+
<?xml version="1.0" encoding="UTF-8"?>\n<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">EU</LocationConstraint>
|
12
|
+
EOLocation
|
13
|
+
|
14
|
+
@response_location = Net::HTTPOK.new("1.1", "200", "OK")
|
15
|
+
@response_location.stubs(:body).returns(@bucket_location_body)
|
16
|
+
|
17
|
+
@bucket_owned_by_you_body = <<-EOOwnedByYou
|
18
|
+
<?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>
|
19
|
+
EOOwnedByYou
|
20
|
+
|
21
|
+
@reponse_owned_by_you = Net::HTTPConflict.new("1.1", "409", "Conflict")
|
22
|
+
@reponse_owned_by_you.stubs(:body).returns(@bucket_owned_by_you_body)
|
23
|
+
|
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
|
+
@reponse_already_exists = Net::HTTPConflict.new("1.1", "409", "Conflict")
|
29
|
+
@response_already_exists.stubs(:body).returns(@bucket_already_exists_body)
|
30
|
+
|
31
|
+
@objects_list_empty = []
|
32
|
+
@objects_list = [
|
33
|
+
S3::Object.send(:new, @bucket, :key => "obj1"),
|
34
|
+
S3::Object.send(:new, @bucket, :key => "obj2")
|
35
|
+
]
|
36
|
+
|
37
|
+
@response_objects_list_empty_body = <<-EOEmpty
|
38
|
+
<?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>
|
39
|
+
EOEmpty
|
40
|
+
|
41
|
+
@response_objects_list_empty = Net::HTTPOK.new("1.1", "200", "OK")
|
42
|
+
@response_objects_list_empty.stubs(:body).returns(@response_objects_list_empty_body)
|
43
|
+
|
44
|
+
@response_objects_list_body = <<-EOObjects
|
45
|
+
<?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>"99519cdf14c255e580e1b7bca85a458c"</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>"99519cdf14c255e586e1b12bca85a458c"</ETag> <Size>179</Size> <Owner> <ID>df864aeb6f42be43f1d9e60aaabe3f17e247b037a4b79d1cfe36c4deaec67205</ID> <DisplayName>owner</DisplayName> </Owner> <StorageClass>STANDARD</StorageClass> </Contents> </ListBucketResult>
|
46
|
+
EOObjects
|
47
|
+
|
48
|
+
@response_objects_list = Net::HTTPOK.new("1.1", "200", "OK")
|
49
|
+
@response_objects_list.stubs(:body).returns(@response_objects_list_body)
|
50
|
+
end
|
51
|
+
|
52
|
+
test "name valid" do
|
53
|
+
assert_raise ArgumentError do S3::Bucket.send(:new, nil, "") end # should not be valid with empty name
|
54
|
+
assert_raise ArgumentError do S3::Bucket.send(:new, nil, "10.0.0.1") end # should not be valid with IP as name
|
55
|
+
assert_raise ArgumentError do S3::Bucket.send(:new, nil, "as") end # should not be valid with name shorter than 3 characters
|
56
|
+
assert_raise ArgumentError do S3::Bucket.send(:new, nil, "a" * 256) end # should not be valid with name longer than 255 characters
|
57
|
+
assert_raise ArgumentError do S3::Bucket.send(:new, nil, ".asdf") end # should not allow special characters as first character
|
58
|
+
assert_raise ArgumentError do S3::Bucket.send(:new, nil, "-asdf") end # should not allow special characters as first character
|
59
|
+
assert_raise ArgumentError do S3::Bucket.send(:new, nil, "_asdf") end # should not allow special characters as first character
|
60
|
+
|
61
|
+
assert_nothing_raised do
|
62
|
+
S3::Bucket.send(:new, nil, "a-a-")
|
63
|
+
S3::Bucket.send(:new, nil, "a.a.")
|
64
|
+
S3::Bucket.send(:new, nil, "a_a_")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
test "path prefix" do
|
69
|
+
expected = ""
|
70
|
+
actual = @bucket_vhost.path_prefix
|
71
|
+
assert_equal expected, actual
|
72
|
+
|
73
|
+
expected = "Data_Bucket/"
|
74
|
+
actual = @bucket_path.path_prefix
|
75
|
+
assert_equal expected, actual
|
76
|
+
end
|
77
|
+
|
78
|
+
test "host" do
|
79
|
+
expected = "Data-Bucket.s3.amazonaws.com"
|
80
|
+
actual = @bucket_vhost.host
|
81
|
+
assert_equal expected, actual
|
82
|
+
|
83
|
+
expected = "s3.amazonaws.com"
|
84
|
+
actual = @bucket_path.host
|
85
|
+
assert_equal expected, actual
|
86
|
+
end
|
87
|
+
|
88
|
+
test "vhost" do
|
89
|
+
assert @bucket_vhost.vhost?
|
90
|
+
assert ! @bucket_path.vhost?
|
91
|
+
end
|
92
|
+
|
93
|
+
test "exists" do
|
94
|
+
@bucket.expects(:retrieve).returns(@bucket_vhost)
|
95
|
+
assert @bucket.exists?
|
96
|
+
|
97
|
+
@bucket.expects(:retrieve).raises(S3::Error::NoSuchBucket.new(nil, nil))
|
98
|
+
assert ! @bucket.exists?
|
99
|
+
end
|
100
|
+
|
101
|
+
test "location and parse location" do
|
102
|
+
@bucket.expects(:bucket_request).with(:get, { :params => { :location => nil } }).returns(@response_location)
|
103
|
+
|
104
|
+
expected = @bucket_location
|
105
|
+
actual = @bucket.location
|
106
|
+
assert_equal expected, actual
|
107
|
+
|
108
|
+
@bucket.stubs(:bucket_request).with(:get, { :params => { :location => nil } })
|
109
|
+
actual = @bucket.location
|
110
|
+
assert_equal expected, actual
|
111
|
+
end
|
112
|
+
|
113
|
+
test "save" do
|
114
|
+
@bucket.expects(:bucket_request).with(:put, { :headers => {} })
|
115
|
+
assert @bucket.save
|
116
|
+
# mock ensures that bucket_request was called
|
117
|
+
end
|
118
|
+
|
119
|
+
test "save failure owned by you" do
|
120
|
+
@bucket.expects(:bucket_request).with(:put, { :headers => {} }).raises(S3::Error::BucketAlreadyOwnedByYou.new(409, @response_owned_by_you))
|
121
|
+
assert_raise S3::Error::BucketAlreadyOwnedByYou do
|
122
|
+
@bucket.save
|
123
|
+
end
|
124
|
+
|
125
|
+
@bucket.expects(:bucket_request).with(:put, { :headers => {} }).raises(S3::Error::BucketAlreadyExists.new(409, @response_already_exists))
|
126
|
+
assert_raise S3::Error::BucketAlreadyExists do
|
127
|
+
@bucket.save
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
test "objects" do
|
132
|
+
@bucket.expects(:list_bucket).returns(@objects_list_empty)
|
133
|
+
expected = @objects_list_empty
|
134
|
+
actual = @bucket.objects
|
135
|
+
assert_equal expected, actual
|
136
|
+
|
137
|
+
@bucket.stubs(:list_bucket).returns(@objects_list_empty)
|
138
|
+
actual = @bucket.objects
|
139
|
+
assert_equal expected, actual
|
140
|
+
|
141
|
+
@bucket.stubs(:list_bucket).returns(@objects_list)
|
142
|
+
|
143
|
+
expected = @objects_list
|
144
|
+
actual = @bucket.objects
|
145
|
+
assert_equal expected, actual
|
146
|
+
end
|
147
|
+
|
148
|
+
test "list bucket and parse objects" do
|
149
|
+
@bucket.expects(:bucket_request).with(:get, :params => { :test=>true }).returns(@response_objects_list_empty)
|
150
|
+
expected = @objects_list_empty
|
151
|
+
actual = @bucket.objects.find_all(:test => true)
|
152
|
+
assert_equal expected, actual
|
153
|
+
|
154
|
+
@bucket.expects(:bucket_request).with(:get, :params => { :test => true }).returns(@response_objects_list)
|
155
|
+
expected = @objects_list
|
156
|
+
actual = @bucket.objects.find_all(:test => true)
|
157
|
+
assert_equal expected, actual
|
158
|
+
end
|
159
|
+
|
160
|
+
test "destroy" do
|
161
|
+
@bucket.expects(:bucket_request).with(:delete)
|
162
|
+
assert @bucket.destroy
|
163
|
+
end
|
164
|
+
|
165
|
+
test "objects build" do
|
166
|
+
@bucket.stubs(:bucket_request)
|
167
|
+
|
168
|
+
expected = "object_name"
|
169
|
+
actual = @bucket.objects.build("object_name")
|
170
|
+
assert_kind_of S3::Object, actual
|
171
|
+
assert_equal expected, actual.key
|
172
|
+
end
|
173
|
+
|
174
|
+
test "objects find first" do
|
175
|
+
assert_nothing_raised do
|
176
|
+
S3::Object.any_instance.stubs(:retrieve).returns(S3::Object.send(:new, nil, :key => "obj2"))
|
177
|
+
expected = "obj2"
|
178
|
+
actual = @bucket.objects.find_first("obj2")
|
179
|
+
assert_equal "obj2", actual.key
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
test "objects find first fail" do
|
184
|
+
assert_raise S3::Error::NoSuchKey do
|
185
|
+
S3::Object.any_instance.stubs(:retrieve).raises(S3::Error::NoSuchKey.new(404, nil))
|
186
|
+
@bucket.objects.find_first("obj3")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
test "objects find all on empty list" do
|
191
|
+
@bucket.stubs(:list_bucket).returns(@objects_list_empty)
|
192
|
+
assert_nothing_raised do
|
193
|
+
expected = @objects_list_empty
|
194
|
+
actual = @bucket.objects.find_all
|
195
|
+
assert_equal expected, actual
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
test "objects find all" do
|
200
|
+
@bucket.stubs(:list_bucket).returns(@objects_list)
|
201
|
+
assert_nothing_raised do
|
202
|
+
expected = @objects_list
|
203
|
+
actual = @bucket.objects.find_all
|
204
|
+
assert_equal expected, actual
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
test "objects destroy all" do
|
209
|
+
@bucket.stubs(:list_bucket).returns(@objects_list)
|
210
|
+
@bucket.objects.each do |obj|
|
211
|
+
obj.expects(:destroy)
|
212
|
+
end
|
213
|
+
@bucket.objects.destroy_all
|
214
|
+
end
|
215
|
+
end
|