FEATURE: Merge discourse-automation (#26432)

Automation (previously known as discourse-automation) is now a core plugin.
This commit is contained in:
Osama Sayegh
2024-04-03 18:20:43 +03:00
committed by GitHub
parent 2190c9b957
commit 3d4faf3272
314 changed files with 21182 additions and 10 deletions

View File

@ -0,0 +1,122 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "AddUserTogroupThroughCustomField" do
fab!(:user_1) { Fabricate(:user) }
fab!(:user_2) { Fabricate(:user) }
fab!(:target_group) { Fabricate(:group, full_name: "Groupity Group") }
fab!(:user_field) do
Fabricate(:user_field, name: "groupity_group", field_type: "text", description: "a nice field")
end
fab!(:automation) { Fabricate(:automation, script: "add_user_to_group_through_custom_field") }
before do
automation.upsert_field!(
"custom_field_name",
"custom_field",
{ value: user_field.id },
target: "script",
)
end
context "with no matching user custom fields" do
it "works" do
expect(user_1.in_any_groups?([target_group.id])).to eq(false)
expect(user_2.in_any_groups?([target_group.id])).to eq(false)
automation.trigger!("kind" => DiscourseAutomation::Triggers::RECURRING)
expect(user_1.reload.in_any_groups?([target_group.id])).to eq(false)
expect(user_2.reload.in_any_groups?([target_group.id])).to eq(false)
end
end
context "with one matching user" do
before do
UserCustomField.create!(
user_id: user_1.id,
name: "user_field_#{user_field.id}",
value: target_group.full_name,
)
end
it "works" do
expect(user_1.in_any_groups?([target_group.id])).to eq(false)
expect(user_2.in_any_groups?([target_group.id])).to eq(false)
automation.trigger!("kind" => DiscourseAutomation::Triggers::RECURRING)
expect(user_1.reload.in_any_groups?([target_group.id])).to eq(true)
expect(user_2.reload.in_any_groups?([target_group.id])).to eq(false)
end
end
context "when group is already present" do
before { target_group.add(user_1) }
it "works" do
expect(user_1.in_any_groups?([target_group.id])).to eq(true)
expect(user_2.in_any_groups?([target_group.id])).to eq(false)
automation.trigger!("kind" => DiscourseAutomation::Triggers::RECURRING)
expect(user_1.reload.in_any_groups?([target_group.id])).to eq(true)
expect(user_2.reload.in_any_groups?([target_group.id])).to eq(false)
end
end
context "with user_first_logged_in trigger" do
fab!(:automation) { Fabricate(:automation, script: "add_user_to_group_through_custom_field") }
context "with existing custom fields" do
before do
UserCustomField.create!(
user_id: user_1.id,
name: "user_field_#{user_field.id}",
value: target_group.full_name,
)
end
it "adds the user to the group" do
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user_1,
)
expect(user_1.reload.in_any_groups?([target_group.id])).to eq(true)
end
end
context "with non existing/matching custom fields" do
it "does nothing" do
expect {
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user_1,
)
}.not_to change { user_1.reload.belonging_to_group_ids.length }
end
end
context "with non existing target group" do
before do
UserCustomField.create!(
user_id: user_1.id,
name: "user_field_#{user_field.id}",
value: "xx",
)
end
it "does nothing" do
expect {
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user_1,
)
}.not_to change { user_1.reload.belonging_to_group_ids.length }
end
end
end
end

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "AppendLastCheckedBy" do
fab!(:post) { Fabricate(:post, raw: "this is a post with no edit") }
fab!(:moderator)
fab!(:automation) do
Fabricate(:automation, script: DiscourseAutomation::Scripts::APPEND_LAST_CHECKED_BY)
end
def trigger_automation(post)
cooked = automation.trigger!("post" => post, "cooked" => post.cooked)
checked_at = post.updated_at + 1.hour
date_time = checked_at.strftime("%Y-%m-%dT%H:%M:%SZ")
[cooked, checked_at, date_time]
end
def text(key)
I18n.t("discourse_automation.scriptables.append_last_checked_by.#{key}")
end
describe "#trigger!" do
it "works for newly created post" do
cooked, checked_at, date_time = trigger_automation(post)
expect(cooked.include?("<blockquote class=\"discourse-automation\">")).to be_truthy
expect(
cooked.include?(
"<details><summary>#{text("summary")}</summary>#{text("details")}<input type=\"button\" value=\"#{text("button_text")}\" class=\"btn btn-checked\"></details>",
),
).to be_truthy
end
it "works for checked post" do
topic = post.topic
topic.custom_fields[DiscourseAutomation::TOPIC_LAST_CHECKED_BY] = moderator.username
topic.custom_fields[DiscourseAutomation::TOPIC_LAST_CHECKED_AT] = post.updated_at + 1.hour
topic.save_custom_fields
cooked, checked_at = trigger_automation(post)
expect(
cooked.include?(
PrettyText.cook(
I18n.t(
"discourse_automation.scriptables.append_last_checked_by.text",
username: moderator.username,
date_time:
"[date=#{checked_at.to_date} time=#{checked_at.strftime("%H:%M:%S")} timezone=UTC]",
),
),
),
).to be_truthy
end
end
end

View File

@ -0,0 +1,73 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "AppendLastEditedBy" do
fab!(:post) { Fabricate(:post, raw: "this is a post with no edit") }
fab!(:moderator)
fab!(:automation) do
Fabricate(:automation, script: DiscourseAutomation::Scripts::APPEND_LAST_EDITED_BY)
end
def trigger_automation(post)
cooked = automation.trigger!("post" => post, "cooked" => post.cooked)
updated_at = post.updated_at
date_time = updated_at.strftime("%Y-%m-%dT%H:%M:%SZ")
[cooked, updated_at]
end
describe "#trigger!" do
it "works for newly created post" do
freeze_time
cooked, updated_at = trigger_automation(post)
expect(
cooked.include?(
PrettyText.cook(
I18n.t(
"discourse_automation.scriptables.append_last_edited_by.text",
username: post.user.username,
date_time:
"[date=#{updated_at.to_date} time=#{updated_at.strftime("%H:%M:%S")} timezone=UTC]",
),
),
),
).to be_truthy
end
it "works for existing post with last edited by detail" do
freeze_time
cooked, updated_at = trigger_automation(post)
expect(
cooked.include?(
PrettyText.cook(
I18n.t(
"discourse_automation.scriptables.append_last_edited_by.text",
username: post.user.username,
date_time:
"[date=#{updated_at.to_date} time=#{updated_at.strftime("%H:%M:%S")} timezone=UTC]",
),
),
),
).to be_truthy
PostRevisor.new(post).revise!(moderator, raw: "this is a post with edit")
cooked, updated_at = trigger_automation(post.reload)
expect(
cooked.include?(
PrettyText.cook(
I18n.t(
"discourse_automation.scriptables.append_last_edited_by.text",
username: moderator.username,
date_time:
"[date=#{updated_at.to_date} time=#{updated_at.strftime("%H:%M:%S")} timezone=UTC]",
),
),
),
).to be_truthy
end
end
end

