require "helper"

class Journalist < ActiveRecord::Base
  extend FriendlyId
  friendly_id :name, :use => :slugged
end

class Article < ActiveRecord::Base
  extend FriendlyId
  friendly_id :name, :use => :slugged
end

class Novelist < ActiveRecord::Base
  extend FriendlyId
  friendly_id :name, :use => :slugged, :sequence_separator => '_'

  def normalize_friendly_id(string)
    super.gsub("-", "_")
  end
end

class SluggedTest < TestCaseClass

  include FriendlyId::Test
  include FriendlyId::Test::Shared::Core
  include FriendlyId::Test::Shared::Slugged

  def model_class
    Journalist
  end

  test "should allow validations on the slug" do
    model_class = Class.new(ActiveRecord::Base) do
      self.table_name = "articles"
      extend FriendlyId
      friendly_id :name, :use => :slugged
      validates_length_of :slug, :maximum => 1
      def self.name
        "Article"
      end
    end
    instance = model_class.new :name => "hello"
    refute instance.valid?
  end

  test "should allow nil slugs" do
    transaction do
      m1 = model_class.create!
      model_class.create!
      assert_nil m1.slug
    end
  end

  test "should not break validates_uniqueness_of" do
    model_class = Class.new(ActiveRecord::Base) do
      self.table_name = "journalists"
      extend FriendlyId
      friendly_id :name, :use => :slugged
      validates_uniqueness_of :slug_en
      def self.name
        "Journalist"
      end
    end
    transaction do
      instance = model_class.create! :name => "hello", :slug_en => "hello"
      instance2 = model_class.create :name => "hello", :slug_en => "hello"
      assert instance.valid?
      refute instance2.valid?
    end
  end

  test 'should allow a record to reuse its own slug' do
    with_instance_of(model_class) do |record|
      old_id = record.friendly_id
      record.slug = nil
      record.save!
      assert_equal old_id, record.friendly_id
    end
  end

  test "should not update matching slug" do
    klass = Class.new model_class do
      def should_generate_new_friendly_id?
        name_changed?
      end
    end
    with_instance_of klass do |record|
      old_id = record.friendly_id
      record.name += " "
      record.save!
      assert_equal old_id, record.friendly_id
    end
  end

  test "should not set slug on create if unrelated validations fail" do
    klass = Class.new model_class do
      validates_presence_of :active
      friendly_id :name, :use => :slugged

      def self.name
        "Journalist"
      end
    end

    transaction do
      instance = klass.new :name => 'foo'
      refute instance.save
      refute instance.valid?
      assert_nil instance.slug
    end
  end

  test "should not set slug on create if unrelated validations fail with custom slug_column" do
    klass = Class.new(ActiveRecord::Base) do
      self.table_name = 'authors'
      extend FriendlyId
      validates_presence_of :active
      friendly_id :name, :use => :slugged, :slug_column => :subdomain

      def self.name
        "Author"
      end
    end

    transaction do
      instance = klass.new :name => 'foo'
      refute instance.save
      refute instance.valid?
      assert_nil instance.subdomain
    end
  end

  test "should not update slug on save if unrelated validations fail" do
    klass = Class.new model_class do
      validates_presence_of :active
      friendly_id :name, :use => :slugged

      def self.name
        "Journalist"
      end
    end

    transaction do
      instance = klass.new :name => 'foo', :active => true
      assert instance.save
      assert instance.valid?
      instance.name = 'foobar'
      instance.slug = nil
      instance.active = nil
      refute instance.save
      refute instance.valid?
      assert_equal 'foo', instance.slug
    end
  end

end

class SlugGeneratorTest < TestCaseClass

  include FriendlyId::Test

  def model_class
    Journalist
  end

  test "should quote column names" do
    model_class = Class.new(ActiveRecord::Base) do
      # This has been added in 635731bb to fix MySQL/Rubinius. It may still
      # be necessary, but causes an exception to be raised on Rails 4, so I'm
      # commenting it out. If it causes MySQL/Rubinius to fail again we'll
      # look for another solution.
      # self.abstract_class = true
      self.table_name = "journalists"
      extend FriendlyId
      friendly_id :name, :use => :slugged, :slug_column => "strange name"
    end

    begin
      with_instance_of(model_class) {|record| assert model_class.friendly.find(record.friendly_id)}
    rescue ActiveRecord::StatementInvalid
      flunk "column name was not quoted"
    end
  end

  test "should not resequence lower sequences on update" do
    transaction do
      m1 = model_class.create! :name => "a b c d"
      assert_equal "a-b-c-d", m1.slug
      model_class.create! :name => "a b c d"
      m1 = model_class.friendly.find(m1.id)
      m1.save!
      assert_equal "a-b-c-d", m1.slug
    end
  end

  test "should correctly sequence slugs that end with numbers" do
    transaction do
      record1 = model_class.create! :name => "Peugeot 206"
      assert_equal "peugeot-206", record1.slug
      record2 = model_class.create! :name => "Peugeot 206"
      assert_match(/\Apeugeot-206-([a-z0-9]+\-){4}[a-z0-9]+\z/, record2.slug)
    end
  end

  test "should correctly sequence slugs with underscores" do
    transaction do
      Novelist.create! :name => 'wordsfail, buildings tumble'
      record2 = Novelist.create! :name => 'word fail'
      assert_equal 'word_fail', record2.slug
    end
  end

  test "should correctly sequence numeric slugs" do
    transaction do
      n2 = 2.times.map {Article.create :name => '123'}.last
      assert_match(/\A123-.*/, n2.friendly_id)
    end
  end

end

