discourse/spec/models/problem_check_tracker_spec.rb
Martin Brennan d8102cb532
FIX: Update AdminNotice details when problem check tracker changes (#31031)
We have many problem check trackers, and some of them
like `OutOfDateThemes` can have a message which has variable
data in it shown to admins. In this case, a list of themes
that need updating. Currently if you resolve one of these
out of date themes and refresh the list of problems, you
do not see any change.

This is happening because we are only updating the `details`
of the `ProblemCheckTracker` record, not the corresponding
`AdminNotice` record which is what is displayed to the admins
on their dashboard. This commit fixes the issue by updating the
details of the notice at the same time as the problem check
tracker details.
2025-01-29 09:44:20 +10:00

295 lines
8.2 KiB
Ruby

# frozen_string_literal: true
RSpec.describe ProblemCheckTracker do
describe "validations" do
let(:record) { described_class.new(identifier: "twitter_login") }
it { expect(record).to validate_presence_of(:identifier) }
it { expect(record).to validate_uniqueness_of(:identifier).scoped_to(:target) }
it { expect(record).to validate_numericality_of(:blips).is_greater_than_or_equal_to(0) }
end
describe ".[]" do
before { Fabricate(:problem_check_tracker, identifier: "twitter_login") }
context "when the problem check tracker already exists" do
it { expect(described_class[:twitter_login]).not_to be_new_record }
end
context "when the problem check tracker doesn't exist yet" do
it { expect(described_class[:facebook_login]).to be_previously_new_record }
end
end
describe "#check" do
before do
Fabricate(:problem_check_tracker, identifier: "twitter_login")
Fabricate(:problem_check_tracker, identifier: "missing_check")
end
context "when the tracker has a corresponding check" do
it { expect(described_class[:twitter_login].check.new).to be_a(ProblemCheck) }
end
context "when the checking logic of the tracker has been removed or renamed" do
it do
expect { described_class[:missing_check].check }.to change { described_class.count }.by(-1)
end
end
end
describe "#ready_to_run?" do
let(:problem_tracker) { described_class.new(next_run_at:) }
context "when the next run timestamp is not set" do
let(:next_run_at) { nil }
it { expect(problem_tracker).to be_ready_to_run }
end
context "when the next run timestamp is in the past" do
let(:next_run_at) { 5.minutes.ago }
it { expect(problem_tracker).to be_ready_to_run }
end
context "when the next run timestamp is in the future" do
let(:next_run_at) { 5.minutes.from_now }
it { expect(problem_tracker).not_to be_ready_to_run }
end
end
describe "#failing?" do
before { freeze_time }
let(:problem_tracker) { described_class.new(last_problem_at:, last_run_at:, last_success_at:) }
context "when the last run passed" do
let(:last_run_at) { 1.minute.ago }
let(:last_success_at) { 1.minute.ago }
let(:last_problem_at) { 11.minutes.ago }
it { expect(problem_tracker).not_to be_failing }
end
context "when the last run had a problem" do
let(:last_run_at) { 1.minute.ago }
let(:last_success_at) { 11.minutes.ago }
let(:last_problem_at) { 1.minute.ago }
it { expect(problem_tracker).to be_failing }
end
end
describe "#passing?" do
before { freeze_time }
let(:problem_tracker) { described_class.new(last_problem_at:, last_run_at:, last_success_at:) }
context "when the last run passed" do
let(:last_run_at) { 1.minute.ago }
let(:last_success_at) { 1.minute.ago }
let(:last_problem_at) { 11.minutes.ago }
it { expect(problem_tracker).to be_passing }
end
context "when the last run had a problem" do
let(:last_run_at) { 1.minute.ago }
let(:last_success_at) { 11.minutes.ago }
let(:last_problem_at) { 1.minute.ago }
it { expect(problem_tracker).not_to be_passing }
end
end
describe "#problem!" do
let(:problem_tracker) do
Fabricate(
:problem_check_tracker,
identifier: "twitter_login",
target: "foo",
**original_attributes,
)
end
let(:original_attributes) do
{
blips:,
last_problem_at: 1.week.ago,
last_success_at: 24.hours.ago,
last_run_at: 24.hours.ago,
next_run_at: nil,
}
end
let(:blips) { 0 }
let(:updated_attributes) { { blips: 1 } }
it do
freeze_time
expect { problem_tracker.problem!(next_run_at: 24.hours.from_now) }.to change {
problem_tracker.attributes
}.to(hash_including(updated_attributes))
end
context "when the maximum number of blips have been surpassed" do
let(:blips) { 1 }
it "sounds the alarm" do
expect { problem_tracker.problem!(next_run_at: 24.hours.from_now) }.to change {
AdminNotice.problem.count
}.by(1)
end
end
context "when the details of the problem change but the problem remains" do
let(:blips) { 1 }
it "updates the notice" do
original_details = {
themes_list:
"<ul><li><a href=\"/admin/customize/themes/13\">discourse-blank-theme</a></li> <li><a href=\"/admin/customize/themes/31\">Simple Theme</a></li></ul>",
base_path: "",
}
expect do problem_tracker.problem!(details: original_details) end.to change {
AdminNotice.problem.count
}.by(1)
admin_notice = AdminNotice.problem.find_by(identifier: "twitter_login")
expect(
admin_notice.details.merge(target: problem_tracker.target).with_indifferent_access,
).to eq(original_details.merge(target: problem_tracker.target).with_indifferent_access)
new_details = {
themes_list: "<ul><li><a href=\"/admin/customize/themes/31\">Simple Theme</a></li></ul>",
base_path: "",
}
expect do problem_tracker.problem!(details: new_details) end.not_to change {
AdminNotice.problem.count
}
admin_notice.reload
expect(
admin_notice.details.merge(target: problem_tracker.target).with_indifferent_access,
).to eq(new_details.merge(target: problem_tracker.target).with_indifferent_access)
end
end
context "when there's an alarm sounding for multi-target trackers" do
let(:blips) { 1 }
before do
Fabricate(
:admin_notice,
subject: "problem",
identifier: "twitter_login",
details: {
target: target,
},
)
end
context "when the alarm is for a different target" do
let(:target) { "bar" }
it "sounds the alarm" do
expect { problem_tracker.problem!(next_run_at: 24.hours.from_now) }.to change {
AdminNotice.problem.count
}.by(1)
end
end
context "when the alarm is for a the same target" do
let(:target) { "foo" }
it "does not duplicate the alarm" do
expect { problem_tracker.problem!(next_run_at: 24.hours.from_now) }.not_to change {
AdminNotice.problem.count
}
end
end
end
context "when there are still blips to go" do
let(:blips) { 0 }
before { ProblemCheck::TwitterLogin.stubs(:max_blips).returns(1) }
it "does not sound the alarm" do
expect { problem_tracker.problem!(next_run_at: 24.hours.from_now) }.not_to change {
AdminNotice.problem.count
}
end
end
end
describe "#no_problem!" do
let(:problem_tracker) do
Fabricate(:problem_check_tracker, identifier: "twitter_login", **original_attributes)
end
let(:original_attributes) do
{
blips: 0,
last_problem_at: 1.week.ago,
last_success_at: Time.current,
last_run_at: 24.hours.ago,
next_run_at: nil,
}
end
let(:updated_attributes) { { blips: 0 } }
it do
freeze_time
expect { problem_tracker.no_problem!(next_run_at: 24.hours.from_now) }.to change {
problem_tracker.attributes
}.to(hash_including(updated_attributes))
end
context "when there's an alarm sounding" do
before { problem_tracker.problem! }
it "silences the alarm" do
expect { problem_tracker.no_problem!(next_run_at: 24.hours.from_now) }.to change {
AdminNotice.problem.count
}.by(-1)
end
end
end
describe "#reset" do
let(:problem_tracker) do
Fabricate(:problem_check_tracker, identifier: "twitter_login", **original_attributes)
end
let(:original_attributes) do
{
blips: 0,
last_problem_at: 1.week.ago,
last_success_at: Time.current,
last_run_at: 24.hours.ago,
next_run_at: nil,
}
end
let(:updated_attributes) { { blips: 0 } }
it do
freeze_time
expect { problem_tracker.reset(next_run_at: 24.hours.from_now) }.to change {
problem_tracker.attributes
}.to(hash_including(updated_attributes))
end
end
end