View File

@ -0,0 +1,208 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "AutoResponder" do
fab!(:topic)
fab!(:automation) { Fabricate(:automation, script: DiscourseAutomation::Scripts::AUTO_RESPONDER) }
context "without word filter" do
before do
automation.upsert_field!(
"word_answer_list",
"key-value",
{ value: [{ key: "", value: "this is the reply" }].to_json },
)
end
it "creates an answer" do
post = create_post(topic: topic, raw: "this is a post")
automation.trigger!("post" => post)
expect(topic.reload.posts.last.raw).to eq("this is the reply")
end
end
context "with present word_answer list" do
before do
automation.upsert_field!(
"word_answer_list",
"key-value",
{
value: [
{ key: "fooz?|bar", value: "this is {{key}}" },
{ key: "bar", value: "this is {{key}}" },
].to_json,
},
)
end
context "when post is first post" do
context "when topic title contains keywords" do
it "creates an answer" do
topic = Fabricate(:topic, title: "What a foo day to walk")
post = create_post(topic: topic, raw: "this is a post with no keyword")
automation.trigger!("post" => post)
expect(topic.reload.posts.last.raw).to eq("this is foo")
end
end
context "when post and topic title contain keyword" do
it "creates only one answer" do
topic = Fabricate(:topic, title: "What a foo day to walk")
post = create_post(topic: topic, raw: "this is a post with foo keyword")
automation.trigger!("post" => post)
expect(topic.reload.posts.last.raw).to eq("this is foo")
end
end
context "when the word answer list has a wildcard (empty string) for key" do
before do
automation.upsert_field!(
"word_answer_list",
"key-value",
{ value: [{ key: "", value: "this is a response" }].to_json },
)
end
it "creates an answer" do
topic = Fabricate(:topic, title: "What a foo day to walk")
post = create_post(topic: topic, raw: "this is a post with no keyword")
automation.trigger!("post" => post)
expect(topic.reload.posts.last.raw).to eq("this is a response")
end
end
end
context "when post contains a keyword" do
it "creates an answer" do
post = create_post(topic: topic, raw: "this is foo a post with foo")
automation.trigger!("post" => post)
expect(topic.reload.posts.last.raw).to eq("this is foo")
end
context "when post has direct replies from answering user" do
fab!(:answering_user) { Fabricate(:user) }
before do
automation.upsert_field!(
"answering_user",
"user",
{ value: answering_user.username },
target: "script",
)
end
it "doesn’t create another answer" do
post_1 = create_post(topic: topic, raw: "this is a post with foo")
create_post(user: answering_user, reply_to_post_number: post_1.post_number, topic: topic)
expect { automation.trigger!("post" => post_1) }.not_to change { Post.count }
end
end
context "when user is replying to own post" do
fab!(:answering_user) { Fabricate(:user) }
before do
automation.upsert_field!(
"answering_user",
"user",
{ value: answering_user.username },
target: "script",
)
end
it "doesn’t create an answer" do
post_1 = create_post(topic: topic)
post_2 =
create_post(
user: answering_user,
topic: topic,
reply_to_post_number: post_1.post_number,
raw: "this is a post with foo",
)
expect { automation.trigger!("post" => post_2) }.not_to change { Post.count }
end
end
context "when once is used" do
before { automation.upsert_field!("once", "boolean", { value: true }, target: "script") }
it "allows only one response by automation" do
post = create_post(topic: topic, raw: "this is a post with foo and bar")
automation.trigger!("post" => post)
expect(post.topic.reload.posts_count).to eq(2)
post = create_post(topic: topic, raw: "this is another post with foo and bar")
automation.trigger!("post" => post)
expect(post.topic.reload.posts_count).to eq(3)
another_automation =
Fabricate(:automation, script: DiscourseAutomation::Scripts::AUTO_RESPONDER)
another_automation.upsert_field!("once", "boolean", { value: true }, target: "script")
another_automation.upsert_field!(
"word_answer_list",
"key-value",
{ value: [{ key: "", value: "this is the reply" }].to_json },
)
post = create_post(topic: topic, raw: "this is the last post with foo and bar")
another_automation.trigger!("post" => post)
expect(post.topic.reload.posts_count).to eq(5)
end
end
end
context "when post contains two keywords" do
it "creates an answer with both answers" do
post = create_post(topic: topic, raw: "this is a post with FOO and bar")
automation.trigger!("post" => post)
expect(topic.reload.posts.last.raw).to eq("this is FOO\n\nthis is bar")
end
end
context "when post doesn’t contain a keyword" do
it "doesn’t create an answer" do
post = create_post(topic: topic, raw: "this is a post with no keyword")
expect { automation.trigger!("post" => post) }.not_to change { Post.count }
end
end
context "when post contains two keywords" do
it "creates an answer with both answers" do
post = create_post(topic: topic, raw: "this is a post with foo and bar")
automation.trigger!("post" => post)
expect(topic.reload.posts.last.raw).to eq("this is foo\n\nthis is bar")
end
end
context "when post doesn’t contain a keyword" do
it "doesn’t create an answer" do
post = create_post(topic: topic, raw: "this is a post bfoo with no keyword fooa")
expect { automation.trigger!("post" => post) }.not_to change { Post.count }
end
end
end
context "when word_answer list is empty" do
it "exits early with no error" do
expect {
post = create_post(topic: topic, raw: "this is a post with foo and bar")
automation.trigger!("post" => post)
}.to_not raise_error
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "AutoTagTopic" do
fab!(:topic)
fab!(:tag1) { Fabricate(:tag, name: "tag1") }
fab!(:tag2) { Fabricate(:tag, name: "tag2") }
fab!(:tag3) { Fabricate(:tag, name: "tag3") }
fab!(:admin) { Fabricate(:admin, refresh_auto_groups: true) }
fab!(:automation) { Fabricate(:automation, script: DiscourseAutomation::Scripts::AUTO_TAG_TOPIC) }
context "when tags list is empty" do
it "exits early with no error" do
expect {
post = create_post(topic: topic)
automation.trigger!("post" => post)
}.to_not raise_error
end
end
context "when there are tags" do
before { automation.upsert_field!("tags", "tags", { value: %w[tag1 tag2] }) }
it "works" do
post = create_post(topic: topic)
automation.trigger!("post" => post)
expect(topic.reload.tags.pluck(:name)).to match_array(%w[tag1 tag2])
end
it "does not remove existing tags" do
post = create_post(topic: topic, tags: %w[totally])
DiscourseTagging.tag_topic_by_names(topic, Guardian.new(admin), ["tag3"])
automation.trigger!("post" => post)
expect(topic.reload.tags.pluck(:name).sort).to match_array(%w[tag1 tag2 tag3])
end
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "BannerTopic" do
before { automation.upsert_field!("topic_id", "text", { value: topic.id }) }
fab!(:automation) { Fabricate(:automation, script: DiscourseAutomation::Scripts::BANNER_TOPIC) }
fab!(:topic)
context "when banner until is set" do
before do
freeze_time
automation.upsert_field!("banner_until", "date_time", { value: 10.days.from_now })
automation.upsert_field!("topic_id", "text", { value: topic.id })
end
it "banners the topic" do
expect(topic.bannered_until).to be_nil
expect(topic.archetype).to eq(Archetype.default)
automation.trigger!
topic.reload
expect(topic.bannered_until).to be_within_one_minute_of(10.days.from_now)
expect(topic.archetype).to eq(Archetype.banner)
end
end
context "when banner until is not set" do
it "banners the topic" do
expect(topic.bannered_until).to be_nil
expect(topic.archetype).to eq(Archetype.default)
automation.trigger!
topic.reload
expect(topic.bannered_until).to be_nil
expect(topic.archetype).to eq(Archetype.banner)
end
end
end

