discourse/spec/jobs/run_problem_checks_spec.rb
Ted Johansson d446cc7318
FIX: Fix scheduled targeted problem checks (#35696)
Scheduled problem checks with multiple targets are not honouring the
`run_every` configuration.

For checks with multiple targets, all targets are checked in a single
instance of the problem check. However, we have one problem check
tracker per target.

This mismatch results in the `#ready_to_run?` method always creating a
tracker with no target when being checked.

This commit fixes that by:

**Expect checks to operate on a single target.**

This change makes it so that instances of a `ProblemCheck` class are
initialized with a target. So instead of 1-N we now have an N-N
relationship between checks and trackers.

Each instance can access their `target` through an attribute of the same
name.

This also means problem checks are back to returning a singular
`Problem` or `nil`, instead of `[Problem]` or `[]`.

For scheduled checks, this means that `ScheduleProblemChecks` now
enqueues `N` jobs (where `N` is the number of targets) per check instead
of `1` job per check.

**Update existing targeted checks to operate on a single target.**

This is essentially just removing the loop inside the check.
2025-11-10 10:09:14 +08:00

165 lines
4.5 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Jobs::RunProblemChecks do
around do |example|
ProblemCheck::ScheduledCheck =
Class.new(ProblemCheck) do
self.perform_every = 30.minutes
def call = []
end
ProblemCheck::NonScheduledCheck = Class.new(ProblemCheck) { def call = [] }
ProblemCheck::DisabledCheck =
Class.new(ProblemCheck) do
self.perform_every = 30.minutes
self.enabled = false
def call = []
end
ProblemCheck::MultiTargetCheck =
Class.new(ProblemCheck) do
self.perform_every = 30.minutes
self.targets = -> { %w[foo bar] }
end
stub_const(
ProblemCheck,
"CORE_PROBLEM_CHECKS",
[
ProblemCheck::ScheduledCheck,
ProblemCheck::NonScheduledCheck,
ProblemCheck::DisabledCheck,
ProblemCheck::MultiTargetCheck,
],
&example
)
ProblemCheck.send(:remove_const, "ScheduledCheck")
ProblemCheck.send(:remove_const, "NonScheduledCheck")
ProblemCheck.send(:remove_const, "DisabledCheck")
ProblemCheck.send(:remove_const, "MultiTargetCheck")
end
context "when a tracker hasn't been created yet" do
it "still schedules checks" do
expect_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "scheduled_check",
target: ProblemCheck::NO_TARGET,
},
) { described_class.new.execute([]) }
end
end
context "when the tracker determines the check is ready to run" do
before do
ProblemCheckTracker.create!(identifier: "scheduled_check", next_run_at: 5.minutes.ago)
end
it "schedules the individual scheduled checks" do
expect_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "scheduled_check",
target: ProblemCheck::NO_TARGET,
},
) { described_class.new.execute([]) }
end
end
context "when the tracker determines the check shouldn't run yet" do
before do
ProblemCheckTracker.create!(identifier: "scheduled_check", next_run_at: 5.minutes.from_now)
end
it "does not schedule any check" do
expect_not_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "scheduled_check",
},
) { described_class.new.execute([]) }
end
end
context "when dealing with a non-scheduled check" do
before { ProblemCheckTracker.create!(identifier: "non_scheduled_check", next_run_at: nil) }
it "does not schedule any check" do
expect_not_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "non_scheduled_check",
},
) { described_class.new.execute([]) }
end
end
context "when dealing with a disabled check" do
before { ProblemCheckTracker.create!(identifier: "disabled_check", next_run_at: nil) }
it "does not schedule any check" do
expect_not_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "disabled_check",
},
) { described_class.new.execute([]) }
end
end
context "when dealing with an uninstalled check" do
before { ProblemCheckTracker.create!(identifier: "uninstalled_check", next_run_at: nil) }
it "does not schedule any check" do
expect_not_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "uninstalled_check",
},
) { described_class.new.execute([]) }
end
end
context "when dealing with a multi-target check" do
it "schedules one check per target" do
expect_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "multi_target_check",
target: "foo",
},
) { described_class.new.execute([]) }
expect_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "multi_target_check",
target: "bar",
},
) { described_class.new.execute([]) }
end
it "creates a problem tracker for each target" do
expect { described_class.new.execute([]) }.to change {
ProblemCheckTracker.where(
identifier: "multi_target_check",
target: ProblemCheck::MultiTargetCheck.targets.call,
).count
}.by(2)
end
it "does not create any no-target tracker" do
expect { described_class.new.execute([]) }.not_to change {
ProblemCheckTracker.where(
identifier: "multi_target_check",
target: ProblemCheck::NO_TARGET,
).count
}
end
end
end