From 18b0107e506e76cc5d4928297889150ad4f35689 Mon Sep 17 00:00:00 2001 From: Darren Cauthon Date: Sun, 3 Jan 2016 19:58:12 -0600 Subject: [PATCH] Add the http status agent. --- app/concerns/web_request_concern.rb | 4 +- app/models/agents/http_status_agent.rb | 81 ++++++ spec/controllers/http_status_agent_spec.rb | 260 ++++++++++++++++++ .../shared_examples/web_request_concern.rb | 11 + 4 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 app/models/agents/http_status_agent.rb create mode 100644 spec/controllers/http_status_agent_spec.rb diff --git a/app/concerns/web_request_concern.rb b/app/concerns/web_request_concern.rb index 756f6dbc..ef6adc4a 100644 --- a/app/concerns/web_request_concern.rb +++ b/app/concerns/web_request_concern.rb @@ -110,7 +110,9 @@ module WebRequestConcern builder.headers[:user_agent] = user_agent - builder.use FaradayMiddleware::FollowRedirects + unless boolify(interpolated['disable_redirect_follow']) + builder.use FaradayMiddleware::FollowRedirects + end builder.request :url_encoded if boolify(interpolated['disable_url_encoding']) diff --git a/app/models/agents/http_status_agent.rb b/app/models/agents/http_status_agent.rb new file mode 100644 index 00000000..780afdaa --- /dev/null +++ b/app/models/agents/http_status_agent.rb @@ -0,0 +1,81 @@ +module Agents + + class HttpStatusAgent < Agent + + include WebRequestConcern + include FormConfigurable + + can_dry_run! + can_order_created_events! + + default_schedule "every_12h" + + form_configurable :url + form_configurable :disable_redirect_follow, type: :array, values: ['true', 'false'] + + description <<-MD + The HttpStatusAgent will check a url and emit the resulting HTTP status code. + + Specify a `Url` and the Http Status Agent will produce an event with the http status code. + + The `disable redirect follow` option causes the Agent to not follow HTTP redirects. For example, setting this to `true` will cause an agent that receives a 301 redirect to `http://yahoo.com` to return a status of 301 instead of following the redirect and returning 200. + MD + + event_description <<-MD + Events will have the following fields: + + { + "url": "...", + "status": "..." + } + MD + + def working? + memory['last_status'].to_i > 0 + end + + def default_options + { + 'url' => "http://google.com", + 'disable_redirect_follow' => "true", + } + end + + def validate_options + errors.add(:base, "a url must be specified") unless options['url'].present? + end + + def check + check_this_url interpolated[:url] + end + + def receive(incoming_events) + incoming_events.each do |event| + interpolate_with(event) do + check_this_url interpolated[:url] + end + end + end + + private + + def check_this_url(url) + if result = ping(url) + create_event payload: { 'url' => url, 'status' => result.status.to_s, 'response_received' => true } + memory['last_status'] = result.status.to_s + else + create_event payload: { 'url' => url, 'response_received' => false } + memory['last_status'] = nil + end + end + + def ping(url) + result = faraday.get url + result.status > 0 ? result : nil + rescue + nil + end + + end + +end diff --git a/spec/controllers/http_status_agent_spec.rb b/spec/controllers/http_status_agent_spec.rb new file mode 100644 index 00000000..a78fa495 --- /dev/null +++ b/spec/controllers/http_status_agent_spec.rb @@ -0,0 +1,260 @@ +require 'rails_helper' + +describe 'HttpStatusAgent' do + + let(:agent) do + Agents::HttpStatusAgent.new(:name => SecureRandom.uuid, :options => valid_params).tap do |a| + a.service = services(:generic) + a.user = users(:jane) + a.options['url'] = 'http://google.com' + a.save! + + def a.interpolate_with(e, &block) + @the_event = e + block.call + end + + def a.interpolated + @the_event.payload + end + + def a.create_event event + @the_created_events ||= [] + @the_created_events << event + end + + def a.the_created_events + @the_created_events || [] + end + + def a.faraday + @faraday ||= Struct.new(:programmed_responses).new({}).tap do |f| + def f.get url + programmed_responses[url] || raise('invalid url') + end + + def f.set url, response, time = nil + sleep(time/1000) if time + programmed_responses[url] = response + end + end + end + end + end + + let(:valid_params) { {} } + + describe "working" do + it "should be working when the last status is 200" do + agent.memory['last_status'] = '200' + expect(agent.working?).to eq(true) + end + + it "should be working when the last status is 304" do + agent.memory['last_status'] = '304' + expect(agent.working?).to eq(true) + end + + it "should not be working if the status is 0" do + agent.memory['last_status'] = '0' + expect(agent.working?).to eq(false) + end + + it "should not be working if the status is missing" do + agent.memory['last_status'] = nil + expect(agent.working?).to eq(false) + end + + it "should not be working if the status is -1" do + agent.memory['last_status'] = '-1' + expect(agent.working?).to eq(false) + end + end + + describe "check" do + + before do + + def agent.interpolated + @interpolated ||= { :url => SecureRandom.uuid } + end + + def agent.check_this_url url + @url = url + end + + def agent.checked_url + @url + end + + end + + it "should check the url" do + agent.check + expect(agent.checked_url).to eq(agent.interpolated[:url]) + end + + end + + describe "receive" do + + describe "with an event with a successful ping" do + + let(:successful_url) { SecureRandom.uuid } + + let(:status_code) { 200 } + + let(:event_with_a_successful_ping) do + agent.faraday.set(successful_url, Struct.new(:status).new(status_code)) + Event.new.tap { |e| e.payload = { url: successful_url } } + end + + let(:events) do + [event_with_a_successful_ping] + end + + it "should create one event" do + agent.receive events + expect(agent.the_created_events.count).to eq(1) + end + + it "should note that the successful response succeeded" do + agent.receive events + expect(agent.the_created_events[0][:payload]['response_received']).to eq(true) + end + + it "should return the status code" do + agent.receive events + expect(agent.the_created_events[0][:payload]['status']).to eq('200') + end + + it "should remember the status" do + agent.receive events + expect(agent.memory['last_status']).to eq('200') + end + + describe "but the status code is not 200" do + let(:status_code) { 500 } + + it "should return the status code" do + agent.receive events + expect(agent.the_created_events[0][:payload]['status']).to eq('500') + end + + it "should remember the status" do + agent.receive events + expect(agent.memory['last_status']).to eq('500') + end + end + + it "should return the original url" do + agent.receive events + expect(agent.the_created_events[0][:payload]['url']).to eq(successful_url) + end + + describe "but the ping returns a status code of 0" do + + let(:event_with_a_successful_ping) do + agent.faraday.set(successful_url, Struct.new(:status).new(0)) + Event.new.tap { |e| e.payload = { url: successful_url } } + end + + it "should create one event" do + agent.receive events + expect(agent.the_created_events.count).to eq(1) + end + + it "should note that no response was received" do + agent.receive events + expect(agent.the_created_events[0][:payload]['response_received']).to eq(false) + end + + it "should return the original url" do + agent.receive events + expect(agent.the_created_events[0][:payload]['url']).to eq(successful_url) + end + + it "should remember no status" do + agent.memory['last_status'] = '200' + agent.receive events + expect(agent.memory['last_status']).to be_nil + end + + end + + describe "but the ping returns a status code of -1" do + + let(:event_with_a_successful_ping) do + agent.faraday.set(successful_url, Struct.new(:status).new(-1)) + Event.new.tap { |e| e.payload = { url: successful_url } } + end + + it "should create one event" do + agent.receive events + expect(agent.the_created_events.count).to eq(1) + end + + it "should note that no response was received" do + agent.receive events + expect(agent.the_created_events[0][:payload]['response_received']).to eq(false) + end + + it "should return the original url" do + agent.receive events + expect(agent.the_created_events[0][:payload]['url']).to eq(successful_url) + end + + end + + describe "and with one event with a failing ping" do + + let(:failing_url) { SecureRandom.uuid } + let(:event_with_a_failing_ping) { Event.new.tap { |e| e.payload = { url: failing_url } } } + + let(:events) do + [event_with_a_successful_ping, event_with_a_failing_ping] + end + + it "should create two events" do + agent.receive events + expect(agent.the_created_events.count).to eq(2) + end + + it "should note that the failed response failed" do + agent.receive events + expect(agent.the_created_events[1][:payload]['response_received']).to eq(false) + end + + it "should note that the successful response succeeded" do + agent.receive events + expect(agent.the_created_events[0][:payload]['response_received']).to eq(true) + end + + it "should return the original url on both events" do + agent.receive events + expect(agent.the_created_events[0][:payload]['url']).to eq(successful_url) + expect(agent.the_created_events[1][:payload]['url']).to eq(failing_url) + end + + end + + end + + describe "validations" do + before do + expect(agent).to be_valid + end + + it "should validate url" do + agent.options['url'] = "" + expect(agent).not_to be_valid + + agent.options['url'] = "http://www.google.com" + expect(agent).to be_valid + end + end + + end + + +end diff --git a/spec/support/shared_examples/web_request_concern.rb b/spec/support/shared_examples/web_request_concern.rb index fd0612c0..8fc0b2ec 100644 --- a/spec/support/shared_examples/web_request_concern.rb +++ b/spec/support/shared_examples/web_request_concern.rb @@ -156,6 +156,17 @@ shared_examples_for WebRequestConcern do agent.options['disable_url_encoding'] = 'true' expect(agent.faraday.options.params_encoder).to eq(WebRequestConcern::DoNotEncoder) end + + describe "redirect follow" do + it "should use FollowRedirects by default" do + expect(agent.faraday.builder.handlers).to include(FaradayMiddleware::FollowRedirects) + end + + it "should not use FollowRedirects when disabled" do + agent.options['disable_redirect_follow'] = true + expect(agent.faraday.builder.handlers).not_to include(FaradayMiddleware::FollowRedirects) + end + end end describe WebRequestConcern::DoNotEncoder do