discourse/plugins/automation/spec/scripts/set_topic_timer_spec.rb
Régis Hanol d4103b8e96
FIX: Prevent automation set_topic_timer from reopening closed topics (#39375)
When the `set_topic_timer` automation script saves a `:close` or
`:silent_close` timer on a topic that is already closed,
`TopicTimer#after_save` treats that state mismatch as the "Open
Temporarily" UI intent and immediately reopens the topic as the system
user.

Example seen on meta: an automation configured to add a 3-day close
timer whenever the `fixed` tag is present and a bug post is edited was
inadvertently reopening already-closed bug topics. Editing such a topic
fired the automation, the `:close` timer was saved, and the topic was
reopened by `system` before a moderator noticed and re-closed it.

Guard `auto_close` and `auto_close_after_last_post` in the script with
an early `next` when `topic.closed?` so the automation no-ops on closed
topics rather than triggering the reopen side-effect.
2026-04-21 15:08:31 +02:00

172 lines
5 KiB
Ruby

# frozen_string_literal: true
describe "SetTopicTimer" do
fab!(:automation) do
Fabricate(
:automation,
script: DiscourseAutomation::Scripts::SET_TOPIC_TIMER,
trigger: DiscourseAutomation::Triggers::POST_CREATED_EDITED,
)
end
def configure_automation(type, duration)
automation.fields.create!(
component: "choices",
name: "type",
metadata: {
value: type,
},
target: "script",
)
automation.fields.create!(
component: "relative_time",
name: "duration",
metadata: {
value: duration,
},
target: "script",
)
end
before { SiteSetting.discourse_automation_enabled = true }
it "handles auto_close timer" do
configure_automation("auto_close", 60)
freeze_time do
post =
PostCreator.new(
Fabricate(:admin),
raw: "my new topic",
title: "Test topic for timer",
).create!
timer = post.topic.reload.topic_timers[0]
expect(timer.status_type).to eq(TopicTimer.types[:close])
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
expect(timer.based_on_last_post).to eq(false)
end
end
it "skips auto_close timer when topic is already closed" do
configure_automation("auto_close", 60)
post =
PostCreator.new(Fabricate(:admin), raw: "my new topic", title: "Test topic for timer").create!
post.topic.update_status("closed", true, Discourse.system_user)
expect { PostRevisor.new(post).revise!(post.user, raw: "an edit") }.not_to change {
post.topic.reload.topic_timers.count
}
expect(post.topic.reload.closed).to eq(true)
end
it "skips auto_close_after_last_post timer when topic is already closed" do
configure_automation("auto_close_after_last_post", 60)
post =
PostCreator.new(Fabricate(:admin), raw: "my new topic", title: "Test topic for timer").create!
post.topic.update_status("closed", true, Discourse.system_user)
expect { PostRevisor.new(post).revise!(post.user, raw: "an edit") }.not_to change {
post.topic.reload.topic_timers.count
}
expect(post.topic.reload.closed).to eq(true)
end
it "handles auto_close_after_last_post timer" do
configure_automation("auto_close_after_last_post", 60)
freeze_time do
post =
PostCreator.new(
Fabricate(:admin),
raw: "my new topic",
title: "Test topic for timer",
).create!
timer = post.topic.reload.topic_timers[0]
expect(timer.status_type).to eq(TopicTimer.types[:close])
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
expect(timer.duration_minutes).to eq(60)
expect(timer.based_on_last_post).to eq(true)
end
end
it "handles auto_delete timer" do
configure_automation("auto_delete", 60)
freeze_time do
post =
PostCreator.new(
Fabricate(:admin),
raw: "my new topic",
title: "Test topic for timer",
).create!
timer = post.topic.reload.topic_timers[0]
expect(timer.status_type).to eq(TopicTimer.types[:delete])
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
expect(timer.duration_minutes).to eq(nil)
expect(timer.based_on_last_post).to eq(false)
end
end
it "handles auto_delete_replies timer" do
configure_automation("auto_delete_replies", 60)
freeze_time do
post =
PostCreator.new(
Fabricate(:admin),
raw: "my new topic",
title: "Test topic for timer",
).create!
timer = post.topic.reload.topic_timers[0]
expect(timer.status_type).to eq(TopicTimer.types[:delete_replies])
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
expect(timer.duration_minutes).to eq(60)
expect(timer.based_on_last_post).to eq(false)
end
end
it "handles auto_delete_after_last_post timer" do
configure_automation("auto_delete_after_last_post", 60)
freeze_time do
post =
PostCreator.new(
Fabricate(:admin),
raw: "my new topic",
title: "Test topic for timer",
).create!
timer = post.topic.reload.topic_timers[0]
expect(timer).not_to be_nil
expect(timer.status_type).to eq(TopicTimer.types[:delete])
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
expect(timer.duration_minutes).to eq(60)
expect(timer.based_on_last_post).to eq(true)
end
end
it "handles auto_bump timer" do
configure_automation("auto_bump", 60)
freeze_time do
post =
PostCreator.new(
Fabricate(:admin),
raw: "my new topic",
title: "Test topic for timer",
).create!
timer = post.topic.reload.topic_timers[0]
expect(timer.status_type).to eq(TopicTimer.types[:bump])
expect(timer.execute_at).to be_within(1.second).of(60.minutes.from_now)
expect(timer.duration_minutes).to eq(nil)
expect(timer.based_on_last_post).to eq(false)
end
end
end