Commit aedb4e70 authored by David Rybolovlev's avatar David Rybolovlev :eyeglasses:
Browse files

Remove: mailer at all.

parent 9d566683
Showing with 113 additions and 419 deletions
+113 -419
......@@ -2,7 +2,7 @@
# frozen_string_literal: true
require 'bundler/setup'
require 'konsierge'
require 'konsierge/notifier'
Telegram.bots_config = {
default: {
......
# frozen_string_literal: true
require_relative 'lib/konsierge/version'
require_relative 'lib/konsierge/notifier'
Gem::Specification.new do |spec|
spec.name = 'konsierge-notifier'
spec.version = Konsierge::VERSION.dup
spec.version = Konsierge::Notifier::VERSION
spec.author = 'David Rybolovlev'
spec.email = ['golifox911@gmail.com']
......@@ -27,14 +27,10 @@ Gem::Specification.new do |spec|
spec.add_dependency 'dotenv', '~> 2.7'
# All notifiers
spec.add_runtime_dependency 'dry-configurable', '~> 1.0'
spec.add_dependency 'dry-configurable', '~> 1.0'
# Telegram notifier
spec.add_runtime_dependency 'telegram-bot', '~> 0.16.1'
# Mail notifier
spec.add_runtime_dependency 'faraday', '~> 2.7'
spec.add_runtime_dependency 'oj', '~> 3.11'
spec.add_dependency 'telegram-bot', '~> 0.16.1'
# Development and test dependencies
spec.add_development_dependency 'bundler', '~> 2.0'
......
# frozen_string_literal: true
require 'dry-configurable'
require 'logger'
require 'telegram/bot'
module Konsierge
module Notifier
class Notifier
extend ::Dry::Configurable
setting :app_name
setting :chat_id, default: ENV.fetch('TELEGRAM_CHANNEL_ID', nil)
setting :bot_enabled, default: ENV.fetch('TELEGRAM_BOT_ENABLED', false)
setting :message_template_path, default: File.expand_path('notifier/template.html.erb', __dir__)
setting :error_template_path, default: File.expand_path('notifier/error_template.html.erb', __dir__)
setting :logger, default: Logger.new($stdout)
setting :parse_mode, default: 'HTML'
VERSION = '0.1.2'
TELEGRAM_CHAT_ID_NOT_SET = 'Telegram Chat ID is not set'
TELEGRAM_BOT_ERROR = 'TelegramBot Error: %s'
TELEGRAM_BOT_DISABLED = 'TelegramBot is disabled! Message not sent.'
PATH_OR_CHAT_ID_NOT_PROVIDED = 'Please, provide path and chat_id'
class << self
def send_message(text, chat_id: nil)
send_with_message_template(
path: config.message_template_path,
chat_id: chat_id,
app_name: config.app_name,
message: text
)
end
def report_error(error, source: nil, chat_id: nil)
send_with_message_template(
path: config.error_template_path,
chat_id: chat_id,
app_name: config.app_name,
error: error,
source: source
)
end
alias send_error report_error
private
def send_with_message_template(**options)
return log_disabled_bot_warning unless bot_enabled?
opts = sanitize_template_options!(options)
chat_id = check_chat_id!(opts[:chat_id])
template = ERB.new(File.read(opts[:path]))
template_params = opts.except(:path, :chat_id)
message = template.result_with_hash(template_params)
send_to_telegram(message, chat_id: chat_id)
end
def send_to_telegram(text, chat_id:)
::Telegram.bot.send_message(chat_id: chat_id, text: text, parse_mode: config.parse_mode)
true
rescue StandardError => e
config.logger.error(TELEGRAM_BOT_ERROR % e.message)
false
end
def sanitize_template_options!(options)
opts = options.dup
raise ArgumentError, PATH_OR_CHAT_ID_NOT_PROVIDED unless %i[path chat_id].all? { |key| opts.key?(key) }
opts
end
def check_chat_id!(chat_id)
chat_id ||= config.chat_id
raise TELEGRAM_CHAT_ID_NOT_SET unless chat_id
chat_id
end
def bot_enabled?
config.bot_enabled
end
def log_disabled_bot_warning
config.logger.warn(TELEGRAM_BOT_DISABLED)
false
end
end
end
end
require 'dry-configurable'
require 'active_support/all'
require 'konsierge/notifier/telegram'
require 'konsierge/notifier/mailer'
# frozen_string_literal: true
require 'faraday'
module Konsierge
module Notifier
class Mailer
class Error < StandardError; end
class ConfigError < StandardError; end
extend Dry::Configurable
setting :smtp_mailer_host, default: ENV.fetch('KONSIERGE_SMTP_MAILER_HOST', nil), reader: true
setting :smtp_mailer_auth_key, default: ENV.fetch('KONSIERGE_SMTP_MAILER_AUTH_KEY', nil), reader: true
setting :endpoint, default: '/v1/message', reader: true
setting :temp_dir, default: "tmp/konsierge/notifier/mailer/#{SecureRandom.uuid}/", reader: true
setting :logger, default: ::Logger.new($stdout)
def initialize(*, **) end
def configure
yield(self.class.config) if block_given?
end
def config
self.class.config
end
def deliver!(mail)
with_error_handle do
with_temp_file_deletion do
payload = build_payload(mail)
response = connection.post(config.endpoint, payload, {Accept: 'application/json'})
raise Error, response.body unless response.success?
end
end
end
private
def connection
check_config!
@connection ||= Faraday.new(url: config.smtp_mailer_host) do |conn|
conn.request :authorization, 'Basic', config.smtp_mailer_auth_key
conn.request :multipart
conn.request :url_encoded
conn.adapter :net_http
end
end
def check_config!
return unless !config.smtp_mailer_host || !config.smtp_mailer_auth_key || !config.endpoint
config.logger.debug("Current config: #{config.to_h.except(:logger)}")
raise ConfigError, 'Please provide config in initializers/konsierge/notifier/mailer.rb'
end
def with_error_handle
yield
rescue ConfigError => e
raise e
rescue StandardError => e
raise Error, e.message
end
def with_temp_file_deletion
yield
ensure
FileUtils.rm_rf(config.temp_dir) if File.directory?(config.temp_dir)
end
def build_payload(mail)
{
subject: mail.subject,
body: determine_body(mail),
recipients: mail.to,
cc: mail.cc,
bcc: mail.bcc,
attachments: process_attachments(mail.attachments)
}
end
def determine_body(mail)
if mail.text_part.present?
mail.text_part.decoded
elsif mail.html_part.present?
mail.html_part.decoded
else
mail.body.decoded
end
end
def process_attachments(attachments)
return [] unless attachments.present?
FileUtils.mkdir_p(config.temp_dir)
attachments.map do |attachment|
tmp_path = File.join(config.temp_dir, attachment.filename)
File.binwrite(tmp_path, attachment.read)
Faraday::Multipart::FilePart.new(File.open(tmp_path), 'multipart/form-data')
end
end
end
end
end
# frozen_string_literal: true
require 'logger'
require 'telegram/bot'
module Konsierge
module Notifier
class Telegram
extend Dry::Configurable
setting :app_name
setting :chat_id, default: ENV.fetch('TELEGRAM_CHANNEL_ID', nil)
setting :bot_enabled, default: ENV.fetch('TELEGRAM_BOT_ENABLED', false)
setting :message_template_path, default: File.expand_path('telegram/template.html.erb', __dir__)
setting :error_template_path, default: File.expand_path('telegram/error_template.html.erb', __dir__)
setting :logger, default: Logger.new($stdout)
setting :parse_mode, default: 'HTML'
TELEGRAM_CHAT_ID_NOT_SET = 'Telegram Chat ID is not set'
TELEGRAM_BOT_ERROR = 'TelegramBot Error: %s'
TELEGRAM_BOT_DISABLED = 'TelegramBot is disabled! Message not sent.'
PATH_OR_CHAT_ID_NOT_PROVIDED = 'Please, provide path and chat_id'
class << self
def send_message(text, chat_id: nil)
send_with_message_template(
path: config.message_template_path,
chat_id: chat_id,
app_name: config.app_name,
message: text
)
end
def report_error(error, source: nil, chat_id: nil)
send_with_message_template(
path: config.error_template_path,
chat_id: chat_id,
app_name: config.app_name,
error: error,
source: source
)
end
alias send_error report_error
private
def send_with_message_template(**options)
return log_disabled_bot_warning unless bot_enabled?
opts = sanitize_template_options!(options)
chat_id = check_chat_id!(opts[:chat_id])
template = ERB.new(File.read(opts[:path]))
template_params = opts.except(:path, :chat_id)
message = template.result_with_hash(template_params)
send_to_telegram(message, chat_id: chat_id)
end
def send_to_telegram(text, chat_id:)
::Telegram.bot.send_message(chat_id: chat_id, text: text, parse_mode: config.parse_mode)
true
rescue StandardError => e
config.logger.error(TELEGRAM_BOT_ERROR % e.message)
false
end
def sanitize_template_options!(options)
opts = options.dup
raise ArgumentError, PATH_OR_CHAT_ID_NOT_PROVIDED unless %i[path chat_id].all? { |key| opts.key?(key) }
opts
end
def check_chat_id!(chat_id)
chat_id ||= config.chat_id
raise TELEGRAM_CHAT_ID_NOT_SET unless chat_id
chat_id
end
def bot_enabled?
config.bot_enabled
end
def log_disabled_bot_warning
config.logger.warn(TELEGRAM_BOT_DISABLED)
false
end
end
end
end
end
# frozen_string_literal: true
module Konsierge
VERSION = '0.1.0'
end
# frozen_string_literal: true
RSpec.describe Konsierge::Notifier::Mailer do
let(:mailer) { described_class.new }
let(:attachments) { [] }
let(:mail_text_part) { double(Mail::Part, decoded: 'This is a text part') }
let(:mail_html_part) { double(Mail::Part, decoded: 'This is a html part') }
let(:mail_body) { double(Mail::Body, decoded: 'This is a mail body') }
let(:mail) do
double(Mail,
subject: 'Test Subject',
to: ['recipient@example.com'],
cc: ['cc@example.com'],
bcc: ['bcc@example.com'],
text_part: mail_text_part,
html_part: mail_html_part,
attachments: attachments,
body: mail_body)
end
let(:response) { instance_double(Faraday::Response, success?: true, body: 'Success') }
let(:connection) { instance_double(Faraday::Connection) }
let!(:payload) { mailer.send(:build_payload, mail) }
before do
mailer.config.update({smtp_mailer_host: 'smtp.example.com', smtp_mailer_auth_key: 'secret', endpoint: '/mail'})
allow(Faraday).to receive(:new).and_return(connection)
allow(connection).to receive(:post).with(mailer.config.endpoint, payload, anything).and_return(response)
end
describe '#configure' do
subject do
mailer.configure do |config|
config.smtp_mailer_host = new_host
config.smtp_mailer_auth_key = new_auth_key
end
end
let(:new_host) { 'new_host' }
let(:new_auth_key) { 'new_auth_key' }
context 'when method called on cls' do
let(:mailer) { described_class }
let(:payload) { nil } # skip create payload callback
it 'change configuration' do
subject
expect(mailer.config).to have_attributes(smtp_mailer_host: new_host, smtp_mailer_auth_key: new_auth_key)
end
end
context 'when method called on instance' do
it 'change configuration' do
subject
expect(mailer.config).to have_attributes(smtp_mailer_host: new_host, smtp_mailer_auth_key: new_auth_key)
end
end
end
describe '#deliver!' do
subject { mailer.deliver!(mail) }
context 'when all is success' do
it 'sends an email successfully' do
subject
expect(connection).to have_received(:post).once
end
it { will_be_expected.not_to raise_error }
end
context 'when text part presented' do
it { expect(payload).to include(body: mail_text_part.decoded) }
end
context 'when html part presented' do
let(:mail_text_part) { double(Mail::Part, decoded: nil, present?: false) }
it { expect(payload).to include(body: mail_html_part.decoded) }
end
context 'when text and html parts not presented' do
let(:mail_text_part) { double(Mail::Part, decoded: nil, present?: false) }
let(:mail_html_part) { double(Mail::Part, decoded: nil, present?: false) }
it { expect(payload).to include(body: mail_body.decoded) }
end
context 'when attachments presented' do
let(:attachment) { instance_double(Mail::Part, filename: 'some_file', read: 'qwe') }
let(:attachments) { double(Array, map: [attachment]) }
before do
allow(mail.attachments).to receive(:present?).and_return(true)
allow(FileUtils).to receive(:mkdir_p).with(mailer.config.temp_dir).and_call_original
allow(FileUtils).to receive(:rm_rf).with(mailer.config.temp_dir).and_call_original
end
it 'create temp dir before mail sending' do
subject
expect(FileUtils).to have_received(:mkdir_p).with(mailer.config.temp_dir).once
end
it 'delete temp dir after mail sending' do
subject
expect(FileUtils).to have_received(:rm_rf).with(mailer.config.temp_dir).once
end
end
context 'when the response is unsuccessful' do
let(:response) { instance_double(Faraday::Response, success?: false, body: 'Error') }
it { will_be_expected.to raise_error(Konsierge::Notifier::Mailer::Error) }
end
context 'when an exception occurs while mail sending' do
before do
allow(connection).to receive(:post).and_raise(StandardError, 'Connection failed')
end
it { will_be_expected.to raise_error(Konsierge::Notifier::Mailer::Error, 'Connection failed') }
end
context 'when an exception occurs without config' do
[{smtp_mailer_host: nil}, {smtp_mailer_auth_key: nil}, {endpoint: nil}].each do |invalid_option|
it 'raises ConfigError' do
mailer.config.update(invalid_option)
will_be_expected.to raise_error(Konsierge::Notifier::Mailer::ConfigError)
end
end
end
end
describe '#process_attachments' do
subject { mailer.send(:process_attachments, attachments) }
let(:mailer) { described_class.new }
let(:attachment) { instance_double(Mail::Part, filename: 'some_file.txt', read: 'content of file') }
let(:attachments) { [attachment] }
before do
allow(mailer.config).to receive(:temp_dir).and_return('/tmp/fake_dir')
allow(FileUtils).to receive(:mkdir_p).with('/tmp/fake_dir').and_call_original
allow(File).to receive(:binwrite).and_call_original
allow(Faraday::Multipart::FilePart).to receive(:new).and_return('FaradayFilePart')
end
after do
FileUtils.remove_dir('tmp', true)
end
it 'write files into temp directory' do
expect(File).to receive(:binwrite).with('/tmp/fake_dir/some_file.txt', 'content of file').once
subject
end
it 'returns array of Faraday::Multipart::FilePart' do
expect(subject.first).to eq('FaradayFilePart')
end
end
describe '#connection' do
subject(:connection) { mailer.send(:connection) }
let(:expected_handlers) do
[Faraday::Request::Authorization, Faraday::Multipart::Middleware, Faraday::Request::UrlEncoded]
end
let(:expected_adapter) { Faraday::RackBuilder::Handler }
before do
mailer.config.smtp_mailer_host = 'smtp.example.com'
mailer.config.smtp_mailer_auth_key = 'secret'
mailer.config.endpoint = 'mail'
end
it { is_expected.to be_a(Faraday::Connection) }
it { expect(subject.builder.handlers).to eq(expected_handlers) }
it { expect(subject.builder.adapter).to be_a(expected_adapter) }
end
end
# frozen_string_literal: true
RSpec.describe Konsierge::Notifier, '.version' do
it 'has a version number' do
expect(described_class::VERSION).not_to be_nil
end
end
# frozen_string_literal: true
RSpec.describe Konsierge::Notifier::Telegram do
RSpec.describe Konsierge::Notifier do
let(:notifier) { described_class }
let(:telegram) { double(Telegram) }
......@@ -26,8 +26,8 @@ RSpec.describe Konsierge::Notifier::Telegram do
:message_template_path, :error_template_path, :logger)
end
it { expect(subject.message_template_path).to include('telegram/template.html.erb') }
it { expect(subject.error_template_path).to include('telegram/error_template.html.erb') }
it { expect(subject.message_template_path).to include('notifier/template.html.erb') }
it { expect(subject.error_template_path).to include('notifier/error_template.html.erb') }
it { expect(subject.logger).to be_a(Logger) }
end
......
......@@ -3,9 +3,10 @@
require 'dry/configurable/test_interface'
module Konsierge
module Notifier
class Telegram
enable_test_interface
end
class Notifier
extend Dry::Configurable::TestInterface
# class << self
# enable_test_interface
# end
end
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment