# encoding: UTF-8

require 'spec_helper'
require_dependency 'post_destroyer'

describe Topic do

  it { should validate_presence_of :title }

  it { should belong_to :category }
  it { should belong_to :user }
  it { should belong_to :last_poster }
  it { should belong_to :featured_user1 }
  it { should belong_to :featured_user2 }
  it { should belong_to :featured_user3 }
  it { should belong_to :featured_user4 }

  it { should have_many :posts }
  it { should have_many :topic_users }
  it { should have_many :topic_links }
  it { should have_many :topic_allowed_users }
  it { should have_many :allowed_users }
  it { should have_many :invites }

  it { should rate_limit }

  it_behaves_like "a versioned model"

  context 'slug' do

    let(:title) { "hello world topic" }
    let(:slug) { "hello-world-slug" }

    it "returns a Slug for a title" do
      Slug.expects(:for).with(title).returns(slug)
      Fabricate.build(:topic, title: title).slug.should == slug
    end

    it "returns 'topic' when the slug is empty (say, non-english chars)" do
      Slug.expects(:for).with(title).returns("")
      Fabricate.build(:topic, title: title).slug.should == "topic"
    end

  end

  context "updating a title to be shorter" do
    let!(:topic) { Fabricate(:topic) }

    it "doesn't update it to be shorter due to cleaning using TextCleaner" do
      topic.title = 'unread    glitch'
      topic.save.should be_false
    end
  end

  context 'private message title' do
    before do
      SiteSetting.stubs(:min_topic_title_length).returns(15)
      SiteSetting.stubs(:min_private_message_title_length).returns(3)
    end

    it 'allows shorter titles' do
      pm = Fabricate.build(:private_message_topic, title: 'a' * SiteSetting.min_private_message_title_length)
      expect(pm).to be_valid
    end

    it 'but not too short' do
      pm = Fabricate.build(:private_message_topic, title: 'a')
      expect(pm).to_not be_valid
    end
  end

  context 'topic title uniqueness' do

    let!(:topic) { Fabricate(:topic) }
    let(:new_topic) { Fabricate.build(:topic, title: topic.title) }

    context "when duplicates aren't allowed" do
      before do
        SiteSetting.expects(:allow_duplicate_topic_titles?).returns(false)
      end

      it "won't allow another topic to be created with the same name" do
        new_topic.should_not be_valid
      end

      it "won't allow another topic with an upper case title to be created" do
        new_topic.title = new_topic.title.upcase
        new_topic.should_not be_valid
      end

      it "allows it when the topic is deleted" do
        topic.destroy
        new_topic.should be_valid
      end

      it "allows a private message to be created with the same topic" do
        new_topic.archetype = Archetype.private_message
        new_topic.should be_valid
      end
    end

    context "when duplicates are allowed" do
      before do
        SiteSetting.expects(:allow_duplicate_topic_titles?).returns(true)
      end

      it "will allow another topic to be created with the same name" do
        new_topic.should be_valid
      end
    end

  end

  context 'html in title' do

    def build_topic_with_title(title)
      build(:topic, title: title).tap{ |t| t.valid? }
    end

    let(:topic_bold) { build_topic_with_title("Topic with <b>bold</b> text in its title" ) }
    let(:topic_image) { build_topic_with_title("Topic with <img src='something'> image in its title" ) }
    let(:topic_script) { build_topic_with_title("Topic with <script>alert('title')</script> script in its title" ) }

    it "escapes script contents" do
      topic_script.title.should == "Topic with script in its title"
    end

    it "escapes bold contents" do
      topic_bold.title.should == "Topic with bold text in its title"
    end

    it "escapes image contents" do
      topic_image.title.should == "Topic with image in its title"
    end

  end

  context 'fancy title' do
    let(:topic) { Fabricate.build(:topic, title: "\"this topic\" -- has ``fancy stuff''" ) }

    context 'title_fancy_entities disabled' do
      before do
        SiteSetting.stubs(:title_fancy_entities).returns(false)
      end

      it "doesn't change the title to add entities" do
        topic.fancy_title.should == topic.title
      end
    end

    context 'title_fancy_entities enabled' do
      before do
        SiteSetting.stubs(:title_fancy_entities).returns(true)
      end

      it "converts the title to have fancy entities" do
        topic.fancy_title.should == "&ldquo;this topic&rdquo; &ndash; has &ldquo;fancy stuff&rdquo;"
      end
    end


  end


  context 'similar_to' do

    it 'returns blank with nil params' do
      Topic.similar_to(nil, nil).should be_blank
    end

    context 'with a similar topic' do
      let!(:topic) { Fabricate(:topic, title: "Evil trout is the dude who posted this topic") }

      it 'returns the similar topic if the title is similar' do
        Topic.similar_to("has evil trout made any topics?", "i am wondering has evil trout made any topics?").should == [topic]
      end

    end

  end

  context 'post_numbers' do
    let!(:topic) { Fabricate(:topic) }
    let!(:p1) { Fabricate(:post, topic: topic, user: topic.user) }
    let!(:p2) { Fabricate(:post, topic: topic, user: topic.user) }
    let!(:p3) { Fabricate(:post, topic: topic, user: topic.user) }

    it "returns the post numbers of the topic" do
      topic.post_numbers.should == [1, 2, 3]
      p2.destroy
      topic.reload
      topic.post_numbers.should == [1, 3]
    end

  end

  context 'move_posts' do
    let(:user) { Fabricate(:user) }
    let(:another_user) { Fabricate(:evil_trout) }
    let(:category) { Fabricate(:category, user: user) }
    let!(:topic) { Fabricate(:topic, user: user, category: category) }
    let!(:p1) { Fabricate(:post, topic: topic, user: user) }
    let!(:p2) { Fabricate(:post, topic: topic, user: another_user)}
    let!(:p3) { Fabricate(:post, topic: topic, user: user)}
    let!(:p4) { Fabricate(:post, topic: topic, user: user)}

    before do
      # add a like to a post
      PostAction.act(another_user, p4, PostActionType.types[:like])
    end

    context 'success' do

      it "enqueues a job to notify users" do
        topic.stubs(:add_moderator_post)
        Jobs.expects(:enqueue).with(:notify_moved_posts, post_ids: [p2.id, p4.id], moved_by_id: user.id)
        topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name")
      end

      it "adds a moderator post at the location of the first moved post" do
        topic.expects(:add_moderator_post).with(user, instance_of(String), has_entries(post_number: 2))
        topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name")
      end

    end

    context "errors" do

      it "raises an error when one of the posts doesn't exist" do
        lambda { topic.move_posts(user, [1003], title: "new testing topic name") }.should raise_error(Discourse::InvalidParameters)
      end

      it "raises an error and does not create a topic if no posts were moved" do
        Topic.count.tap do |original_topic_count|
          lambda {
            topic.move_posts(user, [], title: "new testing topic name")
          }.should raise_error(Discourse::InvalidParameters)

          expect(Topic.count).to eq original_topic_count
        end
      end
    end

    context "successfully moved" do
      before do
        topic.expects(:add_moderator_post)
        TopicUser.update_last_read(user, topic.id, p4.post_number, 0)
      end

      context "to a new topic" do
        let!(:new_topic) { topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name") }

        it "works correctly" do
          TopicUser.where(user_id: user.id, topic_id: topic.id).first.last_read_post_number.should == p3.post_number

          new_topic.should be_present
          new_topic.featured_user1_id.should == another_user.id
          new_topic.like_count.should == 1
          new_topic.category.should == category
          topic.featured_user1_id.should be_blank
          new_topic.posts.should =~ [p2, p4]

          new_topic.reload
          new_topic.posts_count.should == 2
          new_topic.highest_post_number.should == 2

          p2.reload
          p2.sort_order.should == 1
          p2.post_number.should == 1

          p4.reload
          p4.post_number.should == 2
          p4.sort_order.should == 2

          topic.reload
          topic.featured_user1_id.should be_blank
          topic.like_count.should == 0
          topic.posts_count.should == 2
          topic.posts.should =~ [p1, p3]
          topic.highest_post_number.should == p3.post_number
        end
      end

      context "to an existing topic" do

        let!(:destination_topic) { Fabricate(:topic, user: user ) }
        let!(:destination_op) { Fabricate(:post, topic: destination_topic, user: user) }
        let!(:moved_to) { topic.move_posts(user, [p2.id, p4.id], destination_topic_id: destination_topic.id )}

        it "works correctly" do
          moved_to.should == destination_topic

          # Check out new topic
          moved_to.reload
          moved_to.posts_count.should == 3
          moved_to.highest_post_number.should == 3
          moved_to.featured_user1_id.should == another_user.id
          moved_to.like_count.should == 1
          moved_to.category.should be_blank

          # Posts should be re-ordered
          p2.reload
          p2.sort_order.should == 2
          p2.post_number.should == 2
          p2.topic_id.should == moved_to.id

          p4.reload
          p4.post_number.should == 3
          p4.sort_order.should == 3
          p4.topic_id.should == moved_to.id

          # Check out the original topic
          topic.reload
          topic.posts_count.should == 2
          topic.highest_post_number.should == 3
          topic.featured_user1_id.should be_blank
          topic.like_count.should == 0
          topic.posts_count.should == 2
          topic.posts.should =~ [p1, p3]
          topic.highest_post_number.should == p3.post_number

          # Should update last reads
          TopicUser.where(user_id: user.id, topic_id: topic.id).first.last_read_post_number.should == p3.post_number
        end
      end

      context "moving the first post" do

        let!(:new_topic) { topic.move_posts(user, [p1.id, p2.id], title: "new testing topic name") }

        it "copies the OP, doesn't delete it" do
          new_topic.should be_present
          new_topic.posts.first.raw.should == p1.raw

          new_topic.reload
          new_topic.posts_count.should == 2
          new_topic.highest_post_number.should == 2

          # First post didn't move
          p1.reload
          p1.sort_order.should == 1
          p1.post_number.should == 1
          p1.topic_id == topic.id

          # Second post is in a new topic
          p2.reload
          p2.post_number.should == 2
          p2.sort_order.should == 2
          p2.topic_id == new_topic.id

          topic.reload
          topic.posts.should =~ [p1, p3, p4]
          topic.highest_post_number.should == p4.post_number
        end

      end


    end
  end

  context 'private message' do
    let(:coding_horror) { User.where(username: 'CodingHorror').first }
    let(:evil_trout) { Fabricate(:evil_trout) }
    let(:topic) { Fabricate(:private_message_topic) }

    it "should integrate correctly" do
      Guardian.new(topic.user).can_see?(topic).should be_true
      Guardian.new.can_see?(topic).should be_false
      Guardian.new(evil_trout).can_see?(topic).should be_false
      Guardian.new(coding_horror).can_see?(topic).should be_true
      TopicQuery.new(evil_trout).list_latest.topics.should_not include(topic)

      # invites
      topic.invite(topic.user, 'duhhhhh').should be_false
    end

    context 'invite' do

      it "delegates to topic.invite_by_email when the user doesn't exist, but it's an email" do
        topic.expects(:invite_by_email).with(topic.user, 'jake@adventuretime.ooo')
        topic.invite(topic.user, 'jake@adventuretime.ooo')
      end

      context 'existing user' do
        let(:walter) { Fabricate(:walter_white) }

        context 'by username' do

          it 'adds walter to the allowed users' do
            topic.invite(topic.user, walter.username).should be_true
            topic.allowed_users.include?(walter).should be_true
          end

          it 'creates a notification' do
            lambda { topic.invite(topic.user, walter.username) }.should change(Notification, :count)
          end
        end

        context 'by email' do
          it 'returns true' do
            topic.invite(topic.user, walter.email).should be_true
          end

          it 'adds walter to the allowed users' do
            topic.invite(topic.user, walter.email)
            topic.allowed_users.include?(walter).should be_true
          end

          it 'creates a notification' do
            lambda { topic.invite(topic.user, walter.email) }.should change(Notification, :count)
          end

        end
      end

    end

    context "user actions" do
      let(:actions) { topic.user.user_actions }

      it "should set up actions correctly" do
        ActiveRecord::Base.observers.enable :all

        actions.map{|a| a.action_type}.should_not include(UserAction::NEW_TOPIC)
        actions.map{|a| a.action_type}.should include(UserAction::NEW_PRIVATE_MESSAGE)
        coding_horror.user_actions.map{|a| a.action_type}.should include(UserAction::GOT_PRIVATE_MESSAGE)
      end

    end

    context "other user" do

      before do
        # let! is weird, this test need a refactor
        t = topic
      end

      let(:creator) { PostCreator.new(topic.user, raw: Fabricate.build(:post).raw, topic_id: topic.id )}

      it "sends the other user an email when there's a new post" do
        UserNotifications.expects(:private_message).with(coding_horror, has_key(:post))
        creator.create
      end

      it "doesn't send the user an email when they have them disabled" do
        coding_horror.update_column(:email_private_messages, false)
        UserNotifications.expects(:private_message).with(coding_horror, has_key(:post)).never
        creator.create
      end

    end


  end


  context 'bumping topics' do

    before do
      @topic = Fabricate(:topic, bumped_at: 1.year.ago)
    end


    it 'updates the bumped_at field when a new post is made' do
      @topic.bumped_at.should be_present
      lambda {
        Fabricate(:post, topic: @topic, user: @topic.user)
        @topic.reload
      }.should change(@topic, :bumped_at)
    end

    context 'editing posts' do
      before do
        @earlier_post = Fabricate(:post, topic: @topic, user: @topic.user)
        @last_post = Fabricate(:post, topic: @topic, user: @topic.user)
        @topic.reload
      end

      it "doesn't bump the topic on an edit to the last post that doesn't result in a new version" do
        lambda {
          SiteSetting.expects(:ninja_edit_window).returns(5.minutes)
          @last_post.revise(@last_post.user, 'updated contents', revised_at: @last_post.created_at + 10.seconds)
          @topic.reload
        }.should_not change(@topic, :bumped_at)
      end

      it "bumps the topic when a new version is made of the last post" do
        lambda {
          @last_post.revise(Fabricate(:moderator), 'updated contents')
          @topic.reload
        }.should change(@topic, :bumped_at)
      end

      it "doesn't bump the topic when a post that isn't the last post receives a new version" do
        lambda {
          @earlier_post.revise(Fabricate(:moderator), 'updated contents')
          @topic.reload
        }.should_not change(@topic, :bumped_at)
      end
    end
  end

  context 'moderator posts' do
    before do
      @moderator = Fabricate(:moderator)
      @topic = Fabricate(:topic)
      @mod_post = @topic.add_moderator_post(@moderator, "Moderator did something. http://discourse.org", post_number: 999)
    end

    it 'creates a moderator post' do
      @mod_post.should be_present
      @mod_post.post_type.should == Post.types[:moderator_action]
      @mod_post.post_number.should == 999
      @mod_post.sort_order.should == 999
      @topic.topic_links.count.should == 1
      @topic.reload
      @topic.moderator_posts_count.should == 1
    end
  end


  context 'update_status' do
    before do
      @topic = Fabricate(:topic, bumped_at: 1.hour.ago)
      @topic.reload
      @original_bumped_at = @topic.bumped_at.to_f
      @user = @topic.user
      @user.admin = true
    end

    context 'visibility' do
      context 'disable' do
        before do
          @topic.update_status('visible', false, @user)
          @topic.reload
        end

        it 'should not be visible and have correct counts' do
          @topic.should_not be_visible
          @topic.moderator_posts_count.should == 1
          @topic.bumped_at.to_f.should == @original_bumped_at
        end
      end

      context 'enable' do
        before do
          @topic.update_attribute :visible, false
          @topic.update_status('visible', true, @user)
          @topic.reload
        end

        it 'should be visible with correct counts' do
          @topic.should be_visible
          @topic.moderator_posts_count.should == 1
          @topic.bumped_at.to_f.should == @original_bumped_at
        end
      end
    end

    context 'pinned' do
      context 'disable' do
        before do
          @topic.update_status('pinned', false, @user)
          @topic.reload
        end

        it "doesn't have a pinned_at but has correct dates" do
          @topic.pinned_at.should be_blank
          @topic.moderator_posts_count.should == 1
          @topic.bumped_at.to_f.should == @original_bumped_at
        end
      end

      context 'enable' do
        before do
          @topic.update_attribute :pinned_at, nil
          @topic.update_status('pinned', true, @user)
          @topic.reload
        end

        it 'should enable correctly' do
          @topic.pinned_at.should be_present
          @topic.bumped_at.to_f.should == @original_bumped_at
          @topic.moderator_posts_count.should == 1
        end

      end
    end

    context 'archived' do
      context 'disable' do
        before do
          @topic.update_status('archived', false, @user)
          @topic.reload
        end

        it 'should archive correctly' do
          @topic.should_not be_archived
          @topic.bumped_at.to_f.should == @original_bumped_at
          @topic.moderator_posts_count.should == 1
        end
      end

      context 'enable' do
        before do
          @topic.update_attribute :archived, false
          @topic.update_status('archived', true, @user)
          @topic.reload
        end

        it 'should be archived' do
          @topic.should be_archived
          @topic.moderator_posts_count.should == 1
          @topic.bumped_at.to_f.should == @original_bumped_at
        end

      end
    end

    shared_examples_for 'a status that closes a topic' do
      context 'disable' do
        before do
          @topic.update_status(status, false, @user)
          @topic.reload
        end

        it 'should not be pinned' do
          @topic.should_not be_closed
          @topic.moderator_posts_count.should == 1
          @topic.bumped_at.to_f.should_not == @original_bumped_at
        end

      end

      context 'enable' do
        before do
          @topic.update_attribute :closed, false
          @topic.update_status(status, true, @user)
          @topic.reload
        end

        it 'should be closed' do
          @topic.should be_closed
          @topic.bumped_at.to_f.should == @original_bumped_at
          @topic.moderator_posts_count.should == 1
        end
      end
    end

    context 'closed' do
      let(:status) { 'closed' }
      it_should_behave_like 'a status that closes a topic'
    end

    context 'autoclosed' do
      let(:status) { 'autoclosed' }
      it_should_behave_like 'a status that closes a topic'

      it 'puts the autoclose duration in the moderator post' do
        @topic.created_at = 3.days.ago
        @topic.update_status(status, true, @user)

        expect(@topic.posts.last.raw).to include "closed after 3 days"
      end
    end


  end

  describe 'toggle_star' do

    shared_examples_for "adding a star to a topic" do
      it 'triggers a forum topic user change with true' do
        # otherwise no chance the mock will work
        freeze_time do
          TopicUser.expects(:change).with(@user, @topic.id, starred: true, starred_at: DateTime.now, unstarred_at: nil)
          @topic.toggle_star(@user, true)
        end
      end

      it 'increases the star_count of the forum topic' do
        lambda {
          @topic.toggle_star(@user, true)
          @topic.reload
        }.should change(@topic, :star_count).by(1)
      end

      it 'triggers the rate limiter' do
        Topic::FavoriteLimiter.any_instance.expects(:performed!)
        @topic.toggle_star(@user, true)
      end
    end

    before do
      @topic = Fabricate(:topic)
      @user = @topic.user
    end

    it_should_behave_like "adding a star to a topic"

    describe 'removing a star' do
      before do
        @topic.toggle_star(@user, true)
        @topic.reload
      end

      it 'rolls back the rate limiter' do
        Topic::FavoriteLimiter.any_instance.expects(:rollback!)
        @topic.toggle_star(@user, false)
      end

      it 'triggers a forum topic user change with false' do
        freeze_time do
          TopicUser.expects(:change).with(@user, @topic.id, starred: false, unstarred_at: DateTime.now)
          @topic.toggle_star(@user, false)
        end
      end

      it 'reduces the star_count' do
        lambda {
          @topic.toggle_star(@user, false)
          @topic.reload
        }.should change(@topic, :star_count).by(-1)
      end

      describe 'and adding a star again' do
        before do
          @topic.toggle_star(@user, false)
          @topic.reload
        end
        it_should_behave_like "adding a star to a topic"
      end
    end
  end

  context 'last_poster info' do

    before do
      @user = Fabricate(:user)
      @post = Fabricate(:post, user: @user)
      @topic = @post.topic
    end

    it 'initially has the last_post_user_id of the OP' do
      @topic.last_post_user_id.should == @user.id
    end

    context 'after a second post' do
      before do
        @second_user = Fabricate(:coding_horror)
        @new_post = Fabricate(:post, topic: @topic, user: @second_user)
        @topic.reload
      end

      it 'updates the last_post_user_id to the second_user' do
        @topic.last_post_user_id.should == @second_user.id
        @topic.last_posted_at.to_i.should == @new_post.created_at.to_i
        topic_user = @second_user.topic_users.where(topic_id: @topic.id).first
        topic_user.posted?.should be_true
      end

    end
  end

  describe 'with category' do
    before do
      @category = Fabricate(:category)
    end

    it "should not increase the topic_count with no category" do
      lambda { Fabricate(:topic, user: @category.user); @category.reload }.should_not change(@category, :topic_count)
    end

    it "should increase the category's topic_count" do
      lambda { Fabricate(:topic, user: @category.user, category_id: @category.id); @category.reload }.should change(@category, :topic_count).by(1)
    end
  end

  describe 'meta data' do
    let(:topic) { Fabricate(:topic, meta_data: {hello: 'world'}) }

    it 'allows us to create a topic with meta data' do
      topic.meta_data['hello'].should == 'world'
    end

    context 'updating' do

      context 'existing key' do
        before do
          topic.update_meta_data(hello: 'bane')
        end

        it 'updates the key' do
          topic.meta_data['hello'].should == 'bane'
        end
      end

      context 'new key' do
        before do
          topic.update_meta_data(city: 'gotham')
        end

        it 'adds the new key' do
          topic.meta_data['city'].should == 'gotham'
          topic.meta_data['hello'].should == 'world'
        end

      end


    end

  end

  describe 'after create' do

    let(:topic) { Fabricate(:topic) }

    it 'is a regular topic by default' do
      topic.archetype.should == Archetype.default
      topic.has_best_of.should be_false
      topic.percent_rank.should == 1.0
      topic.should be_visible
      topic.pinned_at.should be_blank
      topic.should_not be_closed
      topic.should_not be_archived
      topic.moderator_posts_count.should == 0
    end

    context 'post' do
      let(:post) { Fabricate(:post, topic: topic, user: topic.user) }

      it 'has the same archetype as the topic' do
        post.archetype.should == topic.archetype
      end
    end
  end

  describe 'versions' do
    let(:topic) { Fabricate(:topic) }

    it "has version 1 by default" do
      topic.version.should == 1
    end

    context 'changing title' do
      before do
        topic.title = "new title for the topic"
        topic.save
      end

      it "creates a new version" do
        topic.version.should == 2
      end
    end

    context 'changing category' do
      let(:category) { Fabricate(:category) }

      before do
        topic.change_category(category.name)
      end

      it "creates a new version" do
        topic.version.should == 2
      end

      context "removing a category" do
        before do
          topic.change_category(nil)
        end

        it "creates a new version" do
          topic.version.should == 3
        end
      end

    end

    context 'bumping the topic' do
      before do
        topic.bumped_at = 10.minutes.from_now
        topic.save
      end

      it "doesn't craete a new version" do
        topic.version.should == 1
      end
    end

  end

  describe 'change_category' do

    before do
      @topic = Fabricate(:topic)
      @category = Fabricate(:category, user: @topic.user)
      @user = @topic.user
    end

    describe 'without a previous category' do

      it 'should not change the topic_count when not changed' do
       lambda { @topic.change_category(nil); @category.reload }.should_not change(@category, :topic_count)
      end

      describe 'changed category' do
        before do
          @topic.change_category(@category.name)
          @category.reload
        end

        it 'changes the category' do
          @topic.category.should == @category
          @category.topic_count.should == 1
        end

      end


      it "doesn't change the category when it can't be found" do
        @topic.change_category('made up')
        @topic.category.should be_blank
      end
    end

    describe 'with a previous category' do
      before do
        @topic.change_category(@category.name)
        @topic.reload
        @category.reload
      end

      it 'increases the topic_count' do
        @category.topic_count.should == 1
      end

      it "doesn't change the topic_count when the value doesn't change" do
        lambda { @topic.change_category(@category.name); @category.reload }.should_not change(@category, :topic_count)
      end

      it "doesn't reset the category when given a name that doesn't exist" do
        @topic.change_category('made up')
        @topic.category_id.should be_present
      end

      describe 'to a different category' do
        before do
          @new_category = Fabricate(:category, user: @user, name: '2nd category')
          @topic.change_category(@new_category.name)
          @topic.reload
          @new_category.reload
          @category.reload
        end

        it "should increase the new category's topic count" do
          @new_category.topic_count.should == 1
        end

        it "should lower the original category's topic count" do
          @category.topic_count.should == 0
        end

      end

      describe 'when the category exists' do
        before do
          @topic.change_category(nil)
          @category.reload
        end

        it "resets the category" do
          @topic.category_id.should be_blank
          @category.topic_count.should == 0
        end

      end

    end

  end

  describe 'scopes' do
    describe '#by_most_recently_created' do
      it 'returns topics ordered by created_at desc, id desc' do
        now = Time.now
        a = Fabricate(:topic, created_at: now - 2.minutes)
        b = Fabricate(:topic, created_at: now)
        c = Fabricate(:topic, created_at: now)
        d = Fabricate(:topic, created_at: now - 2.minutes)
        Topic.by_newest.should == [c,b,d,a]
      end
    end

    describe '#created_since' do
      it 'returns topics created after some date' do
        now = Time.now
        a = Fabricate(:topic, created_at: now - 2.minutes)
        b = Fabricate(:topic, created_at: now - 1.minute)
        c = Fabricate(:topic, created_at: now)
        d = Fabricate(:topic, created_at: now + 1.minute)
        e = Fabricate(:topic, created_at: now + 2.minutes)
        Topic.created_since(now).should_not include a
        Topic.created_since(now).should_not include b
        Topic.created_since(now).should_not include c
        Topic.created_since(now).should include d
        Topic.created_since(now).should include e
      end
    end

    describe '#visible' do
      it 'returns topics set as visible' do
        a = Fabricate(:topic, visible: false)
        b = Fabricate(:topic, visible: true)
        c = Fabricate(:topic, visible: true)
        Topic.visible.should_not include a
        Topic.visible.should include b
        Topic.visible.should include c
      end
    end
  end

  describe 'auto-close' do
    context 'a new topic' do
      context 'auto_close_at is set' do
        it 'queues a job to close the topic' do
          Timecop.freeze(Time.zone.now) do
            Jobs.expects(:enqueue_at).with(7.days.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
            Fabricate(:topic, auto_close_at: 7.days.from_now, user: Fabricate(:admin))
          end
        end

        it 'when auto_close_user_id is nil, it will use the topic creator as the topic closer' do
          topic_creator = Fabricate(:admin)
          Jobs.expects(:enqueue_at).with do |datetime, job_name, job_args|
            job_args[:user_id] == topic_creator.id
          end
          Fabricate(:topic, auto_close_at: 7.days.from_now, user: topic_creator)
        end

        it 'when auto_close_user_id is set, it will use it as the topic closer' do
          topic_creator = Fabricate(:admin)
          topic_closer = Fabricate(:user, admin: true)
          Jobs.expects(:enqueue_at).with do |datetime, job_name, job_args|
            job_args[:user_id] == topic_closer.id
          end
          Fabricate(:topic, auto_close_at: 7.days.from_now, auto_close_user: topic_closer, user: topic_creator)
        end

        it "ignores the category's default auto-close" do
          Timecop.freeze(Time.zone.now) do
            Jobs.expects(:enqueue_at).with(7.days.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
            Fabricate(:topic, auto_close_at: 7.days.from_now, user: Fabricate(:admin), category: Fabricate(:category, auto_close_days: 2))
          end
        end
      end
    end

    context 'an existing topic' do
      it 'when auto_close_at is set, it queues a job to close the topic' do
        Timecop.freeze(Time.zone.now) do
          topic = Fabricate(:topic)
          Jobs.expects(:enqueue_at).with(12.hours.from_now, :close_topic, has_entries(topic_id: topic.id, user_id: topic.user_id))
          topic.auto_close_at = 12.hours.from_now
          topic.save.should be_true
        end
      end

      it 'when auto_close_at and auto_closer_user_id are set, it queues a job to close the topic' do
        Timecop.freeze(Time.zone.now) do
          topic  = Fabricate(:topic)
          closer = Fabricate(:admin)
          Jobs.expects(:enqueue_at).with(12.hours.from_now, :close_topic, has_entries(topic_id: topic.id, user_id: closer.id))
          topic.auto_close_at = 12.hours.from_now
          topic.auto_close_user = closer
          topic.save.should be_true
        end
      end

      it 'when auto_close_at is removed, it cancels the job to close the topic' do
        Jobs.stubs(:enqueue_at).returns(true)
        topic = Fabricate(:topic, auto_close_at: 1.day.from_now)
        Jobs.expects(:cancel_scheduled_job).with(:close_topic, {topic_id: topic.id})
        topic.auto_close_at = nil
        topic.save.should be_true
        topic.auto_close_user.should be_nil
      end

      it 'when auto_close_user is removed, it updates the job' do
        Timecop.freeze(Time.zone.now) do
          Jobs.stubs(:enqueue_at).with(1.day.from_now, :close_topic, anything).returns(true)
          topic = Fabricate(:topic, auto_close_at: 1.day.from_now, auto_close_user: Fabricate(:admin))
          Jobs.expects(:cancel_scheduled_job).with(:close_topic, {topic_id: topic.id})
          Jobs.expects(:enqueue_at).with(1.day.from_now, :close_topic, has_entries(topic_id: topic.id, user_id: topic.user_id))
          topic.auto_close_user = nil
          topic.save.should be_true
        end
      end

      it 'when auto_close_at value is changed, it reschedules the job' do
        Timecop.freeze(Time.zone.now) do
          Jobs.stubs(:enqueue_at).returns(true)
          topic = Fabricate(:topic, auto_close_at: 1.day.from_now)
          Jobs.expects(:cancel_scheduled_job).with(:close_topic, {topic_id: topic.id})
          Jobs.expects(:enqueue_at).with(3.days.from_now, :close_topic, has_entry(topic_id: topic.id))
          topic.auto_close_at = 3.days.from_now
          topic.save.should be_true
        end
      end

      it 'when auto_close_user_id is changed, it updates the job' do
        Timecop.freeze(Time.zone.now) do
          admin = Fabricate(:admin)
          Jobs.stubs(:enqueue_at).returns(true)
          topic = Fabricate(:topic, auto_close_at: 1.day.from_now)
          Jobs.expects(:cancel_scheduled_job).with(:close_topic, {topic_id: topic.id})
          Jobs.expects(:enqueue_at).with(1.day.from_now, :close_topic, has_entries(topic_id: topic.id, user_id: admin.id))
          topic.auto_close_user = admin
          topic.save.should be_true
        end
      end

      it 'when auto_close_at and auto_close_user_id are not changed, it should not schedule another CloseTopic job' do
        Timecop.freeze(Time.zone.now) do
          Jobs.expects(:enqueue_at).with(1.day.from_now, :close_topic, has_key(:topic_id)).once.returns(true)
          Jobs.expects(:cancel_scheduled_job).never
          topic = Fabricate(:topic, auto_close_at: 1.day.from_now)
          topic.title = 'A new title that is long enough'
          topic.save.should be_true
        end
      end

      it "ignores the category's default auto-close" do
        Timecop.freeze(Time.zone.now) do
          topic = Fabricate(:topic, category: Fabricate(:category, auto_close_days: 14))
          Jobs.expects(:enqueue_at).with(12.hours.from_now, :close_topic, has_entries(topic_id: topic.id, user_id: topic.user_id))
          topic.auto_close_at = 12.hours.from_now
          topic.save.should be_true
        end
      end
    end
  end

  describe '#secure_category?' do
    let(:category){ Category.new }

    it "is true if the category is secure" do
      category.stubs(:secure).returns(true)
      Topic.new(:category => category).should be_secure_category
    end

    it "is false if the category is not secure" do
      category.stubs(:secure).returns(false)
      Topic.new(:category => category).should_not be_secure_category
    end

    it "is false if there is no category" do
      Topic.new(:category => nil).should_not be_secure_category
    end
  end
end