class SlugSeparatorTest < TestCaseClass

  include FriendlyId::Test

  class Journalist < ActiveRecord::Base
    extend FriendlyId
    friendly_id :name, :use => :slugged, :sequence_separator => ":"
  end

  def model_class
    Journalist
  end

  test "should sequence with configured sequence separator" do
    with_instance_of model_class do |record|
      record2 = model_class.create! :name => record.name
      assert record2.friendly_id.match(/:.*\z/)
    end
  end

  test "should detect when a stored slug has been cleared" do
    with_instance_of model_class do |record|
      record.slug = nil
      assert record.should_generate_new_friendly_id?
    end
  end

  test "should correctly sequence slugs that uses single dashes as sequence separator" do
    model_class = Class.new(ActiveRecord::Base) do
      self.table_name = "journalists"
      extend FriendlyId
      friendly_id :name, :use => :slugged, :sequence_separator => '-'
      def self.name
        "Journalist"
      end
    end
    transaction do
      record1 = model_class.create! :name => "Peugeot 206"
      assert_equal "peugeot-206", record1.slug
      record2 = model_class.create! :name => "Peugeot 206"
      assert_match(/\Apeugeot-206-([a-z0-9]+\-){4}[a-z0-9]+\z/, record2.slug)
    end
  end

  test "should sequence blank slugs without a separator" do
    with_instance_of model_class, :name => "" do |record|
      assert_match(/\A([a-z0-9]+\-){4}[a-z0-9]+\z/, record.slug)
    end
  end

end

class SlugLimitTest < TestCaseClass

  include FriendlyId::Test

  class Journalist < ActiveRecord::Base
    extend FriendlyId
    friendly_id :name, :use => :slugged, :slug_limit => 40
  end

  def model_class
    Journalist
  end

  test "should limit slug size" do
    transaction do
      m1 = model_class.create! :name => 'a' * 50
      assert_equal m1.slug, 'a' * 40
      m2 = model_class.create! :name => m1.name
      m2.save!
      # "aaa-<uid>"
      assert_match(/\Aa{3}\-/, m2.slug)
    end
  end
end

class DefaultScopeTest < TestCaseClass

  include FriendlyId::Test

  class Journalist < ActiveRecord::Base
    extend FriendlyId
    friendly_id :name, :use => :slugged
    default_scope -> { where(:active => true).order('id ASC') }
  end

  test "friendly_id should correctly sequence a default_scoped ordered table" do
    transaction do
      3.times { assert Journalist.create :name => "a", :active => true }
    end
  end

  test "friendly_id should correctly sequence a default_scoped scoped table" do
    transaction do
      assert Journalist.create :name => "a", :active => false
      assert Journalist.create :name => "a", :active => true
    end
  end

end

class UuidAsPrimaryKeyFindTest < TestCaseClass

  include FriendlyId::Test

  class MenuItem < ActiveRecord::Base
    extend FriendlyId
    friendly_id :name, :use => :slugged
    before_create :init_primary_key

    def self.primary_key
      "uuid_key"
    end

    # Overwrite the method added by FriendlyId
    def self.primary_key_type
      :uuid
    end

    private
    def init_primary_key
      self.uuid_key = SecureRandom.uuid
    end
  end

  def model_class
    MenuItem
  end

  test "should have a uuid_key as a primary key" do
    assert_equal "uuid_key", model_class.primary_key
    assert_equal :uuid, model_class.primary_key_type
  end

  test "should be findable by the UUID primary key" do
    with_instance_of(model_class) do |record|
      assert model_class.friendly.find record.id
    end
  end

  test "should handle a string that simply contains a UUID correctly" do
    with_instance_of(model_class) do |record|
      assert_raises(ActiveRecord::RecordNotFound) do
        model_class.friendly.find "test-#{SecureRandom.uuid}"
      end
    end
  end

end

class UnderscoreAsSequenceSeparatorRegressionTest < TestCaseClass

  include FriendlyId::Test

  class Manual < ActiveRecord::Base
    extend FriendlyId
    friendly_id :name, :use => :slugged, :sequence_separator => "_"
  end

  test "should not create duplicate slugs" do
    3.times do
      transaction do
        begin
          assert Manual.create! :name => "foo"
        rescue
          flunk "Tried to insert duplicate slug"
        end
      end
    end
  end

end

# https://github.com/norman/friendly_id/issues/148
class FailedValidationAfterUpdateRegressionTest < TestCaseClass

  include FriendlyId::Test

  class Journalist < ActiveRecord::Base
    extend FriendlyId
    friendly_id :name, :use => :slugged
    validates_presence_of :slug_de
  end

  test "to_param should return the unchanged value if the slug changes before validation fails" do
    transaction do
      journalist = Journalist.create! :name => "Joseph Pulitzer", :slug_de => "value"
      assert_equal "joseph-pulitzer", journalist.to_param
      assert journalist.valid?
      assert journalist.persisted?
      journalist.name = "Joe Pulitzer"
      journalist.slug_de = nil
      assert !journalist.valid?
      assert_equal "joseph-pulitzer", journalist.to_param
    end
  end

end

class ConfigurableRoutesTest < TestCaseClass
  include FriendlyId::Test

  class Article < ActiveRecord::Base
    extend FriendlyId

    friendly_id :name, :use => :slugged, :routes => :friendly
  end

  class Novel < ActiveRecord::Base
    extend FriendlyId

    friendly_id :name, :use => :slugged, :routes => :default
  end

  test "to_param should return a friendly id when the routes option is set to :friendly" do
    transaction do
      article = Article.create! :name => "Titanic Hits; Iceberg Sinks"

      assert_equal "titanic-hits-iceberg-sinks", article.to_param
    end
  end

  test "to_param should return the id when the routes option is set to anything but friendly" do
    transaction do
      novel = Novel.create! :name => "Don Quixote"

      assert_equal novel.id.to_s, novel.to_param
    end
  end
end
