diff options
-rw-r--r-- | app/controllers/health_checks_controller.rb | 16 | ||||
-rw-r--r-- | app/helpers/health_checks_helper.rb | 8 | ||||
-rw-r--r-- | app/views/health_checks/index.html.erb | 12 | ||||
-rw-r--r-- | config/application.rb | 1 | ||||
-rw-r--r-- | config/initializers/health_checks.rb | 23 | ||||
-rw-r--r-- | config/routes.rb | 2 | ||||
-rw-r--r-- | lib/health_checks/checks/days_ago_check.rb | 28 | ||||
-rw-r--r-- | lib/health_checks/health_checkable.rb | 28 | ||||
-rw-r--r-- | lib/health_checks/health_checks.rb | 37 | ||||
-rw-r--r-- | spec/controllers/health_checks_controller_spec.rb | 30 | ||||
-rw-r--r-- | spec/helpers/health_checks_helper_spec.rb | 15 | ||||
-rw-r--r-- | spec/lib/health_checks/checks/days_ago_check_spec.rb | 66 | ||||
-rw-r--r-- | spec/lib/health_checks/health_checkable_spec.rb | 128 | ||||
-rw-r--r-- | spec/lib/health_checks/health_checks_spec.rb | 77 |
14 files changed, 471 insertions, 0 deletions
diff --git a/app/controllers/health_checks_controller.rb b/app/controllers/health_checks_controller.rb new file mode 100644 index 000000000..473a1aacc --- /dev/null +++ b/app/controllers/health_checks_controller.rb @@ -0,0 +1,16 @@ +class HealthChecksController < ApplicationController + + def index + @health_checks = HealthChecks.all + + respond_to do |format| + if HealthChecks.ok? + format.html { render :action => :index, :layout => false } + else + format.html { render :action => :index, :layout => false , :status => 500 } + end + end + + end + +end diff --git a/app/helpers/health_checks_helper.rb b/app/helpers/health_checks_helper.rb new file mode 100644 index 000000000..f5769a9ba --- /dev/null +++ b/app/helpers/health_checks_helper.rb @@ -0,0 +1,8 @@ +module HealthChecksHelper + + def check_status(check) + style = check.ok? ? {} : "color: red" + content_tag(:b, check.message, :style => style) + end + +end diff --git a/app/views/health_checks/index.html.erb b/app/views/health_checks/index.html.erb new file mode 100644 index 000000000..67b1050a9 --- /dev/null +++ b/app/views/health_checks/index.html.erb @@ -0,0 +1,12 @@ +<h1>Health Checks</h1> + +<div class="checks"> + <% @health_checks.each do |check| %> + <div class="check"> + <ul> + <li>Message: <%= check_status(check) %></li> + <li>OK? <%= check.ok? %></li> + </ul> + </div> + <% end %> +</div> diff --git a/config/application.rb b/config/application.rb index fc8e0059e..a514daf3a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -69,6 +69,7 @@ module Alaveteli config.autoload_paths << "#{Rails.root.to_s}/lib/mail_handler" config.autoload_paths << "#{Rails.root.to_s}/lib/attachment_to_html" + config.autoload_paths << "#{Rails.root.to_s}/lib/health_checks" # See Rails::Configuration for more options ENV['RECAPTCHA_PUBLIC_KEY'] = ::AlaveteliConfiguration::recaptcha_public_key diff --git a/config/initializers/health_checks.rb b/config/initializers/health_checks.rb new file mode 100644 index 000000000..7fd1d3dda --- /dev/null +++ b/config/initializers/health_checks.rb @@ -0,0 +1,23 @@ +Rails.application.config.after_initialize do + user_last_created = HealthChecks::Checks::DaysAgoCheck.new( + :failure_message => _('The last user was created over a day ago'), + :success_message => _('The last user was created in the last day')) do + User.last.created_at + end + + incoming_message_last_created = HealthChecks::Checks::DaysAgoCheck.new( + :failure_message => _('The last incoming message was created over a day ago'), + :success_message => _('The last incoming message was created in the last day')) do + IncomingMessage.last.created_at + end + + outgoing_message_last_created = HealthChecks::Checks::DaysAgoCheck.new( + :failure_message => _('The last outgoing message was created over a day ago'), + :success_message => _('The last outgoing message was created in the last day')) do + OutgoingMessage.last.created_at + end + + HealthChecks.add user_last_created + HealthChecks.add incoming_message_last_created + HealthChecks.add outgoing_message_last_created +end diff --git a/config/routes.rb b/config/routes.rb index f557e681b..84ec86792 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -61,6 +61,8 @@ Alaveteli::Application.routes.draw do match '/request/:url_title/download' => 'request#download_entire_request', :as => :download_entire_request #### + resources :health_checks, :only => [:index] + resources :request, :only => [] do resource :report, :only => [:new, :create] end diff --git a/lib/health_checks/checks/days_ago_check.rb b/lib/health_checks/checks/days_ago_check.rb new file mode 100644 index 000000000..793fff586 --- /dev/null +++ b/lib/health_checks/checks/days_ago_check.rb @@ -0,0 +1,28 @@ +module HealthChecks + module Checks + class DaysAgoCheck + include HealthChecks::HealthCheckable + + attr_reader :days, :subject + + def initialize(args = {}, &block) + @days = args.fetch(:days) { 1 } + @subject = block + super(args) + end + + def failure_message + "#{ super }: #{ subject.call }" + end + + def success_message + "#{ super }: #{ subject.call }" + end + + def check + subject.call >= days.days.ago + end + + end + end +end diff --git a/lib/health_checks/health_checkable.rb b/lib/health_checks/health_checkable.rb new file mode 100644 index 000000000..5d674ca32 --- /dev/null +++ b/lib/health_checks/health_checkable.rb @@ -0,0 +1,28 @@ +module HealthChecks + module HealthCheckable + + attr_accessor :failure_message, :success_message + + def initialize(args = {}) + self.failure_message = args.fetch(:failure_message) { _('Failed') } + self.success_message = args.fetch(:success_message) { _('Success') } + end + + def name + self.class.to_s + end + + def check + raise NotImplementedError + end + + def ok? + check ? true : false + end + + def message + ok? ? success_message : failure_message + end + + end +end diff --git a/lib/health_checks/health_checks.rb b/lib/health_checks/health_checks.rb new file mode 100644 index 000000000..6f0c9de8e --- /dev/null +++ b/lib/health_checks/health_checks.rb @@ -0,0 +1,37 @@ +require 'health_checkable' + +Dir[File.dirname(__FILE__) + '/checks/*.rb'].each do |file| + require file +end + +module HealthChecks + extend self + + def all + @checks ||= [] + end + + def add(check) + if assert_valid_check(check) + all << check + check + else + false + end + end + + def each(&block) + all.each(&block) + end + + def ok? + all.all? { |check| check.ok? } + end + + private + + def assert_valid_check(check) + check.respond_to?(:check) + end + +end diff --git a/spec/controllers/health_checks_controller_spec.rb b/spec/controllers/health_checks_controller_spec.rb new file mode 100644 index 000000000..f7ad6d6a4 --- /dev/null +++ b/spec/controllers/health_checks_controller_spec.rb @@ -0,0 +1,30 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe HealthChecksController do + + describe :index do + + describe :index do + + it 'returns a 200 if all health checks pass' do + HealthChecks.stub(:ok? => true) + get :index + expect(response.status).to eq(200) + end + + it 'returns a 500 if the health check fails' do + HealthChecks.stub(:ok? => false) + get :index + expect(response.status).to eq(500) + end + + it 'does not render a layout' do + get :index + expect(response).to render_template(:layout => false) + end + + end + + end + +end diff --git a/spec/helpers/health_checks_helper_spec.rb b/spec/helpers/health_checks_helper_spec.rb new file mode 100644 index 000000000..7d4083da5 --- /dev/null +++ b/spec/helpers/health_checks_helper_spec.rb @@ -0,0 +1,15 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe HealthChecksHelper do + include HealthChecksHelper + + describe :check_status do + + it 'warns that the check is failing' do + check = double(:message => 'Failed', :ok? => false) + expect(check_status(check)).to include('red') + end + + end + +end diff --git a/spec/lib/health_checks/checks/days_ago_check_spec.rb b/spec/lib/health_checks/checks/days_ago_check_spec.rb new file mode 100644 index 000000000..33b4642cd --- /dev/null +++ b/spec/lib/health_checks/checks/days_ago_check_spec.rb @@ -0,0 +1,66 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') + +describe HealthChecks::Checks::DaysAgoCheck do + include HealthChecks::Checks + + it { should be_kind_of(HealthChecks::HealthCheckable) } + + it 'defaults to comparing to one day ago' do + check = HealthChecks::Checks::DaysAgoCheck.new + expect(check.days).to eq(1) + end + + it 'accepts a custom number of days' do + check = HealthChecks::Checks::DaysAgoCheck.new(:days => 4) + expect(check.days).to eq(4) + end + + describe :check do + + it 'is successful if the subject is in the last day' do + check = HealthChecks::Checks::DaysAgoCheck.new { Time.now } + expect(check.check).to be_true + end + + it 'fails if the subject is over a day ago' do + check = HealthChecks::Checks::DaysAgoCheck.new { 2.days.ago } + expect(check.check).to be_false + end + + end + + describe :failure_message do + + it 'includes the check subject in the default message' do + subject = 2.days.ago + check = HealthChecks::Checks::DaysAgoCheck.new { subject } + expect(check.failure_message).to include(subject.to_s) + end + + it 'includes the check subject in a custom message' do + params = { :failure_message => 'This check failed' } + subject = 2.days.ago + check = HealthChecks::Checks::DaysAgoCheck.new(params) { subject } + expect(check.failure_message).to include(subject.to_s) + end + + end + + describe :success_message do + + it 'includes the check subject in the default message' do + subject = Time.now + check = HealthChecks::Checks::DaysAgoCheck.new { subject } + expect(check.failure_message).to include(subject.to_s) + end + + it 'includes the check subject in a custom message' do + params = { :success_message => 'This check succeeded' } + subject = Time.now + check = HealthChecks::Checks::DaysAgoCheck.new(params) { subject } + expect(check.success_message).to include(subject.to_s) + end + + end + +end diff --git a/spec/lib/health_checks/health_checkable_spec.rb b/spec/lib/health_checks/health_checkable_spec.rb new file mode 100644 index 000000000..abfeb5c21 --- /dev/null +++ b/spec/lib/health_checks/health_checkable_spec.rb @@ -0,0 +1,128 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') + +describe HealthChecks::HealthCheckable do + + before(:each) do + class MockCheck + include HealthChecks::HealthCheckable + end + @subject = MockCheck.new + end + + describe :initialize do + + it 'allows a custom failure message to be set' do + @subject = MockCheck.new(:failure_message => 'F') + expect(@subject.failure_message).to eq('F') + end + + it 'allows a custom success message to be set' do + @subject = MockCheck.new(:success_message => 'S') + expect(@subject.success_message).to eq('S') + end + + end + + describe :name do + + it 'returns the name of the check' do + expect(@subject.name).to eq('MockCheck') + end + + end + + describe :check do + + it 'is intended to be overridden by the includer' do + expect{ @subject.check }.to raise_error(NotImplementedError) + end + + end + + describe :ok? do + + it 'returns true if the check was successful' do + @subject.stub(:check => true) + expect(@subject.ok?).to be_true + end + + it 'returns false if the check failed' do + @subject.stub(:check => false) + expect(@subject.ok?).to be_false + end + + end + + describe :failure_message do + + it 'returns a default message if one has not been set' do + expect(@subject.failure_message).to eq('Failed') + end + + end + + describe :failure_message= do + + it 'allows a custom failure message to be set' do + @subject.failure_message = 'F' + expect(@subject.failure_message).to eq('F') + end + + end + + describe :success_message do + + it 'returns a default message if one has not been set' do + expect(@subject.success_message).to eq('Success') + end + + end + + describe :success_message= do + + it 'allows a custom success message to be set' do + @subject.success_message = 'S' + expect(@subject.success_message).to eq('S') + end + + end + + describe :message do + + context 'if the check succeeds' do + + before(:each) do + @subject.stub(:check => true) + end + + it 'returns the default success message' do + expect(@subject.message).to eq('Success') + end + + it 'returns a custom success message if one has been set' do + @subject.success_message = 'Custom Success' + expect(@subject.message).to eq('Custom Success') + end + + end + + context 'if the check fails' do + + before(:each) do + @subject.stub(:check => false) + end + + it 'returns the default failure message' do + expect(@subject.message).to eq('Failed') + end + + it 'returns a custom failure message if one has been set' do + @subject.failure_message = 'Custom Failed' + expect(@subject.message).to eq('Custom Failed') + end + + end + + end + +end diff --git a/spec/lib/health_checks/health_checks_spec.rb b/spec/lib/health_checks/health_checks_spec.rb new file mode 100644 index 000000000..c7037b813 --- /dev/null +++ b/spec/lib/health_checks/health_checks_spec.rb @@ -0,0 +1,77 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') + +describe HealthChecks do + include HealthChecks + + describe :add do + + it 'adds a check to the collection and returns the check' do + check = double('MockCheck', :check => true) + expect(add(check)).to eq(check) + end + + it 'does not add checks that do not define the check method' do + check = double('BadCheck') + expect(add(check)).to eq(false) + end + + end + + describe :all do + + it 'returns all the checks' do + check1 = double('MockCheck', :check => true) + check2 = double('AnotherCheck', :check => false) + add(check1) + add(check2) + expect(all).to include(check1, check2) + end + + end + + describe :each do + + it 'iterates over each check' do + expect(subject).to respond_to(:each) + end + + end + + describe :ok? do + + it 'returns true if all checks are ok' do + checks = [ + double('MockCheck', :ok? => true), + double('FakeCheck', :ok? => true), + double('TestCheck', :ok? => true) + ] + HealthChecks.stub(:all => checks) + + expect(HealthChecks.ok?).to be_true + end + + it 'returns false if all checks fail' do + checks = [ + double('MockCheck', :ok? => false), + double('FakeCheck', :ok? => false), + double('TestCheck', :ok? => false) + ] + HealthChecks.stub(:all => checks) + + expect(HealthChecks.ok?).to be_false + end + + it 'returns false if a single check fails' do + checks = [ + double('MockCheck', :ok? => true), + double('FakeCheck', :ok? => false), + double('TestCheck', :ok? => true) + ] + HealthChecks.stub(:all => checks) + + expect(HealthChecks.ok?).to be_false + end + + end + +end |