Passage de RulseSet/Rule/Action à Filter/Condition/Operation

This commit is contained in:
Jérémy Lecour 2021-02-01 14:02:54 +01:00 committed by Jérémy Lecour
parent 2d62e7d9d1
commit c923f75332
72 changed files with 1371 additions and 567 deletions

View File

@ -33,9 +33,10 @@ gem 'chronic'
# Use Nokogiri to transform HTML to text
gem 'nokogiri', "1.11.0"
# rexml is no longer default gem in Ruby 3.0
# some libraries is no longer default gems in Ruby 3.0
# https://github.com/rails/rails/commit/c23533ee0b50fdc67cc73b579674637ba6f34cb4
gem 'rexml'
gem 'open3'
# Use Elasticsearch as Search database
gem 'elasticsearch-model'

View File

@ -150,6 +150,7 @@ GEM
nokogiri (1.11.0)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
open3 (0.1.1)
orm_adapter (0.5.0)
pry (0.13.1)
coderay (~> 1.1)
@ -271,6 +272,7 @@ DEPENDENCIES
listen (~> 3.2)
net-ldap
nokogiri (= 1.11.0)
open3
pry
puma (~> 5.1)
rails (~> 6.1.0)

View File

@ -1,24 +1 @@
# Idées
RuleSet
* name
* description
* enabled?
* rule_set_id (pour imbriquer des règles)
* rules_logic (AND, OR, XOR…)
Rule
* rule_set_id
* name
* enabled?
* criteria_type (Header, Body…)
* criteria_value
* operator (match, equal, start, end, contain, exist, empty)
* operator_inverted?
* value
Action
* rule_set_id
* name
* enabled?
* klass (EmailAction::MetadataMapping, EmailAction::TicketMapping…)

View File

@ -0,0 +1,3 @@
// Place all the styles related to the conditions controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: https://sass-lang.com/

View File

@ -0,0 +1,7 @@
// Place all the styles related to the filters controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: https://sass-lang.com/
#conditions, #operations {
border: 1px solid blue;
}

View File

@ -0,0 +1,3 @@
// Place all the styles related to the actions controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: https://sass-lang.com/

View File

@ -0,0 +1,62 @@
class ConditionsController < ApplicationController
before_action :set_filter
before_action :set_condition, only: [:show, :edit, :update, :destroy]
# GET /conditions
def index
@conditions = @filter.conditions.all
end
# GET /conditions/1
def show
end
# GET /conditions/new
def new
@condition = @filter.conditions.new
end
# GET /conditions/1/edit
def edit
end
# POST /conditions
def create
@condition = @filter.conditions.new(condition_params)
if @condition.save
redirect_to @filter, notice: 'Filter was successfully created.'
else
render :new
end
end
# PATCH/PUT /conditions/1
def update
if @condition.update(condition_params)
redirect_to @filter, notice: 'Filter was successfully updated.'
else
render :edit
end
end
# DELETE /conditions/1
def destroy
@condition.destroy
redirect_to @filter, notice: 'Filter was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between conditions.
def set_filter
@filter = Filter.find(params[:filter_id])
end
def set_condition
@condition = @filter.conditions.find(params[:id])
end
# Only allow a list of trusted parameters through.
def condition_params
params.require(:condition).permit(:enabled, :property_type, :property_value, :test_method, :test_value, :inverted)
end
end

View File

@ -0,0 +1,58 @@
class FiltersController < ApplicationController
before_action :set_filter, only: [:show, :edit, :update, :destroy]
# GET /filters
def index
@filters = Filter.all
end
# GET /filters/1
def show
end
# GET /filters/new
def new
@filter = Filter.new
end
# GET /filters/1/edit
def edit
end
# POST /filters
def create
@filter = Filter.new(filter_params)
if @filter.save
redirect_to @filter, notice: 'Filter was successfully created.'
else
render :new
end
end
# PATCH/PUT /filters/1
def update
if @filter.update(filter_params)
redirect_to @filter, notice: 'Filter was successfully updated.'
else
render :edit
end
end
# DELETE /filters/1
def destroy
@filter.destroy
redirect_to filters_url, notice: 'Filter was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_filter
@filter = Filter.find(params[:id])
end
# Only allow a list of trusted parameters through.
def filter_params
params.require(:filter).permit(:description, :enabled, :operator, :inverted)
end
end

View File

@ -23,23 +23,19 @@ class MetadataMappingsController < ApplicationController
def create
@metadata_mapping = MetadataMapping.new(metadata_mapping_params)
respond_to do |format|
if @metadata_mapping.save
redirect_to @metadata_mapping, notice: 'Metadata mapping was successfully created.'
else
render :new
end
if @metadata_mapping.save
redirect_to @metadata_mapping, notice: 'Metadata mapping was successfully created.'
else
render :new
end
end
# PATCH/PUT /metadata_mappings/1
def update
respond_to do |format|
if @metadata_mapping.update(metadata_mapping_params)
redirect_to @metadata_mapping, notice: 'Metadata mapping was successfully updated.'
else
render :edit
end
if @metadata_mapping.update(metadata_mapping_params)
redirect_to @metadata_mapping, notice: 'Metadata mapping was successfully updated.'
else
render :edit
end
end
@ -47,10 +43,7 @@ class MetadataMappingsController < ApplicationController
# DELETE /metadata_mappings/1.json
def destroy
@metadata_mapping.destroy
respond_to do |format|
format.html { redirect_to metadata_mappings_url, notice: 'Metadata mapping was successfully destroyed.' }
format.json { head :no_content }
end
redirect_to metadata_mappings_url, notice: 'Metadata mapping was successfully destroyed.'
end
private

View File

@ -0,0 +1,62 @@
class OperationsController < ApplicationController
before_action :set_filter
before_action :set_operation, only: [:show, :edit, :update, :destroy]
# GET /operations
def index
@operations = @filter.operations.all
end
# GET /operations/1
def show
end
# GET /operations/new
def new
@operation = @filter.operations.new
end
# GET /operations/1/edit
def edit
end
# POST /operations
def create
@operation = @filter.operations.new(operation_params)
if @operation.save
redirect_to @filter, notice: 'Operation was successfully created.'
else
render :new
end
end
# PATCH/PUT /operations/1
def update
if @operation.update(operation_params)
redirect_to @filter, notice: 'Operation was successfully updated.'
else
render :edit
end
end
# DELETE /operations/1
def destroy
@operation.destroy
redirect_to @filter, notice: 'Operation was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between operations.
def set_filter
@filter = Filter.find(params[:filter_id])
end
def set_operation
@operation = @filter.operations.find(params[:id])
end
# Only allow a list of trusted parameters through.
def operation_params
params.require(:operation).permit(:enabled, :class_name, :argument)
end
end

View File

@ -0,0 +1,2 @@
module ConditionsHelper
end

View File

@ -0,0 +1,2 @@
module FiltersHelper
end

View File

@ -0,0 +1,2 @@
module OperationsHelper
end

View File

@ -4,8 +4,8 @@ class InMailbox < ApplicationMailbox
email = email_importer.import(mail)
processor = RuleSetProcessor.new
email = processor.process_all(RuleSet.enabled, email)
processor = FilterProcessor.new
email = processor.process_all(Filter.enabled, email)
# repository = EmailRepository.new
# repository.save(email)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Action < ApplicationRecord
belongs_to :rule_set
class Condition < ApplicationRecord
belongs_to :filter
scope :enabled, -> { where(enabled: true) }
end

8
app/models/filter.rb Normal file
View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
class Filter < ApplicationRecord
has_many :conditions, dependent: :destroy
has_many :operations, dependent: :destroy
scope :enabled, -> { where(enabled: true) }
end

View File

@ -1,8 +1,7 @@
# frozen_string_literal: true
class RuleSet < ApplicationRecord
has_many :rules
has_many :actions
class Operation < ApplicationRecord
belongs_to :filter
scope :enabled, -> { where(enabled: true) }
end

View File

@ -1,8 +0,0 @@
# frozen_string_literal: true
class Rule < ApplicationRecord
belongs_to :rule_set
has_many :actions
scope :enabled, -> { where(enabled: true) }
end

View File

@ -1,17 +0,0 @@
# frozen_string_literal: true
module EmailAction
class Postpone < Base
def process(email)
if date = Chronic.parse(action.argument)
email.postponed_until = date
else
Rails.logger.warn "Skipped action##{action.id} '#{action.name}' - Unparsable argument for Chronic : '#{action.argument}'"
end
email
end
end
end

View File

@ -1,16 +1,16 @@
# frozen_string_literal: true
module EmailAction
module EmailOperation
class Error < ::StandardError
end
class Base
attr_reader :action
attr_reader :operation
def initialize(action:)
@action = action
def initialize(operation:)
@operation = operation
end
def process(email)

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module EmailAction
module EmailOperation
class CronMapping < Base
def process(email)

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module EmailAction
module EmailOperation
class IssueMapping < Base
def process(email)

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module EmailAction
module EmailOperation
class Junk < Base
def process(email)

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
module EmailAction
module EmailOperation
class MailingListMapping < Base
def process(email)

View File

@ -1,14 +1,14 @@
# frozen_string_literal: true
module EmailAction
module EmailOperation
class MetadataMapping < Base
attr_accessor :metadata_mapping_class
def initialize(action:, metadata_mapping_class: ::MetadataMapping)
def initialize(operation:, metadata_mapping_class: ::MetadataMapping)
@metadata_mapping_class = metadata_mapping_class
super(action: action)
super(operation: operation)
end
def process(email)
@ -20,7 +20,7 @@ module EmailAction
email
rescue => ex
binding.pry
byebug
end
private

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module EmailOperation
class Postpone < Base
def process(email)
if date = Chronic.parse(operation.argument)
email.postponed_until = date
else
Rails.logger.warn "Skipped operation##{operation.id} '#{operation.class_name}' - Unparsable argument for Chronic : '#{operation.argument}'"
end
email
end
end
end

View File

@ -0,0 +1,139 @@
class FilterProcessor
class InvalidFilter < ::ArgumentError
end
def process_all(filters, email)
filters.each { |filter|
email = process(filter, email)
}
email
end
def process(filter, email)
return email unless filter.enabled?
if evaluate_conditions(filter, email)
email = execute_operations(filter.operations, email)
end
email
end
def evaluate_conditions(filter, email)
filter_result = true
filter_result = catch(:done) {
filter.conditions.each do |condition|
next unless condition.enabled?
properties = prepare_properties(condition, email)
condition_result = apply_condition(properties, condition)
condition_result = !condition_result if filter.inverted?
filter_result = apply_operator(filter_result, filter.operator, condition_result)
rescue InvalidFilter => ex
Rails.logger.error "Skipped filter##{filter.id} '#{filter.description}' - #{ex.inspect}"
next
end
}
filter_result
end
def execute_operations(operations, email)
operations.each do |operation|
next unless operation.enabled?
klass = operation.class_name.constantize
email_operation = klass.new(operation: operation)
email = email_operation.process(email)
rescue NameError => ex
Rails.logger.error "Skipped operation##{operation.id} '#{operation.class_name}' - #{ex.inspect}"
raise InvalidFilter, ex.inspect
end
email
end
def prepare_properties(filter, email)
case filter.property_type.downcase
when "header"
Array(email.header_values(filter.property_value))
when "subject"
Array(email.subject)
when "body"
Array(email.plain_body)
else
raise InvalidFilter, "Unrecognized property type '#{filter.property_type}'"
end
end
private
def apply_condition(properties, condition)
case condition.test_method.downcase
when "match", "matches"
properties.any? { |property|
pattern = Regexp.new(condition.test_value)
property.match? pattern
}
when "equal", "equals"
properties.any? { |property|
property == condition.test_value
}
when "start", "starts"
properties.any? { |property|
property.starts_with? condition.test_value
}
when "end", "ends"
properties.any? { |property|
property.ends_with? condition.test_value
}
when "contain", "contains"
properties.any? { |property|
property.include? condition.test_value
}
when "exist", "exists"
properties.any? { |property|
property.exists?
}
when "empty"
properties.all? { |property|
property.empty?
}
when "date_before"
# properties.all? { |property|
# property.empty?
# }
when "date_after"
# properties.all? { |property|
# property.empty?
# }
else
raise InvalidFilter, "Unrecognized test method '#{condition.test_method}'"
end
end
def apply_operator(state, operator, result)
case operator.upcase
when "AND"
if result
(state and result)
else
throw :done, false
end
when "OR"
if result
throw :done, true
else
(state or result)
end
# when "XOR"
# (state or result) and !(conditions_state and result)
else
raise InvalidFilter, "Unrecognized operator '#{operator}'"
end
end
end

View File

@ -10,7 +10,7 @@ module HtmlToText
end
def convert(html_input)
output, error, status = Open3.capture3("#{elinks_path} -dump -force-html", stdin_data: html_input)
output, error, status = ::Open3.capture3("#{elinks_path} -dump -force-html", stdin_data: html_input)
if status.success?
output
else

View File

@ -1,139 +0,0 @@
class RuleSetProcessor
class InvalidRule < ::ArgumentError
end
def process_all(rule_sets, email)
rule_sets.each { |rule_set|
email = process(rule_set, email)
}
email
end
def process(rule_set, email)
return email unless rule_set.enabled?
if evaluate_rules(rule_set, email)
email = execute_actions(rule_set.actions, email)
end
email
end
def evaluate_rules(rule_set, email)
rule_set_result = true
rule_set_result = catch(:done) {
rule_set.rules.each do |rule|
next unless rule.enabled?
subjects = prepare_subjects(rule, email)
rule_result = apply_rule(subjects, rule)
rule_result = !rule_result if rule.inverted?
rule_set_result = apply_operator(rule_set_result, rule_set.operator, rule_result)
rescue InvalidRule => ex
Rails.logger.error "Skipped rule##{rule.id} '#{rule.name}' - #{ex.inspect}"
next
end
}
rule_set_result
end
def execute_actions(actions, email)
actions.each do |action|
next unless action.enabled?
klass = action.class_name.constantize
email_action = klass.new(action: action)
email = email_action.process(email)
rescue NameError => ex
Rails.logger.error "Skipped action##{action.id} '#{action.name}' - #{ex.inspect}"
raise InvalidRule, ex.inspect
end
email
end
def prepare_subjects(rule, email)
case rule.subject_type.downcase
when "header"
Array(email.header_values(rule.subject_value))
when "subject"
Array(email.subject)
when "body"
Array(email.plain_body)
else
raise InvalidRule, "Unrecognized subject type '#{rule.subject_type}'"
end
end
private
def apply_rule(subjects, rule)
case rule.condition_type.downcase
when "match", "matches"
subjects.any? { |subject|
pattern = Regexp.new(rule.condition_value)
subject.match? pattern
}
when "equal", "equals"
subjects.any? { |subject|
subject == rule.condition_value
}
when "start", "starts"
subjects.any? { |subject|
subject.starts_with? rule.condition_value
}
when "end", "ends"
subjects.any? { |subject|
subject.ends_with? rule.condition_value
}
when "contain", "contains"
subjects.any? { |subject|
subject.include? rule.condition_value
}
when "exist", "exists"
subjects.any? { |subject|
subject.exists?
}
when "empty"
subjects.all? { |subject|
subject.empty?
}
when "date_before"
# subjects.all? { |subject|
# subject.empty?
# }
when "date_after"
# subjects.all? { |subject|
# subject.empty?
# }
else
raise InvalidRule, "Unrecognized condition type '#{rule. condition_type}'"
end
end
def apply_operator(state, operator, result)
case operator.upcase
when "AND"
if result
(state and result)
else
throw :done, false
end
when "OR"
if result
throw :done, true
else
(state or result)
end
# when "XOR"
# (state or result) and !(rules_state and result)
else
raise InvalidRule, "Unrecognized operator '#{operator}'"
end
end
end

View File

@ -0,0 +1,32 @@
<div id="<%= dom_id condition %>">
<p>
<strong>Enabled:</strong>
<%= condition.enabled %>
</p>
<p>
<strong>Property type:</strong>
<%= condition.property_type %>
</p>
<p>
<strong>Property value:</strong>
<%= condition.property_value %>
</p>
<p>
<strong>Test method:</strong>
<%= condition.test_method %>
</p>
<p>
<strong>Test value:</strong>
<%= condition.test_value %>
</p>
<p>
<strong>Inverted:</strong>
<%= condition.inverted %>
</p>
</div>

View File

@ -0,0 +1,48 @@
<%= form_with(model: [ condition.filter, condition ],
data: { controller: "reset_form", action: "turbo:submit-end->reset_form#reset" }) do |form| %>
<% if condition.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(condition.errors.count, "error") %> prohibited this condition from being saved:</h2>
<ul>
<% condition.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :enabled %>
<%= form.check_box :enabled %>
</div>
<div class="field">
<%= form.label :property_type %>
<%= form.text_field :property_type %>
</div>
<div class="field">
<%= form.label :property_value %>
<%= form.text_field :property_value %>
</div>
<div class="field">
<%= form.label :test_method %>
<%= form.text_field :test_method %>
</div>
<div class="field">
<%= form.label :test_value %>
<%= form.text_field :test_value %>
</div>
<div class="field">
<%= form.label :inverted %>
<%= form.check_box :inverted %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>

View File

@ -0,0 +1,6 @@
<h1>Editing Condition</h1>
<%= render 'form', condition: @condition %>
<%= link_to 'Show', @condition %> |
<%= link_to 'Back', @condition.filter %>

View File

@ -0,0 +1,37 @@
<p id="notice"><%= notice %></p>
<h1>Conditions</h1>
<table>
<thead>
<tr>
<th>Enabled</th>
<th>Property type</th>
<th>Property value</th>
<th>Test method</th>
<th>Test value</th>
<th>Inverted</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @conditions.each do |condition| %>
<tr>
<td><%= condition.enabled %></td>
<td><%= condition.property_type %></td>
<td><%= condition.property_value %></td>
<td><%= condition.test_method %></td>
<td><%= condition.test_value %></td>
<td><%= condition.inverted %></td>
<td><%= link_to 'Show', condition %></td>
<td><%= link_to 'Edit', edit_condition_path(condition) %></td>
<td><%= link_to 'Destroy', condition, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Condition', new_condition_path %>

View File

@ -0,0 +1,7 @@
<h1>New Condition</h1>
<%= turbo_frame_tag "new_condition", target: "_top" do %>
<%= render 'form', condition: @condition %>
<% end %>
<%= link_to 'Back', @condition.filter %>

View File

