diff --git a/config/initializers/okcomputer.rb b/config/initializers/okcomputer.rb index b61c407a..33108522 100644 --- a/config/initializers/okcomputer.rb +++ b/config/initializers/okcomputer.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'net/smtp' + # Health check configuration OkComputer.logger = Rails.logger @@ -18,11 +20,49 @@ def check end end +# rubocop:disable Metrics/MethodLength, Metrics/AbcSize +class MailConnectivityCheck < OkComputer::Check + + # Check that the mail password is set + def check + settings = ActionMailer::Base.smtp_settings + begin + Net::SMTP.start( + settings[:address], + settings[:port], + settings[:domain], + settings[:user_name], + settings[:password], + settings[:authentication], + tls: true + ) { mark_message 'Connection for smtp successful' } + rescue Net::SMTPAuthenticationError => e + mark_failure + Rails.logger.warn "SMTP authentication error: #{e}" + mark_message 'SMTP Error: Authentication failed. Check logs for more details' + rescue Net::SMTPServerBusy, Net::SMTPSyntaxError, Net::SMTPFatalError, Net::SMTPUnknownError => e + mark_failure + Rails.logger.warn "SMTP Error: #{e}" + mark_message 'SMTP error. Check logs for more details' + rescue IOError, Net::ReadTimeout => e + mark_failure + Rails.logger.warn "SMTP Timeout: #{e}" + mark_message 'SMTP Connection error: Timeout. Check logs for more details' + rescue StandardError => e + # Catch any other unexpected errors + mark_failure + Rails.logger.warn "SMTP standard error: #{e}" + mark_message 'SMTP ERROR: Could not connect. Check logs for more details' + end + end +end +# rubocop:enable Metrics/MethodLength, Metrics/AbcSize + # Ensure Alma API is working. OkComputer::Registry.register 'alma-patron-lookup', AlmaPatronCheck.new # Ensure database migrations have been run. OkComputer::Registry.register 'database-migrations', OkComputer::ActiveRecordMigrationsCheck.new -# Ensure connectivity to the mail system. -OkComputer::Registry.register 'action-mailer', OkComputer::ActionMailerCheck.new +# Ensure SMTP can connect +OkComputer::Registry.register 'mail-connectivity', MailConnectivityCheck.new if ActionMailer::Base.delivery_method == :smtp diff --git a/spec/request/mail_connectivity_check_spec.rb b/spec/request/mail_connectivity_check_spec.rb new file mode 100644 index 00000000..8e42f4b3 --- /dev/null +++ b/spec/request/mail_connectivity_check_spec.rb @@ -0,0 +1,111 @@ +require 'rails_helper' +require 'net/smtp' + +RSpec.describe MailConnectivityCheck do + subject(:check) { described_class.new } + + let(:smtp_settings) do + { + address: 'smtp.example.com', + port: 587, + domain: 'example.com', + user_name: 'user', + password: 'password', + authentication: 'plain', + tls: true + } + end + + before do + allow(ActionMailer::Base).to receive(:smtp_settings).and_return(smtp_settings) + allow(Rails.logger).to receive(:warn) + end + + describe '#check' do + context 'when SMTP connection succeeds' do + before do + allow(Net::SMTP).to receive(:start).and_yield + end + + it 'marks the check as successful' do + check.check + + expect(check.success?).to be(true) + expect(check.message).to eq('Connection for smtp successful') + end + end + + context 'when authentication fails' do + before do + allow(Net::SMTP).to receive(:start) + .and_raise(Net::SMTPAuthenticationError.new('auth failed')) + end + + it 'marks failure and logs authentication error' do + check.check + + expect(check.success?).to be(false) + expect(check.message) + .to eq('SMTP Error: Authentication failed. Check logs for more details') + expect(Rails.logger) + .to have_received(:warn).with(/SMTP authentication error/) + end + end + + context 'when SMTP protocol errors occur' do + [ + Net::SMTPServerBusy, + Net::SMTPSyntaxError, + Net::SMTPFatalError, + Net::SMTPUnknownError + ].each do |error_class| + it "handles #{error_class.name}" do + allow(Net::SMTP).to receive(:start) + .and_raise(error_class.new('smtp error')) + + check.check + + expect(check.success?).to be(false) + expect(check.message) + .to eq('SMTP error. Check logs for more details') + expect(Rails.logger) + .to have_received(:warn).with(/SMTP Error/) + end + end + end + + context 'when a timeout occurs' do + [IOError, Net::ReadTimeout].each do |error_class| + it "handles #{error_class.name}" do + allow(Net::SMTP).to receive(:start) + .and_raise(error_class.new('timeout')) + + check.check + + expect(check.success?).to be(false) + expect(check.message) + .to eq('SMTP Connection error: Timeout. Check logs for more details') + expect(Rails.logger) + .to have_received(:warn).with(/SMTP Timeout/) + end + end + end + + context 'when an unexpected error occurs' do + before do + allow(Net::SMTP).to receive(:start) + .and_raise(StandardError.new('failed')) + end + + it 'marks failure and logs standard error' do + check.check + + expect(check.success?).to be(false) + expect(check.message) + .to eq('SMTP ERROR: Could not connect. Check logs for more details') + expect(Rails.logger) + .to have_received(:warn).with(/SMTP standard error/) + end + end + end +end diff --git a/spec/request/okcomputer_spec.rb b/spec/request/okcomputer_spec.rb index fbf8d418..1f294365 100644 --- a/spec/request/okcomputer_spec.rb +++ b/spec/request/okcomputer_spec.rb @@ -1,29 +1,61 @@ require 'rails_helper' RSpec.describe 'OKComputer', type: :request do - before { allow(Alma::User).to receive(:find).and_return(Alma::User.new) } + before do + allow(Alma::User).to receive(:find).and_return(Alma::User.new) + end it 'is mounted at /okcomputer' do get '/okcomputer' expect(response).to have_http_status :ok end - it 'returns all checks to /health' do - get '/health' - expect(response.parsed_body.keys).to match_array %w[ - action-mailer - alma-patron-lookup - default - database - database-migrations - ] - pending 'https://github.com/emmahsax/okcomputer/pull/21' - expect(response).to have_http_status :ok + context 'without SMTP enabled' do + before do + allow(ActionMailer::Base).to receive(:delivery_method).and_return(:test) + + OkComputer::Registry.instance_variable_set(:@checks, {}) + load Rails.root.join('config/initializers/okcomputer.rb') + end + + it 'returns checks to /health' do + get '/health' + expect(response.parsed_body.keys).to match_array %w[ + default + database + alma-patron-lookup + database-migrations + ] + end end - it 'fails when Alma lookups fail' do - expect(Alma::User).to receive(:find).and_raise('Uh oh!') - get '/health' - expect(response).not_to have_http_status :ok + context 'with SMTP enabled' do + before do + allow(ActionMailer::Base).to receive(:delivery_method).and_return(:smtp) + allow(Net::SMTP).to receive(:start) + + OkComputer::Registry.instance_variable_set(:@checks, {}) + load Rails.root.join('config/initializers/okcomputer.rb') + end + + it 'returns all checks to /health' do + get '/health' + expect(response.parsed_body.keys).to match_array %w[ + default + database + alma-patron-lookup + database-migrations + mail-connectivity + ] + end + end + + context 'when Alma lookups fail' do + it 'returns a non-200 response' do + expect(Alma::User).to receive(:find).and_raise('Uh oh!') + get '/health' + expect(response).not_to have_http_status :ok + end end + end