View File

@ -0,0 +1,85 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "CloseTopic" do
fab!(:user)
fab!(:category) { Fabricate(:category, user: user) }
fab!(:topic) { Fabricate(:topic, category: category) }
fab!(:automation) { Fabricate(:automation, script: DiscourseAutomation::Scripts::CLOSE_TOPIC) }
before { automation.upsert_field!("topic", "text", { value: topic.id }, target: "script") }
context "with default params" do
it "works" do
expect(topic.closed).to be_falsey
automation.trigger!
topic.reload
expect(topic.closed).to be_truthy
closing_post = topic.posts.where(action_code: "closed.enabled").last
expect(closing_post.raw).to eq("")
expect(closing_post.user_id).to eq(-1)
end
end
context "with message" do
before do
automation.upsert_field!(
"message",
"text",
{ value: "dingity dongity dong, this topic is closed!" },
)
end
it "works" do
expect(topic.closed).to be_falsey
automation.trigger!
topic.reload
expect(topic.closed).to be_truthy
closing_post = topic.posts.where(action_code: "closed.enabled").last
expect(closing_post.raw).to eq("dingity dongity dong, this topic is closed!")
end
end
# NOTE: this is only possible because we skip validations for now.
# As soon as discourse-automation supports proper error handling and validations take place again,
# this test should be removed.
context "with very short message" do
before { automation.upsert_field!("message", "text", { value: "bye" }) }
it "closes the topic" do
expect(topic.closed).to be_falsey
automation.trigger!
topic.reload
expect(topic.closed).to be_truthy
closing_post = topic.posts.where(action_code: "closed.enabled").last
expect(closing_post.raw).to eq("bye")
end
end
context "with a specific user" do
fab!(:specific_user) { Fabricate(:user, admin: true) }
before { automation.upsert_field!("user", "user", { value: specific_user.username }) }
it "closes the topic" do
expect(topic.closed).to be_falsey
automation.trigger!
topic.reload
expect(topic.closed).to be_truthy
closing_post = topic.posts.where(action_code: "closed.enabled").last
expect(closing_post.user_id).to eq(specific_user.id)
end
end
end

View File

@ -0,0 +1,84 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "FlagPostsOnWords" do
fab!(:user)
fab!(:category) { Fabricate(:category, user: user) }
fab!(:topic) { Fabricate(:topic, category_id: category.id) }
fab!(:automation) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::FLAG_POST_ON_WORDS,
trigger: DiscourseAutomation::Triggers::POST_CREATED_EDITED,
)
end
before do
SiteSetting.discourse_automation_enabled = true
automation.fields.create!(
component: "text_list",
name: "words",
metadata: {
value: ["foo,bar"],
},
target: "script",
)
end
context "when editing/creating a post" do
context "when editing a post" do
fab!(:post)
context "when post has flagged words" do
it "flags the post" do
post.revise(post.user, raw: "this is another cool topic foo bar")
expect(post.reviewable_flag).to be_present
end
end
context "when post has no/not all flagged words" do
it "doesn’t flag the post" do
post.revise(post.user, raw: "this is another cool topic")
expect(post.reviewable_flag).to_not be_present
post.revise(post.user, raw: "this is another cool bar topic")
expect(post.reviewable_flag).to_not be_present
end
end
end
context "when creating a post" do
context "when post has flagged words" do
it "flags the post" do
post_creator =
PostCreator.new(
user,
topic_id: topic.id,
raw: "this is quite bar cool a very cool foo post",
)
post = post_creator.create
expect(post.reviewable_flag).to be_present
end
end
context "when post has no/not all flagged words" do
it "doesn’t flag the post" do
post_creator =
PostCreator.new(user, topic_id: topic.id, raw: "this is quite cool a very cool post")
post = post_creator.create
expect(post.reviewable_flag).to_not be_present
post_creator =
PostCreator.new(
user,
topic_id: topic.id,
raw: "this is quite cool a foo very cool post",
)
post = post_creator.create
expect(post.reviewable_flag).to_not be_present
end
end
end
end
end

