diff --git a/Gemfile b/Gemfile index 47f9c58..a0bde61 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index 1941840..bbaef65 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/IDEAS.md b/IDEAS.md index 0d04693..0908716 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -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…) diff --git a/app/assets/stylesheets/conditions.scss b/app/assets/stylesheets/conditions.scss new file mode 100644 index 0000000..c480f62 --- /dev/null +++ b/app/assets/stylesheets/conditions.scss @@ -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/ diff --git a/app/assets/stylesheets/filters.scss b/app/assets/stylesheets/filters.scss new file mode 100644 index 0000000..bc735e4 --- /dev/null +++ b/app/assets/stylesheets/filters.scss @@ -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; +} diff --git a/app/assets/stylesheets/operations.scss b/app/assets/stylesheets/operations.scss new file mode 100644 index 0000000..233a5cd --- /dev/null +++ b/app/assets/stylesheets/operations.scss @@ -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/ diff --git a/app/controllers/conditions_controller.rb b/app/controllers/conditions_controller.rb new file mode 100644 index 0000000..85f39bb --- /dev/null +++ b/app/controllers/conditions_controller.rb @@ -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 diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb new file mode 100644 index 0000000..09f2947 --- /dev/null +++ b/app/controllers/filters_controller.rb @@ -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 diff --git a/app/controllers/metadata_mappings_controller.rb b/app/controllers/metadata_mappings_controller.rb index 7c94e50..5fb4ed5 100644 --- a/app/controllers/metadata_mappings_controller.rb +++ b/app/controllers/metadata_mappings_controller.rb @@ -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 diff --git a/app/controllers/operations_controller.rb b/app/controllers/operations_controller.rb new file mode 100644 index 0000000..0c7254a --- /dev/null +++ b/app/controllers/operations_controller.rb @@ -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 diff --git a/app/helpers/conditions_helper.rb b/app/helpers/conditions_helper.rb new file mode 100644 index 0000000..26355a8 --- /dev/null +++ b/app/helpers/conditions_helper.rb @@ -0,0 +1,2 @@ +module ConditionsHelper +end diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb new file mode 100644 index 0000000..78c4db2 --- /dev/null +++ b/app/helpers/filters_helper.rb @@ -0,0 +1,2 @@ +module FiltersHelper +end diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb new file mode 100644 index 0000000..d114c7c --- /dev/null +++ b/app/helpers/operations_helper.rb @@ -0,0 +1,2 @@ +module OperationsHelper +end diff --git a/app/mailboxes/in_mailbox.rb b/app/mailboxes/in_mailbox.rb index 48026c5..8128a2b 100644 --- a/app/mailboxes/in_mailbox.rb +++ b/app/mailboxes/in_mailbox.rb @@ -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) diff --git a/app/models/action.rb b/app/models/condition.rb similarity index 58% rename from app/models/action.rb rename to app/models/condition.rb index 5430d0f..c84bb17 100644 --- a/app/models/action.rb +++ b/app/models/condition.rb @@ -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 diff --git a/app/models/filter.rb b/app/models/filter.rb new file mode 100644 index 0000000..99b1c59 --- /dev/null +++ b/app/models/filter.rb @@ -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 diff --git a/app/models/rule_set.rb b/app/models/operation.rb similarity index 53% rename from app/models/rule_set.rb rename to app/models/operation.rb index 98b0859..db23dcc 100644 --- a/app/models/rule_set.rb +++ b/app/models/operation.rb @@ -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 diff --git a/app/models/rule.rb b/app/models/rule.rb deleted file mode 100644 index ea54e2b..0000000 --- a/app/models/rule.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -class Rule < ApplicationRecord - belongs_to :rule_set - has_many :actions - - scope :enabled, -> { where(enabled: true) } -end diff --git a/app/services/email_action/postpone.rb b/app/services/email_action/postpone.rb deleted file mode 100644 index 5daeb29..0000000 --- a/app/services/email_action/postpone.rb +++ /dev/null @@ -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 diff --git a/app/services/email_action/base.rb b/app/services/email_operation/base.rb similarity index 61% rename from app/services/email_action/base.rb rename to app/services/email_operation/base.rb index 307a581..c8522b7 100644 --- a/app/services/email_action/base.rb +++ b/app/services/email_operation/base.rb @@ -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) diff --git a/app/services/email_action/cron_mapping.rb b/app/services/email_operation/cron_mapping.rb similarity index 92% rename from app/services/email_action/cron_mapping.rb rename to app/services/email_operation/cron_mapping.rb index 2909d0b..d39135c 100644 --- a/app/services/email_action/cron_mapping.rb +++ b/app/services/email_operation/cron_mapping.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module EmailAction +module EmailOperation class CronMapping < Base def process(email) diff --git a/app/services/email_action/issue_mapping.rb b/app/services/email_operation/issue_mapping.rb similarity index 92% rename from app/services/email_action/issue_mapping.rb rename to app/services/email_operation/issue_mapping.rb index dfa655a..7e3c6ef 100644 --- a/app/services/email_action/issue_mapping.rb +++ b/app/services/email_operation/issue_mapping.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module EmailAction +module EmailOperation class IssueMapping < Base def process(email) diff --git a/app/services/email_action/junk.rb b/app/services/email_operation/junk.rb similarity index 85% rename from app/services/email_action/junk.rb rename to app/services/email_operation/junk.rb index 9a3c738..3960dd2 100644 --- a/app/services/email_action/junk.rb +++ b/app/services/email_operation/junk.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module EmailAction +module EmailOperation class Junk < Base def process(email) diff --git a/app/services/email_action/mailing_list_mapping.rb b/app/services/email_operation/mailing_list_mapping.rb similarity index 90% rename from app/services/email_action/mailing_list_mapping.rb rename to app/services/email_operation/mailing_list_mapping.rb index 2d22b69..6743add 100644 --- a/app/services/email_action/mailing_list_mapping.rb +++ b/app/services/email_operation/mailing_list_mapping.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module EmailAction +module EmailOperation class MailingListMapping < Base def process(email) diff --git a/app/services/email_action/metadata_mapping.rb b/app/services/email_operation/metadata_mapping.rb similarity index 92% rename from app/services/email_action/metadata_mapping.rb rename to app/services/email_operation/metadata_mapping.rb index 74e91ce..764c248 100644 --- a/app/services/email_action/metadata_mapping.rb +++ b/app/services/email_operation/metadata_mapping.rb @@ -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 diff --git a/app/services/email_operation/postpone.rb b/app/services/email_operation/postpone.rb new file mode 100644 index 0000000..f226e7c --- /dev/null +++ b/app/services/email_operation/postpone.rb @@ -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 diff --git a/app/services/filter_processor.rb b/app/services/filter_processor.rb new file mode 100644 index 0000000..8fa0209 --- /dev/null +++ b/app/services/filter_processor.rb @@ -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 diff --git a/app/services/html_to_text/elinks.rb b/app/services/html_to_text/elinks.rb index 4e69b3b..e9e9653 100644 --- a/app/services/html_to_text/elinks.rb +++ b/app/services/html_to_text/elinks.rb @@ -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 diff --git a/app/services/rule_set_processor.rb b/app/services/rule_set_processor.rb deleted file mode 100644 index f3e30db..0000000 --- a/app/services/rule_set_processor.rb +++ /dev/null @@ -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 diff --git a/app/views/conditions/_condition.html.erb b/app/views/conditions/_condition.html.erb new file mode 100644 index 0000000..91b2628 --- /dev/null +++ b/app/views/conditions/_condition.html.erb @@ -0,0 +1,32 @@ +
+ +

+ Enabled: + <%= condition.enabled %> +

+ +

+ Property type: + <%= condition.property_type %> +

+ +

+ Property value: + <%= condition.property_value %> +

+ +

+ Test method: + <%= condition.test_method %> +

+ +

+ Test value: + <%= condition.test_value %> +

+ +

+ Inverted: + <%= condition.inverted %> +

+
diff --git a/app/views/conditions/_form.html.erb b/app/views/conditions/_form.html.erb new file mode 100644 index 0000000..dd6cb48 --- /dev/null +++ b/app/views/conditions/_form.html.erb @@ -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? %> +
+

<%= pluralize(condition.errors.count, "error") %> prohibited this condition from being saved:

+ + +
+ <% end %> + +
+ <%= form.label :enabled %> + <%= form.check_box :enabled %> +
+ +
+ <%= form.label :property_type %> + <%= form.text_field :property_type %> +
+ +
+ <%= form.label :property_value %> + <%= form.text_field :property_value %> +
+ +
+ <%= form.label :test_method %> + <%= form.text_field :test_method %> +
+ +
+ <%= form.label :test_value %> + <%= form.text_field :test_value %> +
+ +
+ <%= form.label :inverted %> + <%= form.check_box :inverted %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/conditions/edit.html.erb b/app/views/conditions/edit.html.erb new file mode 100644 index 0000000..34bc785 --- /dev/null +++ b/app/views/conditions/edit.html.erb @@ -0,0 +1,6 @@ +

