# encoding: UTF-8

require File.expand_path('../../test_helper', __FILE__)
require 'benchmark'

describe "Crypt" do
  Crypt = Spontaneous::Crypt

  before do
    Crypt.default_version
  end

  describe "versions" do
    it "be listable" do
      Crypt.versions.must_equal [Crypt::Version::Fake, Crypt::Version::SHALegacy, Crypt::Version::BCrypt201301]
    end

    it "choose the most up-to-date as the current" do
      Crypt.current.must_equal Crypt::Version::BCrypt201301
    end

    it "be directly loadable" do
      Crypt.version(201102).must_equal Crypt::Version::SHALegacy
    end

    it "be configurable" do
      Crypt.force_version(0)
      Crypt.current.must_equal Crypt::Version::Fake
    end
  end

  describe "fake version" do
    before do
      @pass = "abcdef"
    end
    it "have a version of 0" do
      Crypt::Version::SHALegacy.version.must_equal 201102
    end
    it "be creatable from a pasword" do
      hash = Crypt::Version::Fake.create(@pass)
      assert Crypt.new(@pass, hash).valid?
    end
    it "not report that it should be upgraded" do
      hash = Crypt::Version::Fake.create(@pass)
      auth = Crypt.new(@pass, hash)
      assert auth.valid?
      refute auth.outdated?
      refute auth.needs_upgrade?
    end
  end

  describe "legacy version" do
    before do
      @pass = "abcdef"
      @salt = "aaaaaa"
      @sha  = Crypt::Version::SHALegacy.sha(@salt, @pass)
    end

    it "have a version of 201102" do
      Crypt::Version::SHALegacy.version.must_equal 201102
    end

    it "be creatable from a pasword & salt" do
      hash = Crypt::Version::SHALegacy.create(@sha, @salt)
      hash.must_match /^201102%aaaaaa:\$2a\$13\$/
      assert Crypt.new(@pass, hash).valid?
    end

    it "report that it should be upgraded" do
      hash = Crypt::Version::SHALegacy.create(@sha, @salt)
      auth = Crypt.new(@pass, hash)
      assert auth.valid?
      assert auth.outdated?
      assert auth.needs_upgrade?
    end

    it "upgrade to the current implementation" do
      hash = Crypt::Version::SHALegacy.create(@sha, @salt)
      auth = Crypt.new(@pass, hash)
      new_hash  = auth.upgrade
      new_auth = Crypt.new(@pass, new_hash)
      refute new_auth.outdated?
      assert new_auth.valid?
    end
  end

  describe "password creation" do
    before do
      @password = "abcdefg"
    end

    it "be verifiable" do
      hash = Crypt::hash_password(@password)
      assert Crypt.new(@password, hash).valid?
    end

    it "use the latest version" do
      hash = Crypt::hash_password(@password)
      version, _ = Crypt.version_split(hash)
      version.must_equal Crypt.current.version
    end
  end

  describe "users" do
    before do
      S::Permissions::User.delete
      @pass = "abcdefghijklm"
      @attrs = {:login => "test", :email => "test@example.com", :name => "Test User", :password => @pass}
      @user = S::Permissions::User.new(@attrs)
      @user.save
    end

    after do
      S::Permissions::User.delete
    end

    it "use the current crypt implementation to hash their passwords" do
      hash = @user.crypted_password

      auth = Crypt.new(@pass, hash)
      assert auth.valid?
    end

    it "authenticate successfully" do
      result = Spontaneous::Permissions::User.authenticate(@attrs[:login], @pass)
      result.must_be_instance_of S::Permissions::AccessKey
    end

    it "fail to authenticate with incorrect password" do
      result = Spontaneous::Permissions::User.authenticate(@attrs[:login], @pass + "x")
      result.must_be_nil
    end

    it "transparently upgrade the auth version if outdated" do
      salt = "aaaaaaaa"
      sha  = Crypt::Version::SHALegacy.sha(salt, @pass)
      hash = Crypt::Version::SHALegacy.create(sha, salt)
      @user.model.filter(:id => @user.id).update(:crypted_password => hash)
      @user.reload
      auth = Crypt.new(@pass, @user.crypted_password)
      assert auth.outdated?, "Auth should be outdated"
      result = Spontaneous::Permissions::User.authenticate(@attrs[:login], @pass)
      result.must_be_instance_of S::Permissions::AccessKey
      @user.reload
      auth = Crypt.new(@pass, @user.crypted_password)
      assert !auth.outdated?, "Auth should have upgraded"
      result = Spontaneous::Permissions::User.authenticate(@attrs[:login], @pass)
      result.must_be_instance_of S::Permissions::AccessKey
    end
  end
end