View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "GiftExchange" do
fab!(:automation) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::GIFT_EXCHANGE,
trigger: DiscourseAutomation::Triggers::POINT_IN_TIME,
)
end
fab!(:gift_group) { Fabricate(:group) }
fab!(:user_1) { Fabricate(:user) }
fab!(:user_2) { Fabricate(:user) }
fab!(:user_3) { Fabricate(:user) }
before do
SiteSetting.discourse_automation_enabled = true
gift_group.add(user_1)
gift_group.add(user_2)
gift_group.add(user_3)
automation.upsert_field!(
"gift_exchangers_group",
"group",
{ value: gift_group.id },
target: "script",
)
automation.upsert_field!(
"giftee_assignment_messages",
"pms",
{
value: [
{
title: "Gift {{year}}",
raw: "@{{gifter_username}} you should send a gift to {{giftee_username}}",
},
],
},
target: "script",
)
end
context "when run from point_in_time trigger" do
before do
automation.upsert_field!(
"execute_at",
"date_time",
{ value: 3.hours.from_now },
target: "trigger",
)
end
it "creates expected PM" do
freeze_time 6.hours.from_now do
expect {
Jobs::DiscourseAutomationTracker.new.execute
raws = Post.order(created_at: :desc).limit(3).pluck(:raw)
expect(raws.any? { |r| r.start_with?("@#{user_1.username}") }).to be_truthy
expect(raws.any? { |r| r.start_with?("@#{user_2.username}") }).to be_truthy
expect(raws.any? { |r| r.start_with?("@#{user_3.username}") }).to be_truthy
expect(raws.any? { |r| r.end_with?("#{user_1.username}") }).to be_truthy
expect(raws.any? { |r| r.end_with?("#{user_2.username}") }).to be_truthy
expect(raws.any? { |r| r.end_with?("#{user_3.username}") }).to be_truthy
title = Post.order(created_at: :desc).limit(3).map { |post| post.topic.title }.uniq.first
expect(title).to eq("Gift #{Time.zone.now.year}")
}.to change { Post.count }.by(3) # each pair receives a PM
end
end
end
end

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "GroupCategoryNotificationDefault" do
fab!(:category)
fab!(:group)
before { SiteSetting.discourse_automation_enabled = true }
context "when using category_created_edited trigger" do
fab!(:automation) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::GROUP_CATEGORY_NOTIFICATION_DEFAULT,
trigger: DiscourseAutomation::Triggers::CATEGORY_CREATED_EDITED,
)
end
before do
automation.upsert_field!(
"restricted_category",
"category",
{ value: category.id },
target: "trigger",
)
automation.upsert_field!("group", "group", { value: group.id }, target: "script")
automation.upsert_field!(
"notification_level",
"category_notification_level",
{ value: 4 },
target: "script",
)
end
context "when category is allowed" do
it "creates a GroupCategoryNotificationDefault record" do
subcategory = nil
expect { subcategory = Fabricate(:category, parent_category_id: category.id) }.to change {
GroupCategoryNotificationDefault.count
}.by(1)
record = GroupCategoryNotificationDefault.last
expect(record.category_id).to eq(subcategory.id)
expect(record.group_id).to eq(group.id)
expect(record.notification_level).to eq(4)
end
it "updates category notification level for existing members" do
automation.upsert_field!(
"update_existing_members",
"boolean",
{ value: true },
target: "script",
)
user = Fabricate(:user)
group.add(user)
subcategory = nil
expect { subcategory = Fabricate(:category, parent_category_id: category.id) }.to change {
CategoryUser.count
}.by(1)
record = CategoryUser.last
expect(record.category_id).to eq(subcategory.id)
expect(record.user_id).to eq(user.id)
expect(record.notification_level).to eq(4)
end
end
end
end

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "PinTopic" do
fab!(:user)
fab!(:category) { Fabricate(:category, user: user) }
fab!(:topic) { Fabricate(:topic, category: category) }
fab!(:automation) { Fabricate(:automation, script: DiscourseAutomation::Scripts::PIN_TOPIC) }
before do
automation.upsert_field!("pinnable_topic", "text", { value: topic.id }, target: "script")
end
context "when not pinned globally" do
it "works" do
expect(topic.pinned_at).to be_nil
automation.trigger!
topic.reload
expect(topic.pinned_at).to be_within_one_minute_of(Time.zone.now)
expect(topic.pinned_globally).to be_falsey
expect(topic.pinned_until).to be_nil
end
end
context "when pinned globally" do
before { automation.upsert_field!("pinned_globally", "boolean", { value: true }) }
it "works" do
expect(topic.pinned_at).to be_nil
automation.trigger!
topic.reload
expect(topic.pinned_at).to be_within_one_minute_of(Time.zone.now)
expect(topic.pinned_globally).to be_truthy
expect(topic.pinned_until).to be_nil
end
end
describe "pinned until" do
before do
freeze_time
automation.upsert_field!("pinned_until", "date_time", { value: 10.days.from_now })
end
it "works" do
expect(topic.pinned_at).to be_nil
automation.trigger!
# expect_enqueued_with is sometimes failing with float precision
job = Jobs::UnpinTopic.jobs.last
expect(job["args"][0]["topic_id"]).to eq(topic.id)
expect(Time.at(job["at"])).to be_within_one_minute_of(10.days.from_now)
topic.reload
expect(topic.pinned_at).to be_within_one_minute_of(Time.zone.now)
expect(topic.pinned_globally).to be_falsey
expect(topic.pinned_until).to be_within_one_minute_of(10.days.from_now)
end
end
end

View File

