base = File.dirname(__FILE__) require File.join(base, '../test_setup') require 'time' context 'S33r utility' do setup do @yaml_file = File.join(base, '../files/config.yaml') @for_request_method = 'PUT' @for_request_path = "/quotes/nelson" @for_request_headers = { "Content-Md5" => "c8fdb181845a4ca6b8fec737b3581d76", "Content-Type" => "text/html", "Date" => "Thu, 17 Nov 2005 18:49:58 GMT", "X-Amz-Meta-Author" => "foo@bar.com", "X-Amz-Magic" => "abracadabra" } # create broken request header hash @for_incomplete_headers = @for_request_headers.clone.delete_if do |key,value| 'Content-Type' == key or 'Date' == key end @correct_canonical_string = "PUT\nc8fdb181845a4ca6b8fec737b3581d76\n" + "text/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\n" + "x-amz-meta-author:foo@bar.com\n/quotes/nelson" @correct_signature = "jZNOcbfWmD/A/f3hSvVzXZjM2HU=" @correct_auth_header = "AWS #{Testing::ACCESS_KEY}:#{@correct_signature}" @with_invalid_bucket_name = '/badbucket' @with_invalid_bucket_name2 = 'badbucket/' authenticated_url_end = "s3.amazonaws.com/quotes/nelson?Signature=vjbyPxybdZaNmGa%2ByT272YEAiv4%3D&"+ "AWSAccessKeyId=44CF9590006BF252F707&Expires=1141889120" @correct_authenticated_url = "http://" + authenticated_url_end @correct_authenticated_url_with_ssl = "https://" + authenticated_url_end @correct_public_url = "http://s3.amazonaws.com/quotes/nelson" @correct_public_ssl_url = "https://s3.amazonaws.com/quotes/nelson" @correct_public_url_with_subdomain = "http://quotes.s3.amazonaws.com/nelson" @correct_public_url_with_ssl_and_subdomain = "https://quotes.s3.amazonaws.com/nelson" @correct_public_url_without_key = "http://s3.amazonaws.com/quotes/" @correct_logging_url = "http://s3.amazonaws.com/quotes/?logging" @correct_acl_url = "http://s3.amazonaws.com/quotes/?acl" @correct_acl_url_with_key = "http://s3.amazonaws.com/quotes/nelson?acl" @correct_public_url_with_qstring = "http://s3.amazonaws.com/quotes/?prefix=%2Fhome&max-keys=400" @url_with_escaped_key = "http://s3.amazonaws.com/quotes/some+key" @url_with_unescaped_key = "http://s3.amazonaws.com/quotes/some key" end specify 'should load config files containing standard S33r settings' do config, _ = S33r.load_config(@yaml_file) config[:access].should == Testing::ACCESS_KEY end specify 'can parse ERb embedded in config. file' do config, _ = S33r.load_config(@yaml_file) config[:secret].should == Testing::SECRET_ACCESS_KEY end specify 'can parse extra application-specific settings in config. file' do config, options = S33r.load_config(@yaml_file) options[:email_to].should == Testing::EMAIL # Make sure the 'options' section has not been included in the general config.. config.keys.should.not.include :options end specify 'should generate correct canonical strings' do generate_canonical_string(@for_request_method, @for_request_path, @for_request_headers).should == @correct_canonical_string end specify 'should generate correct signatures' do generate_signature(Testing::SECRET_ACCESS_KEY, @correct_canonical_string).should == @correct_signature end specify 'should generate correct auth headers' do generate_auth_header_value(@for_request_method, @for_request_path, @for_request_headers, Testing::ACCESS_KEY, Testing::SECRET_ACCESS_KEY).should == @correct_auth_header end specify 'should not generate auth header if bad HTTP method passed' do lambda { generate_auth_header_value('duff', nil, nil, nil, nil) }.should.raise \ MethodNotAllowed end specify 'should not generate auth header if required headers missing' do lambda { generate_auth_header_value('PUT', '/', @for_incomplete_headers, nil, nil) }.should.raise MissingRequiredHeaders end specify 'when generating auth header, should allow addition of Date and Content-Type headers' do now = Time.now fixed_headers = default_headers(@for_incomplete_headers, :date => now, :content_type => 'text/html') fixed_headers['Date'].should == now.httpdate fixed_headers['Content-Type'].should == 'text/html' end specify 'should generate correct x-amz-meta- headers from a hash' do meta = {'myname' => 'elliot', 'myage' => 36} headers = metadata_headers(meta) headers.should.include 'x-amz-meta-myname' headers.should.include 'x-amz-meta-myage' headers['x-amz-meta-myname'].should == 'elliot' headers['x-amz-meta-myage'].should == '36' end specify 'should not generate canned ACL header if invalid canned ACL supplied' do lambda { canned_acl_header('duff') }.should.raise \ UnsupportedCannedACL end specify 'should correctly add canned ACL headers' do new_headers = canned_acl_header('private') new_headers.should.have(1).keys new_headers.keys.should.include 'x-amz-acl' new_headers['x-amz-acl'].should == 'private' end specify 'should set sensible defaults for missing Content-Type and Date headers' do fixed_headers = default_headers(@for_incomplete_headers) fixed_headers['Content-Type'].should == '' fixed_headers.include?('Date').should.not.be nil end specify 'should default to text/plain mimetype for unknown file types' do guess_mime_type('hello.madeup').should ==('text/plain') end specify 'should generate correct Content-Disposition, Content-Type and Content-Transfer-Encoding headers' do headers = content_headers('text/plain', 'download.jpg', true) headers['Content-Type'].should == 'text/plain' headers['Content-Disposition'].should == "attachment; filename=download.jpg" headers = content_headers('image/jpeg', '/home/you/me.jpg', true) headers['Content-Type'].should == 'image/jpeg' headers['Content-Transfer-Encoding'].should == 'binary' headers['Content-Disposition'].should == "attachment; filename=me.jpg" end specify 'should recognise invalid bucket names' do lambda { bucket_name_valid?(@with_invalid_bucket_name) }.should.raise \ MalformedBucketName lambda { bucket_name_valid?(@with_invalid_bucket_name2) }.should.raise \ MalformedBucketName end specify 'should return empty string if generating querystring with no key/value pairs' do generate_querystring({}).should == '' end specify 'should correctly format querystring key/value pairs' do generate_querystring({'message' => 'Hello world', 'id' => 1, 'page' => '[2,4]'}).should == \ 'message=Hello+world&id=1&page=%5B2%2C4%5D' end specify 'should allow symbols as names for querystring variables when generating querystrings' do generate_querystring({ :prefix => '/home/ell' }).should ==('prefix=%2Fhome%2Fell') end specify 'should convert integers to strings when generating querystrings' do generate_querystring({ 'max-keys' => 400 }).should ==('max-keys=400') end specify 'should generate URLs with authentication parameters' do s3_authenticated_url(Testing::ACCESS_KEY, Testing::SECRET_ACCESS_KEY, :bucket => 'quotes', \ :key => 'nelson', :expires => 1141889120).should == @correct_authenticated_url end specify 'should be able to pass credentials as an option to the basic URL generator' do s3_url(:access => Testing::ACCESS_KEY, :secret => Testing::SECRET_ACCESS_KEY, \ :authenticated => true, :bucket => 'quotes', :key => 'nelson', \ :expires => 1141889120).should == @correct_authenticated_url end specify 'should generate correct public URLs' do s3_public_url(:bucket => 'quotes', :key => 'nelson').should == @correct_public_url end specify 'should correctly append querystring variables' do s3_url(:bucket => 'quotes', :querystring => {'max-keys' => 400, 'prefix' => '/home'}).should == \ @correct_public_url_with_qstring end specify 'should generate HTTPS URLs' do s3_public_url(:bucket => 'quotes', :key => 'nelson', :use_ssl => true).should == @correct_public_ssl_url end specify 'should generate public URLs with bucket name as subdomain' do s3_public_url(:bucket => 'quotes', :key => 'nelson', :subdomain => true).should == \ @correct_public_url_with_subdomain s3_public_url(:bucket => 'quotes', :key => 'nelson', :subdomain => true, :use_ssl => true).should == \ @correct_public_url_with_ssl_and_subdomain end specify 'should generate URLs for buckets without keys' do s3_public_url(:bucket => 'quotes').should == @correct_public_url_without_key end specify 'should generate logging URLs' do s3_url(:bucket => 'quotes', :logging => true).should == @correct_logging_url end specify 'should generate ACL URLs' do s3_url(:bucket => 'quotes', :acl => true).should == @correct_acl_url s3_url(:bucket => 'quotes', :key => 'nelson', :acl => true).should == @correct_acl_url_with_key end specify 'should turn off subdomain if authenticated URL' do s3_url(:access => Testing::ACCESS_KEY, :secret => Testing::SECRET_ACCESS_KEY, \ :authenticated => true, :bucket => 'quotes', :key => 'nelson', :subdomain => true, \ :expires => 1141889120).should == @correct_authenticated_url end specify 'should turn off subdomain if generating SSL URL' do s3_url(:bucket => 'quotes', :key => 'nelson', :use_ssl => true, \ :subdomain => true).should == @correct_public_ssl_url end specify 'should produce correct default expiry time if none specified, or time 50 years \ from now for :far_flung_future' do class Time alias :old_to_i :to_i # N.B. the number below is the seconds since the epoch of now, defined above def to_i ; 1168349580 ; end end # Default to DEFAULT_EXPIRY_SECS from now expected_expiry = Time.now.to_i + DEFAULT_EXPIRY_SECS expires = S33r.parse_expiry expires.should == expected_expiry expires.should_be_kind_of Integer # If :far_flung_future is passed, default to 20 years from now. expected_expiry = Time.now.to_i + (60 * 60 * 24 * 365.25 * 20).to_i expires = S33r.parse_expiry(:far_flung_future) expires.should == expected_expiry expires.should_be_kind_of Integer class Time remove_method(:to_i) alias :to_i :old_to_i end end specify 'should correctly set expiry from a datetime string' do datetime_str = '9th January 2007 13:33' expected_expiry = Time.parse(datetime_str).to_i expires = S33r.parse_expiry(datetime_str) expires.should == expected_expiry expires.should_be_kind_of Integer end specify 'should provide a method for converting string keys of a hash into symbols' do h = {'access' => Testing::ACCESS_KEY, 'secret' => Testing::SECRET_ACCESS_KEY} symbolised = S33r.keys_to_symbols(h) symbolised.should ==({:access => Testing::ACCESS_KEY, :secret => Testing::SECRET_ACCESS_KEY}) end specify 'can optionally escape keys passed in paths' do s3_url(:bucket => 'quotes', :key => 'some key', :escape => true).should == @url_with_escaped_key s3_url(:bucket => 'quotes', :key => 'some key').should == @url_with_unescaped_key end end