mirror of
https://github.com/we-promise/sure.git
synced 2026-05-24 21:14:56 +00:00
* Add blocked count to rule run summary * test(rules): cover rule run blocked counts * fix(rules): derive blocked count from modified rows Blocked rule transactions are the processed rows that were not modified. This keeps the displayed queued / processed / modified / blocked summary aligned when a run has already processed all matching rows but some were skipped by enrichment locks. * fix(rules): count processed rows for rule jobs Synchronous rule actions return the number of rows they modified, but rule-run processed counts should represent the number of matched transactions the job attempted to process. Using queued matches for processed preserves the distinction between processed and modified rows, which lets locked manual edits appear as blocked instead of making processed collapse to modified. This changes RuleJob counter semantics, so it was committed separately from the derived blocked-count display change.
246 lines
7.2 KiB
Ruby
246 lines
7.2 KiB
Ruby
require "test_helper"
|
|
|
|
class RulesControllerTest < ActionDispatch::IntegrationTest
|
|
setup do
|
|
sign_in @user = users(:family_admin)
|
|
end
|
|
|
|
test "should get new" do
|
|
get new_rule_url(resource_type: "transaction")
|
|
assert_response :success
|
|
end
|
|
|
|
test "should get new with pre-filled name and action" do
|
|
category = categories(:food_and_drink)
|
|
get new_rule_url(
|
|
resource_type: "transaction",
|
|
name: "Starbucks",
|
|
action_type: "set_transaction_category",
|
|
action_value: category.id
|
|
)
|
|
assert_response :success
|
|
|
|
assert_select "input[name='rule[name]'][value='Starbucks']"
|
|
assert_select "input[name*='[value]'][value='Starbucks']"
|
|
assert_select "select[name*='[condition_type]'] option[selected][value='transaction_name']"
|
|
assert_select "select[name*='[action_type]'] option[selected][value='set_transaction_category']"
|
|
assert_select "select[name*='[value]'] option[selected][value='#{category.id}']"
|
|
end
|
|
|
|
test "should get edit" do
|
|
get edit_rule_url(rules(:one))
|
|
assert_response :success
|
|
end
|
|
|
|
# "Set all transactions with a name like 'starbucks' and an amount between 20 and 40 to the 'food and drink' category"
|
|
test "creates rule with nested conditions" do
|
|
post rules_url, params: {
|
|
rule: {
|
|
effective_date: 30.days.ago.to_date,
|
|
resource_type: "transaction",
|
|
conditions_attributes: {
|
|
"0" => {
|
|
condition_type: "transaction_name",
|
|
operator: "like",
|
|
value: "starbucks"
|
|
},
|
|
"1" => {
|
|
condition_type: "compound",
|
|
operator: "and",
|
|
sub_conditions_attributes: {
|
|
"0" => {
|
|
condition_type: "transaction_amount",
|
|
operator: ">",
|
|
value: 20
|
|
},
|
|
"1" => {
|
|
condition_type: "transaction_amount",
|
|
operator: "<",
|
|
value: 40
|
|
}
|
|
}
|
|
}
|
|
},
|
|
actions_attributes: {
|
|
"0" => {
|
|
action_type: "set_transaction_category",
|
|
value: categories(:food_and_drink).id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rule = @user.family.rules.order("created_at DESC").first
|
|
|
|
# Rule
|
|
assert_equal "transaction", rule.resource_type
|
|
assert_not rule.active # Not active by default
|
|
assert_equal 30.days.ago.to_date, rule.effective_date
|
|
|
|
# Conditions assertions
|
|
assert_equal 2, rule.conditions.count
|
|
compound_condition = rule.conditions.find { |condition| condition.condition_type == "compound" }
|
|
assert_equal "compound", compound_condition.condition_type
|
|
assert_equal 2, compound_condition.sub_conditions.count
|
|
|
|
# Actions assertions
|
|
assert_equal 1, rule.actions.count
|
|
assert_equal "set_transaction_category", rule.actions.first.action_type
|
|
assert_equal categories(:food_and_drink).id, rule.actions.first.value
|
|
|
|
assert_redirected_to confirm_rule_url(rule, reload_on_close: true)
|
|
end
|
|
|
|
test "can update rule" do
|
|
rule = rules(:one)
|
|
|
|
assert_difference -> { Rule.count } => 0,
|
|
-> { Rule::Condition.count } => 1,
|
|
-> { Rule::Action.count } => 1 do
|
|
patch rule_url(rule), params: {
|
|
rule: {
|
|
active: false,
|
|
conditions_attributes: {
|
|
"0" => {
|
|
id: rule.conditions.first.id,
|
|
value: "new_value"
|
|
},
|
|
"1" => {
|
|
condition_type: "transaction_amount",
|
|
operator: ">",
|
|
value: 100
|
|
}
|
|
},
|
|
actions_attributes: {
|
|
"0" => {
|
|
id: rule.actions.first.id,
|
|
value: "new_value"
|
|
},
|
|
"1" => {
|
|
action_type: "set_transaction_tags",
|
|
value: tags(:one).id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
rule.reload
|
|
|
|
assert_not rule.active
|
|
assert_equal "new_value", rule.conditions.order("created_at ASC").first.value
|
|
assert_equal "new_value", rule.actions.order("created_at ASC").first.value
|
|
assert_equal tags(:one).id, rule.actions.order("created_at ASC").last.value
|
|
assert_equal "100", rule.conditions.order("created_at ASC").last.value
|
|
|
|
assert_redirected_to rules_url
|
|
end
|
|
|
|
test "can destroy conditions and actions while editing" do
|
|
rule = rules(:one)
|
|
|
|
assert_equal 1, rule.conditions.count
|
|
assert_equal 1, rule.actions.count
|
|
|
|
patch rule_url(rule), params: {
|
|
rule: {
|
|
conditions_attributes: {
|
|
"0" => { id: rule.conditions.first.id, _destroy: true },
|
|
"1" => {
|
|
condition_type: "transaction_name",
|
|
operator: "like",
|
|
value: "new_condition"
|
|
}
|
|
},
|
|
actions_attributes: {
|
|
"0" => { id: rule.actions.first.id, _destroy: true },
|
|
"1" => {
|
|
action_type: "set_transaction_tags",
|
|
value: tags(:one).id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert_redirected_to rules_url
|
|
|
|
rule.reload
|
|
|
|
assert_equal 1, rule.conditions.count
|
|
assert_equal 1, rule.actions.count
|
|
end
|
|
|
|
test "can destroy rule" do
|
|
rule = rules(:one)
|
|
|
|
assert_difference [ "Rule.count", "Rule::Condition.count", "Rule::Action.count" ], -1 do
|
|
delete rule_url(rule)
|
|
end
|
|
|
|
assert_redirected_to rules_url
|
|
end
|
|
|
|
test "index renders when rule has empty compound condition" do
|
|
malformed_rule = @user.family.rules.build(resource_type: "transaction")
|
|
malformed_rule.conditions.build(condition_type: "compound", operator: "and")
|
|
malformed_rule.actions.build(action_type: "exclude_transaction")
|
|
malformed_rule.save!
|
|
|
|
get rules_url
|
|
|
|
assert_response :success
|
|
assert_includes response.body, I18n.t("rules.no_condition")
|
|
end
|
|
|
|
test "index uses next valid condition when first compound condition is empty" do
|
|
rule = @user.family.rules.build(resource_type: "transaction")
|
|
rule.conditions.build(condition_type: "compound", operator: "and")
|
|
rule.conditions.build(condition_type: "transaction_name", operator: "like", value: "edge-case-name")
|
|
rule.actions.build(action_type: "exclude_transaction")
|
|
rule.save!
|
|
|
|
get rules_url
|
|
|
|
assert_response :success
|
|
|
|
assert_select "##{ActionView::RecordIdentifier.dom_id(rule)}" do
|
|
assert_select "span", text: /edge-case-name/
|
|
assert_select "span", text: /#{Regexp.escape(I18n.t("rules.no_condition"))}/, count: 0
|
|
assert_select "p", text: /and 1 more condition/, count: 0
|
|
end
|
|
end
|
|
|
|
test "index shows blocked count in recent runs summary" do
|
|
rule = rules(:one)
|
|
RuleRun.create!(
|
|
rule: rule,
|
|
execution_type: "manual",
|
|
status: "success",
|
|
transactions_queued: 10,
|
|
transactions_processed: 7,
|
|
transactions_modified: 4,
|
|
pending_jobs_count: 0,
|
|
executed_at: Time.current
|
|
)
|
|
|
|
get rules_url
|
|
|
|
assert_response :success
|
|
assert_select "th", text: /Queued\s+Processed\s+Modified\s+Blocked/
|
|
assert_select "td", text: "10 / 7 / 4 / 3"
|
|
end
|
|
|
|
test "should get confirm_all" do
|
|
get confirm_all_rules_url
|
|
assert_response :success
|
|
end
|
|
|
|
test "apply_all enqueues job and redirects" do
|
|
assert_enqueued_with(job: ApplyAllRulesJob) do
|
|
post apply_all_rules_url
|
|
end
|
|
|
|
assert_redirected_to rules_url
|
|
end
|
|
end
|