@ -0,0 +1,154 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "Post" do
fab!(:topic_1) { Fabricate(:topic) }
let!(:raw) { "this is me testing a post" }
before { SiteSetting.discourse_automation_enabled = true }
context "when using point_in_time trigger" do
fab!(:automation) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::POST,
trigger: DiscourseAutomation::Triggers::POINT_IN_TIME,
)
end
before do
automation.upsert_field!(
"execute_at",
"date_time",
{ value: 3.hours.from_now },
target: "trigger",
)
automation.upsert_field!("topic", "text", { value: topic_1.id }, target: "script")
automation.upsert_field!("post", "post", { value: raw }, target: "script")
end
it "creates expected post" do
freeze_time 6.hours.from_now do
expect {
Jobs::DiscourseAutomationTracker.new.execute
expect(topic_1.posts.last.raw).to eq(raw)
}.to change { topic_1.posts.count }.by(1)
end
end
context "when topic is deleted" do
before { topic_1.trash! }
it "does nothing and does not error" do
freeze_time 6.hours.from_now do
expect { Jobs::DiscourseAutomationTracker.new.execute }.not_to change { Post.count }
end
end
end
end
context "when using recurring trigger" do
fab!(:automation) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::POST,
trigger: DiscourseAutomation::Triggers::RECURRING,
)
end
before do
automation.upsert_field!("topic", "text", { value: topic_1.id }, target: "script")
automation.upsert_field!("post", "post", { value: raw }, target: "script")
end
it "creates expected post" do
expect {
automation.trigger!
expect(topic_1.posts.last.raw).to eq(raw)
}.to change { topic_1.posts.count }.by(1)
end
it "does not create post on a closed topic" do
topic_1.update_status(:closed, true, topic_1.user)
expect { automation.trigger! }.not_to change { topic_1.posts.count }
end
it "does not create post on an archived topic" do
topic_1.update_status(:archived, true, topic_1.user)
expect { automation.trigger! }.not_to change { topic_1.posts.count }
end
end
context "when using user_updated trigger" do
fab!(:user_field_1) { Fabricate(:user_field, name: "custom field 1") }
fab!(:user_field_2) { Fabricate(:user_field, name: "custom field 2") }
fab!(:user) do
user = Fabricate(:user, trust_level: TrustLevel[0])
user.set_user_field(user_field_1.id, "Answer custom 1")
user.set_user_field(user_field_2.id, "Answer custom 2")
user.user_profile.location = "Japan"
user.user_profile.save
user.save
user
end
fab!(:automation) do
automation =
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::POST,
trigger: DiscourseAutomation::Triggers::USER_UPDATED,
)
automation.upsert_field!(
"custom_fields",
"custom_fields",
{ value: ["custom field 1", "custom field 2"] },
target: "trigger",
)
automation.upsert_field!(
"user_profile",
"user_profile",
{ value: ["location"] },
target: "trigger",
)
automation.upsert_field!("first_post_only", "boolean", { value: true }, target: "trigger")
automation
end
let!(:user_raw_post) do
"This is a raw test post for user custom field 1: {{custom_field_1}}, custom field 2: {{custom_field_2}} and location: {{location}}"
end
let!(:placeholder_applied_user_raw_post) do
"This is a raw test post for user custom field 1: #{user.custom_fields["user_field_#{user_field_1.id}"]}, custom field 2: #{user.custom_fields["user_field_#{user_field_2.id}"]} and location: #{user.user_profile.location}"
end
before do
automation.upsert_field!("topic", "text", { value: topic_1.id }, target: "script")
automation.upsert_field!("post", "post", { value: user_raw_post }, target: "script")
end
it "Creates a post correctly" do
expect {
UserUpdater.new(user, user).update(location: "Japan")
expect(topic_1.posts.last.raw).to eq(placeholder_applied_user_raw_post)
}.to change { topic_1.posts.count }.by(1)
end
context "when creator is one of accepted context" do
before do
automation.upsert_field!("creator", "user", { value: "updated_user" }, target: "script")
end
it "sets the creator to the post creator" do
expect { UserUpdater.new(user, user).update(location: "Japan") }.to change {
Post.where(user_id: user.id).count
}.by(1)
end
end
end
end

View File

