require 'test_helper'

class UserTest < ActiveSupport::TestCase
  def assert_promoted_to(new_level, user, promoter)
    user.promote_to!(new_level, promoter)
    assert_equal(new_level, user.reload.level)
  end

  def assert_not_promoted_to(new_level, user, promoter)
    assert_raise(User::PrivilegeError) do
      user.promote_to!(new_level, promoter)
    end

    assert_not_equal(new_level, user.reload.level)
  end

  context "A user" do
    setup do
      @user = FactoryBot.create(:user)
      CurrentUser.user = @user
      CurrentUser.ip_addr = "127.0.0.1"
    end

    teardown do
      CurrentUser.user = nil
      CurrentUser.ip_addr = nil
    end

    context "promoting a user" do
      setup do
        @builder = create(:builder_user)
        @mod = create(:moderator_user)
        @admin = create(:admin_user)
        @owner = create(:owner_user)
      end

      should "allow moderators to promote users up to builder level" do
        assert_promoted_to(User::Levels::GOLD, @user, @mod)
        assert_promoted_to(User::Levels::PLATINUM, @user, @mod)
        assert_promoted_to(User::Levels::BUILDER, @user, @mod)

        assert_not_promoted_to(User::Levels::MODERATOR, @user, @mod)
        assert_not_promoted_to(User::Levels::ADMIN, @user, @mod)
        assert_not_promoted_to(User::Levels::OWNER, @user, @mod)
      end

      should "allow admins to promote users up to moderator level" do
        assert_promoted_to(User::Levels::GOLD, @user, @admin)
        assert_promoted_to(User::Levels::PLATINUM, @user, @admin)
        assert_promoted_to(User::Levels::BUILDER, @user, @admin)
        assert_promoted_to(User::Levels::MODERATOR, @user, @admin)

        assert_not_promoted_to(User::Levels::ADMIN, @user, @admin)
        assert_not_promoted_to(User::Levels::OWNER, @user, @admin)
      end

      should "allow the owner to promote users up to admin level" do
        assert_promoted_to(User::Levels::GOLD, @user, @owner)
        assert_promoted_to(User::Levels::PLATINUM, @user, @owner)
        assert_promoted_to(User::Levels::BUILDER, @user, @owner)
        assert_promoted_to(User::Levels::MODERATOR, @user, @owner)
        assert_promoted_to(User::Levels::ADMIN, @user, @owner)

        assert_not_promoted_to(User::Levels::OWNER, @user, @owner)
      end

      should "not allow non-moderators to promote other users" do
        assert_not_promoted_to(User::Levels::GOLD, @user, @builder)
        assert_not_promoted_to(User::Levels::PLATINUM, @user, @builder)
        assert_not_promoted_to(User::Levels::BUILDER, @user, @builder)
        assert_not_promoted_to(User::Levels::MODERATOR, @user, @builder)
        assert_not_promoted_to(User::Levels::ADMIN, @user, @builder)
        assert_not_promoted_to(User::Levels::OWNER, @user, @builder)
      end

      should "not allow users to promote or demote other users at their rank or above" do
        assert_not_promoted_to(User::Levels::ADMIN, create(:moderator_user), @mod)
        assert_not_promoted_to(User::Levels::BUILDER, create(:moderator_user), @mod)

        assert_not_promoted_to(User::Levels::OWNER, create(:admin_user), @admin)
        assert_not_promoted_to(User::Levels::MODERATOR, create(:admin_user), @admin)

        assert_not_promoted_to(User::Levels::ADMIN, create(:owner_user), @owner)
      end

      should "not allow users to promote themselves" do
        assert_not_promoted_to(User::Levels::ADMIN, @mod, @mod)
        assert_not_promoted_to(User::Levels::OWNER, @admin, @admin)
      end

      should "not allow users to demote themselves" do
        assert_not_promoted_to(User::Levels::MEMBER, @mod, @mod)
        assert_not_promoted_to(User::Levels::MEMBER, @admin, @admin)
        assert_not_promoted_to(User::Levels::MEMBER, @owner, @owner)
      end

      should "create a neutral feedback" do
        @user.promote_to!(User::Levels::GOLD, @mod)
        assert_equal("You have been promoted to a Gold level account from Member.", @user.feedback.last.body)
      end

      should "send an automated dmail to the user" do
        bot = FactoryBot.create(:user)
        User.stubs(:system).returns(bot)

        assert_difference("Dmail.count", 1) do
          @user.promote_to!(User::Levels::GOLD, @admin)
        end

        assert(@user.dmails.exists?(from: bot, to: @user, title: "Your account has been updated"))
        refute(@user.dmails.exists?(from: bot, to: @user, title: "Your user record has been updated"))
      end
    end

    should "authenticate password" do
      assert_equal(@user, @user.authenticate_password("password"))
      assert_equal(false, @user.authenticate_password("password2"))
    end

    should "normalize its level" do
      user = FactoryBot.create(:user, :level => User::Levels::OWNER)
      assert(user.is_owner?)
      assert(user.is_admin?)
      assert(user.is_moderator?)
      assert(user.is_gold?)

      user = FactoryBot.create(:user, :level => User::Levels::ADMIN)
      assert(!user.is_owner?)
      assert(user.is_moderator?)
      assert(user.is_gold?)

      user = FactoryBot.create(:user, :level => User::Levels::MODERATOR)
      assert(!user.is_admin?)
      assert(user.is_moderator?)
      assert(user.is_gold?)

      user = FactoryBot.create(:user, :level => User::Levels::GOLD)
      assert(!user.is_admin?)
      assert(!user.is_moderator?)
      assert(user.is_gold?)

      user = FactoryBot.create(:user)
      assert(!user.is_admin?)
      assert(!user.is_moderator?)
      assert(!user.is_gold?)
    end

    context "name" do
      should "not contain whitespace" do
        # U+2007: https://en.wikipedia.org/wiki/Figure_space
        user = build(:user, name: "foo\u2007bar")
        user.save
        assert_equal(["Name can't contain whitespace"], user.errors.full_messages)
      end

      should "be less than 25 characters long" do
        user = build(:user, name: "a"*25)
        user.save
        assert_equal(["Name must be less than 25 characters long"], user.errors.full_messages)
      end

      should "not contain a colon" do
        user = build(:user, name: "a:b")
        user.save
        assert_equal(["Name can't contain ':'"], user.errors.full_messages)
      end

      should "not begin with an underscore" do
        user = build(:user, name: "_x")
        user.save
        assert_equal(["Name can't start with '_'"], user.errors.full_messages)
      end

      should "not end with an underscore" do
        user = build(:user, name: "x_")
        user.save
        assert_equal(["Name can't end with '_'"], user.errors.full_messages)
      end

      should "not contain consecutive underscores" do
        user = build(:user, name: "x__y")
        user.save
        assert_equal(["Name can't contain multiple underscores in a row"], user.errors.full_messages)
      end

      should "not allow non-ASCII characters" do
        user = build(:user, name: "Pokémon")
        user.save
        assert_equal(["Name must contain only basic letters or numbers"], user.errors.full_messages)
      end

      should "not allow names ending in file extensions" do
        user = build(:user, name: "evazion.json")
        user.save
        assert_equal(["Name can't end with a file extension"], user.errors.full_messages)
      end

      should "not be in the same format as a deleted user" do
        user = build(:user, name: "user_1234")
        user.save
        assert_equal(["Name can't be the same as a deleted user"], user.errors.full_messages)
      end

      should "not allow blacklisted names" do
        Danbooru.config.stubs(:user_name_blacklist).returns(["voldemort"])
        user = build(:user, name: "voldemort42")
        user.save
        assert_equal(["Name is not allowed"], user.errors.full_messages)
      end
    end

    context "searching for users by name" do
      setup do
        @miku = create(:user, name: "hatsune_miku")
      end

      should "be case-insensitive" do
        assert_equal("hatsune_miku", User.normalize_name("Hatsune_Miku"))
        assert_equal(@miku.id, User.find_by_name("Hatsune_Miku").id)
        assert_equal(@miku.id, User.name_to_id("Hatsune_Miku"))
      end

      should "handle whitespace" do
        assert_equal("hatsune_miku", User.normalize_name(" hatsune miku "))
        assert_equal(@miku.id, User.find_by_name(" hatsune miku ").id)
        assert_equal(@miku.id, User.name_to_id(" hatsune miku "))
      end

      should "return nil for nonexistent names" do
        assert_nil(User.find_by_name("does_not_exist"))
        assert_nil(User.name_to_id("does_not_exist"))
      end

      should "work for names containing asterisks or backslashes" do
        @user1 = build(:user, name: "user*1")
        @user2 = build(:user, name: "user*2")
        @user3 = build(:user, name: "user\*3")

        @user1.save(validate: false)
        @user2.save(validate: false)
        @user3.save(validate: false)

        assert_equal(@user1.id, User.find_by_name("user*1").id)
        assert_equal(@user2.id, User.find_by_name("user*2").id)
        assert_equal(@user3.id, User.find_by_name("user\*3").id)
      end
    end

    context "ip address" do
      setup do
        @user = FactoryBot.create(:user)
      end

      context "in the json representation" do
        should "not appear" do
          assert(@user.to_json !~ /addr/)
        end
      end

      context "in the xml representation" do
        should "not appear" do
          assert(@user.to_xml !~ /addr/)
        end
      end
    end

    context "password" do
      context "in the json representation" do
        setup do
          @user = FactoryBot.create(:user)
        end

        should "not appear" do
          assert(@user.to_json !~ /password/)
        end
      end

      context "in the xml representation" do
        setup do
          @user = FactoryBot.create(:user)
        end

        should "not appear" do
          assert(@user.to_xml !~ /password/)
        end
      end
    end

    context "when searched by name" do
      should "match wildcards" do
        user1 = build(:user, name: "foo")
        user2 = build(:user, name: "foo*bar")
        user3 = build(:user, name: "bar\*baz")

        user1.save(validate: false)
        user2.save(validate: false)
        user3.save(validate: false)

        assert_equal([user2.id, user1.id], User.search(name: "foo*").map(&:id))
        assert_equal([user2.id], User.search(name: "foo\*bar").map(&:id))
        assert_equal([user3.id], User.search(name: "bar\\\*baz").map(&:id))
      end
    end

    context "custom CSS" do
      should "raise a validation error on invalid custom CSS" do
        user = build(:user, custom_style: "}}}")

        assert_equal(true, user.invalid?)
        assert_match(/Custom CSS contains a syntax error/, user.errors[:base].first)
      end

      should "allow blank CSS" do
        user = build(:user, custom_style: " ")

        assert_equal(true, user.valid?)
      end
    end
  end
end