Editing Condition

+ +<%= render 'form', condition: @condition %> + +<%= link_to 'Show', @condition %> | +<%= link_to 'Back', @condition.filter %> diff --git a/app/views/conditions/index.html.erb b/app/views/conditions/index.html.erb new file mode 100644 index 0000000..a4ddc2d --- /dev/null +++ b/app/views/conditions/index.html.erb @@ -0,0 +1,37 @@ +

<%= notice %>

+ +

Conditions

+ + + + + + + + + + + + + + + + <% @conditions.each do |condition| %> + + + + + + + + + + + + <% end %> + +
EnabledProperty typeProperty valueTest methodTest valueInverted
<%= condition.enabled %><%= condition.property_type %><%= condition.property_value %><%= condition.test_method %><%= condition.test_value %><%= condition.inverted %><%= link_to 'Show', condition %><%= link_to 'Edit', edit_condition_path(condition) %><%= link_to 'Destroy', condition, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Condition', new_condition_path %> diff --git a/app/views/conditions/new.html.erb b/app/views/conditions/new.html.erb new file mode 100644 index 0000000..a0c86d5 --- /dev/null +++ b/app/views/conditions/new.html.erb @@ -0,0 +1,7 @@ +

New Condition

+ +<%= turbo_frame_tag "new_condition", target: "_top" do %> + <%= render 'form', condition: @condition %> +<% end %> + +<%= link_to 'Back', @condition.filter %> diff --git a/app/views/conditions/show.html.erb b/app/views/conditions/show.html.erb new file mode 100644 index 0000000..de41125 --- /dev/null +++ b/app/views/conditions/show.html.erb @@ -0,0 +1,6 @@ +

<%= notice %>

+ +<%= render @condition %> + +<%= link_to 'Edit', edit_condition_path(@condition) %> | +<%= link_to 'Back', @condition.filter %> diff --git a/app/views/filters/_filter.html.erb b/app/views/filters/_filter.html.erb new file mode 100644 index 0000000..8b9dab2 --- /dev/null +++ b/app/views/filters/_filter.html.erb @@ -0,0 +1,22 @@ +
+ +

+ Description: + <%= filter.description %> +

+ +

+ Enabled: + <%= filter.enabled %> +

+ +

+ Operator: + <%= filter.operator %> +

+ +

+ Inverted: + <%= filter.inverted %> +

+
diff --git a/app/views/filters/_form.html.erb b/app/views/filters/_form.html.erb new file mode 100644 index 0000000..c3acd6d --- /dev/null +++ b/app/views/filters/_form.html.erb @@ -0,0 +1,37 @@ +<%= form_with(model: filter) do |form| %> + <% if filter.errors.any? %> +
+

