extraction de la logique d'évaluation

This commit is contained in:
Jérémy Lecour 2021-01-23 18:09:55 +01:00 committed by Jérémy Lecour
parent 5818252c75
commit 56b610949a
15 changed files with 254 additions and 148 deletions

View file

@ -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…)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -6,6 +6,7 @@
#
one: {}
# column: value
#
two: {}
# column: value
alice:
email: alice@example.com
encrypted_password: <%= Devise::Encryptor.digest(User, '123password') %>

View file

@ -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

View file

@ -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

View file

@ -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)