discourse/plugins/automation/spec/models/automation_spec.rb
Osama Sayegh 0e44072b2b
FIX: Prevent infinite loop of automations triggering each other (#26814)
It's currently possible to setup multiple automation rules that trigger each other resulting in an infinite loop. To prevent that, this commit adds a global "circuit breaker" that prevents all automations from triggering while an automation rule is executing.

Internal topic: t/124365.
2024-04-30 20:13:29 +03:00

176 lines
4.8 KiB
Ruby
Raw 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
require_relative "../discourse_automation_helper"
describe DiscourseAutomation::Automation do
describe "#trigger!" do
context "when not enabled" do
fab!(:automation) { Fabricate(:automation, enabled: false) }
it "doesnt do anything" do
list = capture_contexts { automation.trigger!("Howdy!") }
expect(list).to eq([])
end
end
context "when enabled" do
fab!(:automation) { Fabricate(:automation, enabled: true) }
it "runs the script" do
list = capture_contexts { automation.trigger!("Howdy!") }
expect(list).to eq(["Howdy!"])
end
end
end
describe "when a script is meant to be triggered in the background" do
fab!(:automation) do
Fabricate(:automation, enabled: true, script: "test-background-scriptable")
end
before do
DiscourseAutomation::Scriptable.add("test_background_scriptable") do
run_in_background
script do |context|
DiscourseAutomation::CapturedContext.add(context)
nil
end
end
end
it "runs a sidekiq job to trigger it" do
expect { automation.trigger!({ val: "Howdy!" }) }.to change {
Jobs::DiscourseAutomationTrigger.jobs.size
}.by(1)
end
end
describe "#detach_custom_field" do
fab!(:automation)
it "expects a User/Topic/Post instance" do
expect { automation.detach_custom_field(Invite.new) }.to raise_error(RuntimeError)
end
end
describe "#attach_custom_field" do
fab!(:automation)
it "expects a User/Topic/Post instance" do
expect { automation.attach_custom_field(Invite.new) }.to raise_error(RuntimeError)
end
end
context "when automations script has a field with validor" do
before do
DiscourseAutomation::Scriptable.add("required_dogs") do
field :dog, component: :text, validator: ->(input) { "must have dog" if input != "dog" }
end
end
context "when validating automation" do
fab!(:automation) { Fabricate(:automation, enabled: false, script: "required_dogs") }
it "raises an error if invalid" do
expect {
automation.fields.create!(
name: "dog",
component: "text",
metadata: {
value: nil,
},
target: "script",
)
}.to raise_error(ActiveRecord::RecordInvalid, /must have dog/)
end
it "does nothing if valid" do
expect {
automation.fields.create!(
name: "dog",
component: "text",
metadata: {
value: "dog",
},
target: "script",
)
}.not_to raise_error
end
end
end
context "when automations script has a required field" do
before do
DiscourseAutomation::Scriptable.add("required_dogs") do
field :dog, component: :text, required: true
end
end
context "when field is not filled" do
fab!(:automation) { Fabricate(:automation, enabled: false, script: "required_dogs") }
context "when validating automation" do
it "raises an error" do
expect {
automation.fields.create!(
name: "dog",
component: "text",
metadata: {
value: nil,
},
target: "script",
)
}.to raise_error(ActiveRecord::RecordInvalid, /dog/)
end
end
end
end
context "when automations trigger has a required field" do
before do
DiscourseAutomation::Triggerable.add("required_dogs") do
field :dog, component: :text, required: true
end
end
context "when field is not filled" do
fab!(:automation) { Fabricate(:automation, enabled: false, trigger: "required_dogs") }
context "when validating automation" do
it "raises an error" do
expect {
automation.fields.create!(
name: "dog",
component: "text",
metadata: {
value: nil,
},
target: "trigger",
)
}.to raise_error(ActiveRecord::RecordInvalid, /dog/)
end
end
end
end
describe "after_destroy" do
fab!(:automation) { Fabricate(:automation, enabled: false) }
fab!(:automation2) { Fabricate(:automation, enabled: false) }
it "deletes user custom fields that indicate new users" do
user = Fabricate(:user)
user.custom_fields[automation.new_user_custom_field_name] = "1"
user.custom_fields[automation2.new_user_custom_field_name] = "1"
user.save_custom_fields
automation.destroy!
user.reload
expect(user.custom_fields).to eq({ automation2.new_user_custom_field_name => "1" })
end
end
end