# encoding: utf-8
# frozen_string_literal: true
RSpec.describe Topic do
let(:now) { Time.zone.local(2013, 11, 20, 8, 0) }
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
fab!(:user1) { Fabricate(:user, refresh_auto_groups: true) }
fab!(:whisperers_group) { Fabricate(:group) }
fab!(:user2) { Fabricate(:user, groups: [whisperers_group]) }
fab!(:moderator)
fab!(:coding_horror)
fab!(:evil_trout)
fab!(:admin)
fab!(:group)
fab!(:trust_level_2)
it_behaves_like "it has custom fields"
describe "Validations" do
let(:topic) { Fabricate.build(:topic) }
describe "#featured_link" do
describe "when featured_link contains more than a URL" do
it "should not be valid" do
topic.featured_link = "http://meta.discourse.org TEST"
expect(topic).to_not be_valid
end
end
describe "when featured_link is a valid URL" do
it "should be valid" do
topic.featured_link = "http://meta.discourse.org"
expect(topic).to be_valid
end
end
end
describe "#external_id" do
describe "when external_id is too long" do
it "should not be valid" do
topic.external_id = "a" * (Topic::EXTERNAL_ID_MAX_LENGTH + 1)
expect(topic).to_not be_valid
end
end
describe "when external_id has invalid characters" do
it "should not be valid" do
topic.external_id = "a*&^!@()#"
expect(topic).to_not be_valid
end
end
describe "when external_id is an empty string" do
it "should not be valid" do
topic.external_id = ""
expect(topic).to_not be_valid
end
end
describe "when external_id has already been used" do
it "should not be valid" do
topic2 = Fabricate(:topic, external_id: "asdf")
topic.external_id = "asdf"
expect(topic).to_not be_valid
end
end
describe "when external_id is nil" do
it "should be valid" do
topic.external_id = nil
expect(topic).to be_valid
end
end
describe "when external_id is valid" do
it "should be valid" do
topic.external_id = "abc_123-ZXY"
expect(topic).to be_valid
end
end
end
describe "#title" do
it { is_expected.to validate_presence_of :title }
describe "censored words" do
after { Discourse.redis.flushdb }
describe "when title contains censored words" do
after { WordWatcher.clear_cache! }
it "should not be valid" do
%w[pineapple pen].each do |w|
Fabricate(:watched_word, word: w, action: WatchedWord.actions[:censor])
end
topic.title = "pen PinEapple apple pen is a complete sentence"
expect(topic).to_not be_valid
expect(topic.errors.full_messages.first).to include(
I18n.t("errors.messages.contains_censored_words", censored_words: "pen, pineapple"),
)
end
end
describe "titles with censored words not on boundaries" do
it "should be valid" do
Fabricate(:watched_word, word: "apple", action: WatchedWord.actions[:censor])
topic.title = "Pineapples are great fruit! Applebee's is a great restaurant"
expect(topic).to be_valid
end
end
describe "when title does not contain censored words" do
it "should be valid" do
topic.title = "The cake is a lie"
expect(topic).to be_valid
end
end
describe "escape special characters in censored words" do
before do
%w[co(onut coconut a**le].each do |w|
Fabricate(:watched_word, word: w, action: WatchedWord.actions[:censor])
end
end
it "should not be valid" do
topic.title = "I have a co(onut a**le"
expect(topic.valid?).to eq(false)
expect(topic.errors.full_messages.first).to include(
I18n.t("errors.messages.contains_censored_words", censored_words: "co(onut, a**le"),
)
end
end
end
describe "blocked words" do
describe "when title contains watched words" do
after { WordWatcher.clear_cache! }
it "should not be valid" do
Fabricate(:watched_word, word: "pineapple", action: WatchedWord.actions[:block])
topic.title = "pen PinEapple apple pen is a complete sentence"
expect(topic).to_not be_valid
expect(topic.errors.full_messages.first).to include(
I18n.t("contains_blocked_word", word: "PinEapple"),
)
end
end
end
end
end
it { is_expected.to rate_limit }
describe "#shared_draft?" do
fab!(:topic)
context "when topic does not have a shared draft record" do
it { expect(topic).not_to be_shared_draft }
end
context "when topic has a shared draft record" do
before { Fabricate(:shared_draft, topic: topic) }
it { expect(topic).to be_shared_draft }
end
end
describe "#visible_post_types" do
let(:types) { Post.types }
before do
SiteSetting.whispers_allowed_groups = "#{Group::AUTO_GROUPS[:staff]}|#{whisperers_group.id}"
end
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(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
it "returns the appropriate types for whisperer users" do
post_types = Topic.visible_post_types(user2)
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
describe "slug" do
context "with encoded generator" do
before { SiteSetting.slug_generation_method = "encoded" }
context "with ascii letters" do
let!(:title) { "hello world topic" }
let!(:slug) { "hello-world-topic" }
let!(:topic) { Fabricate.build(:topic, title: title) }
it "returns a Slug for a title" do
expect(topic.title).to eq(title)
expect(topic.slug).to eq(slug)
end
end
context "for cjk characters" do
let!(:title) { "熱帶風暴畫眉" }
let!(:topic) { Fabricate.build(:topic, title: title) }
it "returns encoded Slug for a title" do
expect(topic.title).to eq(title)
expect(topic.slug).to eq("%E7%86%B1%E5%B8%B6%E9%A2%A8%E6%9A%B4%E7%95%AB%E7%9C%89")
end
end
context "for numbers" do
let!(:title) { "123456789" }
let!(:slug) { "topic" }
let!(:topic) { Fabricate.build(:topic, title: title) }
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 "with none generator" do
let!(:title) { "熱帶風暴畫眉" }
let!(:slug) { "topic" }
let!(:topic) { Fabricate.build(:topic, title: title) }
before { SiteSetting.slug_generation_method = "none" }
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
describe "#ascii_generator" do
before { SiteSetting.slug_generation_method = "ascii" }
context "with ascii letters" do
let!(:title) { "hello world topic" }
let!(:slug) { "hello-world-topic" }
let!(:topic) { Fabricate.build(:topic, title: title) }
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
end
context "for cjk characters" do
let!(:title) { "熱帶風暴畫眉" }
let!(:slug) { "topic" }
let!(:topic) { Fabricate.build(:topic, title: title) }
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
describe "slug computed hooks" do
before do
invert_slug = ->(topic, slug, title) { slug.reverse }
Topic.slug_computed_callbacks << invert_slug
end
let!(:title) { "hello test topic" }
let!(:slug) { "hello-test-topic".reverse }
let!(:other_title) { "other title" }
let!(:other_slug) { "other-title".reverse }
let!(:topic) { Fabricate.build(:topic, title: title) }
it "returns a reversed slug for a title" do
expect(topic.title).to eq(title)
expect(topic.slug).to eq(slug)
end
it "returns a reversed slug after the title is changed" do
expect(topic.title).to eq(title)
expect(topic.slug).to eq(slug)
topic.title = other_title
expect(topic.title).to eq(other_title)
expect(topic.slug).to eq(other_slug)
end
after { Topic.slug_computed_callbacks.clear }
end
end
describe "slugless_url" do
fab!(:topic)
it "returns the correct url" do
expect(topic.slugless_url).to eq("/t/#{topic.id}")
end
it "works with post id" do
expect(topic.slugless_url(123)).to eq("/t/#{topic.id}/123")
end
it "works with subfolder install" do
set_subfolder "/forum"
expect(topic.slugless_url).to eq("/forum/t/#{topic.id}")
end
end
describe "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
describe "private message title" do
before do
SiteSetting.min_topic_title_length = 15
SiteSetting.min_personal_message_title_length = 3
end
it "allows shorter titles" do
pm =
Fabricate.build(
:private_message_topic,
title: "a" * SiteSetting.min_personal_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
describe "admin topic title" do
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
describe "topic title uniqueness" do
fab!(:category1) { Fabricate(:category) }
fab!(:category2) { Fabricate(:category) }
fab!(:topic) { Fabricate(:topic, category: category1) }
let(:new_topic) { Fabricate.build(:topic, title: topic.title, category: category1) }
let(:new_topic_different_cat) do
Fabricate.build(:topic, title: topic.title, category: category2)
end
context "when duplicates aren't allowed" do
before do
SiteSetting.allow_duplicate_topic_titles = false
SiteSetting.allow_duplicate_topic_titles_category = 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 even allow another topic to be created with the same name but different category" do
expect(new_topic_different_cat).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.allow_duplicate_topic_titles = true
SiteSetting.allow_duplicate_topic_titles_category = false
end
it "will allow another topic to be created with the same name" do
expect(new_topic).to be_valid
end
end
context "when duplicates are allowed if the category is different" do
before do
SiteSetting.allow_duplicate_topic_titles = false
SiteSetting.allow_duplicate_topic_titles_category = true
end
it "will allow another topic to be created with the same name but different category" do
expect(new_topic_different_cat).to be_valid
end
it "won't allow another topic to be created with the same name in same category" do
expect(new_topic).not_to be_valid
end
it "other errors will not be cleared" do
SiteSetting.min_topic_title_length = 5
topic.update!(title: "more than 5 characters but less than 134")
SiteSetting.min_topic_title_length = 134
new_topic_different_cat.title = "more than 5 characters but less than 134"
expect(new_topic_different_cat).not_to be_valid
expect(new_topic_different_cat.errors[:title]).to include(
I18n.t("errors.messages.too_short", count: 134),
)
end
end
end
describe "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 bold text in its title") }
let(:topic_image) do
build_topic_with_title("Topic with image in its title")
end
let(:topic_script) do
build_topic_with_title("Topic with script in its title")
end
let(:topic_emoji) { build_topic_with_title("I 💖 candy alot") }
let(:topic_modifier_emoji) { build_topic_with_title("I 👨🌾 candy alot") }
let(:topic_shortcut_emoji) { build_topic_with_title("I love candy :)") }
let(:topic_inline_emoji) { build_topic_with_title("Hello😊World") }
it "escapes script contents" do
expect(topic_script.fancy_title).to eq(
"Topic with <script>alert(‘title’)</script> script in its title",
)
end
it "expands emojis" do
expect(topic_emoji.fancy_title).to eq("I :sparkling_heart: candy alot")
end
it "keeps combined emojis" do
expect(topic_modifier_emoji.fancy_title).to eq("I :man_farmer: candy alot")
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
it "always escapes title" do
topic_script.title = topic_script.title + "x" * Topic.max_fancy_title_length
expect(topic_script.fancy_title).to eq(ERB::Util.html_escape(topic_script.title))
# not really needed, but just in case
expect(topic_script.fancy_title).not_to include("