<%= pluralize(filter.errors.count, "error") %> prohibited this filter from being saved:

+ + +
+ <% end %> + +
+ <%= form.label :description %> + <%= form.text_field :description %> +
+ +
+ <%= form.label :enabled %> + <%= form.check_box :enabled %> +
+ +
+ <%= form.label :operator %> + <%= form.text_field :operator %> +
+ +
+ <%= form.label :inverted %> + <%= form.check_box :inverted %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/filters/edit.html.erb b/app/views/filters/edit.html.erb new file mode 100644 index 0000000..632c26f --- /dev/null +++ b/app/views/filters/edit.html.erb @@ -0,0 +1,6 @@ +

Editing Filter

+ +<%= render 'form', filter: @filter %> + +<%= link_to 'Show', @filter %> | +<%= link_to 'Back', filters_path %> diff --git a/app/views/filters/index.html.erb b/app/views/filters/index.html.erb new file mode 100644 index 0000000..fe58c5f --- /dev/null +++ b/app/views/filters/index.html.erb @@ -0,0 +1,33 @@ +

<%= notice %>

+ +

Filters

+ + + + + + + + + + + + + + <% @filters.each do |filter| %> + + + + + + + + + + <% end %> + +
DescriptionEnabledOperatorInverted
<%= filter.description %><%= filter.enabled %><%= filter.operator %><%= filter.inverted %><%= link_to 'Show', filter %><%= link_to 'Edit', edit_filter_path(filter) %><%= link_to 'Destroy', filter, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Filter', new_filter_path %> diff --git a/app/views/filters/new.html.erb b/app/views/filters/new.html.erb new file mode 100644 index 0000000..8e748b1 --- /dev/null +++ b/app/views/filters/new.html.erb @@ -0,0 +1,5 @@ +

New Filter

+ +<%= render 'form', filter: @filter %> + +<%= link_to 'Back', filters_path %> diff --git a/app/views/filters/show.html.erb b/app/views/filters/show.html.erb new file mode 100644 index 0000000..0d4ce5d --- /dev/null +++ b/app/views/filters/show.html.erb @@ -0,0 +1,25 @@ +

<%= notice %>

+ + +<%= turbo_frame_tag "filter" do %> + <%= render @filter %> + +

+ <%= link_to 'Edit', edit_filter_path(@filter) %> | + <%= link_to 'Back', filters_path, "data-turbo-frame": "_top" %> +

+<% end %> + +
+

Conditions

+ <%= render(@filter.conditions) || "No conditions yet" %> +
+ +<%= turbo_frame_tag "new_condition", src: new_filter_condition_path(@filter), target: "_top" %> + +
+

Operations

+ <%= render(@filter.operations) || "No operations yet" %> +
+ +<%= turbo_frame_tag "new_operation", src: new_filter_operation_path(@filter), target: "_top" %> diff --git a/app/views/operations/_form.html.erb b/app/views/operations/_form.html.erb new file mode 100644 index 0000000..fde3f23 --- /dev/null +++ b/app/views/operations/_form.html.erb @@ -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? %> +
+

<%= pluralize(operation.errors.count, "error") %> prohibited this operation from being saved:

+ + +
+ <% end %> + +
+ <%= form.label :enabled %> + <%= form.check_box :enabled %> +
+ +
+ <%= form.label :class_name %> + <%= form.text_field :class_name %> +
+ +
+ <%= form.label :argument %> + <%= form.text_field :argument %> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/operations/_operation.html.erb b/app/views/operations/_operation.html.erb new file mode 100644 index 0000000..9240263 --- /dev/null +++ b/app/views/operations/_operation.html.erb @@ -0,0 +1,17 @@ +
+ +

+ Enabled: + <%= operation.enabled %> +

+ +

+ Class name: + <%= operation.class_name %> +

+ +

+ Argument: + <%= operation.argument %> +