@ -0,0 +1,6 @@
<p id="notice"><%= notice %></p>
<%= render @condition %>
<%= link_to 'Edit', edit_condition_path(@condition) %> |
<%= link_to 'Back', @condition.filter %>

View File

@ -0,0 +1,22 @@
<div id="<%= dom_id filter %>">
<p>
<strong>Description:</strong>
<%= filter.description %>
</p>
<p>
<strong>Enabled:</strong>
<%= filter.enabled %>
</p>
<p>
<strong>Operator:</strong>
<%= filter.operator %>
</p>
<p>
<strong>Inverted:</strong>
<%= filter.inverted %>
</p>
</div>

View File

@ -0,0 +1,37 @@
<%= form_with(model: filter) do |form| %>
<% if filter.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(filter.errors.count, "error") %> prohibited this filter from being saved:</h2>
<ul>
<% filter.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :description %>
<%= form.text_field :description %>
</div>
<div class="field">
<%= form.label :enabled %>
<%= form.check_box :enabled %>
</div>
<div class="field">
<%= form.label :operator %>
<%= form.text_field :operator %>
</div>
<div class="field">
<%= form.label :inverted %>
<%= form.check_box :inverted %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>

View File

@ -0,0 +1,6 @@
<h1>Editing Filter</h1>
<%= render 'form', filter: @filter %>
<%= link_to 'Show', @filter %> |
<%= link_to 'Back', filters_path %>

View File

@ -0,0 +1,33 @@
<p id="notice"><%= notice %></p>
<h1>Filters</h1>
<table>
<thead>
<tr>
<th>Description</th>
<th>Enabled</th>
<th>Operator</th>
<th>Inverted</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @filters.each do |filter| %>
<tr>
<td><%= filter.description %></td>
<td><%= filter.enabled %></td>
<td><%= filter.operator %></td>
<td><%= filter.inverted %></td>
<td><%= link_to 'Show', filter %></td>
<td><%= link_to 'Edit', edit_filter_path(filter) %></td>
<td><%= link_to 'Destroy', filter, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Filter', new_filter_path %>

View File

@ -0,0 +1,5 @@
<h1>New Filter</h1>
<%= render 'form', filter: @filter %>
<%= link_to 'Back', filters_path %>

View File

@ -0,0 +1,25 @@
<p id="notice"><%= notice %></p>
<%= turbo_frame_tag "filter" do %>
<%= render @filter %>
<p>
<%= link_to 'Edit', edit_filter_path(@filter) %> |
<%= link_to 'Back', filters_path, "data-turbo-frame": "_top" %>
</p>
<% end %>
<div id="conditions">
<h2>Conditions</h2>
<%= render(@filter.conditions) || "No conditions yet" %>
</div>
<%= turbo_frame_tag "new_condition", src: new_filter_condition_path(@filter), target: "_top" %>
<div id="operations">
<h2>Operations</h2>
<%= render(@filter.operations) || "No operations yet" %>
</div>
<%= turbo_frame_tag "new_operation", src: new_filter_operation_path(@filter), target: "_top" %>

View File

@ -0,0 +1,33 @@
<%= form_with(model: [ operation.filter, operation ],
data: { controller: "reset_form", operation: "turbo:submit-end->reset_form#reset" }) do |form| %>
<% if operation.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(operation.errors.count, "error") %> prohibited this operation from being saved:</h2>
<ul>
<% operation.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :enabled %>
<%= form.check_box :enabled %>
</div>
<div class="field">
<%= form.label :class_name %>
<%= form.text_field :class_name %>
</div>
<div class="field">
<%= form.label :argument %>
<%= form.text_field :argument %>
</div>
<div class="operations">
<%= form.submit %>
</div>
<% end %>

View File

@ -0,0 +1,17 @@
<div id="<%= dom_id operation %>">
<p>
<strong>Enabled:</strong>
<%= operation.enabled %>
</p>
<p>
<strong>Class name:</strong>
<%= operation.class_name %>
</p>
<p>
<strong>Argument:</strong>
<%= operation.argument %>
</p>
</div>

View File

@ -0,0 +1,6 @@
<h1>Editing Operation</h1>
<%= render 'form', operation: @operation %>
<%= link_to 'Show', @operation %> |
<%= link_to 'Back', @operation.filter %>

View File

@ -0,0 +1,31 @@
<p id="notice"><%= notice %></p>
<h1>Operations</h1>
<table>
<thead>
<tr>
<th>Enabled</th>
<th>Class name</th>
<th>Argument</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @operations.each do |operation| %>
<tr>
<td><%= operation.enabled %></td>
<td><%= operation.class_name %></td>
<td><%= operation.argument %></td>
<td><%= link_to 'Show', operation %></td>
<td><%= link_to 'Edit', edit_operation_path(operation) %></td>
<td><%= link_to 'Destroy', operation, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Operation', new_operation_path %>

View File

@ -0,0 +1,7 @@
<h1>New Operation</h1>
<%= turbo_frame_tag "new_operation", target: "_top" do %>
<%= render 'form', operation: @operation %>
<% end %>
<%= link_to 'Back', @operation.filter %>

View File

@ -0,0 +1,6 @@
<p id="notice"><%= notice %></p>
<%= render @operation %>
<%= link_to 'Edit', edit_operation_path(@operation) %> |
<%= link_to 'Back', @operation.filter %>

View File

@ -3,11 +3,14 @@ require 'sidekiq/web'
Rails.application.routes.draw do
devise_for :users
resources :metadata_mappings
resources :emails
resources :metadata_mappings
resources :filters do
resources :conditions
resources :operations
end
root to: "emails#index"
mount Sidekiq::Web => '/sidekiq'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

View File

@ -1,8 +1,7 @@
class CreateRuleSets < ActiveRecord::Migration[6.1]
class CreateFilters < ActiveRecord::Migration[6.1]
def change
create_table :rule_sets do |t|
t.string :name, null: false
t.text :description
create_table :filters do |t|
t.string :description, null: false
t.boolean :enabled, default: true, null: false
t.string :operator, default: "AND", limit: 3, null: false
t.boolean :inverted, default: false, null: false

View File

@ -0,0 +1,15 @@
class CreateConditions < ActiveRecord::Migration[6.1]
def change
create_table :conditions do |t|
t.references :filter, null: false, foreign_key: true
t.boolean :enabled, default: true, null: false
t.string :property_type, null: false
t.string :property_value
t.string :test_method, default: "contain", null: false
t.string :test_value
t.boolean :inverted, default: false, null: false
t.timestamps
end
end
end

