# 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("