+
diff --git a/app/views/operations/edit.html.erb b/app/views/operations/edit.html.erb new file mode 100644 index 0000000..ff23b2d --- /dev/null +++ b/app/views/operations/edit.html.erb @@ -0,0 +1,6 @@ +

Editing Operation

+ +<%= render 'form', operation: @operation %> + +<%= link_to 'Show', @operation %> | +<%= link_to 'Back', @operation.filter %> diff --git a/app/views/operations/index.html.erb b/app/views/operations/index.html.erb new file mode 100644 index 0000000..cf5877a --- /dev/null +++ b/app/views/operations/index.html.erb @@ -0,0 +1,31 @@ +

<%= notice %>

+ +

Operations

+ + + + + + + + + + + + + <% @operations.each do |operation| %> + + + + + + + + + <% end %> + +
EnabledClass nameArgument
<%= operation.enabled %><%= operation.class_name %><%= operation.argument %><%= link_to 'Show', operation %><%= link_to 'Edit', edit_operation_path(operation) %><%= link_to 'Destroy', operation, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Operation', new_operation_path %> diff --git a/app/views/operations/new.html.erb b/app/views/operations/new.html.erb new file mode 100644 index 0000000..69e82ac --- /dev/null +++ b/app/views/operations/new.html.erb @@ -0,0 +1,7 @@ +

New Operation

+ +<%= turbo_frame_tag "new_operation", target: "_top" do %> + <%= render 'form', operation: @operation %> +<% end %> + +<%= link_to 'Back', @operation.filter %> diff --git a/app/views/operations/show.html.erb b/app/views/operations/show.html.erb new file mode 100644 index 0000000..ff812b1 --- /dev/null +++ b/app/views/operations/show.html.erb @@ -0,0 +1,6 @@ +

<%= notice %>