View File

@ -1,16 +0,0 @@
class CreateRules < ActiveRecord::Migration[6.1]
def change
create_table :rules do |t|
t.references :rule_set, null: false, foreign_key: true
t.string :name, null: false
t.boolean :enabled, default: true, null: false
t.string :subject_type, null: false
t.string :subject_value
t.string :condition_type, default: "contain", null: false
t.string :condition_value
t.boolean :inverted, default: false, null: false
t.timestamps
end
end
end

View File

@ -1,13 +0,0 @@
class CreateActions < ActiveRecord::Migration[6.1]
def change
create_table :actions do |t|
t.references :rule_set, null: false, foreign_key: true
t.string :name, null: false
t.boolean :enabled, default: true, null: false
t.string :class_name, null: false
t.string :argument
t.timestamps
end
end
end

View File

@ -0,0 +1,12 @@
class CreateOperations < ActiveRecord::Migration[6.1]
def change
create_table :operations do |t|
t.references :filter, null: false, foreign_key: true
t.boolean :enabled, default: true, null: false
t.string :class_name, null: false
t.string :argument
t.timestamps
end
end
end

View File

@ -21,17 +21,6 @@ ActiveRecord::Schema.define(version: 2021_01_29_124143) do
t.index ["message_id", "message_checksum"], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true
end
create_table "actions", force: :cascade do |t|
t.integer "rule_set_id", null: false
t.string "name", null: false
t.boolean "enabled", default: true, null: false
t.string "class_name", null: false
t.string "argument"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["rule_set_id"], name: "index_actions_on_rule_set_id"
end
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@ -60,6 +49,19 @@ ActiveRecord::Schema.define(version: 2021_01_29_124143) do
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "conditions", force: :cascade do |t|
t.integer "filter_id", null: false
t.boolean "enabled", default: true, null: false
t.string "property_type", null: false
t.string "property_value"
t.string "test_method", default: "contain", null: false
t.string "test_value"
t.boolean "inverted", default: false, null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["filter_id"], name: "index_conditions_on_filter_id"
end
create_table "emails", force: :cascade do |t|
t.string "message_id"
t.string "subject"
@ -80,6 +82,15 @@ ActiveRecord::Schema.define(version: 2021_01_29_124143) do
t.datetime "updated_at", precision: 6, null: false
end
create_table "filters", force: :cascade do |t|
t.string "description", null: false
t.boolean "enabled", default: true, null: false
t.string "operator", limit: 3, default: "AND", null: false
t.boolean "inverted", default: false, null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "metadata_mappings", force: :cascade do |t|
t.string "input"
t.string "server"
@ -90,28 +101,14 @@ ActiveRecord::Schema.define(version: 2021_01_29_124143) do
t.index ["input"], name: "index_metadata_mappings_on_input"
end
create_table "rule_sets", force: :cascade do |t|
t.string "name", null: false
t.text "description"
create_table "operations", force: :cascade do |t|
t.integer "filter_id", null: false
t.boolean "enabled", default: true, null: false
t.string "operator", limit: 3, default: "AND", null: false
t.boolean "inverted", default: false, null: false
t.string "class_name", null: false
t.string "argument"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "rules", force: :cascade do |t|
t.integer "rule_set_id", null: false
t.string "name", null: false
t.boolean "enabled", default: true, null: false
t.string "subject_type", null: false
t.string "subject_value"
t.string "condition_type", default: "contain", null: false
t.string "condition_value"
t.boolean "inverted", default: false, null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["rule_set_id"], name: "index_rules_on_rule_set_id"
t.index ["filter_id"], name: "index_operations_on_filter_id"
end
create_table "users", force: :cascade do |t|
@ -131,8 +128,8 @@ ActiveRecord::Schema.define(version: 2021_01_29_124143) do
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "actions", "rule_sets"
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "rules", "rule_sets"
add_foreign_key "conditions", "filters"
add_foreign_key "operations", "filters"
end

View File