@ -0,0 +1,137 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "SendPms" do
fab!(:automation) do
Fabricate(:automation, script: DiscourseAutomation::Scripts::SEND_PMS, trigger: "stalled_wiki")
end
before do
SiteSetting.discourse_automation_enabled = true
automation.upsert_field!("sender", "user", { value: Discourse.system_user.username })
automation.upsert_field!(
"sendable_pms",
"pms",
{
value: [
{
title: "A message from {{sender_username}}",
raw: "This is a message sent to @{{receiver_username}}",
},
],
},
)
end
context "when run from stalled_wiki trigger" do
fab!(:post_creator_1) { Fabricate(:user, admin: true) }
fab!(:post_1) { Fabricate(:post, user: post_creator_1) }
before do
automation.upsert_field!("stalled_after", "choices", { value: "PT1H" }, target: "trigger")
automation.upsert_field!("retriggered_after", "choices", { value: "PT1H" }, target: "trigger")
post_1.revise(
post_creator_1,
{ wiki: true },
{ force_new_version: true, revised_at: 2.hours.ago },
)
end
it "creates expected PM" do
expect {
Jobs::StalledWikiTracker.new.execute(nil)
post = Post.last
expect(post.topic.title).to eq("A message from #{Discourse.system_user.username}")
expect(post.raw).to eq("This is a message sent to @#{post_creator_1.username}")
expect(post.topic.topic_allowed_users.exists?(user_id: post_creator_1.id)).to eq(true)
expect(post.topic.topic_allowed_users.exists?(user_id: Discourse.system_user.id)).to eq(
true,
)
}.to change { Post.count }.by(1)
end
end
context "when run from user_added_to_group trigger" do
fab!(:user_1) { Fabricate(:user) }
fab!(:tracked_group_1) { Fabricate(:group) }
before do
automation.update!(trigger: "user_added_to_group")
automation.upsert_field!(
"joined_group",
"group",
{ value: tracked_group_1.id },
target: "trigger",
)
end
it "creates expected PM" do
expect {
tracked_group_1.add(user_1)
post = Post.last
expect(post.topic.title).to eq("A message from #{Discourse.system_user.username}")
expect(post.raw).to eq("This is a message sent to @#{user_1.username}")
expect(post.topic.topic_allowed_users.exists?(user_id: user_1.id)).to eq(true)
expect(post.topic.topic_allowed_users.exists?(user_id: Discourse.system_user.id)).to eq(
true,
)
}.to change { Post.count }.by(1)
end
end
context "when delayed" do
fab!(:user_1) { Fabricate(:user) }
before { automation.update!(trigger: DiscourseAutomation::Triggers::RECURRING) }
it "correctly sets encrypt preference to false even when option is not specified" do
automation.upsert_field!(
"sendable_pms",
"pms",
{
value: [
{
title: "A message from {{sender_username}}",
raw: "This is a message sent to @{{receiver_username}}",
delay: 1,
},
],
},
target: "script",
)
automation.upsert_field!("receiver", "user", { value: Discourse.system_user.username })
automation.trigger!
expect(DiscourseAutomation::PendingPm.last.prefers_encrypt).to eq(false)
end
it "correctly stores encrypt preference to false" do
automation.upsert_field!(
"sendable_pms",
"pms",
{
value: [
{
title: "A message from {{sender_username}}",
raw: "This is a message sent to @{{receiver_username}}",
delay: 1,
prefers_encrypt: false,
},
],
},
target: "script",
)
automation.upsert_field!("receiver", "user", { value: Discourse.system_user.username })
automation.trigger!
expect(DiscourseAutomation::PendingPm.last.prefers_encrypt).to eq(false)
end
end
end

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "SuspendUserByEmail" do
let(:suspend_until) { 10.days.from_now }
let(:reason) { "banned for spam" }
fab!(:automation) do
Fabricate(:automation, script: DiscourseAutomation::Scripts::SUSPEND_USER_BY_EMAIL)
end
fab!(:user)
before do
automation.upsert_field!("suspend_until", "date_time", { value: suspend_until })
automation.upsert_field!("reason", "text", { value: reason })
end
describe "using fields" do
it "suspends the user" do
expect(user.suspended?).to be(false)
expect { automation.trigger!("email" => user.email) }.to change { UserHistory.count }.by(1)
user.reload
expect(user.suspended?).to be(true)
expect(user.suspended_till).to be_within_one_minute_of(suspend_until)
user_history = UserHistory.last
expect(user_history.details).to eq(reason)
expect(user_history.acting_user_id).to eq(Discourse.system_user.id)
end
end
describe "trigger override" do
let(:reason) { "very bad behavior" }
let(:suspend_until) { 20.days.from_now }
it "suspends the user" do
expect(user.suspended?).to be(false)
expect {
automation.trigger!(
"email" => user.email,
"reason" => reason,
"suspend_until" => suspend_until,
)
}.to change { UserHistory.count }.by(1)
user.reload
expect(user.suspended?).to be(true)
expect(user.suspended_till).to be_within_one_minute_of(suspend_until)
expect(UserHistory.last.details).to eq(reason)
end
end
end

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "TopicRequiredWords" do
fab!(:user)
fab!(:category) { Fabricate(:category, user: user) }
fab!(:topic) { Fabricate(:topic, category: category) }
fab!(:automation) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::TOPIC_REQUIRED_WORDS,
trigger: DiscourseAutomation::Triggers::TOPIC,
)
end
before do
SiteSetting.discourse_automation_enabled = true
topic.upsert_custom_fields(discourse_automation_ids: automation.id)
automation.upsert_field!("words", "text_list", { value: %w[#foo #bar] })
end
describe "editing/creating a post" do
before do
automation.upsert_field!("restricted_topic", "text", { value: topic.id }, target: "trigger")
end
context "when topic has a topic_required_words automation associated" do
context "when post has at least a required word" do
it "validates the post" do
post_creator = PostCreator.new(user, topic_id: topic.id, raw: "this is quite cool #foo")
post = post_creator.create
expect(post.valid?).to be(true)
end
end
context "when post has no required word" do
it "doesn’t validate the post" do
post_creator = PostCreator.new(user, topic_id: topic.id, raw: "this is quite cool")
post = post_creator.create
expect(post.valid?).to be(false)
end
end
end
context "when topic has no topic_required_words automation associated" do
context "when post has no required word" do
it "validates the post" do
no_automation_topic = create_topic(category: category)
post_creator =
PostCreator.new(user, topic_id: no_automation_topic.id, raw: "this is quite cool")
post = post_creator.create
expect(post.valid?).to be(true)
end
end
end
end
describe "moving posts" do
fab!(:admin)
fab!(:first_post) { Fabricate(:post, topic: topic, raw: "this is quite cool #foo") }
fab!(:post) { Fabricate(:post, topic: topic, raw: "this is quite cool #bar") }
fab!(:destination_topic) { Fabricate(:post).topic }
it "works" do
expect do
topic.move_posts(admin, [post.id], destination_topic_id: destination_topic.id)
end.to_not raise_error
expect(topic.posts.where(post_type: Post.types[:regular]).size).to eq(1)
expect(destination_topic.posts.where(post_type: Post.types[:regular]).size).to eq(2)
end
end
end

View File

@ -0,0 +1,129 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "UserGlobalNotice" do
before { SiteSetting.discourse_automation_enabled = true }
context "when triggered by a stalled topic" do
fab!(:automation_1) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::USER_GLOBAL_NOTICE,
trigger: "stalled_topic",
)
end
fab!(:topic_1) { Fabricate(:topic) }
before do
automation_1.upsert_field!("stalled_after", "choices", { value: "PT1H" }, target: "trigger")
automation_1.upsert_field!("notice", "message", { value: "foo bar" }, target: "script")
automation_1.upsert_field!("level", "choices", { value: "error" }, target: "script")
end
describe "script" do
describe "StalledTopic trigger" do
it "creates a notice for the topic owner" do
expect do
automation_1.trigger!(
"kind" => DiscourseAutomation::Triggers::STALLED_TOPIC,
"topic" => topic_1,
)
end.to change { DiscourseAutomation::UserGlobalNotice.count }.by(1)
user_notice = DiscourseAutomation::UserGlobalNotice.last
expect(user_notice.user_id).to eq(topic_1.user_id)
expect(user_notice.level).to eq("error")
expect(user_notice.notice).to eq("foo bar")
end
end
end
it "creates and destroy global notices" do
post = Fabricate(:post, created_at: 1.day.ago)
expect { Jobs::StalledTopicTracker.new.execute }.to change {
DiscourseAutomation::UserGlobalNotice.count
}.by(1)
expect {
PostCreator.create!(post.user, topic_id: post.topic_id, raw: "lorem ipsum dolor sit amet")
}.to change { DiscourseAutomation::UserGlobalNotice.count }.by(-1)
end
end
context "when triggered by a first accepted solution" do
fab!(:automation_1) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::USER_GLOBAL_NOTICE,
trigger: "first_accepted_solution",
)
end
fab!(:user_1) { Fabricate(:user, username: "user_solved_1") }
fab!(:post_1) { Fabricate(:post, user: user_1) }
before do
automation_1.upsert_field!(
"notice",
"message",
{ value: "notice for {{username}}" },
target: "script",
)
automation_1.upsert_field!("level", "choices", { value: "success" }, target: "script")
end
it "creates a notice for the solution author" do
expect do
automation_1.trigger!(
"kind" => "first_accepted_solution",
"accepted_post_id" => post_1.id,
"usernames" => [post_1.user.username],
"placeholders" => {
"post_url" => Discourse.base_url + post_1.url,
},
)
end.to change { DiscourseAutomation::UserGlobalNotice.count }.by(1)
user_notice = DiscourseAutomation::UserGlobalNotice.last
expect(user_notice.user_id).to eq(post_1.user.id)
expect(user_notice.level).to eq("success")
expect(user_notice.notice).to eq("notice for user_solved_1")
end
end
describe "on_reset" do
fab!(:topic_1) { Fabricate(:topic) }
fab!(:automation_1) do
Fabricate(:automation, script: DiscourseAutomation::Scripts::USER_GLOBAL_NOTICE)
end
fab!(:automation_2) do
Fabricate(:automation, script: DiscourseAutomation::Scripts::USER_GLOBAL_NOTICE)
end
before do
[automation_1, automation_2].each do |automation|
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::STALLED_TOPIC,
"topic" => topic_1,
)
end
end
it "destroys all existing notices" do
klass = DiscourseAutomation::UserGlobalNotice
expect(klass.exists?(identifier: automation_1.id)).to eq(true)
expect(klass.exists?(identifier: automation_2.id)).to eq(true)
automation_1.scriptable.on_reset.call(automation_1)
expect(klass.exists?(identifier: automation_1.id)).to eq(false)
expect(klass.exists?(identifier: automation_2.id)).to eq(true)
end
end
end

