Merge pull request #8404 from mfo/US/force-email-delivery

Us/force important email delivery
This commit is contained in:
mfo 2023-01-11 18:00:35 +01:00 committed by GitHub
commit 1a6b693312
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 166 additions and 1 deletions

View file

@ -0,0 +1,4 @@
module Manager
class SafeMailersController < Manager::ApplicationController
end
end

View file

@ -0,0 +1,43 @@
require "administrate/base_dashboard"
class SafeMailerDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
created_at: Field::DateTime,
updated_at: Field::DateTime,
forced_delivery_method: Field::Enum
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = [
:id,
:created_at,
:updated_at,
:forced_delivery_method
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = [
:created_at,
:updated_at,
:forced_delivery_method
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [
:forced_delivery_method
].freeze
end

View file

@ -14,6 +14,7 @@
#
# Be sure to restart your server when you modify this file.
class BalancerDeliveryMethod
FORCE_DELIVERY_METHOD_HEADER = 'X-deliver-with'
# Allows configuring the random number generator used for selecting a delivery method,
# mostly for testing purposes.
mattr_accessor :random, default: Random.new
@ -39,7 +40,13 @@ class BalancerDeliveryMethod
private
def force_delivery_method?(mail)
@delivery_methods.keys.map(&:to_s).include?(mail[FORCE_DELIVERY_METHOD_HEADER]&.value)
end
def delivery_method(mail)
return mail[FORCE_DELIVERY_METHOD_HEADER].value.to_sym if force_delivery_method?(mail)
@delivery_methods
.flat_map { |delivery_method, weight| [delivery_method] * weight }
.sample(random: self.class.random)

View file

@ -5,6 +5,7 @@ class DeviseUserMailer < Devise::Mailer
include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
include MailerMonitoringConcern
layout 'mailers/layout'
before_action :add_delivery_method, if: :forced_delivery?
def template_paths
['devise_mailer']
@ -16,4 +17,12 @@ class DeviseUserMailer < Devise::Mailer
@prefill_token = opts[:prefill_token]
super
end
def add_delivery_method
headers[BalancerDeliveryMethod::FORCE_DELIVERY_METHOD_HEADER] = SafeMailer.forced_delivery_method
end
def forced_delivery?
SafeMailer.forced_delivery_method.present?
end
end

20
app/models/safe_mailer.rb Normal file
View file

@ -0,0 +1,20 @@
# == Schema Information
#
# Table name: safe_mailers
#
# id :bigint not null, primary key
# forced_delivery_method :string
# created_at :datetime not null
# updated_at :datetime not null
#
class SafeMailer < ApplicationRecord
before_create do
raise if SafeMailer.count == 1
end
enum forced_delivery_method: Rails.application.config.action_mailer&.balancer_settings&.keys&.map(&:to_s) || []
def self.forced_delivery_method
first&.forced_delivery_method
end
end

View file

@ -1,4 +1,5 @@
require "active_support/core_ext/integer/time"
require Rails.root.join("app/lib/balancer_delivery_method")
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
@ -76,7 +77,12 @@ Rails.application.configure do
config.assets.raise_runtime_errors = true
# Action Mailer settings
config.action_mailer.delivery_method = ENV['HELO_ENABLED'] == 'enabled' ? :helo : :letter_opener
ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
config.action_mailer.balancer_settings = {
helo: ENV['HELO_ENABLED'] == 'enabled' ? 100 : 0,
letter_opener: ENV['HELO_ENABLED'] == 'enabled' ? 0 : 100
}
config.action_mailer.delivery_method = :balancer
config.action_mailer.default_url_options = { host: ENV.fetch("APP_HOST") }
config.action_mailer.asset_host = "http://" + ENV.fetch("APP_HOST")

View file

@ -71,6 +71,7 @@ Rails.application.routes.draw do
resources :outdated_procedures, only: [:index] do
patch :bulk_update, on: :collection
end
resources :safe_mailers, only: [:index, :edit, :update, :destroy]
post 'demandes/create_administrateur'
post 'demandes/refuse_administrateur'

View file

@ -0,0 +1,9 @@
class CreateSafeMailers < ActiveRecord::Migration[6.1]
def change
create_table :safe_mailers do |t|
t.string :forced_delivery_method
t.timestamps
end
end
end

View file

@ -785,6 +785,12 @@ ActiveRecord::Schema.define(version: 2023_01_11_094621) do
t.index ["procedure_id"], name: "index_refused_mails_on_procedure_id"
end
create_table "safe_mailers", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.string "forced_delivery_method"
t.datetime "updated_at", precision: 6, null: false
end
create_table "services", force: :cascade do |t|
t.bigint "administrateur_id"
t.text "adresse"

View file

@ -5,6 +5,20 @@ RSpec.describe BalancerDeliveryMethod do
end
end
class ImportantEmail < ApplicationMailer
before_action :set_x_deliver_with
def greet(name)
mail(to: "smtp_to", from: "smtp_from", body: "Hello #{name}")
end
private
def set_x_deliver_with
headers['X-deliver-with'] = :mock_smtp
end
end
class TestMail
def self.deliveries
@deliveries ||= []
@ -46,6 +60,7 @@ RSpec.describe BalancerDeliveryMethod do
ActionMailer::Base.add_delivery_method :balancer, BalancerDeliveryMethod
ExampleMailer.delivery_method = :balancer
ImportantEmail.delivery_method = :balancer
end
context 'when a single delivery method is provided' do
@ -101,6 +116,35 @@ RSpec.describe BalancerDeliveryMethod do
end
end
context 'SafeMailer.important_email_use_delivery_method is present' do
before do
allow(SafeMailer).to receive(:important_email_use_delivery_method).and_return(delivery_method)
ActionMailer::Base.balancer_settings = { mock_smtp: 10, mock_sendmail: 5 }
rng_sequence = [3, 14, 1]
BalancerDeliveryMethod.random = FixedSequence.new(rng_sequence)
end
after do
BalancerDeliveryMethod.random = Random.new
end
context 'known delivery_method & email is important' do
let(:delivery_method) { :mock_smtp }
it 'sends emails given the forced_delivery_method' do
mail1 = ImportantEmail.greet('Lucia').deliver_now
expect(mail1).to have_been_delivered_using(MockSmtp)
mail2 = ImportantEmail.greet('Damian').deliver_now
expect(mail2).to have_been_delivered_using(MockSmtp)
mail3 = ImportantEmail.greet('Rahwa').deliver_now
expect(mail3).to have_been_delivered_using(MockSmtp)
end
end
end
# Helpers
def have_been_delivered_using(delivery_class)

View file

@ -0,0 +1,16 @@
RSpec.describe DeviseUserMailer, type: :mailer do
let(:user) { create(:user) }
let(:token) { SecureRandom.hex }
describe '.confirmation_instructions' do
context 'without SafeMailer configured' do
subject { described_class.confirmation_instructions(user, token, opts = {}) }
it { expect(subject[BalancerDeliveryMethod::FORCE_DELIVERY_METHOD_HEADER]&.value).to eq(nil) }
end
context 'with SafeMailer configured' do
let(:forced_delivery_method) { :kikoo }
before { allow(SafeMailer).to receive(:forced_delivery_method).and_return(forced_delivery_method) }
subject { described_class.confirmation_instructions(user, token, opts = {}) }
it { expect(subject[BalancerDeliveryMethod::FORCE_DELIVERY_METHOD_HEADER]&.value).to eq(forced_delivery_method.to_s) }
end
end
end