aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/controllers/health_checks_controller.rb16
-rw-r--r--app/helpers/health_checks_helper.rb8
-rw-r--r--app/views/health_checks/index.html.erb12
-rw-r--r--config/application.rb1
-rw-r--r--config/initializers/health_checks.rb23
-rw-r--r--config/routes.rb2
-rw-r--r--lib/health_checks/checks/days_ago_check.rb28
-rw-r--r--lib/health_checks/health_checkable.rb28
-rw-r--r--lib/health_checks/health_checks.rb37
-rw-r--r--spec/controllers/health_checks_controller_spec.rb30
-rw-r--r--spec/helpers/health_checks_helper_spec.rb15
-rw-r--r--spec/lib/health_checks/checks/days_ago_check_spec.rb66
-rw-r--r--spec/lib/health_checks/health_checkable_spec.rb128
-rw-r--r--spec/lib/health_checks/health_checks_spec.rb77
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