2024-04-03 23:20:43 +08:00
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
|
|
describe DiscourseAutomation::Scriptable do
|
|
|
|
|
before do
|
|
|
|
|
DiscourseAutomation::Scriptable.add("cats_everywhere") do
|
|
|
|
|
version 1
|
|
|
|
|
|
|
|
|
|
placeholder :foo
|
|
|
|
|
placeholder :bar
|
|
|
|
|
placeholder { |fields, automation| "baz-#{automation.id}" }
|
|
|
|
|
placeholder { |fields, automation| ["foo-baz-#{automation.id}"] }
|
|
|
|
|
|
|
|
|
|
field :cat, component: :text
|
|
|
|
|
field :dog, component: :text, accepts_placeholders: true, accepted_contexts: ["user"]
|
|
|
|
|
field :bird, component: :text, triggerable: "recurring"
|
|
|
|
|
|
|
|
|
|
script { p "script" }
|
|
|
|
|
|
|
|
|
|
on_reset { p "on_reset" }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
DiscourseAutomation::Triggerable.add("dog") { field :kind, component: :text }
|
|
|
|
|
|
|
|
|
|
DiscourseAutomation::Scriptable.add("only_dogs") { triggerable! :dog, { kind: "good_boy" } }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
fab!(:automation) do
|
|
|
|
|
Fabricate(:automation, script: "cats_everywhere", trigger: DiscourseAutomation::Triggers::TOPIC)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "#fields" do
|
|
|
|
|
it "returns the fields" do
|
|
|
|
|
expect(automation.scriptable.fields).to match_array(
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
extra: {
|
|
|
|
|
},
|
|
|
|
|
name: :cat,
|
|
|
|
|
component: :text,
|
|
|
|
|
accepts_placeholders: false,
|
|
|
|
|
triggerable: nil,
|
|
|
|
|
required: false,
|
|
|
|
|
accepted_contexts: [],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
extra: {
|
|
|
|
|
},
|
|
|
|
|
name: :dog,
|
|
|
|
|
component: :text,
|
|
|
|
|
accepts_placeholders: true,
|
|
|
|
|
triggerable: nil,
|
|
|
|
|
required: false,
|
|
|
|
|
accepted_contexts: ["user"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
extra: {
|
|
|
|
|
},
|
|
|
|
|
name: :bird,
|
|
|
|
|
component: :text,
|
|
|
|
|
accepts_placeholders: false,
|
|
|
|
|
triggerable: "recurring",
|
|
|
|
|
required: false,
|
|
|
|
|
accepted_contexts: [],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "#script" do
|
|
|
|
|
it "returns the script proc" do
|
|
|
|
|
output = capture_stdout { automation.scriptable.script.call }
|
|
|
|
|
|
|
|
|
|
expect(output).to include("script")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "#on_reset" do
|
|
|
|
|
it "returns the on_reset proc" do
|
|
|
|
|
output = capture_stdout { automation.scriptable.on_reset.call }
|
|
|
|
|
|
|
|
|
|
expect(output).to include("on_reset")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "#placeholders" do
|
|
|
|
|
it "returns the specified placeholders" do
|
|
|
|
|
expect(automation.scriptable.placeholders).to eq(
|
|
|
|
|
[
|
|
|
|
|
{ name: :foo, triggerable: nil },
|
|
|
|
|
{ name: :bar, triggerable: nil },
|
|
|
|
|
{ name: :"baz-#{automation.id}", triggerable: nil },
|
|
|
|
|
{ name: :"foo-baz-#{automation.id}", triggerable: nil },
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "#version" do
|
|
|
|
|
it "returns the specified version" do
|
|
|
|
|
expect(automation.scriptable.version).to eq(1)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe ".add" do
|
|
|
|
|
it "adds the script to the list of available scripts" do
|
|
|
|
|
expect(automation.scriptable).to respond_to(:__scriptable_cats_everywhere)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe ".all" do
|
|
|
|
|
it "returns the list of available scripts" do
|
|
|
|
|
expect(DiscourseAutomation::Scriptable.all).to include(:__scriptable_cats_everywhere)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe ".name" do
|
|
|
|
|
it "returns the name of the script" do
|
|
|
|
|
expect(automation.scriptable.name).to eq("cats_everywhere")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "triggerable!" do
|
|
|
|
|
fab!(:automation) { Fabricate(:automation, script: "only_dogs", trigger: "dog") }
|
|
|
|
|
|
|
|
|
|
it "has a forced triggerable" do
|
|
|
|
|
expect(automation.scriptable.forced_triggerable).to eq(
|
|
|
|
|
triggerable: :dog,
|
|
|
|
|
state: {
|
|
|
|
|
kind: "good_boy",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "returns the forced triggerable in triggerables" do
|
|
|
|
|
expect(automation.scriptable.triggerables).to eq([:dog])
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe ".utils" do
|
|
|
|
|
describe ".fetch_report" do
|
|
|
|
|
context "when the report doesn’t exist" do
|
|
|
|
|
it "does nothing" do
|
|
|
|
|
expect(automation.scriptable.utils.fetch_report(:foo)).to eq(nil)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the report exists" do
|
|
|
|
|
it "returns the data" do
|
|
|
|
|
freeze_time DateTime.parse("2022-02-25")
|
|
|
|
|
Fabricate(:like, user: Fabricate(:user))
|
|
|
|
|
Fabricate(:like, user: Fabricate(:user))
|
|
|
|
|
|
|
|
|
|
expect(automation.scriptable.utils.fetch_report(:likes)).to eq(
|
|
|
|
|
"\n|Day|Count|\n|-|-|\n|2022-02-25|2|\n",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe ".apply_placeholders" do
|
|
|
|
|
it "replaces the given string by placeholders" do
|
|
|
|
|
input = "hello %%COOL_CAT%% {{cool_cat}}"
|
|
|
|
|
map = { cool_cat: "siberian cat" }
|
|
|
|
|
output = automation.scriptable.utils.apply_placeholders(input, map)
|
|
|
|
|
expect(output).to eq("hello siberian cat siberian cat")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "replaces site_title by default" do
|
|
|
|
|
input = "hello {{site_title}}"
|
|
|
|
|
output = automation.scriptable.utils.apply_placeholders(input)
|
|
|
|
|
expect(output).to eq("hello #{SiteSetting.title}")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when using the REPORT key" do
|
|
|
|
|
context "with no filters specified" do
|
|
|
|
|
it "replaces REPORT key" do
|
|
|
|
|
freeze_time DateTime.parse("2022-02-22")
|
|
|
|
|
Fabricate(:like, user: Fabricate(:user))
|
|
|
|
|
Fabricate(:like, user: Fabricate(:user))
|
|
|
|
|
input = "hello %%REPORT=likes%%"
|
|
|
|
|
|
|
|
|
|
output = automation.scriptable.utils.apply_placeholders(input, {})
|
|
|
|
|
expect(output).to eq("hello \n|Day|Count|\n|-|-|\n|2022-02-22|2|\n")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with dates specified" do
|
|
|
|
|
it "replaces REPORT key using dates" do
|
|
|
|
|
freeze_time DateTime.parse("2022-02-14")
|
|
|
|
|
group = Fabricate(:group)
|
|
|
|
|
group.add(Fabricate(:user, created_at: DateTime.parse("2022-02-01")))
|
|
|
|
|
group.add(Fabricate(:user, created_at: DateTime.parse("2022-02-12")))
|
|
|
|
|
input = "hello %%REPORT=signups start_date=2022-02-10%%"
|
|
|
|
|
|
|
|
|
|
output = automation.scriptable.utils.apply_placeholders(input, {})
|
|
|
|
|
expect(output).to eq("hello \n|Day|Count|\n|-|-|\n|2022-02-12|1|\n")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with filters specified" do
|
|
|
|
|
it "replaces REPORT key using filters" do
|
|
|
|
|
freeze_time DateTime.parse("2022-02-15")
|
|
|
|
|
group = Fabricate(:group)
|
|
|
|
|
group.add(Fabricate(:user))
|
|
|
|
|
Fabricate(:user)
|
|
|
|
|
input = "hello %%REPORT=signups group=#{group.id}%%"
|
|
|
|
|
|
|
|
|
|
output = automation.scriptable.utils.apply_placeholders(input, {})
|
|
|
|
|
expect(output).to eq("hello \n|Day|Count|\n|-|-|\n|2022-02-15|1|\n")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-10-08 20:55:11 +08:00
|
|
|
|
describe ".build_quote" do
|
|
|
|
|
subject(:quote) { DiscourseAutomation::Scriptable::Utils.build_quote(post) }
|
|
|
|
|
|
|
|
|
|
fab!(:user) { Fabricate(:user, name: "John Doe", username: "johndoe") }
|
|
|
|
|
fab!(:post) { Fabricate(:post, user: user, raw: "This is a post content", post_number: 1) }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.display_name_on_posts = false
|
|
|
|
|
SiteSetting.prioritize_username_in_ux = false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when post is nil" do
|
|
|
|
|
let(:post) { nil } # Define post as nil in this context
|
|
|
|
|
|
|
|
|
|
it "returns an empty string" do
|
|
|
|
|
expect(quote).to eq("")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when post.raw is nil" do
|
|
|
|
|
before { post.raw = nil }
|
|
|
|
|
|
|
|
|
|
it "returns an empty string" do
|
|
|
|
|
expect(quote).to eq("")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when display_name_on_posts is true and prioritize_username_in_ux is false" do
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.display_name_on_posts = true
|
|
|
|
|
SiteSetting.prioritize_username_in_ux = false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "returns a quote with display name" do
|
|
|
|
|
expect(quote).to eq(
|
|
|
|
|
"[quote=John Doe, post:#{post.post_number}, topic:#{post.topic_id}, username:johndoe]\nThis is a post content\n[/quote]\n\n",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when display_name_on_posts is false or prioritize_username_in_ux is true" do
|
|
|
|
|
it "returns a quote with username" do
|
|
|
|
|
expect(quote).to eq(
|
|
|
|
|
"[quote=johndoe, post:#{post.post_number}, topic:#{post.topic_id}]\nThis is a post content\n[/quote]\n\n",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when full_name is nil and display_name_on_posts is true" do
|
|
|
|
|
before do
|
|
|
|
|
user.update(name: nil)
|
|
|
|
|
SiteSetting.display_name_on_posts = true
|
|
|
|
|
SiteSetting.prioritize_username_in_ux = false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "returns a quote with username" do
|
|
|
|
|
expect(quote).to eq(
|
|
|
|
|
"[quote=johndoe, post:#{post.post_number}, topic:#{post.topic_id}]\nThis is a post content\n[/quote]\n\n",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when display_name_on_posts is true and prioritize_username_in_ux is true" do
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.display_name_on_posts = true
|
|
|
|
|
SiteSetting.prioritize_username_in_ux = true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "returns a quote with username prioritized" do
|
|
|
|
|
expect(quote).to eq(
|
|
|
|
|
"[quote=johndoe, post:#{post.post_number}, topic:#{post.topic_id}]\nThis is a post content\n[/quote]\n\n",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-04-03 23:20:43 +08:00
|
|
|
|
describe ".send_pm" do
|
|
|
|
|
let(:user) { Fabricate(:user) }
|
|
|
|
|
|
|
|
|
|
context "when pm is delayed" do
|
|
|
|
|
it "creates a pending pm" do
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Tell me and I forget.",
|
|
|
|
|
raw: "Teach me and I remember. Involve me and I learn.",
|
|
|
|
|
target_usernames: Array(user.username),
|
|
|
|
|
},
|
|
|
|
|
delay: 2,
|
|
|
|
|
automation_id: automation.id,
|
|
|
|
|
)
|
|
|
|
|
}.to change { DiscourseAutomation::PendingPm.count }.by(1)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when pm is not delayed" do
|
|
|
|
|
it "creates a pm" do
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Tell me and I forget.",
|
|
|
|
|
raw: "Teach me and I remember. Involve me and I learn.",
|
|
|
|
|
target_usernames: Array(user.username),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.to change { Post.count }.by(1)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when pm exceeds max_post_length" do
|
|
|
|
|
it "throws an error" do
|
|
|
|
|
SiteSetting.max_post_length = 250
|
|
|
|
|
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Tell me and I forget.",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_usernames: [user.username],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.to raise_error(ActiveRecord::RecordNotSaved)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when pm target_usernames contain an invalid user" do
|
|
|
|
|
it "skips sending if there is only one target" do
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Tell me and I forget.",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_usernames: ["non-existent-user"],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.not_to change { Topic.count }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "sends the pm without the invalid user" do
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Tell me and I forget.",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_usernames: ["non-existent-user", user.username],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.to change { Topic.count }
|
|
|
|
|
|
|
|
|
|
expect(Topic.last.allowed_users).to contain_exactly(Discourse.system_user, user)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when pm target_groups with valid group" do
|
|
|
|
|
it "sends the pm" do
|
|
|
|
|
group = Fabricate(:group)
|
|
|
|
|
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Tell me and I forget.",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_group_names: [group.name],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.to change { Topic.count }
|
|
|
|
|
|
|
|
|
|
expect(Topic.last.allowed_groups).to contain_exactly(group)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when pm target_groups contain an invalid group" do
|
|
|
|
|
it "skips sending if there is only one target" do
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Tell me and I forget.",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_group_names: ["non-existent-group"],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.not_to change { Topic.count }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "sends the pm without the invalid group" do
|
|
|
|
|
group = Fabricate(:group)
|
|
|
|
|
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Tell me and I forget.",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_group_names: ["non-existent-group", group.name],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.to change { Topic.count }
|
|
|
|
|
|
|
|
|
|
expect(Topic.last.allowed_groups).to contain_exactly(group)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when pm target_emails with valid email" do
|
|
|
|
|
it "sends the pm" do
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Private Message Title",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_emails: ["john@doe.com"],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.to change { Topic.private_messages.count }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "sends shared pm when multiple emails" do
|
|
|
|
|
email_1 = "john@doe.com"
|
|
|
|
|
email_2 = "jane@doe.com"
|
|
|
|
|
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Private Message Title",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_emails: [email_1, email_2],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.to change { Topic.private_messages.count }
|
|
|
|
|
|
|
|
|
|
# creates new users if they don't exist
|
|
|
|
|
user_1 = User.find_by_email(email_1)
|
|
|
|
|
user_2 = User.find_by_email(email_2)
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
Topic.private_messages.first.topic_allowed_users.pluck(:user_id),
|
|
|
|
|
).to contain_exactly(Discourse.system_user.id, user_1.id, user_2.id)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when pm target_emails contain an invalid email" do
|
|
|
|
|
it "skips sending if there is only one target" do
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Private Message Title",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_emails: ["invalid-email"],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.not_to change { Topic.private_messages.count }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "sends the pm without the invalid email" do
|
|
|
|
|
expect {
|
|
|
|
|
DiscourseAutomation::Scriptable::Utils.send_pm(
|
|
|
|
|
{
|
|
|
|
|
title: "Private Message Title",
|
|
|
|
|
raw: "0123456789" * 25 + "a",
|
|
|
|
|
target_emails: %w[invalid-email john@doe.com],
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}.to change { Topic.private_messages.count }.by(1)
|
|
|
|
|
|
|
|
|
|
new_user = User.find_by_email("john@doe.com")
|
|
|
|
|
expect(
|
|
|
|
|
Topic.private_messages.first.topic_allowed_users.pluck(:user_id),
|
|
|
|
|
).to contain_exactly(Discourse.system_user.id, new_user.id)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|