diff --git a/enterprise/app/services/captain/audience_matcher.rb b/enterprise/app/services/captain/audience_matcher.rb index ba707e179..a78eb8fc7 100644 --- a/enterprise/app/services/captain/audience_matcher.rb +++ b/enterprise/app/services/captain/audience_matcher.rb @@ -72,11 +72,17 @@ class Captain::AudienceMatcher # ignore the "+" prefix, and text compares case-insensitively. def value_equal?(key, actual, expected) return Array(actual).include?(expected) if key == 'labels' - return ActiveModel::Type::Boolean.new.cast(expected) == actual if [true, false].include?(actual) + return ActiveModel::Type::Boolean.new.cast(expected) == (actual == true) if boolean_condition?(actual, expected) normalize(key, actual) == normalize(key, expected) end + # An unset checkbox attribute counts as false; the expected value identifies + # the condition as boolean when the attribute is missing. + def boolean_condition?(actual, expected) + [true, false].include?(actual) || (actual.nil? && %w[true false].include?(expected.to_s)) + end + def normalize(key, value) return value if value.nil? return "+#{value.to_s.delete('+')}" if key == 'phone_number' diff --git a/spec/enterprise/services/captain/audience_matcher_spec.rb b/spec/enterprise/services/captain/audience_matcher_spec.rb index 2a6604007..0c1db8032 100644 --- a/spec/enterprise/services/captain/audience_matcher_spec.rb +++ b/spec/enterprise/services/captain/audience_matcher_spec.rb @@ -37,6 +37,18 @@ RSpec.describe Captain::AudienceMatcher do expect(matches?(leaf('plan_tier', 'not_equal_to', 'free'))).to be(true) end + it 'matches checkbox custom attributes' do + contact.update!(custom_attributes: contact.custom_attributes.merge('newsletter_opt_in' => true)) + expect(matches?(leaf('newsletter_opt_in', 'equal_to', 'true'))).to be(true) + expect(matches?(leaf('newsletter_opt_in', 'equal_to', 'false'))).to be(false) + end + + it 'treats a missing checkbox attribute as false' do + expect(matches?(leaf('newsletter_opt_in', 'equal_to', 'false'))).to be(true) + expect(matches?(leaf('newsletter_opt_in', 'equal_to', 'true'))).to be(false) + expect(matches?(leaf('newsletter_opt_in', 'not_equal_to', 'true'))).to be(true) + end + it 'supports contains / starts_with on text' do expect(matches?(leaf('email', 'contains', contact.email[2..5]))).to be(true) expect(matches?(leaf('city', 'starts_with', 'Bos'))).to be(true)