discourse/spec/jobs/run_problem_check_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

112 lines
3 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Jobs::RunProblemCheck do
after { Discourse.redis.flushdb }
context "when there are problems" do
around do |example|
ProblemCheck::TestCheck =
Class.new(ProblemCheck) do
self.perform_every = 30.minutes
self.max_retries = 0
def call
ProblemCheck::Problem.new("Big problem")
end
end
stub_const(ProblemCheck, "CORE_PROBLEM_CHECKS", [ProblemCheck::TestCheck], &example)
ProblemCheck.send(:remove_const, "TestCheck")
end
it "updates the problem check tracker" do
expect {
described_class.new.execute(
check_identifier: "test_check",
retry_count: 0,
target: ProblemCheck::NO_TARGET,
)
}.to change { ProblemCheckTracker.failing.count }.by(1)
end
end
context "when there are retries remaining" do
around do |example|
ProblemCheck::TestCheck =
Class.new(ProblemCheck) do
self.perform_every = 30.minutes
self.max_retries = 2
def call
ProblemCheck::Problem.new("Yuge problem")
end
end
stub_const(ProblemCheck, "CORE_PROBLEM_CHECKS", [ProblemCheck::TestCheck], &example)
ProblemCheck.send(:remove_const, "TestCheck")
end
it "does not yet update the problem check tracker" do
expect {
described_class.new.execute(
check_identifier: "test_check",
retry_count: 1,
target: ProblemCheck::NO_TARGET,
)
}.not_to change { ProblemCheckTracker.where("blips > ?", 0).count }
end
it "schedules a retry" do
expect_enqueued_with(
job: :run_problem_check,
args: {
check_identifier: "test_check",
retry_count: 1,
target: ProblemCheck::NO_TARGET,
},
) do
described_class.new.execute(check_identifier: "test_check", target: ProblemCheck::NO_TARGET)
end
end
end
context "when there are no retries remaining" do
around do |example|
ProblemCheck::TestCheck =
Class.new(ProblemCheck) do
self.perform_every = 30.minutes
self.max_retries = 1
def call
ProblemCheck::Problem.new("Yuge problem")
end
end
stub_const(ProblemCheck, "CORE_PROBLEM_CHECKS", [ProblemCheck::TestCheck], &example)
ProblemCheck.send(:remove_const, "TestCheck")
end
it "updates the problem check tracker" do
expect {
described_class.new.execute(
check_identifier: "test_check",
retry_count: 1,
target: ProblemCheck::NO_TARGET,
)
}.to change { ProblemCheckTracker.where("blips > ?", 0).count }.by(1)
end
it "does not schedule a retry" do
expect_not_enqueued_with(job: :run_problem_check) do
described_class.new.execute(
check_identifier: "test_check",
retry_count: 1,
target: ProblemCheck::NO_TARGET,
)
end
end
end
end