View File

@ -0,0 +1,390 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "UserGroupMembershipThroughBadge" do
fab!(:user)
fab!(:other_users) { Fabricate.times(5, :user) }
fab!(:badge)
fab!(:target_group) { Fabricate(:group, title: "Target Title", flair_icon: "ad") }
fab!(:automation) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::USER_GROUP_MEMBERSHIP_THROUGH_BADGE,
)
end
before { BadgeGranter.enable_queue }
after do
BadgeGranter.disable_queue
BadgeGranter.clear_queue!
end
def target_group_member?(user_ids)
GroupUser.exists?(group_id: target_group.id, user_id: user_ids)
end
def owns_badge?(user_ids)
UserBadge.exists?(user_id: user_ids, badge_id: badge.id)
end
context "with invalid field values" do
before do
@original_logger = Rails.logger
Rails.logger = @fake_logger = FakeLogger.new
end
after { Rails.logger = @original_logger }
context "with an unknown badge" do
let(:unknown_badge_id) { -1 }
before do
automation.upsert_field!("badge", "choices", { value: unknown_badge_id }, target: "script")
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
end
it "logs warning message and does nothing" do
expect(@fake_logger.warnings).to include(
"[discourse-automation] Couldn’t find badge with id #{unknown_badge_id}",
)
expect(user.reload.groups).to be_empty
end
end
context "with a non-existent group" do
before do
automation.upsert_field!("badge", "choices", { value: badge.id }, target: "script")
automation.upsert_field!("group", "group", { value: target_group.id }, target: "script")
end
it "logs warning message and does nothing" do
target_group.destroy
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
expect(@fake_logger.warnings).to include(
"[discourse-automation] Couldn’t find group with id #{target_group.id}",
)
expect(user.reload.groups).to be_empty
end
end
end
context "with valid field values" do
before do
automation.upsert_field!("badge", "choices", { value: badge.id }, target: "script")
automation.upsert_field!("group", "group", { value: target_group.id }, target: "script")
end
context "when triggered with a user" do
context "when user has badge" do
before { BadgeGranter.grant(badge, user) }
it "adds user to group" do
expect(target_group_member?([user.id])).to eq(false)
expect do
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
end.to change { target_group.users.count }.by(1)
expect(target_group_member?([user.id])).to eq(true)
expect(owns_badge?([user.id])).to eq(true)
end
it "does nothing if user is an existing group member" do
target_group.add(user)
user.reload
current_membership = user.group_users.find_by(group_id: target_group.id)
expect(current_membership).not_to be_nil
expect do
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
end.not_to change { target_group.reload.users.count }
expect(GroupUser.find_by(group_id: target_group.id, user_id: user.id)).to eq(
current_membership,
)
expect(owns_badge?([user.id])).to eq(true)
end
it "does not add other badge owners" do
other_users.each { |u| BadgeGranter.grant(badge, u) }
expect(badge.user_badges.count).to eq(6)
expect(target_group.users.count).to eq(0)
expect do
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
end.to change { target_group.reload.users.count }.by(1)
expect(target_group_member?([user.id])).to eq(true)
expect(owns_badge?([user.id])).to eq(true)
end
end
context "when user does not have badge" do
it "does not add user to group" do
expect(target_group_member?([user.id])).to eq(false)
expect(owns_badge?([user.id])).to eq(false)
expect do
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
end.not_to change { target_group.users.count }
expect(target_group_member?([user.id])).to eq(false)
expect(owns_badge?([user.id])).to eq(false)
end
it "does not add other badge owners" do
other_users.each { |u| BadgeGranter.grant(badge, u) }
expect(badge.user_badges.count).to eq(5)
expect(target_group.users.count).to eq(0)
expect do
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
end.not_to change { target_group.reload.users.count }
expect(target_group_member?([user.id])).to eq(false)
expect(owns_badge?([user.id])).to eq(false)
expect(target_group.users.count).to eq(0)
end
end
end
context "when triggered without a user" do
let(:badge_owners) { other_users.first(3) }
let(:non_badge_owners) { other_users.last(2) }
let(:badge_owner_ids) { badge_owners.map(&:id) }
let(:non_badge_owner_ids) { non_badge_owners.map(&:id) }
before { badge_owners.each { |u| BadgeGranter.grant(badge, u) } }
it "adds all users with badge to group" do
expect(target_group_member?(badge_owner_ids)).to eq(false)
expect(target_group_member?(non_badge_owner_ids)).to eq(false)
expect do
automation.trigger!("kind" => DiscourseAutomation::Triggers::RECURRING)
end.to change { target_group.reload.users.count }.by(badge_owners.size)
expect(target_group_member?(badge_owner_ids)).to eq(true)
expect(target_group_member?(non_badge_owner_ids)).to eq(false)
end
it "skips existing group members with badge" do
badge_owners.each { |u| target_group.add(u) }
expect(target_group_member?(badge_owner_ids)).to eq(true)
expect(target_group_member?(non_badge_owner_ids)).to eq(false)
expect do
automation.trigger!("kind" => DiscourseAutomation::Triggers::RECURRING)
end.not_to change { target_group.reload.users.count }
expect(target_group_member?(badge_owner_ids)).to eq(true)
expect(target_group_member?(non_badge_owner_ids)).to eq(false)
end
end
context "with remove_members_without_badge = true" do
before do
automation.upsert_field!(
"remove_members_without_badge",
"boolean",
{ value: true },
target: "script",
)
other_users.each { |u| target_group.add(u) }
end
it "removes existing members without badge" do
expect(target_group_member?(other_users.map(&:id))).to eq(true)
expect do
automation.trigger!("kind" => DiscourseAutomation::Triggers::RECURRING)
end.to change { target_group.reload.users.count }.by(-other_users.count)
expect(target_group_member?(other_users.map(&:id))).to eq(false)
end
it "keeps existing members with badge" do
BadgeGranter.grant(badge, user)
target_group.add(user)
expect(target_group_member?(other_users.map(&:id))).to eq(true)
expect(owns_badge?(other_users.map(&:id))).to eq(false)
expect(target_group_member?([user.id])).to eq(true)
expect(owns_badge?([user.id])).to eq(true)
expect do
automation.trigger!("kind" => DiscourseAutomation::Triggers::RECURRING)
end.to change { target_group.reload.users.count }
expect(target_group_member?(other_users.map(&:id))).to eq(false)
expect(owns_badge?(other_users.map(&:id))).to eq(false)
expect(target_group_member?([user.id])).to eq(true)
expect(owns_badge?([user.id])).to eq(true)
end
end
context "with remove_members_without_badge = false" do
before do
automation.upsert_field!(
"remove_members_without_badge",
"boolean",
{ value: false },
target: "script",
)
end
it "keeps existing members without badge" do
other_users.each { |u| target_group.add(u) }
expect(target_group_member?(other_users.map(&:id))).to eq(true)
expect(owns_badge?(other_users.map(&:id))).to eq(false)
expect do
automation.trigger!("kind" => DiscourseAutomation::Triggers::RECURRING)
end.not_to change { target_group.reload.users.count }
expect(target_group_member?(other_users.map(&:id))).to eq(true)
expect(owns_badge?(other_users.map(&:id))).to eq(false)
end
end
context "with update_user_title_and_flair = true" do
before do
BadgeGranter.grant(badge, user)
automation.upsert_field!(
"update_user_title_and_flair",
"boolean",
{ value: true },
target: "script",
)
end
it "sets user title and flair" do
expect(user.title).to be_nil
expect(user.flair_group_id).to be_nil
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
user.reload
expect(user.title).to eq("Target Title")
expect(user.flair_group_id).to eq(target_group.id)
end
it "updates existing user title and flair" do
existing_flair_group = Fabricate(:group)
user.update(title: "Existing Title", flair_group_id: existing_flair_group.id)
expect(user.title).to eq("Existing Title")
expect(user.flair_group_id).to eq(existing_flair_group.id)
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
user.reload
expect(user.title).to eq("Target Title")
expect(user.flair_group_id).to eq(target_group.id)
user_badge = UserBadge.find_by(user_id: user.id, badge_id: badge.id)
user_badge.destroy
automation.upsert_field!(
"remove_members_without_badge",
"boolean",
{ value: true },
target: "script",
)
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
user.reload
expect(user.title).to be_nil
expect(user.flair_group_id).to be_nil
end
end
context "with update_user_title_and_flair = false" do
before do
BadgeGranter.grant(badge, user)
automation.upsert_field!(
"update_user_title_and_flair",
"boolean",
{ value: false },
target: "script",
)
end
it "does not update existing user title and flair" do
existing_flair_group = Fabricate(:group)
user.update(title: "Existing Title", flair_group_id: existing_flair_group.id)
expect(user.title).to eq("Existing Title")
expect(user.flair_group_id).to eq(existing_flair_group.id)
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
user.reload
expect(user.title).to eq("Existing Title")
expect(user.flair_group_id).to eq(existing_flair_group.id)
user_badge = UserBadge.find_by(user_id: user.id, badge_id: badge.id)
user_badge.destroy
automation.upsert_field!(
"remove_members_without_badge",
"boolean",
{ value: true },
target: "script",
)
automation.trigger!(
"kind" => DiscourseAutomation::Triggers::USER_FIRST_LOGGED_IN,
"user" => user,
)
user.reload
expect(user.title).to eq("Existing Title")
expect(user.flair_group_id).to eq(existing_flair_group.id)
end
end
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
require_relative "../discourse_automation_helper"
describe "ZapierWebhook" do
fab!(:topic)
fab!(:automation) { Fabricate(:automation, script: DiscourseAutomation::Scripts::ZAPIER_WEBHOOK) }
context "with valid webhook url" do
before do
automation.upsert_field!(
"webhook_url",
"text",
{ value: "https://hooks.zapier.com/hooks/catch/foo/bar" },
)
end
it "enqueues the zapier call" do
expect { automation.trigger! }.to change {
Jobs::DiscourseAutomationCallZapierWebhook.jobs.length
}.by(1)
end
end
context "with invalid webhook url" do
before do
@orig_logger = Rails.logger
Rails.logger = @fake_logger = FakeLogger.new
end
after { Rails.logger = @orig_logger }
it "logs an error and do nothing" do
expect { automation.trigger! }.not_to change {
Jobs::DiscourseAutomationCallZapierWebhook.jobs.length
}
expect(Rails.logger.warnings.first).to match(/is not a valid Zapier/)
end
end
end