Files
sure/test/system/rules_test.rb
T
c3cfdf03b2 fix(rules): validate Rule::Condition type registry + normalize legacy values (#1908)
* fix(rules): validate Rule::Condition type registry + normalize legacy 'name' values

Closes #1229.

/rules crashes with `ActionView::Template::Error (Unsupported condition
type: name)` when a Rule::Condition row has a condition_type the
registry doesn't know about. Reporter found two cases in the wild:

  - "name" — invalid, crashes the rules index because the view path
    (`_rule.html.erb` → `displayed_condition.filter.label`) ends up
    in `Rule::Registry#get_filter!` which raises.
  - "transaction_details" — actually a valid filter that searches the
    `transactions.extra` JSONB metadata field (see existing tests in
    test/models/rule/condition_test.rb). Reporter assumed it was a
    transaction-name synonym; it isn't. This PR leaves it alone.

Changes:

- Rule::Condition gains a SUPPORTED_CONDITION_TYPES registry constant
  matching Rule::Registry::TransactionResource#condition_filters plus
  "compound", an inclusion validation against it, and a
  before_validation callback that maps the one known legacy alias
  ("name" -> "transaction_name") so saving an existing rule fixes
  itself.
- Rule::Condition#filter now rescues UnsupportedConditionError and
  returns a Rule::ConditionFilter::Unsupported placeholder. The
  placeholder labels itself "Unsupported (<key>)" (i18n) and its
  #apply returns `scope.none`, so any stale row that survives the
  migration stops matching rather than silently matching everything.
- A one-shot migration normalizes existing "name" rows. Single UPDATE
  statement — rule_conditions is a small per-family table.

Tests added in test/models/rule/condition_test.rb cover the inclusion
validation, the normalization callback, and the graceful-render path
(uses update_columns to simulate a row written before the validation
existed — that's the exact codepath that crashes /rules today).

* test(rules): add system test for unsupported condition_type render + raise on migration down

- test/system/rules_test.rb: visit /rules with a row whose condition_type
  has been update_columns'd to "name" — assert page renders and shows
  the "Unsupported (name)" label instead of raising.
- db/migrate/...normalize_rule_condition_types.rb: replace the empty
  #down with `raise ActiveRecord::IrreversibleMigration` to match the
  repo's data-migration convention (see e.g. 20260219190000_scope_*).

* chore(rules): log unsupported condition + cross-check supported types

Address maintainer review on #1908:
- Log a Rails.logger.warn (with rule_id and condition_type) from
  Rule::ConditionFilter::Unsupported#apply so silent zero-match rules
  are traceable when debugging.
- Add a comment above Rule::Condition::SUPPORTED_CONDITION_TYPES and a
  test that cross-checks it against the registry's filter keys, so
  drift between the two surfaces as a failing test rather than a
  confusing validation error.

* refactor(rules): derive supported condition types from registry

---------

Co-authored-by: John Baillie <johnbaillie2007@gmail.com>
Co-authored-by: Khaostica <256858950+Khaostica@users.noreply.github.com>
Co-authored-by: sure-admin <sure-admin@splashblot.com>
2026-08-26 07:09:55 +02:00

59 lines
1.8 KiB
Ruby

require "application_system_test_case"
class RulesTest < ApplicationSystemTestCase
setup do
sign_in @user = users(:family_admin)
end
test "shows queued processed modified and blocked counts for recent rule runs" do
rule = @user.family.rules.create!(
name: "Whole Foods Testing",
resource_type: "transaction",
effective_date: 1.year.ago.to_date,
conditions: [
Rule::Condition.new(condition_type: "transaction_name", operator: "like", value: "Whole Foods")
],
actions: [
Rule::Action.new(action_type: "set_transaction_category", value: categories(:food_and_drink).id)
]
)
rule.rule_runs.create!(
rule_name: rule.name,
execution_type: "manual",
status: "success",
transactions_queued: 20,
transactions_processed: 20,
transactions_modified: 10,
pending_jobs_count: 0,
executed_at: Time.current
)
visit rules_path
assert_selector "th", text: /queued\s+processed\s+modified\s+blocked/i
assert_selector "td", text: "20 / 20 / 10 / 10"
end
test "rules page renders gracefully when a condition has an unsupported condition_type" do
rule = @user.family.rules.create!(
name: "Legacy bad rule",
resource_type: "transaction",
conditions: [
Rule::Condition.new(condition_type: "transaction_name", operator: "like", value: "x")
],
actions: [
Rule::Action.new(action_type: "set_transaction_category", value: categories(:food_and_drink).id)
]
)
# Simulate a legacy row written before the inclusion validation existed.
rule.conditions.first.update_columns(condition_type: "name")
visit rules_path
assert_selector "h3", text: "Legacy bad rule"
assert_text "Unsupported (name)"
end
end