@ -11,35 +11,29 @@ User.create(
encrypted_password: Devise::Encryptor.digest(User, '123password')
)
rule_set = RuleSet.create(
name: "CronMapping",
filter = Filter.create(
description: "CronMapping",
enabled: true
)
Action.create({
rule_set: rule_set,
name: "CronMapping",
class_name: "EmailAction::CronMapping",
filter.operations.create({
class_name: "EmailOperation::CronMapping",
enabled: true
})
rule_set = RuleSet.create(
name: "MailingListMapping",
filter = Filter.create(
description: "MailingListMapping",
enabled: true
)
Action.create({
rule_set: rule_set,
name: "MailingListMapping",
class_name: "EmailAction::MailingListMapping",
filter.operations.create({
class_name: "EmailOperation::MailingListMapping",
enabled: true
})
rule_set = RuleSet.create(
name: "MetadataMapping",
filter = Filter.create(
description: "MetadataMapping",
enabled: true
)
Action.create({
rule_set: rule_set,
name: "MetadataMapping",
class_name: "EmailAction::MetadataMapping",
filter.operations.create({
class_name: "EmailOperation::MetadataMapping",
enabled: true
})

View File

@ -0,0 +1,51 @@
# require "test_helper"
#
# class ConditionsControllerTest < ActionDispatch::IntegrationTest
# include Devise::Test::IntegrationHelpers
#
# setup do
# sign_in users(:alice)
# @condition = conditions(:one)
# end
#
# test "should get index" do
# get conditions_url
# assert_response :success
# end
#
# test "should get new" do
# get new_condition_url
# assert_response :success
# end
#
# test "should create condition" do
# assert_difference('Condition.count') do
# post conditions_url, params: { condition: { test_method: @condition.test_method, test_value: @condition.test_value, enabled: @condition.enabled, inverted: @condition.inverted, property_type: @condition.property_type, property_value: @condition.property_value } }
# end
#
# assert_redirected_to condition_url(Condition.last)
# end
#
# test "should show condition" do
# get condition_url(@condition)
# assert_response :success
# end
#
# test "should get edit" do
# get edit_condition_url(@condition)
# assert_response :success
# end
#
# test "should update condition" do
# patch condition_url(@condition), params: { condition: { test_method: @condition.test_method, test_value: @condition.test_value, enabled: @condition.enabled, inverted: @condition.inverted, property_type: @condition.property_type, property_value: @condition.property_value } }
# assert_redirected_to condition_url(@condition)
# end
#
# test "should destroy condition" do
# assert_difference('Condition.count', -1) do
# delete condition_url(@condition)
# end
#
# assert_redirected_to conditions_url
# end
# end

View File

@ -0,0 +1,51 @@
require "test_helper"
class FilterControllerTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
setup do
sign_in users(:alice)
@filter = filters(:one)
end
test "should get index" do
get filters_url
assert_response :success
end
test "should get new" do
get new_filter_url
assert_response :success
end
test "should create filter" do
assert_difference('Filter.count') do
post filters_url, params: { filter: { description: @filter.description, enabled: @filter.enabled, inverted: @filter.inverted, operator: @filter.operator } }
end
assert_redirected_to filter_url(Filter.last)
end
test "should show filter" do
get filter_url(@filter)
assert_response :success
end
test "should get edit" do
get edit_filter_url(@filter)
assert_response :success
end
test "should update filter" do
patch filter_url(@filter), params: { filter: { description: @filter.description, enabled: @filter.enabled, inverted: @filter.inverted, operator: @filter.operator } }
assert_redirected_to filter_url(@filter)
end
test "should destroy filter" do
assert_difference('Filter.count', -1) do
delete filter_url(@filter)
end
assert_redirected_to filters_url
end
end

View File

@ -0,0 +1,51 @@
# require "test_helper"
#
# class OperationsControllerTest < ActionDispatch::IntegrationTest
# include Devise::Test::IntegrationHelpers
#
# setup do
# sign_in users(:alice)
# @operation = operations(:one)
# end
#
# test "should get index" do
# get operations_url
# assert_response :success
# end
#
# test "should get new" do
# get new_operation_url
# assert_response :success
# end
#
# test "should create operation" do
# assert_difference('Operation.count') do
# post operations_url, params: { operation: { argument: @operation.argument, class_name: @operation.class_name, enabled: @operation.enabled } }
# end
#
# assert_redirected_to operation_url(Operation.last)
# end
#
# test "should show operation" do
# get operation_url(@operation)
# assert_response :success
# end
#
# test "should get edit" do
# get edit_operation_url(@operation)
# assert_response :success
# end
#
# test "should update operation" do
# patch operation_url(@operation), params: { operation: { argument: @operation.argument, class_name: @operation.class_name, enabled: @operation.enabled } }
# assert_redirected_to operation_url(@operation)
# end
#
# test "should destroy operation" do
# assert_difference('Operation.count', -1) do
# delete operation_url(@operation)
# end
#
# assert_redirected_to operations_url
# end
# end

View File

@ -1,64 +0,0 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
rule_set: one
name: MyString
enabled: false
class_name: MyString
two:
rule_set: two
name: MyString
enabled: false
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
postpone_future_valid:
rule_set: postpone_future_valid
name: Postpone for 5 days
enabled: true
class_name: EmailAction::Postpone
argument: <%= Chronic.parse "5 days from now" %>
postpone_past_valid:
rule_set: postpone_past_valid
name: Postpone in the past
enabled: true
class_name: EmailAction::Postpone
argument: <%= Chronic.parse "2 days ago" %>
postpone_invalid:
rule_set: postpone_invalid
name: Postpone to invalid date
enabled: true
class_name: EmailAction::Postpone
argument: Foo Bar Baz
junk:
rule_set: junk
name: Mark mail as junk
enabled: true
class_name: EmailAction::Junk

78
test/fixtures/conditions.yml vendored Normal file
View File

@ -0,0 +1,78 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
filter: one
enabled: true
property_type: Header
property_value: Subject
test_method: contain
test_value: MyString
inverted: false
two:
filter: two
enabled: true
property_type: Header
property_value: Subject
test_method: contain
test_value: MyString
inverted: true
invalid_subject:
filter: invalid_subject
enabled: true
property_type: Invalid
property_value: Subject
test_method: contain
test_value: MyString
inverted: false
invalid_test_method:
filter: invalid_test
enabled: true
property_type: Header
property_value: Subject
test_method: invalid
test_value: MyString
inverted: false
invalid_operator:
filter: invalid_operator
enabled: true
property_type: Header
property_value: Subject
test_method: contain
test_value: MyString
inverted: false
postpone_future_valid:
filter: postpone_future_valid
enabled: true
property_type: Subject
test_method: match
test_value: Postponable
inverted: false
postpone_past_valid:
filter: postpone_past_valid
enabled: true
property_type: Subject
test_method: match
test_value: Postponable
inverted: false
postpone_invalid:
filter: postpone_invalid
enabled: true
property_type: Subject
test_method: match
test_value: Postponable
inverted: false
junk_subject:
filter: junk
enabled: true
property_type: Subject
test_method: match
test_value: Junk
inverted: false

69
test/fixtures/filters.yml vendored Normal file
View File

@ -0,0 +1,69 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
description: Default Filter
enabled: true
operator: "AND"
inverted: false
and_filter:
description: Filters with AND
enabled: true
operator: "AND"
inverted: false
or_filter:
description: Filters with OR
enabled: true
operator: "OR"
inverted: false
invalid_subject:
description: Filters with an invalid filter subject
enabled: true
operator: "AND"
inverted: false
invalid_condition_type:
description: Filters with an invalid filter Property type
enabled: true
operator: "AND"
inverted: false
invalid_operator:
description: Filters with an invalid filter operator
enabled: true
operator: "FOO"
inverted: false
metadata:
description: Metadata
enabled: true
issue:
description: Linked to issue(s)
enabled: true
cron:
description: FromCron
enabled: true
mailing_list:
description: FromMailingList
enabled: true
postpone_future_valid:
description: Postpone to valid date in the future
enabled: true
postpone_past_valid:
description: Postpone to valid date in the past
enabled: true
postpone_invalid:
description: Postpone to invalid date
enabled: true
junk:
description: Junk mail
enabled: true

54
test/fixtures/operations.yml vendored Normal file
View File

@ -0,0 +1,54 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
filter: one
enabled: false
class_name: MyString
two:
filter: two
enabled: false
class_name: MyString
issue:
filter: issue
enabled: true
class_name: EmailOperation::IssueMapping
cron:
filter: cron
enabled: true
class_name: EmailOperation::CronMapping
mailing_list:
filter: mailing_list
enabled: true
class_name: EmailOperation::MailingListMapping
metadata:
filter: metadata
enabled: true
class_name: EmailOperation::MetadataMapping
postpone_future_valid:
filter: postpone_future_valid
enabled: true
class_name: EmailOperation::Postpone
argument: <%= Chronic.parse "5 days from now" %>
postpone_past_valid:
filter: postpone_past_valid
enabled: true
class_name: EmailOperation::Postpone
argument: <%= Chronic.parse "2 days ago" %>
postpone_invalid:
filter: postpone_invalid
enabled: true
class_name: EmailOperation::Postpone
argument: Foo Bar Baz
junk:
filter: junk
enabled: true
class_name: EmailOperation::Junk

View File

@ -1,76 +0,0 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: Default RuleSet
description: MyText
enabled: true
operator: "AND"
inverted: false
and_rules:
name: Rules with AND
description: MyText
enabled: true
operator: "AND"
inverted: false
or_rules:
name: Rules with OR
description: MyText
enabled: true
operator: "OR"
inverted: false
invalid_subject:
name: Rules with an invalid rule subject
enabled: true
operator: "AND"
inverted: false
invalid_condition_type:
name: Rules with an invalid rule condition type
enabled: true
operator: "AND"
inverted: false
invalid_operator:
name: Rules with an invalid rule operator
enabled: true
operator: "FOO"
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
postpone_future_valid:
name: Postpone to valid date in the future
enabled: true
postpone_past_valid:
name: Postpone to valid date in the past
enabled: true
postpone_invalid:
name: Postpone to invalid date
enabled: true
junk:
name: Junk mail
enabled: true

View File

@ -1,87 +0,0 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
rule_set: one
name: Subject contains MyString
enabled: true
subject_type: Header
subject_value: Subject
condition_type: contain
condition_value: MyString
inverted: false
two:
rule_set: two
name: Subject doesn't contain MyString
enabled: true
subject_type: Header
subject_value: Subject
condition_type: contain
condition_value: MyString
inverted: true
invalid_subject:
rule_set: invalid_subject
name: Rule with invalid subject type
enabled: true
subject_type: Invalid
subject_value: Subject
condition_type: contain
condition_value: MyString
inverted: false
invalid_condition_type:
rule_set: invalid_condition_type
name: Rule with invalid condition type
enabled: true
subject_type: Header
subject_value: Subject
condition_type: invalid
condition_value: MyString
inverted: false
invalid_operator:
rule_set: invalid_operator
name: Rule with valid arguments
enabled: true
subject_type: Header
subject_value: Subject
condition_type: contain
condition_value: MyString
inverted: false
postpone_future_valid:
rule_set: postpone_future_valid
name: Match Postponable in Subject
enabled: true
subject_type: Subject
condition_type: match
condition_value: Postponable
inverted: false
postpone_past_valid:
rule_set: postpone_past_valid
name: Match Postponable in Subject
enabled: true
subject_type: Subject
condition_type: match
condition_value: Postponable
inverted: false
postpone_invalid:
rule_set: postpone_invalid
name: Match Postponable in Subject
enabled: true
subject_type: Subject
condition_type: match
condition_value: Postponable
inverted: false
junk_subject:
rule_set: junk
name: Junk in subject
enabled: true
subject_type: Subject
condition_type: match
condition_value: Junk
inverted: false

View File

@ -1,6 +1,6 @@
require "test_helper"
class RuleSetTest < ActiveSupport::TestCase
class ConditionTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end

View File

@ -1,6 +1,6 @@
require "test_helper"
class RuleTest < ActiveSupport::TestCase
class FilterTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end

View File

@ -1,6 +1,6 @@
require "test_helper"
class ActionTest < ActiveSupport::TestCase
class OperationTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end

View File

@ -1,27 +1,27 @@
require 'test_helper'
class RuleSetProcessorTest < ActiveSupport::TestCase
class FilterProcessorTest < ActiveSupport::TestCase
test "mark cron from subject" do
email = email_from_eml_with_rules("cron_subject.eml")
email = email_from_eml_with_filters("cron_subject.eml")
assert_predicate email, :cron?
end
test "mark cron from headers" do
email = email_from_eml_with_rules("cron_headers.eml")
email = email_from_eml_with_filters("cron_headers.eml")
assert_predicate email, :cron?
end
test "mark not cron" do
email = email_from_eml_with_rules("cron_not.eml")
email = email_from_eml_with_filters("cron_not.eml")
assert_not_predicate email, :cron?
end
test "single issue" do
email = email_from_eml_with_rules("issues_single.eml")
email = email_from_eml_with_filters("issues_single.eml")
expected = ["49123"]
actual = email.issues
@ -30,7 +30,7 @@ class RuleSetProcessorTest < ActiveSupport::TestCase
end
test "multiple issues" do
email = email_from_eml_with_rules("issues_multiple.eml")
email = email_from_eml_with_filters("issues_multiple.eml")
expected = ["49123", "12345"]
actual = email.issues
@ -39,7 +39,7 @@ class RuleSetProcessorTest < ActiveSupport::TestCase
end
test "single organisation" do
email = email_from_eml_with_rules("organisations_single.eml")
email = email_from_eml_with_filters("organisations_single.eml")
expected = ["quux"]
actual = email.organisations
@ -48,7 +48,7 @@ class RuleSetProcessorTest < ActiveSupport::TestCase
end
test "multiple organisations" do
email = email_from_eml_with_rules("organisations_multiple.eml")
email = email_from_eml_with_filters("organisations_multiple.eml")
expected = ["quux", "foobar"]
actual = email.organisations
@ -56,34 +56,34 @@ class RuleSetProcessorTest < ActiveSupport::TestCase
assert_equal expected, actual
end
test "invalid subject type" do
test "invalid Test" do
email = Email.new
processor = RuleSetProcessor.new
email = processor.process(rule_sets(:invalid_subject), email)
processor = FilterProcessor.new
email = processor.process(filters(:invalid_subject), email)
assert_not_predicate email, :changed?
end
test "invalid condition type" do
test "invalid Property type" do
email = Email.new
processor = RuleSetProcessor.new
email = processor.process(rule_sets(:invalid_condition_type), email)
processor = FilterProcessor.new
email = processor.process(filters(:invalid_condition_type), email)
assert_not_predicate email, :changed?
end
test "invalid operator" do
email = Email.new
processor = RuleSetProcessor.new
email = processor.process(rule_sets(:invalid_operator), email)
processor = FilterProcessor.new
email = processor.process(filters(:invalid_operator), email)
assert_not_predicate email, :changed?
end
test "postponed to valid future date" do
email = Email.new(subject: "Postponable")
processor = RuleSetProcessor.new
email = processor.process(rule_sets(:postpone_future_valid), email)
processor = FilterProcessor.new
email = processor.process(filters(:postpone_future_valid), email)
assert_not_nil email.postponed_until
assert_predicate email, :postponed?
@ -91,8 +91,8 @@ class RuleSetProcessorTest < ActiveSupport::TestCase
test "postponed to valid past date" do
email = Email.new(subject: "Postponable")
processor = RuleSetProcessor.new
email = processor.process(rule_sets(:postpone_past_valid), email)
processor = FilterProcessor.new
email = processor.process(filters(:postpone_past_valid), email)
assert_not_nil email.postponed_until
assert_not_predicate email, :postponed?
@ -100,8 +100,8 @@ class RuleSetProcessorTest < ActiveSupport::TestCase
test "postponed to invalid date" do
email = Email.new(subject: "Postponable")
processor = RuleSetProcessor.new
email = processor.process(rule_sets(:postpone_invalid), email)
processor = FilterProcessor.new
email = processor.process(filters(:postpone_invalid), email)
assert_nil email.postponed_until
assert_not_predicate email, :postponed?
@ -109,8 +109,8 @@ class RuleSetProcessorTest < ActiveSupport::TestCase
test "junk mail" do
email = Email.new(subject: "Junk")
processor = RuleSetProcessor.new
email = processor.process(rule_sets(:junk), email)
processor = FilterProcessor.new
email = processor.process(filters(:junk), email)
assert_predicate email, :junk?
end

View File

@ -0,0 +1,53 @@
# require "application_system_test_case"
#
# class ConditionsTest < ApplicationSystemTestCase
# setup do
# @condition = conditions(:one)
# end
#
# test "visiting the index" do
# visit conditions_url
# assert_selector "h1", text: "Conditions"
# end
#
# test "creating a Condition" do
# visit conditions_url
# click_on "New Condition"
#
# fill_in "Property type", with: @condition.property_type
# fill_in "Property value", with: @condition.property_value
# check "Enabled" if @condition.enabled
# check "Inverted" if @condition.inverted
# fill_in "Test method", with: @condition.test_method
# fill_in "Test value", with: @condition.test_value
# click_on "Create Condition"
#
# assert_text "Condition was successfully created"
# click_on "Back"
# end
#
# test "updating a Condition" do
# visit conditions_url
# click_on "Edit", match: :first
#
# fill_in "Property type", with: @condition.property_type
# fill_in "Property value", with: @condition.property_value
# check "Enabled" if @condition.enabled
# check "Inverted" if @condition.inverted
# fill_in "Test method", with: @condition.test_method
# fill_in "Test value", with: @condition.test_value
# click_on "Update Condition"
#
# assert_text "Condition was successfully updated"
# click_on "Back"
# end
#
# test "destroying a Condition" do
# visit conditions_url
# page.accept_confirm do
# click_on "Destroy", match: :first
# end
#
# assert_text "Condition was successfully destroyed"
# end
# end

View File

@ -0,0 +1,49 @@
require "application_system_test_case"
class FiltersTest < ApplicationSystemTestCase
setup do
@filter = filters(:one)
end
test "visiting the index" do
visit filters_url
assert_selector "h1", text: "Filters"
end
test "creating a Filter" do
visit filters_url
click_on "New Filter"
fill_in "Description", with: @filter.description
check "Enabled" if @filter.enabled
check "Inverted" if @filter.inverted
fill_in "Operator", with: @filter.operator
click_on "Create Filter"
assert_text "Filter was successfully created"
click_on "Back"
end
test "updating a Filter" do
visit filters_url
click_on "Edit", match: :first
fill_in "Description", with: @filter.description
check "Enabled" if @filter.enabled
check "Inverted" if @filter.inverted
fill_in "Operator", with: @filter.operator
click_on "Update Filter"
assert_text "Filter was successfully updated"
click_on "Back"
end
test "destroying a Filter" do
visit filters_url
page.accept_confirm do
click_on "Destroy", match: :first
end
assert_text "Filter was successfully destroyed"
end
end

View File

@ -0,0 +1,47 @@
# require "application_system_test_case"
#
# class OperationsTest < ApplicationSystemTestCase
# setup do
# @operation = operations(:one)
# end
#
# test "visiting the index" do
# visit operations_url
# assert_selector "h1", text: "Operations"
# end
#
# test "creating a Operation" do
# visit operations_url
# click_on "New Operation"
#
# fill_in "Argument", with: @operation.argument
# fill_in "Class name", with: @operation.class_name
# check "Enabled" if @operation.enabled
# click_on "Create Operation"
#
# assert_text "Operation was successfully created"
# click_on "Back"
# end
#
# test "updating a Operation" do
# visit operations_url
# click_on "Edit", match: :first
#
# fill_in "Argument", with: @operation.argument
# fill_in "Class name", with: @operation.class_name
# check "Enabled" if @operation.enabled
# click_on "Update Operation"
#
# assert_text "Operation was successfully updated"
# click_on "Back"
# end
#
# test "destroying a Operation" do
# visit operations_url
# page.accept_confirm do
# click_on "Destroy", match: :first
# end
#
# assert_text "Operation was successfully destroyed"
# end
# end

View File

@ -4,7 +4,7 @@ require 'rails/test_help'
class ActiveSupport::TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)
# parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
@ -19,10 +19,10 @@ class ActiveSupport::TestCase
email
end
def email_from_eml_with_rules(file_fixture_name)
def email_from_eml_with_filters(file_fixture_name)
email = email_from_eml(file_fixture_name)
processor = RuleSetProcessor.new
email = processor.process_all(RuleSet.enabled, email)
processor = FilterProcessor.new
email = processor.process_all(Filter.enabled, email)
end
def assert_no_html(text)