require './test/helper'

class AwsStorageTest < Test::Unit::TestCase
  def rails_env(env)
    silence_warnings do
      Object.const_set(:Rails, stub('Rails', :env => env))
    end
  end
  
  def default_model_options(options={})
    options.reverse_merge!({
      :storage => :aws,
      :bucket => "testing",
      :s3_credentials => {
        :access_key_id => "ACCESS_KEY_ID",
        :secret_access_key => "SECRET_ACCESS_KEY"
      },
      :path => ":attachment/:basename.:extension"
    })
  end
  
  context "Parsing S3 credentials" do    
    should "get the correct credentials when RAILS_ENV is production" do
      rails_env("production")
      
      rebuild_model default_model_options(
        :s3_credentials => {
          :production => { :access_key_id => '12345' },
          :development => { :access_key_id => '54321' }          
        }
      )

      @dummy = Dummy.new      
      assert_equal({ :access_key_id => "12345" }, @dummy.avatar.s3_credentials)
    end
    
    should "get the correct credentials when RAILS_ENV is development" do
      rails_env("development")
      
      rebuild_model default_model_options(
        :s3_credentials => {
          :production => { :access_key_id => '12345' },
          :development => { :access_key_id => '54321' }          
        }
      )

      @dummy = Dummy.new          
      assert_equal({ :access_key_id => "54321" }, @dummy.avatar.s3_credentials)
    end
    
    should "return the argument if the key does not exist" do
      rails_env("not really an env")
      
      rebuild_model default_model_options(
        :s3_credentials => {
          :test => "12345"
        }
      )

      @dummy = Dummy.new
      
      assert_equal({:test => "12345"}, @dummy.avatar.s3_credentials)
    end
    
  end
  
  context "Working with endpoints" do
    should "return a correct url based on a path with default endpoint" do
      rebuild_model default_model_options
      
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")
      
      assert_match %r{^http://s3.amazonaws.com/testing/avatars/stringio.txt}, @dummy.avatar.url
    end
    
    
    should "return a correct url based on a path with custom endpoint" do
      rebuild_model default_model_options(:s3_credentials => { :endpoint => 's3-eu-west-1.amazonaws.com' })

      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")
      
      assert_match %r{^http://s3-eu-west-1.amazonaws.com/testing/avatars/stringio.txt}, @dummy.avatar.url
    end    
  end
  
  context "Working with protocols" do
    should "return a correct url with http protocol predefined" do
      rebuild_model default_model_options(:s3_protocol => 'http')
      
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")
      
      assert_match  /\Ahttp:\/\/.+/,  @dummy.avatar.url
    end
    
    
    should "return a correct url with https protocol predefined" do
      rebuild_model default_model_options(:s3_protocol => 'https')
      
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")
      
      assert_match  /\Ahttps:\/\/.+/,  @dummy.avatar.url
    end
    
    should "return a scheme-less url with empty protocol predefined" do
      rebuild_model default_model_options(:s3_protocol => '')

      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")

      assert_match  /\A\/\/.+/,  @dummy.avatar.url
    end

    should "return a correct protocol protocol based on s3_permissions" do
      rebuild_model default_model_options(:s3_permissions => :public_read)
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")      
      assert_match  /\Ahttp:\/\/.+/,  @dummy.avatar.url
      
      rebuild_model default_model_options(:s3_permissions => :private)
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")      
      assert_match  /\Ahttps:\/\/.+/,  @dummy.avatar.url
      
      rebuild_model default_model_options(:s3_permissions => :public_read_write)
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")      
      assert_match  /\Ahttps:\/\/.+/,  @dummy.avatar.url
      
      rebuild_model default_model_options(:s3_permissions => :authenticated_read)
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")      
      assert_match  /\Ahttps:\/\/.+/,  @dummy.avatar.url
      
      rebuild_model default_model_options(:s3_permissions => :bucket_owner_read)
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")      
      assert_match  /\Ahttps:\/\/.+/,  @dummy.avatar.url
      
      rebuild_model default_model_options(:s3_permissions => :bucket_owner_full_control)
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")      
      assert_match  /\Ahttps:\/\/.+/,  @dummy.avatar.url      
    end
    
    should "return a correct url when protocol explicitely defined from url" do
      rebuild_model default_model_options
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")      
      assert_match  /\Ahttps:\/\/.+/,  @dummy.avatar.url(:original, :protocol => "https")
      
      rebuild_model default_model_options(:s3_permissions => :private)
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")      
      assert_match  /\Ahttp:\/\/.+/,  @dummy.avatar.url(:original, :protocol => "http")      
    end
  end
  
  context "Working with expiring urls" do
    should "return a correct url which contains 'Expires' parameter" do
      rebuild_model default_model_options
      @dummy = Dummy.new
      @dummy.avatar = StringIO.new(".")
      
      assert_match  /\Ahttp:\/\/.+Expires.+/,  @dummy.avatar.url(:original, :expires => 1.day)      
    end
  end
  
  context "Working with default urls" do
    should "return a correct default url without data initialized" do
      rebuild_model default_model_options
      @dummy = Dummy.new
      
      assert_match  /avatars\/original\/missing/,  @dummy.avatar.url
      assert_match  /avatars\/another\/missing/,  @dummy.avatar.url(:another)
    end
    
    should "return a correct default url without data initialized and with default_url parameter set" do
      rebuild_model default_model_options(:default_url => '/:attachment/:style_missing.png')
      @dummy = Dummy.new
      
      assert_match  /avatars\/original_missing/,  @dummy.avatar.url
      assert_match  /avatars\/another_missing/,  @dummy.avatar.url(:another)
    end
    
  end
  
  
  context "An attachment that uses S3 for storage and has styles that return different file types" do
    setup do
      rebuild_model default_model_options(:styles  => { :large => ['500x500#', :jpg] })

      @dummy = Dummy.new
      @dummy.avatar = File.new(fixture_file('5k.png'), 'rb')
    end

    should "return a url containing the correct original file mime type" do
      assert_match /.+\/5k.png/, @dummy.avatar.url
    end

    should "return a url containing the correct processed file mime type" do
      assert_match /.+\/5k.jpg/, @dummy.avatar.url(:large)
    end
  end
  
  context "An attachment that uses S3 for storage and has spaces in file name" do
    setup do
      rebuild_model default_model_options(
        :styles  => { :large => ['500x500#', :jpg] },
        :restricted_characters => /[&$+,\/:;=?@<>\[\]\{\}\|\\\^~%#]/
      )
      
      @dummy = Dummy.new
      @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
    end

    should "return an unescaped version for path" do
      assert_match /.+\/spaced file\.png/, @dummy.avatar.path
    end

    should "return an escaped version for url" do
      assert_match /.+\/spaced%20file\.png/, @dummy.avatar.url
    end
  end
  
  context "An attachment with AWS storage" do
    setup do
      rebuild_model default_model_options
    end

    should "be extended by the AWS module" do
      assert Dummy.new.avatar.is_a?(Paperclip::Storage::Aws)
    end

    should "not be extended by the Filesystem module" do
      assert ! Dummy.new.avatar.is_a?(Paperclip::Storage::Filesystem)
    end
  end

  context "S3 options" do
    setup do
      @writer = mock()
      AWS::S3.expects(:new).returns(
        stub(:buckets =>
          stub(:[] =>
            stub(:objects =>
              stub(:[] => @writer)
            )
          )
        )
      )
    end

    should "be set to default" do
      rebuild_model default_model_options

      @writer.expects(:write).with do |value|
        value[:sse] == nil &&
        value[:server_side_encryption] == false &&
        value[:storage_class] == :standard &&
        value[:content_disposition] == nil &&
        value[:expires] == nil
      end

      @dummy = Dummy.new
      @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
      @dummy.save
    end

    should "be configured" do
      rebuild_model default_model_options(:s3_options => {
        :sse => 'AES256',
        :storage_class => :reduced_redundancy,
        :content_disposition => :attachment,
        :cache_control => 'max-age=86400'
      })

      @writer.expects(:write).with do |value|
        value[:sse] == nil &&
        value[:server_side_encryption] == 'AES256' &&
        value[:storage_class] == :reduced_redundancy &&
        value[:content_disposition] == :attachment &&
        value[:cache_control] == 'max-age=86400'
      end

      @dummy = Dummy.new
      @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
      @dummy.save
    end

    should "work with string-keyed hash" do
      rebuild_model default_model_options(:s3_options => {
        'cache_control' => 'max-age=86400'
      })

      @writer.expects(:write).with do |value|
        value['cache_control'] == nil &&
        value[:cache_control] == 'max-age=86400'
      end

      @dummy = Dummy.new
      @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
      @dummy.save
    end
    
    should "allow to modify options in instance" do
      rebuild_model default_model_options

      @writer.expects(:write).with do |value|
        value[:sse] == nil &&
        value[:server_side_encryption] == false &&
        value[:storage_class] == :standard &&
        value[:content_disposition] == "attachment; filename=spaced_file.png" &&
        value[:expires] == nil
      end
      
      Dummy.class_eval do
        before_save do      
          self.avatar.s3_options[:content_disposition] = "attachment; filename=#{self.avatar_file_name}"
        end        
      end
      
      @dummy = Dummy.new
      @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
      @dummy.save      
    end
  end
end
