discourse/plugins/automation/spec/triggers/post_created_edited_spec.rb
Chris Alberti 0b65360094
FIX: Make post created/edited automation trigger regexes more specific (#32944)
- For `with_images`, ignore `<img>` tags representing emoji or a quote
avatar
- For `with_links` and `with_uploads`, exclude tags starting with the
letter "a" other than `<a>` (like `<aside>`)
- For `with_code`, the `<pre>` tag wasn't being detected because it was
expecting an extra char before the ">"
2025-05-27 15:40:00 -05:00

818 lines
23 KiB
Ruby
Vendored
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# frozen_string_literal: true
describe "PostCreatedEdited" do
before { SiteSetting.discourse_automation_enabled = true }
let(:basic_topic_params) do
{ title: "hello world topic", raw: "my name is fred", archetype: Archetype.default }
end
let(:parent_category) { Fabricate(:category_with_definition) }
let(:subcategory) { Fabricate(:category_with_definition, parent_category_id: parent_category.id) }
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
fab!(:admin) { Fabricate(:admin, refresh_auto_groups: true) }
fab!(:automation) { Fabricate(:automation, trigger: "post_created_edited") }
context "when filtering on first post only" do
before do
automation.upsert_field!("first_post_only", "boolean", { value: true }, target: "trigger")
end
it "fires on first post, but not on second" do
post = create_post(title: "hello world topic", raw: "my name is fred")
topic = post.topic
list =
capture_contexts do
PostCreator.create!(user, raw: "this is a test reply", topic_id: topic.id)
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
expect(list[0]["action"].to_s).to eq("create")
list =
capture_contexts do
PostCreator.create!(user, raw: "this is another test reply", topic_id: topic.id)
end
expect(list.length).to eq(0)
end
end
context "when filtering on first topic only" do
before do
automation.upsert_field!("first_topic_only", "boolean", { value: true }, target: "trigger")
end
it "does not fire if it is not a topic" do
post = create_post(title: "hello world topic", raw: "my name is fred")
topic = post.topic
list =
capture_contexts do
PostCreator.create!(user, raw: "this is a test reply", topic_id: topic.id)
end
expect(list.length).to eq(0)
end
it "fires if it is a first topic (and not on second)" do
list =
capture_contexts do
PostCreator.create!(user, raw: "this is a test reply", title: "hello there mister")
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
expect(list[0]["action"].to_s).to eq("create")
list =
capture_contexts do
PostCreator.create!(user, raw: "this is a test reply 2", title: "hello there mister 2")
end
expect(list.length).to eq(0)
end
end
context "when skipping posts created via email" do
before do
automation.upsert_field!("skip_via_email", "boolean", { value: true }, target: "trigger")
end
let(:parent_post) { create_post(title: "hello world topic", raw: "my name is fred") }
it "fires if the post didn't come via email" do
topic = parent_post.topic
list =
capture_contexts do
PostCreator.create!(user, raw: "this is a test reply", topic_id: topic.id)
end
expect(list.length).to eq(1)
end
it "skips the trigger if the post came via email" do
topic = parent_post.topic
list =
capture_contexts do
PostCreator.create!(
user,
raw: "this is a test reply",
topic_id: topic.id,
via_email: true,
)
end
expect(list.length).to eq(0)
end
end
context "when editing/creating a post" do
it "fires the trigger" do
post = nil
list = capture_contexts { post = PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
expect(list[0]["action"].to_s).to eq("create")
list = capture_contexts { post.revise(post.user, raw: "this is another cool topic") }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
expect(list[0]["action"].to_s).to eq("edit")
end
context "when user group is restricted" do
fab!(:group)
before do
automation.upsert_field!(
"restricted_groups",
"groups",
{ value: [group.id] },
target: "trigger",
)
end
context "when user is member of the group" do
before { group.add(user) }
it "fires the trigger" do
list = capture_contexts { PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
end
context "when user is not member of the group" do
it "doesnt fire the trigger" do
list = capture_contexts { PostCreator.create(user, basic_topic_params) }
expect(list).to be_blank
end
end
end
context "when trust_levels are restricted" do
before do
automation.upsert_field!(
"valid_trust_levels",
"trust-levels",
{ value: [0] },
target: "trigger",
)
end
context "when trust level is allowed" do
it "fires the trigger" do
list =
capture_contexts do
user.trust_level = TrustLevel[0]
PostCreator.create(user, basic_topic_params)
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
end
context "when trust level is not allowed" do
it "doesnt fire the trigger" do
list =
capture_contexts do
user.trust_level = TrustLevel[1]
PostCreator.create(user, basic_topic_params)
end
expect(list).to be_blank
end
end
end
context "when group is restricted" do
fab!(:target_group) { Fabricate(:group, messageable_level: Group::ALIAS_LEVELS[:everyone]) }
fab!(:another_group) { Fabricate(:group, messageable_level: Group::ALIAS_LEVELS[:everyone]) }
before do
automation.upsert_field!(
"restricted_inbox_groups",
"groups",
{ value: [target_group.id, another_group.id] },
target: "trigger",
)
end
context "when PM is not sent to the group" do
it "doesnt fire the trigger" do
list =
capture_contexts do
PostCreator.create(
user,
basic_topic_params.merge(
target_group_names: [Fabricate(:group).name],
archetype: Archetype.private_message,
),
)
end
expect(list.length).to eq(0)
end
end
context "when PM is sent to the group" do
it "fires the trigger" do
list =
capture_contexts do
PostCreator.create(
user,
basic_topic_params.merge(
target_group_names: [target_group.name],
archetype: Archetype.private_message,
),
)
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
end
context "when the topic is not a PM" do
it "doesnt fire the trigger" do
list =
capture_contexts do
user.groups << target_group
PostCreator.create(user, basic_topic_params)
end
expect(list).to be_blank
end
end
context "when a different group is used" do
it "does not fire the trigger" do
list =
capture_contexts do
PostCreator.create(
user,
basic_topic_params.merge(
target_group_names: [Fabricate(:group).name],
archetype: Archetype.private_message,
),
)
end
expect(list).to be_blank
end
end
end
context "when the post is being created from an incoming email" do
let(:reply_key) { "4f97315cc828096c9cb34c6f1a0d6fe8" }
fab!(:user) { Fabricate(:user, email: "discourse@bar.com", refresh_auto_groups: true) }
fab!(:topic) { create_topic(user: user) }
fab!(:post) { create_post(topic: topic) }
let!(:post_reply_key) do
Fabricate(:post_reply_key, reply_key: reply_key, user: user, post: post)
end
before do
SiteSetting.email_in = true
SiteSetting.reply_by_email_address = "reply+%{reply_key}@bar.com"
SiteSetting.alternative_reply_by_email_addresses = "alt+%{reply_key}@bar.com"
end
it "fires the trigger" do
list = capture_contexts { Email::Receiver.new(email("html_reply")).process! }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
expect(list[0]["action"].to_s).to eq("create")
end
context "when the incoming email is automated" do
before { SiteSetting.block_auto_generated_emails = false }
it "fires the trigger" do
list =
capture_contexts { Email::Receiver.new(email("auto_generated_unblocked")).process! }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
context "when ignore_automated is true" do
before do
automation.upsert_field!(
"ignore_automated",
"boolean",
{ value: true },
target: "trigger",
)
end
it "doesn't fire the trigger" do
list =
capture_contexts { Email::Receiver.new(email("auto_generated_unblocked")).process! }
expect(list).to be_blank
end
end
end
end
context "with original_post_only" do
before do
automation.upsert_field!(
"original_post_only",
"boolean",
{ value: true },
target: "trigger",
)
end
it "fires the trigger only for OP" do
list = capture_contexts { PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(1)
list =
capture_contexts do
PostCreator.create(
user,
basic_topic_params.merge({ topic_id: list[0]["post"].topic_id }),
)
end
expect(list.length).to eq(0)
end
end
context "when tags is restricted" do
fab!(:tag_1) { Fabricate(:tag) }
before do
automation.upsert_field!(
"restricted_tags",
"tags",
{ value: [tag_1.name] },
target: "trigger",
)
end
context "when tag is allowed" do
it "fires the trigger" do
list =
capture_contexts do
PostCreator.create(user, basic_topic_params.merge({ tags: [tag_1.name] }))
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
end
context "when tag is not allowed" do
it "fires the trigger" do
list = capture_contexts { PostCreator.create(user, basic_topic_params.merge()) }
expect(list.length).to eq(0)
end
end
end
context "when using restricted_categories with deeply nested categories" do
before_all { SiteSetting.max_category_nesting = 3 }
fab!(:top_category) { Fabricate(:category) }
fab!(:mid_category) { Fabricate(:category, parent_category_id: top_category.id) }
fab!(:bottom_category) { Fabricate(:category, parent_category_id: mid_category.id) }
fab!(:another_category) { Fabricate(:category) }
before do
automation.upsert_field!(
"restricted_categories",
"categories",
{ value: [top_category.id] },
target: "trigger",
)
end
it "fires the trigger for posts in any grand child category" do
list =
capture_contexts do
PostCreator.create(user, basic_topic_params.merge({ category: bottom_category.id }))
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
it "will not fire on unrelated categories" do
list =
capture_contexts do
PostCreator.create(user, basic_topic_params.merge({ category: another_category.id }))
end
expect(list.length).to eq(0)
end
it "fires the trigger for posts in any child category" do
list =
capture_contexts do
PostCreator.create(user, basic_topic_params.merge({ category: mid_category.id }))
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
context "when exclude_subcategories is enabled" do
before do
automation.upsert_field!(
"exclude_subcategories",
"boolean",
{ value: true },
target: "trigger",
)
end
it "does not fire for children" do
list =
capture_contexts do
PostCreator.create(user, basic_topic_params.merge({ category: bottom_category.id }))
end
expect(list.length).to eq(0)
end
it "fires for the exact category match" do
list =
capture_contexts do
PostCreator.create(user, basic_topic_params.merge({ category: top_category.id }))
end
expect(list.length).to eq(1)
end
end
end
context "when action_type is set to create" do
before do
automation.upsert_field!("action_type", "choices", { value: "created" }, target: "trigger")
end
it "fires the trigger only for create" do
post = nil
list = capture_contexts { post = PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
expect(list[0]["action"].to_s).to eq("create")
list = capture_contexts { post.revise(post.user, raw: "this is another cool topic") }
expect(list.length).to eq(0)
end
end
context "when only public topics are allowed" do
before do
automation.upsert_field!(
"restricted_archetype",
"choices",
{ value: "public" },
target: "trigger",
)
end
it "fires the trigger for public topics" do
list = capture_contexts { PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
it "doesn't fire the trigger for secure categories" do
secure_category = Fabricate(:category, read_restricted: true)
list =
capture_contexts do
PostCreator.create(admin, basic_topic_params.merge(category: secure_category.id))
end
expect(list.length).to eq(0)
end
end
context "when archetype is restricted" do
context "when only regular topics are allowed" do
before do
automation.upsert_field!(
"restricted_archetype",
"choices",
{ value: "regular" },
target: "trigger",
)
end
it "fires the trigger for regular topics" do
list = capture_contexts { PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
it "doesn't fire the trigger for private messages" do
list =
capture_contexts do
PostCreator.create(
user,
basic_topic_params.merge(
archetype: Archetype.private_message,
target_usernames: [Fabricate(:user).username],
),
)
end
expect(list.length).to eq(0)
end
end
context "when only private messages are allowed" do
before do
automation.upsert_field!(
"restricted_archetype",
"choices",
{ value: "private_message" },
target: "trigger",
)
end
it "fires the trigger for private messages" do
list =
capture_contexts do
PostCreator.create(
user,
basic_topic_params.merge(
archetype: Archetype.private_message,
target_usernames: [Fabricate(:user).username],
),
)
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
it "doesn't fire the trigger for regular topics" do
list = capture_contexts { PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(0)
end
end
end
context "when action_type is set to edit" do
before do
automation.upsert_field!("action_type", "choices", { value: "edited" }, target: "trigger")
end
it "fires the trigger only for edit" do
post = nil
list = capture_contexts { post = PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(0)
list = capture_contexts { post.revise(post.user, raw: "this is another cool topic") }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
expect(list[0]["action"].to_s).to eq("edit")
end
end
end
context "when excluded groups are defined" do
fab!(:excluded_group) { Fabricate(:group) }
before do
automation.upsert_field!(
"excluded_groups",
"groups",
{ value: [excluded_group.id] },
target: "trigger",
)
end
context "when user is in an excluded group" do
before { excluded_group.add(user) }
it "doesn't fire the trigger" do
list = capture_contexts { PostCreator.create(user, basic_topic_params) }
expect(list).to be_blank
end
end
context "when user is not in any excluded group" do
it "fires the trigger" do
list = capture_contexts { PostCreator.create(user, basic_topic_params) }
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
end
end
context "when filtering on post features" do
fab!(:topic) { Fabricate(:topic, user: user) }
context "with images filter" do
before do
automation.upsert_field!(
"post_features",
"choices",
{ value: ["with_images"] },
target: "trigger",
)
end
it "fires the trigger when post has an image" do
list =
capture_contexts do
PostCreator.create(
user,
raw: "Look at this image: ![image](https://example.com/image.jpg)",
topic_id: topic.id,
)
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
it "doesn't fire the trigger when post has no image" do
list =
capture_contexts do
PostCreator.create(
user,
raw: "This is just regular text with no images",
topic_id: topic.id,
)
end
expect(list.length).to eq(0)
end
it "doesn't fire the trigger when post has emojis but no regular images" do
list =
capture_contexts do
PostCreator.create(
user,
raw:
"This is regular text with an emoji but no non-emoji images :face_savoring_food:",
topic_id: topic.id,
)
end
expect(list.length).to eq(0)
end
it "doesn't fire the trigger when post has an avatar in a quote but no regular images" do
original_post =
PostCreator.create(user, raw: "This is regular text with no images", topic_id: topic.id)
quote_post_text = <<~QUOTE_POST
[quote=\"#{user.username}}, post:#{original_post.post_number}, topic:#{original_post.topic_id}\"]
regular text
[/quote]
This is a regular text post with a regular text quote, no image (but an avatar image in the quote)
QUOTE_POST
list =
capture_contexts { PostCreator.create(user, raw: quote_post_text, topic_id: topic.id) }
expect(list.length).to eq(0)
end
end
context "with links filter" do
before do
automation.upsert_field!(
"post_features",
"choices",
{ value: ["with_links"] },
target: "trigger",
)
end
it "fires the trigger when post has a link" do
list =
capture_contexts do
PostCreator.create(
user,
raw: "Check out this [link](https://example.com)",
topic_id: topic.id,
)
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
it "doesn't fire the trigger when post has no link" do
list =
capture_contexts do
PostCreator.create(
user,
raw: "This is just regular text with no links",
topic_id: topic.id,
)
end
expect(list.length).to eq(0)
end
end
context "with code filter" do
before do
automation.upsert_field!(
"post_features",
"choices",
{ value: ["with_code"] },
target: "trigger",
)
end
it "fires the trigger when post has a code block" do
list =
capture_contexts do
PostCreator.create(
user,
raw: "```ruby\ndef hello_world\n puts 'hello world'\nend\n```",
topic_id: topic.id,
)
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
it "doesn't fire the trigger when post has no code block" do
list =
capture_contexts do
PostCreator.create(
user,
raw: "This is just regular text with no code blocks",
topic_id: topic.id,
)
end
expect(list.length).to eq(0)
end
end
context "with multiple features" do
before do
automation.upsert_field!(
"post_features",
"choices",
{ value: %w[with_links with_images] },
target: "trigger",
)
end
it "fires the trigger when post has all required features" do
list =
capture_contexts do
PostCreator.create(
user,
raw:
"Check this [link](https://example.com) and ![image](https://example.com/image.jpg)",
topic_id: topic.id,
)
end
expect(list.length).to eq(1)
expect(list[0]["kind"]).to eq("post_created_edited")
end
it "doesn't fire the trigger when post is missing a required feature" do
list =
capture_contexts do
PostCreator.create(
user,
raw: "This only has a [link](https://example.com) but no image",
topic_id: topic.id,
)
end
expect(list.length).to eq(0)
end
end
end
end