mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-30 19:08:10 -05:00
6dd4bc7d57
Group owners are regular users that can add or remove users to a group The Admin UX allows admins to appoint group owners The public group UX will display group owners first and unlock UI to add and remove members Group owners can only be appointed on non automatic groups Group owners may not appoint another group owner
1568 lines
50 KiB
Ruby
1568 lines
50 KiB
Ruby
# encoding: utf-8
|
|
|
|
require 'spec_helper'
|
|
require_dependency 'post_destroyer'
|
|
|
|
describe Topic do
|
|
|
|
let(:now) { Time.zone.local(2013,11,20,8,0) }
|
|
|
|
it { is_expected.to validate_presence_of :title }
|
|
|
|
it { is_expected.to rate_limit }
|
|
|
|
context '#visible_post_types' do
|
|
let(:types) { Post.types }
|
|
|
|
it "returns the appropriate types for anonymous users" do
|
|
post_types = Topic.visible_post_types
|
|
|
|
expect(post_types).to include(types[:regular])
|
|
expect(post_types).to include(types[:moderator_action])
|
|
expect(post_types).to include(types[:small_action])
|
|
expect(post_types).to_not include(types[:whisper])
|
|
end
|
|
|
|
it "returns the appropriate types for regular users" do
|
|
post_types = Topic.visible_post_types(Fabricate.build(:user))
|
|
|
|
expect(post_types).to include(types[:regular])
|
|
expect(post_types).to include(types[:moderator_action])
|
|
expect(post_types).to include(types[:small_action])
|
|
expect(post_types).to_not include(types[:whisper])
|
|
end
|
|
|
|
it "returns the appropriate types for staff users" do
|
|
post_types = Topic.visible_post_types(Fabricate.build(:moderator))
|
|
|
|
expect(post_types).to include(types[:regular])
|
|
expect(post_types).to include(types[:moderator_action])
|
|
expect(post_types).to include(types[:small_action])
|
|
expect(post_types).to include(types[:whisper])
|
|
end
|
|
end
|
|
|
|
context 'slug' do
|
|
let(:title) { "hello world topic" }
|
|
let(:slug) { "hello-world-topic" }
|
|
context 'encoded generator' do
|
|
before { SiteSetting.slug_generation_method = 'encoded' }
|
|
after { SiteSetting.slug_generation_method = 'ascii' }
|
|
|
|
it "returns a Slug for a title" do
|
|
Slug.expects(:for).with(title).returns(slug)
|
|
expect(Fabricate.build(:topic, title: title).slug).to eq(slug)
|
|
end
|
|
|
|
context 'for cjk characters' do
|
|
let(:title) { "熱帶風暴畫眉" }
|
|
let(:slug) { "熱帶風暴畫眉" }
|
|
it "returns encoded Slug for a title" do
|
|
Slug.expects(:for).with(title).returns(slug)
|
|
expect(Fabricate.build(:topic, title: title).slug).to eq(slug)
|
|
end
|
|
end
|
|
|
|
context 'for numbers' do
|
|
let(:title) { "123456789" }
|
|
let(:slug) { "topic" }
|
|
it 'generates default slug' do
|
|
Slug.expects(:for).with(title).returns("topic")
|
|
expect(Fabricate.build(:topic, title: title).slug).to eq("topic")
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'none generator' do
|
|
before { SiteSetting.slug_generation_method = 'none' }
|
|
after { SiteSetting.slug_generation_method = 'ascii' }
|
|
let(:title) { "熱帶風暴畫眉" }
|
|
let(:slug) { "topic" }
|
|
|
|
it "returns a Slug for a title" do
|
|
Slug.expects(:for).with(title).returns('topic')
|
|
expect(Fabricate.build(:topic, title: title).slug).to eq(slug)
|
|
end
|
|
end
|
|
|
|
context '#ascii_generator' do
|
|
before { SiteSetting.slug_generation_method = 'ascii' }
|
|
it "returns a Slug for a title" do
|
|
Slug.expects(:for).with(title).returns(slug)
|
|
expect(Fabricate.build(:topic, title: title).slug).to eq(slug)
|
|
end
|
|
|
|
context 'for cjk characters' do
|
|
let(:title) { "熱帶風暴畫眉" }
|
|
let(:slug) { 'topic' }
|
|
it "returns 'topic' when the slug is empty (say, non-latin characters)" do
|
|
Slug.expects(:for).with(title).returns("topic")
|
|
expect(Fabricate.build(:topic, title: title).slug).to eq("topic")
|
|
end
|
|
end
|
|
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'
|
|
expect(topic.save).to eq(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 'admin topic title' do
|
|
let(:admin) { Fabricate(:admin) }
|
|
|
|
it 'allows really short titles' do
|
|
pm = Fabricate.build(:private_message_topic, user: admin, title: 'a')
|
|
expect(pm).to be_valid
|
|
end
|
|
|
|
it 'but not blank' do
|
|
pm = Fabricate.build(:private_message_topic, title: '')
|
|
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
|
|
expect(new_topic).not_to 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
|
|
expect(new_topic).not_to be_valid
|
|
end
|
|
|
|
it "allows it when the topic is deleted" do
|
|
topic.destroy
|
|
expect(new_topic).to be_valid
|
|
end
|
|
|
|
it "allows a private message to be created with the same topic" do
|
|
new_topic.archetype = Archetype.private_message
|
|
expect(new_topic).to 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
|
|
expect(new_topic).to 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
|
|
expect(topic_script.fancy_title).to eq("Topic with <script>alert(‘title’)</script> script in its title")
|
|
end
|
|
|
|
it "escapes bold contents" do
|
|
expect(topic_bold.fancy_title).to eq("Topic with <b>bold</b> text in its title")
|
|
end
|
|
|
|
it "escapes image contents" do
|
|
expect(topic_image.fancy_title).to eq("Topic with <img src=‘something’> 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.title_fancy_entities = false
|
|
end
|
|
|
|
it "doesn't add entities to the title" do
|
|
expect(topic.fancy_title).to eq(""this topic" -- has ``fancy stuff''")
|
|
end
|
|
end
|
|
|
|
context 'title_fancy_entities enabled' do
|
|
before do
|
|
SiteSetting.title_fancy_entities = true
|
|
end
|
|
|
|
it "converts the title to have fancy entities and updates" do
|
|
expect(topic.fancy_title).to eq("“this topic” – has “fancy stuff”")
|
|
topic.title = "this is my test hello world... yay"
|
|
topic.user.save!
|
|
topic.save!
|
|
topic.reload
|
|
expect(topic.fancy_title).to eq("This is my test hello world… yay")
|
|
|
|
topic.title = "I made a change to the title"
|
|
topic.save!
|
|
|
|
topic.reload
|
|
expect(topic.fancy_title).to eq("I made a change to the title")
|
|
|
|
# another edge case
|
|
topic.title = "this is another edge case"
|
|
expect(topic.fancy_title).to eq("this is another edge case")
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'category validation' do
|
|
context 'allow_uncategorized_topics is false' do
|
|
before do
|
|
SiteSetting.stubs(:allow_uncategorized_topics).returns(false)
|
|
end
|
|
|
|
it "does not allow nil category" do
|
|
topic = Fabricate.build(:topic, category: nil)
|
|
expect(topic).not_to be_valid
|
|
expect(topic.errors[:category_id]).to be_present
|
|
end
|
|
|
|
it "allows PMs" do
|
|
topic = Fabricate.build(:topic, category: nil, archetype: Archetype.private_message)
|
|
expect(topic).to be_valid
|
|
end
|
|
|
|
it 'passes for topics with a category' do
|
|
expect(Fabricate.build(:topic, category: Fabricate(:category))).to be_valid
|
|
end
|
|
end
|
|
|
|
context 'allow_uncategorized_topics is true' do
|
|
before do
|
|
SiteSetting.stubs(:allow_uncategorized_topics).returns(true)
|
|
end
|
|
|
|
it "passes for topics with nil category" do
|
|
expect(Fabricate.build(:topic, category: nil)).to be_valid
|
|
end
|
|
|
|
it 'passes for topics with a category' do
|
|
expect(Fabricate.build(:topic, category: Fabricate(:category))).to be_valid
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
context 'similar_to' do
|
|
|
|
it 'returns blank with nil params' do
|
|
expect(Topic.similar_to(nil, nil)).to be_blank
|
|
end
|
|
|
|
context "with a category definition" do
|
|
let!(:category) { Fabricate(:category) }
|
|
|
|
it "excludes the category definition topic from similar_to" do
|
|
expect(Topic.similar_to('category definition for', "no body")).to be_blank
|
|
end
|
|
end
|
|
|
|
context 'with a similar topic' do
|
|
let!(:topic) {
|
|
ActiveRecord::Base.observers.enable :search_observer
|
|
post = create_post(title: "Evil trout is the dude who posted this topic")
|
|
post.topic
|
|
}
|
|
|
|
it 'returns the similar topic if the title is similar' do
|
|
expect(Topic.similar_to("has evil trout made any topics?", "i am wondering has evil trout made any topics?")).to eq([topic])
|
|
end
|
|
|
|
context "secure categories" do
|
|
|
|
let(:user) { Fabricate(:user) }
|
|
let(:category) { Fabricate(:category, read_restricted: true) }
|
|
|
|
before do
|
|
topic.category = category
|
|
topic.save
|
|
end
|
|
|
|
it "doesn't return topics from private categories" do
|
|
expect(Topic.similar_to("has evil trout made any topics?", "i am wondering has evil trout made any topics?", user)).to be_blank
|
|
end
|
|
|
|
it "should return the cat since the user can see it" do
|
|
Guardian.any_instance.expects(:secure_category_ids).returns([category.id])
|
|
expect(Topic.similar_to("has evil trout made any topics?", "i am wondering has evil trout made any topics?", user)).to include(topic)
|
|
end
|
|
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
|
|
expect(topic.post_numbers).to eq([1, 2, 3])
|
|
p2.destroy
|
|
topic.reload
|
|
expect(topic.post_numbers).to eq([1, 3])
|
|
end
|
|
|
|
end
|
|
|
|
|
|
context 'private message' do
|
|
let(:coding_horror) { User.find_by(username: "CodingHorror") }
|
|
let(:evil_trout) { Fabricate(:evil_trout) }
|
|
let(:topic) { Fabricate(:private_message_topic) }
|
|
|
|
it "should integrate correctly" do
|
|
expect(Guardian.new(topic.user).can_see?(topic)).to eq(true)
|
|
expect(Guardian.new.can_see?(topic)).to eq(false)
|
|
expect(Guardian.new(evil_trout).can_see?(topic)).to eq(false)
|
|
expect(Guardian.new(coding_horror).can_see?(topic)).to eq(true)
|
|
expect(TopicQuery.new(evil_trout).list_latest.topics).not_to include(topic)
|
|
|
|
# invites
|
|
expect(topic.invite(topic.user, 'duhhhhh')).to eq(false)
|
|
end
|
|
|
|
context 'invite' do
|
|
|
|
context 'existing user' do
|
|
let(:walter) { Fabricate(:walter_white) }
|
|
|
|
context 'by username' do
|
|
|
|
it 'adds and removes walter to the allowed users' do
|
|
expect(topic.invite(topic.user, walter.username)).to eq(true)
|
|
expect(topic.allowed_users.include?(walter)).to eq(true)
|
|
|
|
expect(topic.remove_allowed_user(walter.username)).to eq(true)
|
|
topic.reload
|
|
expect(topic.allowed_users.include?(walter)).to eq(false)
|
|
end
|
|
|
|
it 'creates a notification' do
|
|
expect { topic.invite(topic.user, walter.username) }.to change(Notification, :count)
|
|
end
|
|
end
|
|
|
|
context 'by email' do
|
|
|
|
it 'adds user correctly' do
|
|
expect {
|
|
expect(topic.invite(topic.user, walter.email)).to eq(true)
|
|
}.to change(Notification, :count)
|
|
expect(topic.allowed_users.include?(walter)).to eq(true)
|
|
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
|
|
|
|
expect(actions.map{|a| a.action_type}).not_to include(UserAction::NEW_TOPIC)
|
|
expect(actions.map{|a| a.action_type}).to include(UserAction::NEW_PRIVATE_MESSAGE)
|
|
expect(coding_horror.user_actions.map{|a| a.action_type}).to include(UserAction::GOT_PRIVATE_MESSAGE)
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
it "rate limits topic invitations" do
|
|
SiteSetting.stubs(:max_topic_invitations_per_day).returns(2)
|
|
RateLimiter.stubs(:disabled?).returns(false)
|
|
RateLimiter.clear_all!
|
|
|
|
start = Time.now.tomorrow.beginning_of_day
|
|
freeze_time(start)
|
|
|
|
user = Fabricate(:user)
|
|
topic = Fabricate(:topic)
|
|
|
|
freeze_time(start + 10.minutes)
|
|
topic.invite(topic.user, user.username)
|
|
|
|
freeze_time(start + 20.minutes)
|
|
topic.invite(topic.user, "walter@white.com")
|
|
|
|
freeze_time(start + 30.minutes)
|
|
|
|
expect {
|
|
topic.invite(topic.user, "user@example.com")
|
|
}.to raise_exception
|
|
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
|
|
expect(@topic.bumped_at).to be_present
|
|
expect {
|
|
create_post(topic: @topic, user: @topic.user)
|
|
@topic.reload
|
|
}.to 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
|
|
expect {
|
|
SiteSetting.expects(:ninja_edit_window).returns(5.minutes)
|
|
@last_post.revise(@last_post.user, { raw: 'updated contents' }, revised_at: @last_post.created_at + 10.seconds)
|
|
@topic.reload
|
|
}.not_to change(@topic, :bumped_at)
|
|
end
|
|
|
|
it "bumps the topic when a new version is made of the last post" do
|
|
expect {
|
|
@last_post.revise(Fabricate(:moderator), { raw: 'updated contents' })
|
|
@topic.reload
|
|
}.to 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
|
|
expect {
|
|
@earlier_post.revise(Fabricate(:moderator), { raw: 'updated contents' })
|
|
@topic.reload
|
|
}.not_to 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
|
|
expect(@mod_post).to be_present
|
|
expect(@mod_post.post_type).to eq(Post.types[:moderator_action])
|
|
expect(@mod_post.post_number).to eq(999)
|
|
expect(@mod_post.sort_order).to eq(999)
|
|
expect(@topic.topic_links.count).to eq(1)
|
|
@topic.reload
|
|
expect(@topic.moderator_posts_count).to eq(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
|
|
expect(@topic).not_to be_visible
|
|
expect(@topic.moderator_posts_count).to eq(1)
|
|
expect(@topic.bumped_at.to_f).to eq(@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
|
|
expect(@topic).to be_visible
|
|
expect(@topic.moderator_posts_count).to eq(1)
|
|
expect(@topic.bumped_at.to_f).to eq(@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
|
|
expect(@topic.pinned_at).to be_blank
|
|
expect(@topic.moderator_posts_count).to eq(1)
|
|
expect(@topic.bumped_at.to_f).to eq(@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
|
|
expect(@topic.pinned_at).to be_present
|
|
expect(@topic.bumped_at.to_f).to eq(@original_bumped_at)
|
|
expect(@topic.moderator_posts_count).to eq(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
|
|
expect(@topic).not_to be_archived
|
|
expect(@topic.bumped_at.to_f).to eq(@original_bumped_at)
|
|
expect(@topic.moderator_posts_count).to eq(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
|
|
expect(@topic).to be_archived
|
|
expect(@topic.moderator_posts_count).to eq(1)
|
|
expect(@topic.bumped_at.to_f).to eq(@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
|
|
expect(@topic).not_to be_closed
|
|
expect(@topic.moderator_posts_count).to eq(1)
|
|
expect(@topic.bumped_at.to_f).not_to eq(@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
|
|
expect(@topic).to be_closed
|
|
expect(@topic.bumped_at.to_f).to eq(@original_bumped_at)
|
|
expect(@topic.moderator_posts_count).to eq(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'
|
|
|
|
context 'topic was set to close when it was created' do
|
|
it 'puts the autoclose duration in the moderator post' do
|
|
freeze_time(Time.new(2000,1,1)) 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
|
|
|
|
context 'topic was set to close after it was created' do
|
|
it 'puts the autoclose duration in the moderator post' do
|
|
freeze_time(Time.new(2000,1,1)) do
|
|
@topic.created_at = 7.days.ago
|
|
freeze_time(2.days.ago) do
|
|
@topic.set_auto_close(48)
|
|
end
|
|
@topic.update_status(status, true, @user)
|
|
expect(@topic.posts.last.raw).to include "closed after 2 days"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "banner" do
|
|
|
|
let(:topic) { Fabricate(:topic) }
|
|
let(:user) { topic.user }
|
|
let(:banner) { { html: "<p>BANNER</p>", url: topic.url, key: topic.id } }
|
|
|
|
before { topic.stubs(:banner).returns(banner) }
|
|
|
|
describe "make_banner!" do
|
|
|
|
it "changes the topic archetype to 'banner'" do
|
|
messages = MessageBus.track_publish do
|
|
topic.make_banner!(user)
|
|
expect(topic.archetype).to eq(Archetype.banner)
|
|
end
|
|
|
|
channels = messages.map(&:channel)
|
|
expect(channels).to include('/site/banner')
|
|
expect(channels).to include('/distributed_hash')
|
|
end
|
|
|
|
it "ensures only one banner topic at all time" do
|
|
_banner_topic = Fabricate(:banner_topic)
|
|
expect(Topic.where(archetype: Archetype.banner).count).to eq(1)
|
|
|
|
topic.make_banner!(user)
|
|
expect(Topic.where(archetype: Archetype.banner).count).to eq(1)
|
|
end
|
|
|
|
end
|
|
|
|
describe "remove_banner!" do
|
|
|
|
it "resets the topic archetype" do
|
|
topic.expects(:add_moderator_post)
|
|
MessageBus.expects(:publish).with("/site/banner", nil)
|
|
topic.remove_banner!(user)
|
|
expect(topic.archetype).to eq(Archetype.default)
|
|
end
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
context 'last_poster info' do
|
|
|
|
before do
|
|
@post = create_post
|
|
@user = @post.user
|
|
@topic = @post.topic
|
|
end
|
|
|
|
it 'initially has the last_post_user_id of the OP' do
|
|
expect(@topic.last_post_user_id).to eq(@user.id)
|
|
end
|
|
|
|
context 'after a second post' do
|
|
before do
|
|
@second_user = Fabricate(:coding_horror)
|
|
@new_post = create_post(topic: @topic, user: @second_user)
|
|
@topic.reload
|
|
end
|
|
|
|
it 'updates the last_post_user_id to the second_user' do
|
|
expect(@topic.last_post_user_id).to eq(@second_user.id)
|
|
expect(@topic.last_posted_at.to_i).to eq(@new_post.created_at.to_i)
|
|
topic_user = @second_user.topic_users.find_by(topic_id: @topic.id)
|
|
expect(topic_user.posted?).to eq(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
|
|
expect { Fabricate(:topic, user: @category.user); @category.reload }.not_to change(@category, :topic_count)
|
|
end
|
|
|
|
it "should increase the category's topic_count" do
|
|
expect { Fabricate(:topic, user: @category.user, category_id: @category.id); @category.reload }.to 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
|
|
expect(topic.meta_data['hello']).to eq('world')
|
|
end
|
|
|
|
context 'updating' do
|
|
|
|
context 'existing key' do
|
|
before do
|
|
topic.update_meta_data('hello' => 'bane')
|
|
end
|
|
|
|
it 'updates the key' do
|
|
expect(topic.meta_data['hello']).to eq('bane')
|
|
end
|
|
end
|
|
|
|
context 'new key' do
|
|
before do
|
|
topic.update_meta_data('city' => 'gotham')
|
|
end
|
|
|
|
it 'adds the new key' do
|
|
expect(topic.meta_data['city']).to eq('gotham')
|
|
expect(topic.meta_data['hello']).to eq('world')
|
|
end
|
|
|
|
end
|
|
|
|
context 'new key' do
|
|
before do
|
|
topic.update_meta_data('other' => 'key')
|
|
topic.save!
|
|
end
|
|
|
|
it "can be loaded" do
|
|
expect(Topic.find(topic.id).meta_data["other"]).to eq("key")
|
|
end
|
|
|
|
it "is in sync with custom_fields" do
|
|
expect(Topic.find(topic.id).custom_fields["other"]).to eq("key")
|
|
end
|
|
end
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
describe 'after create' do
|
|
|
|
let(:topic) { Fabricate(:topic) }
|
|
|
|
it 'is a regular topic by default' do
|
|
expect(topic.archetype).to eq(Archetype.default)
|
|
expect(topic.has_summary).to eq(false)
|
|
expect(topic.percent_rank).to eq(1.0)
|
|
expect(topic).to be_visible
|
|
expect(topic.pinned_at).to be_blank
|
|
expect(topic).not_to be_closed
|
|
expect(topic).not_to be_archived
|
|
expect(topic.moderator_posts_count).to eq(0)
|
|
end
|
|
|
|
context 'post' do
|
|
let(:post) { Fabricate(:post, topic: topic, user: topic.user) }
|
|
|
|
it 'has the same archetype as the topic' do
|
|
expect(post.archetype).to eq(topic.archetype)
|
|
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
|
|
expect { @topic.change_category_to_id(@topic.category.id); @category.reload }.not_to change(@category, :topic_count)
|
|
end
|
|
|
|
describe 'changed category' do
|
|
before do
|
|
@topic.change_category_to_id(@category.id)
|
|
@category.reload
|
|
end
|
|
|
|
it 'changes the category' do
|
|
expect(@topic.category).to eq(@category)
|
|
expect(@category.topic_count).to eq(1)
|
|
end
|
|
|
|
end
|
|
|
|
it "doesn't change the category when it can't be found" do
|
|
@topic.change_category_to_id(12312312)
|
|
expect(@topic.category_id).to eq(SiteSetting.uncategorized_category_id)
|
|
end
|
|
end
|
|
|
|
describe 'with a previous category' do
|
|
before do
|
|
@topic.change_category_to_id(@category.id)
|
|
@topic.reload
|
|
@category.reload
|
|
end
|
|
|
|
it 'increases the topic_count' do
|
|
expect(@category.topic_count).to eq(1)
|
|
end
|
|
|
|
it "doesn't change the topic_count when the value doesn't change" do
|
|
expect { @topic.change_category_to_id(@category.id); @category.reload }.not_to change(@category, :topic_count)
|
|
end
|
|
|
|
it "doesn't reset the category when given a name that doesn't exist" do
|
|
@topic.change_category_to_id(55556)
|
|
expect(@topic.category_id).to be_present
|
|
end
|
|
|
|
describe 'to a different category' do
|
|
before do
|
|
@new_category = Fabricate(:category, user: @user, name: '2nd category')
|
|
@topic.change_category_to_id(@new_category.id)
|
|
@topic.reload
|
|
@new_category.reload
|
|
@category.reload
|
|
end
|
|
|
|
it "should increase the new category's topic count" do
|
|
expect(@new_category.topic_count).to eq(1)
|
|
end
|
|
|
|
it "should lower the original category's topic count" do
|
|
expect(@category.topic_count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context 'when allow_uncategorized_topics is false' do
|
|
before do
|
|
SiteSetting.stubs(:allow_uncategorized_topics).returns(false)
|
|
end
|
|
|
|
let!(:topic) { Fabricate(:topic, category: Fabricate(:category)) }
|
|
|
|
it 'returns false' do
|
|
expect(topic.change_category_to_id(nil)).to eq(false) # don't use "== false" here because it would also match nil
|
|
end
|
|
end
|
|
|
|
describe 'when the category exists' do
|
|
before do
|
|
@topic.change_category_to_id(nil)
|
|
@category.reload
|
|
end
|
|
|
|
it "resets the category" do
|
|
expect(@topic.category_id).to eq(SiteSetting.uncategorized_category_id)
|
|
expect(@category.topic_count).to eq(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)
|
|
expect(Topic.by_newest).to eq([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)
|
|
expect(Topic.created_since(now)).not_to include a
|
|
expect(Topic.created_since(now)).not_to include b
|
|
expect(Topic.created_since(now)).not_to include c
|
|
expect(Topic.created_since(now)).to include d
|
|
expect(Topic.created_since(now)).to 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)
|
|
expect(Topic.visible).not_to include a
|
|
expect(Topic.visible).to include b
|
|
expect(Topic.visible).to 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(now) do
|
|
Jobs.expects(:enqueue_at).with(7.hours.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
|
|
topic = Fabricate(:topic, user: Fabricate(:admin))
|
|
topic.set_auto_close(7).save
|
|
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
|
|
topic = Fabricate(:topic, user: topic_creator)
|
|
topic.set_auto_close(7).save
|
|
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
|
|
topic = Fabricate(:topic, user: topic_creator)
|
|
topic.set_auto_close(7, {by_user: topic_closer}).save
|
|
end
|
|
|
|
it "ignores the category's default auto-close" do
|
|
Timecop.freeze(now) do
|
|
Jobs.expects(:enqueue_at).with(7.hours.from_now, :close_topic, all_of( has_key(:topic_id), has_key(:user_id) ))
|
|
topic = Fabricate(:topic, user: Fabricate(:admin), ignore_category_auto_close: true, category_id: Fabricate(:category, auto_close_hours: 2).id)
|
|
topic.set_auto_close(7).save
|
|
end
|
|
end
|
|
|
|
it 'sets the time when auto_close timer starts' do
|
|
Timecop.freeze(now) do
|
|
topic = Fabricate(:topic, user: Fabricate(:admin))
|
|
topic.set_auto_close(7).save
|
|
expect(topic.auto_close_started_at).to eq(now)
|
|
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(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
|
|
expect(topic.save).to eq(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(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
|
|
expect(topic.save).to eq(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
|
|
expect(topic.save).to eq(true)
|
|
expect(topic.auto_close_user).to eq(nil)
|
|
end
|
|
|
|
it 'when auto_close_user is removed, it updates the job' do
|
|
Timecop.freeze(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
|
|
expect(topic.save).to eq(true)
|
|
end
|
|
end
|
|
|
|
it 'when auto_close_at value is changed, it reschedules the job' do
|
|
Timecop.freeze(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
|
|
expect(topic.save).to eq(true)
|
|
end
|
|
end
|
|
|
|
it 'when auto_close_user_id is changed, it updates the job' do
|
|
Timecop.freeze(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
|
|
expect(topic.save).to eq(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(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'
|
|
expect(topic.save).to eq(true)
|
|
end
|
|
end
|
|
|
|
it "ignores the category's default auto-close" do
|
|
Timecop.freeze(now) do
|
|
mod = Fabricate(:moderator)
|
|
# NOTE, only moderators can auto-close, if missing system user is used
|
|
topic = Fabricate(:topic, category: Fabricate(:category, auto_close_hours: 14), user: mod)
|
|
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
|
|
|
|
topic.reload
|
|
expect(topic.closed).to eq(false)
|
|
|
|
Timecop.freeze(24.hours.from_now) do
|
|
Topic.auto_close
|
|
topic.reload
|
|
expect(topic.closed).to eq(true)
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'set_auto_close' do
|
|
let(:topic) { Fabricate.build(:topic) }
|
|
let(:closing_topic) { Fabricate.build(:topic, auto_close_hours: 5, auto_close_at: 5.hours.from_now, auto_close_started_at: 5.hours.from_now) }
|
|
let(:admin) { Fabricate.build(:user, id: 123) }
|
|
let(:trust_level_4) { Fabricate.build(:trust_level_4) }
|
|
|
|
before { Discourse.stubs(:system_user).returns(admin) }
|
|
|
|
it 'can take a number of hours as an integer' do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close(72, {by_user: admin})
|
|
expect(topic.auto_close_at).to eq(3.days.from_now)
|
|
end
|
|
end
|
|
|
|
it 'can take a number of hours as an integer, with timezone offset' do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close(72, {by_user: admin, timezone_offset: 240})
|
|
expect(topic.auto_close_at).to eq(3.days.from_now)
|
|
end
|
|
end
|
|
|
|
it 'can take a number of hours as a string' do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('18', {by_user: admin})
|
|
expect(topic.auto_close_at).to eq(18.hours.from_now)
|
|
end
|
|
end
|
|
|
|
it 'can take a number of hours as a string, with timezone offset' do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('18', {by_user: admin, timezone_offset: 240})
|
|
expect(topic.auto_close_at).to eq(18.hours.from_now)
|
|
end
|
|
end
|
|
|
|
it "can take a time later in the day" do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('13:00', {by_user: admin})
|
|
expect(topic.auto_close_at).to eq(Time.zone.local(2013,11,20,13,0))
|
|
end
|
|
end
|
|
|
|
it "can take a time later in the day, with timezone offset" do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('13:00', {by_user: admin, timezone_offset: 240})
|
|
expect(topic.auto_close_at).to eq(Time.zone.local(2013,11,20,17,0))
|
|
end
|
|
end
|
|
|
|
it "can take a time for the next day" do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('5:00', {by_user: admin})
|
|
expect(topic.auto_close_at).to eq(Time.zone.local(2013,11,21,5,0))
|
|
end
|
|
end
|
|
|
|
it "can take a time for the next day, with timezone offset" do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('1:00', {by_user: admin, timezone_offset: 240})
|
|
expect(topic.auto_close_at).to eq(Time.zone.local(2013,11,21,5,0))
|
|
end
|
|
end
|
|
|
|
it "can take a timestamp for a future time" do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('2013-11-22 5:00', {by_user: admin})
|
|
expect(topic.auto_close_at).to eq(Time.zone.local(2013,11,22,5,0))
|
|
end
|
|
end
|
|
|
|
it "can take a timestamp for a future time, with timezone offset" do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('2013-11-22 5:00', {by_user: admin, timezone_offset: 240})
|
|
expect(topic.auto_close_at).to eq(Time.zone.local(2013,11,22,9,0))
|
|
end
|
|
end
|
|
|
|
it "sets a validation error when given a timestamp in the past" do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('2013-11-19 5:00', {by_user: admin})
|
|
expect(topic.auto_close_at).to eq(Time.zone.local(2013,11,19,5,0))
|
|
expect(topic.errors[:auto_close_at]).to be_present
|
|
end
|
|
end
|
|
|
|
it "can take a timestamp with timezone" do
|
|
Timecop.freeze(now) do
|
|
topic.set_auto_close('2013-11-25T01:35:00-08:00', {by_user: admin})
|
|
expect(topic.auto_close_at).to eq(Time.utc(2013,11,25,9,35))
|
|
end
|
|
end
|
|
|
|
it 'sets auto_close_user to given user if it is a staff or TL4 user' do
|
|
topic.set_auto_close(3, {by_user: admin})
|
|
expect(topic.auto_close_user_id).to eq(admin.id)
|
|
end
|
|
|
|
it 'sets auto_close_user to given user if it is a TL4 user' do
|
|
topic.set_auto_close(3, {by_user: trust_level_4})
|
|
expect(topic.auto_close_user_id).to eq(trust_level_4.id)
|
|
end
|
|
|
|
it 'sets auto_close_user to system user if given user is not staff or a TL4 user' do
|
|
topic.set_auto_close(3, {by_user: Fabricate.build(:user, id: 444)})
|
|
expect(topic.auto_close_user_id).to eq(admin.id)
|
|
end
|
|
|
|
it 'sets auto_close_user to system user if user is not given and topic creator is not staff nor TL4 user' do
|
|
topic.set_auto_close(3)
|
|
expect(topic.auto_close_user_id).to eq(admin.id)
|
|
end
|
|
|
|
it 'sets auto_close_user to topic creator if it is a staff user' do
|
|
staff_topic = Fabricate.build(:topic, user: Fabricate.build(:admin, id: 999))
|
|
staff_topic.set_auto_close(3)
|
|
expect(staff_topic.auto_close_user_id).to eq(999)
|
|
end
|
|
|
|
it 'sets auto_close_user to topic creator if it is a TL4 user' do
|
|
tl4_topic = Fabricate.build(:topic, user: Fabricate.build(:trust_level_4, id: 998))
|
|
tl4_topic.set_auto_close(3)
|
|
expect(tl4_topic.auto_close_user_id).to eq(998)
|
|
end
|
|
|
|
it 'clears auto_close_at if arg is nil' do
|
|
closing_topic.set_auto_close(nil)
|
|
expect(closing_topic.auto_close_at).to be_nil
|
|
end
|
|
|
|
it 'clears auto_close_started_at if arg is nil' do
|
|
closing_topic.set_auto_close(nil)
|
|
expect(closing_topic.auto_close_started_at).to be_nil
|
|
end
|
|
|
|
it 'updates auto_close_at if it was already set to close' do
|
|
Timecop.freeze(now) do
|
|
closing_topic.set_auto_close(48)
|
|
expect(closing_topic.auto_close_at).to eq(2.days.from_now)
|
|
end
|
|
end
|
|
|
|
it 'does not update auto_close_started_at if it was already set to close' do
|
|
expect{
|
|
closing_topic.set_auto_close(14)
|
|
}.to_not change(closing_topic, :auto_close_started_at)
|
|
end
|
|
end
|
|
|
|
describe 'for_digest' do
|
|
let(:user) { Fabricate.build(:user) }
|
|
|
|
it "returns none when there are no topics" do
|
|
expect(Topic.for_digest(user, 1.year.ago, top_order: true)).to be_blank
|
|
end
|
|
|
|
it "doesn't return category topics" do
|
|
Fabricate(:category)
|
|
expect(Topic.for_digest(user, 1.year.ago, top_order: true)).to be_blank
|
|
end
|
|
|
|
it "returns regular topics" do
|
|
topic = Fabricate(:topic)
|
|
expect(Topic.for_digest(user, 1.year.ago, top_order: true)).to eq([topic])
|
|
end
|
|
|
|
it "doesn't return topics from muted categories" do
|
|
user = Fabricate(:user)
|
|
category = Fabricate(:category)
|
|
Fabricate(:topic, category: category)
|
|
|
|
CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[:muted], category.id)
|
|
|
|
expect(Topic.for_digest(user, 1.year.ago, top_order: true)).to be_blank
|
|
end
|
|
|
|
it "doesn't return topics from TL0 users" do
|
|
new_user = Fabricate(:user, trust_level: 0)
|
|
Fabricate(:topic, user_id: new_user.id)
|
|
|
|
expect(Topic.for_digest(user, 1.year.ago, top_order: true)).to be_blank
|
|
end
|
|
|
|
end
|
|
|
|
describe 'secured' do
|
|
it 'can remove secure groups' do
|
|
category = Fabricate(:category, read_restricted: true)
|
|
Fabricate(:topic, category: category)
|
|
|
|
expect(Topic.secured(Guardian.new(nil)).count).to eq(0)
|
|
expect(Topic.secured(Guardian.new(Fabricate(:admin))).count).to eq(2)
|
|
|
|
# for_digest
|
|
|
|
expect(Topic.for_digest(Fabricate(:user), 1.year.ago).count).to eq(0)
|
|
expect(Topic.for_digest(Fabricate(:admin), 1.year.ago).count).to eq(1)
|
|
end
|
|
end
|
|
|
|
describe 'all_allowed_users' do
|
|
let(:group) { Fabricate(:group) }
|
|
let(:topic) { Fabricate(:topic, allowed_groups: [group]) }
|
|
let!(:allowed_user) { Fabricate(:user) }
|
|
let!(:allowed_group_user) { Fabricate(:user) }
|
|
let!(:moderator) { Fabricate(:user, moderator: true) }
|
|
let!(:rando) { Fabricate(:user) }
|
|
|
|
before do
|
|
topic.allowed_users << allowed_user
|
|
group.users << allowed_group_user
|
|
end
|
|
|
|
it 'includes allowed_users' do
|
|
expect(topic.all_allowed_users).to include allowed_user
|
|
end
|
|
|
|
it 'includes allowed_group_users' do
|
|
expect(topic.all_allowed_users).to include allowed_group_user
|
|
end
|
|
|
|
it 'includes moderators if flagged and a pm' do
|
|
topic.stubs(:has_flags?).returns(true)
|
|
topic.stubs(:private_message?).returns(true)
|
|
expect(topic.all_allowed_users).to include moderator
|
|
end
|
|
|
|
it 'does not include moderators if pm without flags' do
|
|
topic.stubs(:private_message?).returns(true)
|
|
expect(topic.all_allowed_users).not_to include moderator
|
|
end
|
|
|
|
it 'does not include moderators for regular topic' do
|
|
expect(topic.all_allowed_users).not_to include moderator
|
|
end
|
|
|
|
it 'does not include randos' do
|
|
expect(topic.all_allowed_users).not_to include rando
|
|
end
|
|
end
|
|
|
|
describe '#listable_count_per_day' do
|
|
before(:each) do
|
|
Timecop.freeze
|
|
Fabricate(:topic)
|
|
Fabricate(:topic, created_at: 1.day.ago)
|
|
Fabricate(:topic, created_at: 1.day.ago)
|
|
Fabricate(:topic, created_at: 2.days.ago)
|
|
Fabricate(:topic, created_at: 4.days.ago)
|
|
end
|
|
after(:each) do
|
|
Timecop.return
|
|
end
|
|
let(:listable_topics_count_per_day) { {1.day.ago.to_date => 2, 2.days.ago.to_date => 1, Time.now.utc.to_date => 1 } }
|
|
|
|
it 'collect closed interval listable topics count' do
|
|
expect(Topic.listable_count_per_day(2.days.ago, Time.now)).to include(listable_topics_count_per_day)
|
|
expect(Topic.listable_count_per_day(2.days.ago, Time.now)).not_to include({4.days.ago.to_date => 1})
|
|
end
|
|
end
|
|
|
|
describe '#secure_category?' do
|
|
let(:category){ Category.new }
|
|
|
|
it "is true if the category is secure" do
|
|
category.stubs(:read_restricted).returns(true)
|
|
expect(Topic.new(:category => category)).to be_read_restricted_category
|
|
end
|
|
|
|
it "is false if the category is not secure" do
|
|
category.stubs(:read_restricted).returns(false)
|
|
expect(Topic.new(:category => category)).not_to be_read_restricted_category
|
|
end
|
|
|
|
it "is false if there is no category" do
|
|
expect(Topic.new(:category => nil)).not_to be_read_restricted_category
|
|
end
|
|
end
|
|
|
|
describe 'trash!' do
|
|
context "its category's topic count" do
|
|
let(:moderator) { Fabricate(:moderator) }
|
|
let(:category) { Fabricate(:category) }
|
|
|
|
it "subtracts 1 if topic is being deleted" do
|
|
topic = Fabricate(:topic, category: category)
|
|
expect { topic.trash!(moderator) }.to change { category.reload.topic_count }.by(-1)
|
|
end
|
|
|
|
it "doesn't subtract 1 if topic is already deleted" do
|
|
topic = Fabricate(:topic, category: category, deleted_at: 1.day.ago)
|
|
expect { topic.trash!(moderator) }.to_not change { category.reload.topic_count }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'recover!' do
|
|
context "its category's topic count" do
|
|
let(:category) { Fabricate(:category) }
|
|
|
|
it "adds 1 if topic is deleted" do
|
|
topic = Fabricate(:topic, category: category, deleted_at: 1.day.ago)
|
|
expect { topic.recover! }.to change { category.reload.topic_count }.by(1)
|
|
end
|
|
|
|
it "doesn't add 1 if topic is not deleted" do
|
|
topic = Fabricate(:topic, category: category)
|
|
expect { topic.recover! }.to_not change { category.reload.topic_count }
|
|
end
|
|
end
|
|
end
|
|
|
|
it "limits new users to max_topics_in_first_day and max_posts_in_first_day" do
|
|
SiteSetting.stubs(:max_topics_in_first_day).returns(1)
|
|
SiteSetting.stubs(:max_replies_in_first_day).returns(1)
|
|
SiteSetting.stubs(:client_settings_json).returns(SiteSetting.client_settings_json_uncached)
|
|
RateLimiter.stubs(:rate_limit_create_topic).returns(100)
|
|
RateLimiter.stubs(:disabled?).returns(false)
|
|
RateLimiter.clear_all!
|
|
|
|
start = Time.now.tomorrow.beginning_of_day
|
|
|
|
freeze_time(start)
|
|
|
|
user = Fabricate(:user)
|
|
topic_id = create_post(user: user).topic_id
|
|
|
|
freeze_time(start + 10.minutes)
|
|
expect {
|
|
create_post(user: user)
|
|
}.to raise_exception
|
|
|
|
freeze_time(start + 20.minutes)
|
|
create_post(user: user, topic_id: topic_id)
|
|
|
|
freeze_time(start + 30.minutes)
|
|
|
|
expect {
|
|
create_post(user: user, topic_id: topic_id)
|
|
}.to raise_exception
|
|
end
|
|
|
|
describe ".count_exceeds_minimun?" do
|
|
before { SiteSetting.stubs(:minimum_topics_similar).returns(20) }
|
|
|
|
context "when Topic count is geater than minimum_topics_similar" do
|
|
it "should be true" do
|
|
Topic.stubs(:count).returns(30)
|
|
expect(Topic.count_exceeds_minimum?).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "when topic's count is less than minimum_topics_similar" do
|
|
it "should be false" do
|
|
Topic.stubs(:count).returns(10)
|
|
expect(Topic.count_exceeds_minimum?).to_not be_truthy
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
describe "calculate_avg_time" do
|
|
it "does not explode" do
|
|
Topic.calculate_avg_time
|
|
Topic.calculate_avg_time(1.day.ago)
|
|
end
|
|
end
|
|
|
|
describe "expandable_first_post?" do
|
|
|
|
let(:topic) { Fabricate.build(:topic) }
|
|
|
|
it "is false if embeddable_host is blank" do
|
|
expect(topic.expandable_first_post?).to eq(false)
|
|
end
|
|
|
|
describe 'with an emeddable host' do
|
|
before do
|
|
Fabricate(:embeddable_host)
|
|
SiteSetting.embed_truncate = true
|
|
topic.stubs(:has_topic_embed?).returns(true)
|
|
end
|
|
|
|
it "is true with the correct settings and topic_embed" do
|
|
expect(topic.expandable_first_post?).to eq(true)
|
|
end
|
|
it "is false if embed_truncate? is false" do
|
|
SiteSetting.embed_truncate = false
|
|
expect(topic.expandable_first_post?).to eq(false)
|
|
end
|
|
|
|
it "is false if has_topic_embed? is false" do
|
|
topic.stubs(:has_topic_embed?).returns(false)
|
|
expect(topic.expandable_first_post?).to eq(false)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
it "has custom fields" do
|
|
topic = Fabricate(:topic)
|
|
expect(topic.custom_fields["a"]).to eq(nil)
|
|
|
|
topic.custom_fields["bob"] = "marley"
|
|
topic.custom_fields["jack"] = "black"
|
|
topic.save
|
|
|
|
topic = Topic.find(topic.id)
|
|
expect(topic.custom_fields).to eq({"bob" => "marley", "jack" => "black"})
|
|
end
|
|
|
|
it "doesn't validate the title again if it isn't changing" do
|
|
SiteSetting.stubs(:min_topic_title_length).returns(5)
|
|
topic = Fabricate(:topic, title: "Short")
|
|
expect(topic).to be_valid
|
|
|
|
SiteSetting.stubs(:min_topic_title_length).returns(15)
|
|
topic.last_posted_at = 1.minute.ago
|
|
expect(topic.save).to eq(true)
|
|
end
|
|
|
|
context 'invite by group manager' do
|
|
let(:group_manager) { Fabricate(:user) }
|
|
let(:group) { Fabricate(:group).tap { |g| g.add_owner(group_manager) } }
|
|
let(:private_category) { Fabricate(:private_category, group: group) }
|
|
let(:group_private_topic) { Fabricate(:topic, category: private_category, user: group_manager) }
|
|
|
|
context 'to an email' do
|
|
let(:randolph) { 'randolph@duke.ooo' }
|
|
|
|
it "should attach group to the invite" do
|
|
invite = group_private_topic.invite(group_manager, randolph)
|
|
expect(invite.groups).to eq([group])
|
|
end
|
|
end
|
|
|
|
# should work for an existing user - give access, send notification
|
|
context 'to an existing user' do
|
|
let(:walter) { Fabricate(:walter_white) }
|
|
|
|
it "should add user to the group" do
|
|
expect(Guardian.new(walter).can_see?(group_private_topic)).to be_falsey
|
|
invite = group_private_topic.invite(group_manager, walter.email)
|
|
expect(invite).to be_nil
|
|
expect(walter.groups).to include(group)
|
|
expect(Guardian.new(walter).can_see?(group_private_topic)).to be_truthy
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|