+ +<%= render @operation %> + +<%= link_to 'Edit', edit_operation_path(@operation) %> | +<%= link_to 'Back', @operation.filter %> diff --git a/config/routes.rb b/config/routes.rb index 4f6d00f..da3b587 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/db/migrate/20210118130305_create_rule_sets.rb b/db/migrate/20210118130305_create_filters.rb similarity index 60% rename from db/migrate/20210118130305_create_rule_sets.rb rename to db/migrate/20210118130305_create_filters.rb index 52b56bb..2925004 100644 --- a/db/migrate/20210118130305_create_rule_sets.rb +++ b/db/migrate/20210118130305_create_filters.rb @@ -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 diff --git a/db/migrate/20210118130313_create_conditions.rb b/db/migrate/20210118130313_create_conditions.rb new file mode 100644 index 0000000..41f7141 --- /dev/null +++ b/db/migrate/20210118130313_create_conditions.rb @@ -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 diff --git a/db/migrate/20210118130313_create_rules.rb b/db/migrate/20210118130313_create_rules.rb deleted file mode 100644 index 467feb9..0000000 --- a/db/migrate/20210118130313_create_rules.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20210118132809_create_actions.rb b/db/migrate/20210118132809_create_actions.rb deleted file mode 100644 index aa73121..0000000 --- a/db/migrate/20210118132809_create_actions.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20210118132809_create_operations.rb b/db/migrate/20210118132809_create_operations.rb new file mode 100644 index 0000000..1e7b8f3 --- /dev/null +++ b/db/migrate/20210118132809_create_operations.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index c7bf7ec..8e9e78b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/db/seeds.rb b/db/seeds.rb index a507d7f..0c6ba81 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -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 }) diff --git a/test/controllers/conditions_controller_test.rb b/test/controllers/conditions_controller_test.rb new file mode 100644 index 0000000..1bc887d --- /dev/null +++ b/test/controllers/conditions_controller_test.rb @@ -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 diff --git a/test/controllers/filters_controller_test.rb b/test/controllers/filters_controller_test.rb new file mode 100644 index 0000000..7074d0b --- /dev/null +++ b/test/controllers/filters_controller_test.rb @@ -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 diff --git a/test/controllers/operations_controller_test.rb b/test/controllers/operations_controller_test.rb new file mode 100644 index 0000000..03251e8 --- /dev/null +++ b/test/controllers/operations_controller_test.rb @@ -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 diff --git a/test/fixtures/actions.yml b/test/fixtures/actions.yml deleted file mode 100644 index eabf490..0000000 --- a/test/fixtures/actions.yml +++ /dev/null @@ -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 diff --git a/test/fixtures/conditions.yml b/test/fixtures/conditions.yml new file mode 100644 index 0000000..2a51859 --- /dev/null +++ b/test/fixtures/conditions.yml @@ -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 diff --git a/test/fixtures/filters.yml b/test/fixtures/filters.yml new file mode 100644 index 0000000..5ed6e91 --- /dev/null +++ b/test/fixtures/filters.yml @@ -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 diff --git a/test/fixtures/operations.yml b/test/fixtures/operations.yml new file mode 100644 index 0000000..03b4dd1 --- /dev/null +++ b/test/fixtures/operations.yml @@ -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 diff --git a/test/fixtures/rule_sets.yml b/test/fixtures/rule_sets.yml deleted file mode 100644 index df07f83..0000000 --- a/test/fixtures/rule_sets.yml +++ /dev/null @@ -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 diff --git a/test/fixtures/rules.yml b/test/fixtures/rules.yml deleted file mode 100644 index 2969a72..0000000 --- a/test/fixtures/rules.yml +++ /dev/null @@ -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 diff --git a/test/models/rule_set_test.rb b/test/models/condition_test.rb similarity index 62% rename from test/models/rule_set_test.rb rename to test/models/condition_test.rb index 616301d..59d3e62 100644 --- a/test/models/rule_set_test.rb +++ b/test/models/condition_test.rb @@ -1,6 +1,6 @@ require "test_helper" -class RuleSetTest < ActiveSupport::TestCase +class ConditionTest < ActiveSupport::TestCase # test "the truth" do # assert true # end diff --git a/test/models/rule_test.rb b/test/models/filter_test.rb similarity index 64% rename from test/models/rule_test.rb rename to test/models/filter_test.rb index ac1654f..f9dee97 100644 --- a/test/models/rule_test.rb +++ b/test/models/filter_test.rb @@ -1,6 +1,6 @@ require "test_helper" -class RuleTest < ActiveSupport::TestCase +class FilterTest < ActiveSupport::TestCase # test "the truth" do # assert true # end diff --git a/test/models/action_test.rb b/test/models/operation_test.rb similarity index 62% rename from test/models/action_test.rb rename to test/models/operation_test.rb index 585c5df..01ad874 100644 --- a/test/models/action_test.rb +++ b/test/models/operation_test.rb @@ -1,6 +1,6 @@ require "test_helper" -class ActionTest < ActiveSupport::TestCase +class OperationTest < ActiveSupport::TestCase # test "the truth" do # assert true # end diff --git a/test/services/rule_set_processor_test.rb b/test/services/filter_processor_test.rb similarity index 57% rename from test/services/rule_set_processor_test.rb rename to test/services/filter_processor_test.rb index c067f6d..b608794 100644 --- a/test/services/rule_set_processor_test.rb +++ b/test/services/filter_processor_test.rb @@ -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 diff --git a/test/system/conditions_test.rb b/test/system/conditions_test.rb new file mode 100644 index 0000000..f994c45 --- /dev/null +++ b/test/system/conditions_test.rb @@ -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 diff --git a/test/system/filters_test.rb b/test/system/filters_test.rb new file mode 100644 index 0000000..b28c086 --- /dev/null +++ b/test/system/filters_test.rb @@ -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 diff --git a/test/system/operations_test.rb b/test/system/operations_test.rb new file mode 100644 index 0000000..d84437c --- /dev/null +++ b/test/system/operations_test.rb @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb index cbe396b..4235528 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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)