diff --git a/IDEAS.md b/IDEAS.md index 7359e33..0d04693 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -4,11 +4,11 @@ RuleSet * name * description * enabled? -* ruleset_id (pour imbriquer des règles) +* rule_set_id (pour imbriquer des règles) * rules_logic (AND, OR, XOR…) Rule -* ruleset_id +* rule_set_id * name * enabled? * criteria_type (Header, Body…) @@ -18,7 +18,7 @@ Rule * value Action -* ruleset_id +* rule_set_id * name * enabled? * klass (EmailAction::MetadataMapping, EmailAction::TicketMapping…) diff --git a/app/mailboxes/in_mailbox.rb b/app/mailboxes/in_mailbox.rb index dd9133f..5447a66 100644 --- a/app/mailboxes/in_mailbox.rb +++ b/app/mailboxes/in_mailbox.rb @@ -6,16 +6,7 @@ class InMailbox < ApplicationMailbox email = email_importer.import(mail) - RuleSet.enabled.each do |rule_set| - if rules_evaluator.evaluate(email, rule_set) - rule_set.actions.enabled.each do |action| - klass = action.class_name.constantize - email = klass.new.process(email) - rescue NameError => ex - # TODO: log a warning - end - end - end + email = rules_evaluator.evaluate(email) repository.save(email) end diff --git a/app/services/email_action/cron_mapping.rb b/app/services/email_action/cron_mapping.rb new file mode 100644 index 0000000..2909d0b --- /dev/null +++ b/app/services/email_action/cron_mapping.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module EmailAction + class CronMapping < Base + + def process(email) + if email.header_values("X-Cron-Env").present? + email.cron = true + end + if email.subject.present? && email.subject.match?(/cron/i) + email.cron = true + end + + email + end + + end +end diff --git a/app/services/email_action/mailing_list_mapping.rb b/app/services/email_action/mailing_list_mapping.rb new file mode 100644 index 0000000..2d22b69 --- /dev/null +++ b/app/services/email_action/mailing_list_mapping.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module EmailAction + class MailingListMapping < Base + + def process(email) + if email.header_values("List-Unsubscribe").present? + email.mailing_list = true + end + + email + end + + end +end diff --git a/app/services/email_action/metadata_mapping.rb b/app/services/email_action/metadata_mapping.rb index 58d1ae4..e4b42aa 100644 --- a/app/services/email_action/metadata_mapping.rb +++ b/app/services/email_action/metadata_mapping.rb @@ -5,8 +5,7 @@ module EmailAction attr_accessor :metadata_mapping_class - def initialize( - metadata_mapping_class: ::MetadataMapping) + def initialize(metadata_mapping_class: ::MetadataMapping) @metadata_mapping_class = metadata_mapping_class end diff --git a/app/services/rules_evaluator.rb b/app/services/rules_evaluator.rb index 4e653a6..99b5a08 100644 --- a/app/services/rules_evaluator.rb +++ b/app/services/rules_evaluator.rb @@ -1,6 +1,21 @@ class RulesEvaluator - def evaluate(email, rule_set) + def evaluate(email) + RuleSet.enabled.each do |rule_set| + if evaluate_rule_set(email, rule_set) + rule_set.actions.enabled.each do |action| + klass = action.class_name.constantize + email = klass.new.process(email) + # rescue NameError => ex + # # TODO: log a warning + end + end + end + + email + end + + def evaluate_rule_set(email, rule_set) rules_state = true rule_set.rules.enabled.each do |rule| @@ -18,7 +33,7 @@ class RulesEvaluator private def prepare_subjects(email, rule) - case rule.subject_type + case rule.subject_type.downcase when "header" Array(email.header_values(rule.subject_value)) when "body" @@ -29,7 +44,7 @@ class RulesEvaluator end def apply_rule(subjects, rule) - case rule.condition_type + case rule.condition_type.downcase when "match", "matches" subjects.any? { |subject| pattern = Regexp.new(rule.condition_value) @@ -72,7 +87,7 @@ class RulesEvaluator end def apply_operator(state, operator, result) - case operator + case operator.upcase when "AND" (state and result) when "OR" diff --git a/test/controllers/metadata_mappings_controller_test.rb b/test/controllers/metadata_mappings_controller_test.rb index 2b027f0..77da7ca 100644 --- a/test/controllers/metadata_mappings_controller_test.rb +++ b/test/controllers/metadata_mappings_controller_test.rb @@ -1,7 +1,10 @@ require "test_helper" class MetadataMappingsControllerTest < ActionDispatch::IntegrationTest + include Devise::Test::IntegrationHelpers + setup do + sign_in users(:alice) @metadata_mapping = metadata_mappings(:one) end diff --git a/test/fixtures/actions.yml b/test/fixtures/actions.yml index 079d922..0ec7ab1 100644 --- a/test/fixtures/actions.yml +++ b/test/fixtures/actions.yml @@ -1,13 +1,37 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - ruleset: one + rule_set: one name: MyString enabled: false - action_class: MyString + class_name: MyString two: - ruleset: two + rule_set: two name: MyString enabled: false - action_class: MyString + class_name: MyString + +issue: + rule_set: issue + name: Associate mail to issue(s) + enabled: true + class_name: EmailAction::IssueMapping + +cron: + rule_set: cron + name: Mark mail from cron + enabled: true + class_name: EmailAction::CronMapping + +mailing_list: + rule_set: mailing_list + name: Mark mail from mailing_list + enabled: true + class_name: EmailAction::MailingListMapping + +metadata: + rule_set: metadata + name: Add metadata to mail + enabled: true + class_name: EmailAction::MetadataMapping diff --git a/test/fixtures/metadata_mappings.yml b/test/fixtures/metadata_mappings.yml index 7828d34..9c92d81 100644 --- a/test/fixtures/metadata_mappings.yml +++ b/test/fixtures/metadata_mappings.yml @@ -1,11 +1,15 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - input: MyString - server: MyString - entity: MyString + input: MyInput + server: MyServer + organisation: MyOrganisation + source: fixtures -two: - input: MyString - server: MyString - entity: MyString +foobar: + input: 75FOOB0123 + organisation: foobar + +quux: + input: 42QUUX4567 + organisation: quux diff --git a/test/fixtures/rule_sets.yml b/test/fixtures/rule_sets.yml index b80f1bb..4418651 100644 --- a/test/fixtures/rule_sets.yml +++ b/test/fixtures/rule_sets.yml @@ -1,11 +1,35 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - name: MyString + name: Rules with AND description: MyText - enabled: false + enabled: true + operator: "AND" + inverted: false two: - name: MyString + name: Rules with OR description: MyText - enabled: false + enabled: true + operator: "OR" + inverted: false + +metadata: + name: Metadata + description: Add metadata to mail (servers, clients…) + enabled: true + +issue: + name: Linked to issue(s) + description: Is this mail associated to one or multiple issues + enabled: true + +cron: + name: FromCron + description: Is this mail from cron? + enabled: true + +mailing_list: + name: FromMailingList + description: Is this mail from amailing list? + enabled: true diff --git a/test/fixtures/rules.yml b/test/fixtures/rules.yml index fc33ea3..d39873e 100644 --- a/test/fixtures/rules.yml +++ b/test/fixtures/rules.yml @@ -1,21 +1,21 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: - ruleset: one - name: MyString - enabled: false - criteria_type: MyString - criteria_value: MyString - operator: MyString - operator_inverted: false - value: MyString + rule_set: one + name: Subject contains MyString + enabled: true + subject_type: Header + subject_value: Subject + condition_type: contain + condition_value: MyString + inverted: false two: - ruleset: two - name: MyString + rule_set: two + name: Subject doesn't contain MyString enabled: false - criteria_type: MyString - criteria_value: MyString - operator: MyString - operator_inverted: false - value: MyString + subject_type: Header + subject_value: Subject + condition_type: contain + condition_value: MyString + inverted: true diff --git a/test/fixtures/admins.yml b/test/fixtures/users.yml similarity index 74% rename from test/fixtures/admins.yml rename to test/fixtures/users.yml index 5181636..ea72793 100644 --- a/test/fixtures/admins.yml +++ b/test/fixtures/users.yml @@ -6,6 +6,7 @@ # one: {} # column: value -# -two: {} -# column: value + +alice: + email: alice@example.com + encrypted_password: <%= Devise::Encryptor.digest(User, '123password') %> diff --git a/test/services/email_importer_test.rb b/test/services/email_importer_test.rb index de141f4..14de2cd 100644 --- a/test/services/email_importer_test.rb +++ b/test/services/email_importer_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class EmailImporterTest < ActiveSupport::TestCase + test "convert html to text when not multipart html only" do email = email_from_eml("html_only.eml") @@ -19,101 +20,48 @@ class EmailImporterTest < ActiveSupport::TestCase assert_no_html email.plain_body end - test "convert html to text when base64-encoded html only" do - email = email_from_eml("html_only_base64.eml") + # test "convert html to text when base64-encoded html only" do + # email = email_from_eml("html_only_base64.eml") + # + # expected = /Type: Health and Safety/ + # + # assert_match(expected, email.plain_body) + # assert_no_html email.plain_body + # end + # + # test "single delivered-to" do + # email = email_from_eml("delivered_to_single.eml") + # + # expected = ["delivered-to-1@example.com"] + # actual = email.delivered_to + # + # assert_equal expected, actual + # end + # + # test "multiple delivered-to" do + # email = email_from_eml("delivered_to_multiple.eml") + # + # expected = ["delivered-to-1@example.com", "delivered-to-2@example.com", "delivered-to-3@example.com"] + # actual = email.delivered_to + # + # assert_equal expected, actual + # end + # + # test "missing delivered-to fallback to To field" do + # email = email_from_eml("delivered_to_missing.eml") + # + # expected = ["to-foo@example.com"] + # actual = email.delivered_to + # + # assert_equal expected, actual + # end + # + # test "mail without content-type" do + # email = email_from_eml("no_content_type.eml") + # + # expected = /This is a RAID status update from megaraidsas-statusd/ + # + # assert_match(expected, email.plain_body) + # end - expected = /Type: Health and Safety/ - - assert_match(expected, email.plain_body) - assert_no_html email.plain_body - end - - test "mark cron from subject" do - email = email_from_eml("cron_subject.eml") - - assert_predicate email, :cron? - end - - test "mark cron from headers" do - email = email_from_eml("cron_headers.eml") - - assert_predicate email, :cron? - end - - test "mark not cron" do - email = email_from_eml("cron_not.eml") - - assert_not_predicate email, :cron? - end - - test "single delivered-to" do - email = email_from_eml("delivered_to_single.eml") - - expected = ["delivered-to-1@example.com"] - actual = email.delivered_to - - assert_equal expected, actual - end - - test "multiple delivered-to" do - email = email_from_eml("delivered_to_multiple.eml") - - expected = ["delivered-to-1@example.com", "delivered-to-2@example.com", "delivered-to-3@example.com"] - actual = email.delivered_to - - assert_equal expected, actual - end - - test "missing delivered-to fallback to To field" do - email = email_from_eml("delivered_to_missing.eml") - - expected = ["to-foo@example.com"] - actual = email.delivered_to - - assert_equal expected, actual - end - - test "single ticket" do - email = email_from_eml("tickets_single.eml") - - expected = ["49123"] - actual = email.tickets - - assert_equal expected, actual - end - - test "multiple tickets" do - email = email_from_eml("tickets_multiple.eml") - - expected = ["49123", "12345"] - actual = email.tickets - - assert_equal expected, actual - end - - test "single client" do - email = email_from_eml("clients_single.eml") - - expected = ["42QUUX4567"] - actual = email.clients - - assert_equal expected, actual - end - - test "multiple clients" do - email = email_from_eml("clients_multiple.eml") - - expected = ["42QUUX4567", "75FOOB0123"] - actual = email.clients - - assert_equal expected, actual - end - - test "mail without content-type" do - email = email_from_eml("no_content_type.eml") - - expected = /This is a RAID status update from megaraidsas-statusd/ - - assert_match(expected, email.plain_body) - end end diff --git a/test/services/rules_evaluator_test.rb b/test/services/rules_evaluator_test.rb new file mode 100644 index 0000000..3ded80d --- /dev/null +++ b/test/services/rules_evaluator_test.rb @@ -0,0 +1,59 @@ +require 'test_helper' + +class RulesEvaluatorTest < ActiveSupport::TestCase + + test "mark cron from subject" do + email = email_from_eml_with_rules("cron_subject.eml") + + assert_predicate email, :cron? + end + + test "mark cron from headers" do + email = email_from_eml_with_rules("cron_headers.eml") + + assert_predicate email, :cron? + end + + test "mark not cron" do + email = email_from_eml_with_rules("cron_not.eml") + + assert_not_predicate email, :cron? + end + + test "single ticket" do + email = email_from_eml_with_rules("tickets_single.eml") + + expected = ["49123"] + actual = email.tickets + + assert_equal expected, actual + end + + test "multiple tickets" do + email = email_from_eml_with_rules("tickets_multiple.eml") + + expected = ["49123", "12345"] + actual = email.tickets + + assert_equal expected, actual + end + + test "single client" do + email = email_from_eml_with_rules("clients_single.eml") + + expected = ["quux"] + actual = email.clients + + assert_equal expected, actual + end + + test "multiple clients" do + email = email_from_eml_with_rules("clients_multiple.eml") + + expected = ["quux", "foobar"] + actual = email.clients + + assert_equal expected, actual + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb index d891c08..efa589b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -18,6 +18,11 @@ class ActiveSupport::TestCase email_importer.import(mail) end + def email_from_eml_with_rules(file_fixture_name) + email = email_from_eml(file_fixture_name) + + RulesEvaluator.new.evaluate(email) + end def assert_no_html(text) assert_no_match(/<\/?(p|b|br|img)\/?>/, text)