discourse/spec/lib/topic_creator_spec.rb
Osama Sayegh 72c4709a5a
FIX: Skip tags-related validations when the skip_validations option is present (#26379)
The `TopicCreator` class has a `skip_validations` option that can force-create a topic without performing permission checks or validation rules. However, at the moment it doesn't skip validations that are related to tags, so topics that are created by the system or by some scrip can still fail if they use tags. This commit makes the `TopicCreator` class skip all tags-related checks if the `skip_validations` is specified.

Internal topic: t/124280.
2024-03-27 12:56:21 +03:00

680 lines
23 KiB
Ruby

# frozen_string_literal: true
RSpec.describe TopicCreator do
fab!(:user) { Fabricate(:user, trust_level: TrustLevel[2]) }
fab!(:moderator)
fab!(:admin)
let(:valid_attrs) { Fabricate.attributes_for(:topic) }
let(:pm_valid_attrs) do
{
raw: "this is a new post",
title: "this is a new title",
archetype: Archetype.private_message,
target_usernames: moderator.username,
}
end
let(:pm_to_email_valid_attrs) do
{
raw: "this is a new email",
title: "this is a new subject",
archetype: Archetype.private_message,
target_emails: "moderator@example.com",
}
end
describe "#create" do
context "with topic success cases" do
before do
TopicCreator.any_instance.expects(:save_topic).returns(true)
TopicCreator.any_instance.expects(:watch_topic).returns(true)
SiteSetting.allow_duplicate_topic_titles = true
end
it "should be possible for an admin to create a topic" do
expect(TopicCreator.create(admin, Guardian.new(admin), valid_attrs)).to be_valid
end
it "should be possible for a moderator to create a topic" do
expect(TopicCreator.create(moderator, Guardian.new(moderator), valid_attrs)).to be_valid
end
it "supports custom_fields that has been registered to the DiscoursePluginRegistry" do
opts = valid_attrs.merge(custom_fields: { import_id: "bar" })
topic = TopicCreator.create(admin, Guardian.new(admin), opts)
expect(topic.custom_fields["import_id"]).to eq("bar")
end
context "with regular user" do
before { SiteSetting.create_topic_allowed_groups = Group::AUTO_GROUPS[:trust_level_0] }
it "should be possible for a regular user to create a topic" do
expect(TopicCreator.create(user, Guardian.new(user), valid_attrs)).to be_valid
end
it "should be possible for a regular user to create a topic with blank auto_close_time" do
expect(
TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(auto_close_time: "")),
).to be_valid
end
it "ignores auto_close_time without raising an error" do
topic =
TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(auto_close_time: "24"))
expect(topic).to be_valid
expect(topic.public_topic_timer).to eq(nil)
end
it "can create a topic in a category" do
category = Fabricate(:category, name: "Neil's Blog")
topic =
TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(category: category.id))
expect(topic).to be_valid
expect(topic.category).to eq(category)
end
it "ignores participant_count without raising an error" do
topic =
TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(participant_count: 3))
expect(topic.participant_count).to eq(1)
end
it "accepts participant_count in import mode" do
topic =
TopicCreator.create(
user,
Guardian.new(user),
valid_attrs.merge(import_mode: true, participant_count: 3),
)
expect(topic.participant_count).to eq(3)
end
end
end
context "with tags" do
fab!(:tag1) { Fabricate(:tag, name: "fun") }
fab!(:tag2) { Fabricate(:tag, name: "fun2") }
fab!(:tag3) { Fabricate(:tag, name: "fun3") }
fab!(:tag4) { Fabricate(:tag, name: "fun4") }
fab!(:tag5) { Fabricate(:tag, name: "fun5") }
fab!(:tag_group1) { Fabricate(:tag_group, tags: [tag1]) }
fab!(:tag_group2) { Fabricate(:tag_group, tags: [tag2]) }
before do
SiteSetting.tagging_enabled = true
SiteSetting.create_tag_allowed_groups = Group::AUTO_GROUPS[:trust_level_0]
SiteSetting.tag_topic_allowed_groups = Group::AUTO_GROUPS[:trust_level_0]
end
context "with regular tags" do
it "user can add tags to topic" do
topic =
TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(tags: [tag1.name]))
expect(topic).to be_valid
expect(topic.tags.length).to eq(1)
end
end
context "when assigned via matched watched words" do
fab!(:word1) do
Fabricate(:watched_word, action: WatchedWord.actions[:tag], replacement: tag1.name)
end
fab!(:word2) do
Fabricate(:watched_word, action: WatchedWord.actions[:tag], replacement: tag2.name)
end
fab!(:word3) do
Fabricate(
:watched_word,
action: WatchedWord.actions[:tag],
replacement: tag3.name,
case_sensitive: true,
)
end
it "adds watched words as tags" do
topic =
TopicCreator.create(
user,
Guardian.new(user),
valid_attrs.merge(
title: "This is a #{word1.word} title",
raw: "#{word2.word.upcase} is not the same as #{word3.word.upcase}",
),
)
expect(topic).to be_valid
expect(topic.tags).to contain_exactly(tag1, tag2)
end
end
context "with skip_validations option" do
it "allows the user to add tags even if they're not permitted" do
SiteSetting.tag_topic_allowed_groups = Group::AUTO_GROUPS[:trust_level_2]
user.update!(trust_level: TrustLevel[1])
Group.user_trust_level_change!(user.id, user.trust_level)
topic =
TopicCreator.create(
user,
Guardian.new(user),
valid_attrs.merge(
title: "This is a valid title",
raw: "Somewhat lengthy body for my cool topic",
tags: [tag1.name, tag2.name, "brandnewtag434"],
skip_validations: true,
),
)
expect(topic).to be_persisted
expect(topic.tags.pluck(:name)).to contain_exactly(tag1.name, tag2.name, "brandnewtag434")
end
end
context "with staff-only tags" do
before { create_staff_only_tags(["alpha"]) }
it "regular users can't add staff-only tags" do
expect do
TopicCreator.create(user, Guardian.new(user), valid_attrs.merge(tags: ["alpha"]))
end.to raise_error(ActiveRecord::Rollback)
end
it "staff can add staff-only tags" do
topic =
TopicCreator.create(admin, Guardian.new(admin), valid_attrs.merge(tags: ["alpha"]))
expect(topic).to be_valid
expect(topic.tags.length).to eq(1)
end
end
context "when minimum_required_tags is present" do
fab!(:category) { Fabricate(:category, name: "beta", minimum_required_tags: 2) }
it "fails for regular user if minimum_required_tags is not satisfied" do
expect(
TopicCreator.new(
user,
Guardian.new(user),
valid_attrs.merge(category: category.id),
).valid?,
).to be_falsy
end
it "lets admin create a topic regardless of minimum_required_tags" do
topic =
TopicCreator.create(
admin,
Guardian.new(admin),
valid_attrs.merge(tags: [tag1.name], category: category.id),
)
expect(topic).to be_valid
expect(topic.tags.length).to eq(1)
end
it "works for regular user if minimum_required_tags is satisfied" do
topic =
TopicCreator.create(
user,
Guardian.new(user),
valid_attrs.merge(tags: [tag1.name, tag2.name], category: category.id),
)
expect(topic).to be_valid
expect(topic.tags.length).to eq(2)
end
it "minimum_required_tags is satisfying for new tags if user can create" do
topic =
TopicCreator.create(
user,
Guardian.new(user),
valid_attrs.merge(tags: ["new tag", "another tag"], category: category.id),
)
expect(topic).to be_valid
expect(topic.tags.length).to eq(2)
end
it "lets new user create a topic if they don't have sufficient trust level to tag topics" do
SiteSetting.tag_topic_allowed_groups = Group::AUTO_GROUPS[:trust_level_1]
new_user = Fabricate(:newuser, refresh_auto_groups: true)
topic =
TopicCreator.create(
new_user,
Guardian.new(new_user),
valid_attrs.merge(category: category.id),
)
expect(topic).to be_valid
end
end
context "with required tag group" do
fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1]) }
fab!(:category) do
Fabricate(
:category,
name: "beta",
category_required_tag_groups: [
CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1),
],
)
end
it "when no tags are not present" do
expect(
TopicCreator.new(
user,
Guardian.new(user),
valid_attrs.merge(category: category.id),
).valid?,
).to be_falsy
end
it "when tags are not part of the tag group" do
expect(
TopicCreator.new(
user,
Guardian.new(user),
valid_attrs.merge(category: category.id, tags: ["nope"]),
).valid?,
).to be_falsy
end
it "when requirement is met" do
expect(
TopicCreator.new(
user,
Guardian.new(user),
valid_attrs.merge(category: category.id, tags: [tag1.name, tag2.name]),
).valid?,
).to be_truthy
end
it "lets staff ignore the restriction" do
expect(
TopicCreator.new(
user,
Guardian.new(admin),
valid_attrs.merge(category: category.id),
).valid?,
).to be_truthy
end
end
context "when category has restricted tags or tag groups" do
fab!(:category) { Fabricate(:category, tags: [tag3], tag_groups: [tag_group1]) }
it "allows topics without any tags" do
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic without tags",
raw: "hello this is a test topic without tags",
category: category.id,
)
expect(tc.valid?).to eq(true)
expect(tc.errors).to be_empty
topic = tc.create
expect(topic.tags).to be_empty
end
it "allows topics if they use tags only from the tags set that the category restricts" do
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags",
raw: "hello this is a test topic with tags",
category: category.id,
tags: [tag1.name, tag3.name],
)
expect(tc.valid?).to eq(true)
expect(tc.errors).to be_empty
topic = tc.create
expect(topic.tags).to contain_exactly(tag1, tag3)
end
it "allows topics to use tags that are restricted in multiple categories" do
category2 = Fabricate(:category, tags: [tag5], tag_groups: [tag_group1])
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags",
raw: "hello this is a test topic with tags",
category: category2.id,
tags: [tag1.name, tag5.name],
)
expect(tc.valid?).to eq(true)
expect(tc.errors).to be_empty
topic = tc.create
expect(topic.tags).to contain_exactly(tag1, tag5)
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags 1",
raw: "hello this is a test topic with tags",
category: category.id,
tags: [tag1.name, tag3.name],
)
expect(tc.valid?).to eq(true)
expect(tc.errors).to be_empty
topic = tc.create
expect(topic.tags).to contain_exactly(tag1, tag3)
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags 2",
raw: "hello this is a test topic with tags",
category: category.id,
tags: [tag1.name, tag5.name],
)
expect(tc.valid?).to eq(false)
expect(tc.errors.full_messages).to contain_exactly(
I18n.t(
"tags.forbidden.restricted_tags_cannot_be_used_in_category",
count: 1,
tags: tag5.name,
category: category.name,
),
)
end
it "rejects topics if they use a tag outside the set of tags that the category restricts" do
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags",
raw: "hello this is a test topic with tags",
category: category.id,
tags: [tag2.name, tag1.name],
)
expect(tc.valid?).to eq(false)
expect(tc.errors.full_messages).to contain_exactly(
I18n.t(
"tags.forbidden.category_does_not_allow_tags",
count: 1,
tags: tag2.name,
category: category.name,
),
)
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags",
raw: "hello this is a test topic with tags",
category: category.id,
tags: [tag2.name, tag5.name, tag3.name],
)
expect(tc.valid?).to eq(false)
expect(tc.errors.full_messages).to contain_exactly(
I18n.t(
"tags.forbidden.category_does_not_allow_tags",
count: 2,
tags: [tag2, tag5].map(&:name).sort.join(", "),
category: category.name,
),
)
end
it "rejects topics in other categories if a restricted tag of a category are used" do
category2 = Fabricate(:category)
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags",
raw: "hello this is a test topic with tags",
category: category2.id,
tags: [tag1.name, tag2.name],
)
expect(tc.valid?).to eq(false)
expect(tc.errors.full_messages).to contain_exactly(
I18n.t(
"tags.forbidden.restricted_tags_cannot_be_used_in_category",
count: 1,
tags: tag1.name,
category: category2.name,
),
)
end
context "when allowing other tags" do
before { category.update!(allow_global_tags: true) }
it "allows topics to use tags that aren't restricted by any category" do
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags",
raw: "hello this is a test topic with tags",
category: category.id,
tags: [tag1.name, tag2.name, tag3.name, tag5.name],
)
expect(tc.valid?).to eq(true)
expect(tc.errors).to be_empty
topic = tc.create
expect(topic.tags).to contain_exactly(tag1, tag2, tag3, tag5)
end
it "rejects topics if they use restricted tags of another category" do
Fabricate(:category, tags: [tag5], tag_groups: [tag_group2])
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags",
raw: "hello this is a test topic with tags",
category: category.id,
tags: [tag1.name, tag5.name],
)
expect(tc.valid?).to eq(false)
expect(tc.errors.full_messages).to contain_exactly(
I18n.t(
"tags.forbidden.restricted_tags_cannot_be_used_in_category",
count: 1,
tags: tag5.name,
category: category.name,
),
)
tc =
TopicCreator.new(
user,
Guardian.new(user),
title: "hello this is a test topic with tags",
raw: "hello this is a test topic with tags",
category: category.id,
tags: [tag1.name, tag2.name, tag5.name],
)
expect(tc.valid?).to eq(false)
expect(tc.errors.full_messages).to contain_exactly(
I18n.t(
"tags.forbidden.restricted_tags_cannot_be_used_in_category",
count: 2,
tags: [tag2, tag5].map(&:name).sort.join(", "),
category: category.name,
),
)
end
end
end
end
context "with personal message" do
context "with success cases" do
before do
TopicCreator.any_instance.expects(:save_topic).returns(true)
TopicCreator.any_instance.expects(:watch_topic).returns(true)
SiteSetting.allow_duplicate_topic_titles = true
SiteSetting.enable_staged_users = true
end
it "should be possible for a regular user to send private message" do
expect(TopicCreator.create(user, Guardian.new(user), pm_valid_attrs)).to be_valid
end
it "create_topic_allowed_groups setting should not be checked when sending private message" do
SiteSetting.create_topic_allowed_groups = Group::AUTO_GROUPS[:trust_level_4]
expect(TopicCreator.create(user, Guardian.new(user), pm_valid_attrs)).to be_valid
end
it "personal_message_enabled_groups setting should not be checked when sending private messages to staff via flag" do
SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:staff]
expect(
TopicCreator.create(
user,
Guardian.new(user),
pm_valid_attrs.merge(subtype: TopicSubtype.notify_moderators),
),
).to be_valid
end
end
context "with failure cases" do
it "should be rollback the changes when email is invalid" do
SiteSetting.manual_polling_enabled = true
SiteSetting.reply_by_email_address = "sam+%{reply_key}@sam.com"
SiteSetting.reply_by_email_enabled = true
SiteSetting.send_email_messages_allowed_groups =
"1|3|#{Group::AUTO_GROUPS[:trust_level_1]}"
attrs = pm_to_email_valid_attrs.dup
attrs[:target_emails] = "t" * 256
expect do TopicCreator.create(user, Guardian.new(user), attrs) end.to raise_error(
ActiveRecord::Rollback,
)
end
it "personal_message_enabled_groups setting should be checked when sending private message" do
SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:trust_level_4]
expect do
TopicCreator.create(user, Guardian.new(user), pm_valid_attrs)
end.to raise_error(ActiveRecord::Rollback)
end
end
context "with to emails" do
it "works for staff" do
SiteSetting.send_email_messages_allowed_groups = "1|3"
expect(
TopicCreator.create(admin, Guardian.new(admin), pm_to_email_valid_attrs),
).to be_valid
end
it "work for trusted users" do
SiteSetting.send_email_messages_allowed_groups =
"1|3|#{Group::AUTO_GROUPS[:trust_level_3]}"
user.change_trust_level!(TrustLevel[3])
expect(TopicCreator.create(user, Guardian.new(user), pm_to_email_valid_attrs)).to be_valid
end
it "does not work for non-staff" do
SiteSetting.send_email_messages_allowed_groups = "1|3"
expect {
TopicCreator.create(user, Guardian.new(user), pm_to_email_valid_attrs)
}.to raise_error(ActiveRecord::Rollback)
end
it "does not work for untrusted users" do
SiteSetting.send_email_messages_allowed_groups =
"1|3|#{Group::AUTO_GROUPS[:trust_level_3]}"
user.change_trust_level!(TrustLevel[2])
expect {
TopicCreator.create(user, Guardian.new(user), pm_to_email_valid_attrs)
}.to raise_error(ActiveRecord::Rollback)
end
end
end
context "when setting timestamps" do
it "supports Time instances" do
freeze_time
topic =
TopicCreator.create(
user,
Guardian.new(user),
valid_attrs.merge(created_at: 1.week.ago, pinned_at: 3.days.ago),
)
expect(topic.created_at).to eq_time(1.week.ago)
expect(topic.pinned_at).to eq_time(3.days.ago)
end
it "supports strings" do
freeze_time
time1 = Time.zone.parse("2019-09-02")
time2 = Time.zone.parse("2020-03-10 15:17")
topic =
TopicCreator.create(
user,
Guardian.new(user),
valid_attrs.merge(created_at: "2019-09-02", pinned_at: "2020-03-10 15:17"),
)
expect(topic.created_at).to eq_time(time1)
expect(topic.pinned_at).to eq_time(time2)
end
end
context "with external_id" do
it "adds external_id" do
topic =
TopicCreator.create(
user,
Guardian.new(user),
valid_attrs.merge(external_id: "external_id"),
)
expect(topic.external_id).to eq("external_id")
end
end
context "when invisible/unlisted" do
let(:unlisted_attrs) { valid_attrs.merge(visible: false) }
it "throws an exception for a non-staff user" do
expect do TopicCreator.create(user, Guardian.new(user), unlisted_attrs) end.to raise_error(
ActiveRecord::Rollback,
)
end
it "is invalid for a non-staff user" do
expect(TopicCreator.new(user, Guardian.new(user), unlisted_attrs).valid?).to eq(false)
end
it "creates unlisted topic for an admin" do
expect(TopicCreator.create(admin, Guardian.new(admin), unlisted_attrs)).to be_valid
end
it "is valid for an admin" do
expect(TopicCreator.new(admin, Guardian.new(admin), unlisted_attrs).valid?).to eq(true)
end
context "when embedded" do
let(:embedded_unlisted_attrs) do
unlisted_attrs.merge(embed_url: "http://eviltrout.com/stupid-url")
end
it "is valid for a non-staff user" do
expect(TopicCreator.new(user, Guardian.new(user), embedded_unlisted_attrs).valid?).to eq(
true,
)
end
end
end
end
end