1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069 |
- require 'rails_helper'
- describe Agent do
- it_behaves_like WorkingHelpers
- describe '.active/inactive' do
- let(:agent) { agents(:jane_website_agent) }
- it 'is active per default' do
- expect(Agent.active).to include(agent)
- expect(Agent.inactive).not_to include(agent)
- end
- it 'is not active when disabled' do
- agent.update_attribute(:disabled, true)
- expect(Agent.active).not_to include(agent)
- expect(Agent.inactive).to include(agent)
- end
- it 'is not active when deactivated' do
- agent.update_attribute(:deactivated, true)
- expect(Agent.active).not_to include(agent)
- expect(Agent.inactive).to include(agent)
- end
- it 'is not active when disabled and deactivated' do
- agent.update_attribute(:disabled, true)
- agent.update_attribute(:deactivated, true)
- expect(Agent.active).not_to include(agent)
- expect(Agent.inactive).to include(agent)
- end
- end
- describe ".bulk_check" do
- before do
- @weather_agent_count = Agents::WeatherAgent.where(:schedule => "midnight", :disabled => false).count
- end
- it "should run all Agents with the given schedule" do
- mock(Agents::WeatherAgent).async_check(anything).times(@weather_agent_count)
- Agents::WeatherAgent.bulk_check("midnight")
- end
- it "should skip disabled Agents" do
- agents(:bob_weather_agent).update_attribute :disabled, true
- mock(Agents::WeatherAgent).async_check(anything).times(@weather_agent_count - 1)
- Agents::WeatherAgent.bulk_check("midnight")
- end
- it "should skip agents of deactivated accounts" do
- agents(:bob_weather_agent).user.deactivate!
- mock(Agents::WeatherAgent).async_check(anything).times(@weather_agent_count - 1)
- Agents::WeatherAgent.bulk_check("midnight")
- end
- end
- describe ".run_schedule" do
- before do
- expect(Agents::WeatherAgent.count).to be > 0
- expect(Agents::WebsiteAgent.count).to be > 0
- end
- it "runs agents with the given schedule" do
- weather_agent_ids = [agents(:bob_weather_agent), agents(:jane_weather_agent)].map(&:id)
- stub(Agents::WeatherAgent).async_check(anything) {|agent_id| weather_agent_ids.delete(agent_id) }
- stub(Agents::WebsiteAgent).async_check(agents(:bob_website_agent).id)
- Agent.run_schedule("midnight")
- expect(weather_agent_ids).to be_empty
- end
- it "groups agents by type" do
- mock(Agents::WeatherAgent).bulk_check("midnight").once
- mock(Agents::WebsiteAgent).bulk_check("midnight").once
- Agent.run_schedule("midnight")
- end
- it "ignores unknown types" do
- Agent.where(id: agents(:bob_weather_agent).id).update_all type: 'UnknownTypeAgent'
- mock(Agents::WeatherAgent).bulk_check("midnight").once
- mock(Agents::WebsiteAgent).bulk_check("midnight").once
- Agent.run_schedule("midnight")
- end
- it "only runs agents with the given schedule" do
- do_not_allow(Agents::WebsiteAgent).async_check
- Agent.run_schedule("blah")
- end
- it "will not run the 'never' schedule" do
- agents(:bob_weather_agent).update_attribute 'schedule', 'never'
- do_not_allow(Agents::WebsiteAgent).async_check
- Agent.run_schedule("never")
- end
- end
- describe "credential" do
- it "should return the value of the credential when credential is present" do
- expect(agents(:bob_weather_agent).credential("aws_secret")).to eq(user_credentials(:bob_aws_secret).credential_value)
- end
- it "should return nil when credential is not present" do
- expect(agents(:bob_weather_agent).credential("non_existing_credential")).to eq(nil)
- end
- it "should memoize the load" do
- mock.any_instance_of(UserCredential).credential_value.twice { "foo" }
- expect(agents(:bob_weather_agent).credential("aws_secret")).to eq("foo")
- expect(agents(:bob_weather_agent).credential("aws_secret")).to eq("foo")
- agents(:bob_weather_agent).reload
- expect(agents(:bob_weather_agent).credential("aws_secret")).to eq("foo")
- expect(agents(:bob_weather_agent).credential("aws_secret")).to eq("foo")
- end
- end
- describe "changes to type" do
- it "validates types" do
- source = Agent.new
- source.type = "Agents::WeatherAgent"
- expect(source).to have(0).errors_on(:type)
- source.type = "Agents::WebsiteAgent"
- expect(source).to have(0).errors_on(:type)
- source.type = "Agents::Fake"
- expect(source).to have(1).error_on(:type)
- end
- it "disallows changes to type once a record has been saved" do
- source = agents(:bob_website_agent)
- source.type = "Agents::WeatherAgent"
- expect(source).to have(1).error_on(:type)
- end
- it "should know about available types" do
- expect(Agent.types).to include(Agents::WeatherAgent, Agents::WebsiteAgent)
- end
- end
- describe "with an example Agent" do
- class Agents::SomethingSource < Agent
- default_schedule "2pm"
- def check
- create_event :payload => {}
- end
- def validate_options
- errors.add(:base, "bad is bad") if options[:bad]
- end
- end
- class Agents::CannotBeScheduled < Agent
- cannot_be_scheduled!
- def receive(events)
- events.each do |event|
- create_event :payload => { :events_received => 1 }
- end
- end
- end
- before do
- stub(Agents::SomethingSource).valid_type?("Agents::SomethingSource") { true }
- stub(Agents::CannotBeScheduled).valid_type?("Agents::CannotBeScheduled") { true }
- end
- describe Agents::SomethingSource do
- let(:new_instance) do
- agent = Agents::SomethingSource.new(:name => "some agent")
- agent.user = users(:bob)
- agent
- end
- it_behaves_like LiquidInterpolatable
- it_behaves_like HasGuid
- end
- describe ".short_type" do
- it "returns a short name without 'Agents::'" do
- expect(Agents::SomethingSource.new.short_type).to eq("SomethingSource")
- expect(Agents::CannotBeScheduled.new.short_type).to eq("CannotBeScheduled")
- end
- end
- describe ".default_schedule" do
- it "stores the default on the class" do
- expect(Agents::SomethingSource.default_schedule).to eq("2pm")
- expect(Agents::SomethingSource.new.default_schedule).to eq("2pm")
- end
- it "sets the default on new instances, allows setting new schedules, and prevents invalid schedules" do
- @checker = Agents::SomethingSource.new(:name => "something")
- @checker.user = users(:bob)
- expect(@checker.schedule).to eq("2pm")
- @checker.save!
- expect(@checker.reload.schedule).to eq("2pm")
- @checker.update_attribute :schedule, "5pm"
- expect(@checker.reload.schedule).to eq("5pm")
- expect(@checker.reload.schedule).to eq("5pm")
- @checker.schedule = "this_is_not_real"
- expect(@checker).to have(1).errors_on(:schedule)
- end
- it "should have an empty schedule if it cannot_be_scheduled" do
- @checker = Agents::CannotBeScheduled.new(:name => "something")
- @checker.user = users(:bob)
- expect(@checker.schedule).to be_nil
- expect(@checker).to be_valid
- @checker.schedule = "5pm"
- @checker.save!
- expect(@checker.schedule).to be_nil
- @checker.schedule = "5pm"
- expect(@checker).to have(0).errors_on(:schedule)
- expect(@checker.schedule).to be_nil
- end
- end
- describe "#create_event" do
- before do
- @checker = Agents::SomethingSource.new(:name => "something")
- @checker.user = users(:bob)
- @checker.save!
- end
- it "should use the checker's user" do
- @checker.check
- expect(Event.last.user).to eq(@checker.user)
- end
- it "should log an error if the Agent has been marked with 'cannot_create_events!'" do
- mock(@checker).can_create_events? { false }
- expect {
- @checker.check
- }.not_to change { Event.count }
- expect(@checker.logs.first.message).to match(/cannot create events/i)
- end
- end
- describe ".async_check" do
- before do
- @checker = Agents::SomethingSource.new(:name => "something")
- @checker.user = users(:bob)
- @checker.save!
- end
- it "records last_check_at and calls check on the given Agent" do
- mock(@checker).check.once {
- @checker.options[:new] = true
- }
- mock(Agent).find(@checker.id) { @checker }
- expect(@checker.last_check_at).to be_nil
- Agents::SomethingSource.async_check(@checker.id)
- expect(@checker.reload.last_check_at).to be_within(2).of(Time.now)
- expect(@checker.reload.options[:new]).to be_truthy # Show that we save options
- end
- it "should log exceptions" do
- mock(@checker).check.once {
- raise "foo"
- }
- mock(Agent).find(@checker.id) { @checker }
- expect {
- Agents::SomethingSource.async_check(@checker.id)
- }.to raise_error(RuntimeError)
- log = @checker.logs.first
- expect(log.message).to match(/Exception/)
- expect(log.level).to eq(4)
- end
- it "should not run disabled Agents" do
- mock(Agent).find(agents(:bob_weather_agent).id) { agents(:bob_weather_agent) }
- do_not_allow(agents(:bob_weather_agent)).check
- agents(:bob_weather_agent).update_attribute :disabled, true
- Agent.async_check(agents(:bob_weather_agent).id)
- end
- end
- describe ".receive!" do
- before do
- stub_request(:any, /darksky/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/weather.json")), :status => 200)
- stub.any_instance_of(Agents::WeatherAgent).is_tomorrow?(anything) { true }
- end
- it "should use available events" do
- Agent.async_check(agents(:bob_weather_agent).id)
- mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(1)
- Agent.receive!
- end
- it "should not propagate to disabled Agents" do
- Agent.async_check(agents(:bob_weather_agent).id)
- agents(:bob_rain_notifier_agent).update_attribute :disabled, true
- mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
- Agent.receive!
- end
- it "should not propagate to Agents with unknown types" do
- Agent.async_check(agents(:jane_weather_agent).id)
- Agent.async_check(agents(:bob_weather_agent).id)
- Agent.where(id: agents(:bob_rain_notifier_agent).id).update_all type: 'UnknownTypeAgent'
- mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
- mock(Agent).async_receive(agents(:jane_rain_notifier_agent).id, anything).times(1)
- Agent.receive!
- end
- it "should not propagate from Agents with unknown types" do
- Agent.async_check(agents(:jane_weather_agent).id)
- Agent.async_check(agents(:bob_weather_agent).id)
- Agent.where(id: agents(:bob_weather_agent).id).update_all type: 'UnknownTypeAgent'
- mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
- mock(Agent).async_receive(agents(:jane_rain_notifier_agent).id, anything).times(1)
- Agent.receive!
- end
- it "should log exceptions" do
- mock.any_instance_of(Agents::TriggerAgent).receive(anything).once {
- raise "foo"
- }
- Agent.async_check(agents(:bob_weather_agent).id)
- expect {
- Agent.async_receive(agents(:bob_rain_notifier_agent).id, [agents(:bob_weather_agent).events.last.id])
- }.to raise_error(RuntimeError)
- log = agents(:bob_rain_notifier_agent).logs.first
- expect(log.message).to match(/Exception/)
- expect(log.level).to eq(4)
- end
- it "should track when events have been seen and not received them again" do
- mock.any_instance_of(Agents::TriggerAgent).receive(anything).once
- Agent.async_check(agents(:bob_weather_agent).id)
- expect {
- Agent.receive!
- }.to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
- expect {
- Agent.receive!
- }.not_to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
- end
- it "should not run consumers that have nothing to do" do
- do_not_allow.any_instance_of(Agents::TriggerAgent).receive(anything)
- Agent.receive!
- end
- it "should group events" do
- mock.any_instance_of(Agents::TriggerAgent).receive(anything).twice { |events|
- expect(events.map(&:user).map(&:username).uniq.length).to eq(1)
- }
- Agent.async_check(agents(:bob_weather_agent).id)
- Agent.async_check(agents(:jane_weather_agent).id)
- Agent.receive!
- end
- it "should call receive for each event when no_bulk_receive! is used" do
- mock.any_instance_of(Agents::TriggerAgent).receive(anything).twice
- stub(Agents::TriggerAgent).no_bulk_receive? { true }
- Agent.async_check(agents(:bob_weather_agent).id)
- Agent.async_check(agents(:bob_weather_agent).id)
- Agent.receive!
- end
- it "should ignore events that were created before a particular Link" do
- agent2 = Agents::SomethingSource.new(:name => "something")
- agent2.user = users(:bob)
- agent2.save!
- agent2.check
- mock.any_instance_of(Agents::TriggerAgent).receive(anything).twice
- agents(:bob_weather_agent).check # bob_weather_agent makes an event
- expect {
- Agent.receive! # event gets propagated
- }.to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
- # This agent creates a few events before we link to it, but after our last check.
- agent2.check
- agent2.check
- # Now we link to it.
- agents(:bob_rain_notifier_agent).sources << agent2
- expect(agent2.links_as_source.first.event_id_at_creation).to eq(agent2.events.reorder("events.id desc").first.id)
- expect {
- Agent.receive! # but we don't receive those events because they're too old
- }.not_to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
- # Now a new event is created by agent2
- agent2.check
- expect {
- Agent.receive! # and we receive it
- }.to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
- end
- it "should not run agents of deactivated accounts" do
- agents(:bob_weather_agent).user.deactivate!
- Agent.async_check(agents(:bob_weather_agent).id)
- mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
- Agent.receive!
- end
- end
- describe ".async_receive" do
- it "should not run disabled Agents" do
- mock(Agent).find(agents(:bob_rain_notifier_agent).id) { agents(:bob_rain_notifier_agent) }
- do_not_allow(agents(:bob_rain_notifier_agent)).receive
- agents(:bob_rain_notifier_agent).update_attribute :disabled, true
- Agent.async_receive(agents(:bob_rain_notifier_agent).id, [1, 2, 3])
- end
- end
- describe "creating a new agent and then calling .receive!" do
- it "should not backfill events for a newly created agent" do
- Event.delete_all
- sender = Agents::SomethingSource.new(:name => "Sending Agent")
- sender.user = users(:bob)
- sender.save!
- sender.create_event :payload => {}
- sender.create_event :payload => {}
- expect(sender.events.count).to eq(2)
- receiver = Agents::CannotBeScheduled.new(:name => "Receiving Agent")
- receiver.user = users(:bob)
- receiver.sources << sender
- receiver.save!
- expect(receiver.events.count).to eq(0)
- Agent.receive!
- expect(receiver.events.count).to eq(0)
- sender.create_event :payload => {}
- Agent.receive!
- expect(receiver.events.count).to eq(1)
- end
- end
- describe "creating agents with propagate_immediately = true" do
- it "should schedule subagent events immediately" do
- Event.delete_all
- sender = Agents::SomethingSource.new(:name => "Sending Agent")
- sender.user = users(:bob)
- sender.save!
- receiver = Agents::CannotBeScheduled.new(
- :name => "Receiving Agent",
- )
- receiver.propagate_immediately = true
- receiver.user = users(:bob)
- receiver.sources << sender
- receiver.save!
- sender.create_event :payload => {"message" => "new payload"}
- expect(sender.events.count).to eq(1)
- expect(receiver.events.count).to eq(1)
- #should be true without calling Agent.receive!
- end
- it "should only schedule receiving agents that are set to propagate_immediately" do
- Event.delete_all
- sender = Agents::SomethingSource.new(:name => "Sending Agent")
- sender.user = users(:bob)
- sender.save!
- im_receiver = Agents::CannotBeScheduled.new(
- :name => "Immediate Receiving Agent",
- )
- im_receiver.propagate_immediately = true
- im_receiver.user = users(:bob)
- im_receiver.sources << sender
- im_receiver.save!
- slow_receiver = Agents::CannotBeScheduled.new(
- :name => "Slow Receiving Agent",
- )
- slow_receiver.user = users(:bob)
- slow_receiver.sources << sender
- slow_receiver.save!
- sender.create_event :payload => {"message" => "new payload"}
- expect(sender.events.count).to eq(1)
- expect(im_receiver.events.count).to eq(1)
- #we should get the quick one
- #but not the slow one
- expect(slow_receiver.events.count).to eq(0)
- Agent.receive!
- #now we should have one in both
- expect(im_receiver.events.count).to eq(1)
- expect(slow_receiver.events.count).to eq(1)
- end
- end
- describe "validations" do
- it "calls validate_options" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.user = users(:bob)
- agent.options[:bad] = true
- expect(agent).to have(1).error_on(:base)
- agent.options[:bad] = false
- expect(agent).to have(0).errors_on(:base)
- end
- it "makes options symbol-indifferent before validating" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.user = users(:bob)
- agent.options["bad"] = true
- expect(agent).to have(1).error_on(:base)
- agent.options["bad"] = false
- expect(agent).to have(0).errors_on(:base)
- end
- it "makes memory symbol-indifferent before validating" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.user = users(:bob)
- agent.memory["bad"] = 2
- agent.save
- expect(agent.memory[:bad]).to eq(2)
- end
- it "should work when assigned a hash or JSON string" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.memory = {}
- expect(agent.memory).to eq({})
- expect(agent.memory["foo"]).to be_nil
- agent.memory = ""
- expect(agent.memory["foo"]).to be_nil
- expect(agent.memory).to eq({})
- agent.memory = '{"hi": "there"}'
- expect(agent.memory).to eq({ "hi" => "there" })
- agent.memory = '{invalid}'
- expect(agent.memory).to eq({ "hi" => "there" })
- expect(agent).to have(1).errors_on(:memory)
- agent.memory = "{}"
- expect(agent.memory["foo"]).to be_nil
- expect(agent.memory).to eq({})
- expect(agent).to have(0).errors_on(:memory)
- agent.options = "{}"
- expect(agent.options["foo"]).to be_nil
- expect(agent.options).to eq({})
- expect(agent).to have(0).errors_on(:options)
- agent.options = '{"hi": 2}'
- expect(agent.options["hi"]).to eq(2)
- expect(agent).to have(0).errors_on(:options)
- agent.options = '{"hi": wut}'
- expect(agent.options["hi"]).to eq(2)
- expect(agent).to have(1).errors_on(:options)
- expect(agent.errors_on(:options)).to include("was assigned invalid JSON")
- agent.options = 5
- expect(agent.options["hi"]).to eq(2)
- expect(agent).to have(1).errors_on(:options)
- expect(agent.errors_on(:options)).to include("cannot be set to an instance of #{2.class}") # Integer (ruby >=2.4) or Fixnum (ruby <2.4)
- end
- it "should not allow source agents owned by other people" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.user = users(:bob)
- agent.source_ids = [agents(:bob_weather_agent).id]
- expect(agent).to have(0).errors_on(:sources)
- agent.source_ids = [agents(:jane_weather_agent).id]
- expect(agent).to have(1).errors_on(:sources)
- agent.user = users(:jane)
- expect(agent).to have(0).errors_on(:sources)
- end
- it "should not allow target agents owned by other people" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.user = users(:bob)
- agent.receiver_ids = [agents(:bob_weather_agent).id]
- expect(agent).to have(0).errors_on(:receivers)
- agent.receiver_ids = [agents(:jane_weather_agent).id]
- expect(agent).to have(1).errors_on(:receivers)
- agent.user = users(:jane)
- expect(agent).to have(0).errors_on(:receivers)
- end
- it "should not allow controller agents owned by other people" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.user = users(:bob)
- agent.controller_ids = [agents(:bob_weather_agent).id]
- expect(agent).to have(0).errors_on(:controllers)
- agent.controller_ids = [agents(:jane_weather_agent).id]
- expect(agent).to have(1).errors_on(:controllers)
- agent.user = users(:jane)
- expect(agent).to have(0).errors_on(:controllers)
- end
- it "should not allow control target agents owned by other people" do
- agent = Agents::CannotBeScheduled.new(:name => "something")
- agent.user = users(:bob)
- agent.control_target_ids = [agents(:bob_weather_agent).id]
- expect(agent).to have(0).errors_on(:control_targets)
- agent.control_target_ids = [agents(:jane_weather_agent).id]
- expect(agent).to have(1).errors_on(:control_targets)
- agent.user = users(:jane)
- expect(agent).to have(0).errors_on(:control_targets)
- end
- it "should not allow scenarios owned by other people" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.user = users(:bob)
- agent.scenario_ids = [scenarios(:bob_weather).id]
- expect(agent).to have(0).errors_on(:scenarios)
- agent.scenario_ids = [scenarios(:bob_weather).id, scenarios(:jane_weather).id]
- expect(agent).to have(1).errors_on(:scenarios)
- agent.scenario_ids = [scenarios(:jane_weather).id]
- expect(agent).to have(1).errors_on(:scenarios)
- agent.user = users(:jane)
- expect(agent).to have(0).errors_on(:scenarios)
- end
- it "validates keep_events_for" do
- agent = Agents::SomethingSource.new(:name => "something")
- agent.user = users(:bob)
- expect(agent).to be_valid
- agent.keep_events_for = nil
- expect(agent).to have(1).errors_on(:keep_events_for)
- agent.keep_events_for = 1000
- expect(agent).to have(1).errors_on(:keep_events_for)
- agent.keep_events_for = ""
- expect(agent).to have(1).errors_on(:keep_events_for)
- agent.keep_events_for = 5.days.to_i
- expect(agent).to be_valid
- agent.keep_events_for = 0
- expect(agent).to be_valid
- agent.keep_events_for = 365.days.to_i
- expect(agent).to be_valid
- # Rails seems to call to_i on the input. This guards against future changes to that behavior.
- agent.keep_events_for = "drop table;"
- expect(agent.keep_events_for).to eq(0)
- end
- end
- describe "cleaning up now-expired events" do
- before do
- @time = "2014-01-01 01:00:00 +00:00"
- travel_to @time do
- @agent = Agents::SomethingSource.new(:name => "something")
- @agent.keep_events_for = 5.days
- @agent.user = users(:bob)
- @agent.save!
- @event = @agent.create_event :payload => { "hello" => "world" }
- expect(@event.expires_at.to_i).to be_within(2).of(5.days.from_now.to_i)
- end
- end
- describe "when keep_events_for has not changed" do
- it "does nothing" do
- mock(@agent).update_event_expirations!.times(0)
- @agent.options[:foo] = "bar1"
- @agent.save!
- @agent.options[:foo] = "bar1"
- @agent.keep_events_for = 5.days
- @agent.save!
- end
- end
- describe "when keep_events_for is changed" do
- it "updates events' expires_at" do
- travel_to @time do
- expect {
- @agent.options[:foo] = "bar1"
- @agent.keep_events_for = 3.days
- @agent.save!
- }.to change { @event.reload.expires_at }
- expect(@event.expires_at.to_i).to be_within(2).of(3.days.from_now.to_i)
- end
- end
- it "updates events relative to their created_at" do
- @event.update_attribute :created_at, 2.days.ago
- expect(@event.reload.created_at.to_i).to be_within(2).of(2.days.ago.to_i)
- expect {
- @agent.options[:foo] = "bar2"
- @agent.keep_events_for = 3.days
- @agent.save!
- }.to change { @event.reload.expires_at }
- expect(@event.expires_at.to_i).to be_within(60 * 61).of(1.days.from_now.to_i) # The larger time is to deal with daylight savings
- end
- it "nulls out expires_at when keep_events_for is set to 0" do
- expect {
- @agent.options[:foo] = "bar"
- @agent.keep_events_for = 0
- @agent.save!
- }.to change { @event.reload.expires_at }.to(nil)
- end
- end
- end
- describe "Agent.build_clone" do
- before do
- Event.delete_all
- @sender = Agents::SomethingSource.new(
- name: 'Agent (2)',
- options: { foo: 'bar2' },
- schedule: '5pm')
- @sender.user = users(:bob)
- @sender.save!
- @sender.create_event :payload => {}
- @sender.create_event :payload => {}
- expect(@sender.events.count).to eq(2)
- @receiver = Agents::CannotBeScheduled.new(
- name: 'Agent',
- options: { foo: 'bar3' },
- keep_events_for: 3.days,
- propagate_immediately: true)
- @receiver.user = users(:bob)
- @receiver.sources << @sender
- @receiver.memory[:test] = 1
- @receiver.save!
- end
- it "should create a clone of a given agent for editing" do
- sender_clone = users(:bob).agents.build_clone(@sender)
- expect(sender_clone.attributes).to eq(Agent.new.attributes.
- update(@sender.slice(:user_id, :type,
- :options, :schedule, :keep_events_for, :propagate_immediately)).
- update('name' => 'Agent (2) (2)', 'options' => { 'foo' => 'bar2' }))
- expect(sender_clone.source_ids).to eq([])
- receiver_clone = users(:bob).agents.build_clone(@receiver)
- expect(receiver_clone.attributes).to eq(Agent.new.attributes.
- update(@receiver.slice(:user_id, :type,
- :options, :schedule, :keep_events_for, :propagate_immediately)).
- update('name' => 'Agent (3)', 'options' => { 'foo' => 'bar3' }))
- expect(receiver_clone.source_ids).to eq([@sender.id])
- end
- end
- end
- describe ".trigger_web_request" do
- class Agents::WebRequestReceiver < Agent
- cannot_be_scheduled!
- end
- before do
- stub(Agents::WebRequestReceiver).valid_type?("Agents::WebRequestReceiver") { true }
- end
- context "when .receive_web_request is defined" do
- before do
- @agent = Agents::WebRequestReceiver.new(:name => "something")
- @agent.user = users(:bob)
- @agent.save!
- def @agent.receive_web_request(params, method, format)
- memory['last_request'] = [params, method, format]
- ['Ok!', 200]
- end
- end
- it "calls the .receive_web_request hook, updates last_web_request_at, and saves" do
- request = ActionDispatch::Request.new({
- 'action_dispatch.request.request_parameters' => { :some_param => "some_value" },
- 'REQUEST_METHOD' => "POST",
- 'HTTP_ACCEPT' => 'text/html'
- })
- @agent.trigger_web_request(request)
- expect(@agent.reload.memory['last_request']).to eq([ { "some_param" => "some_value" }, "post", "text/html" ])
- expect(@agent.last_web_request_at.to_i).to be_within(1).of(Time.now.to_i)
- end
- end
- context "when .receive_web_request is defined with just request" do
- before do
- @agent = Agents::WebRequestReceiver.new(:name => "something")
- @agent.user = users(:bob)
- @agent.save!
- def @agent.receive_web_request(request)
- memory['last_request'] = [request.params, request.method_symbol.to_s, request.format, {'HTTP_X_CUSTOM_HEADER' => request.headers['HTTP_X_CUSTOM_HEADER']}]
- ['Ok!', 200]
- end
- end
- it "calls the .trigger_web_request with headers, and they get passed to .receive_web_request" do
- request = ActionDispatch::Request.new({
- 'action_dispatch.request.request_parameters' => { :some_param => "some_value" },
- 'REQUEST_METHOD' => "POST",
- 'HTTP_ACCEPT' => 'text/html',
- 'HTTP_X_CUSTOM_HEADER' => "foo"
- })
- @agent.trigger_web_request(request)
- expect(@agent.reload.memory['last_request']).to eq([ { "some_param" => "some_value" }, "post", "text/html", {'HTTP_X_CUSTOM_HEADER' => "foo"} ])
- expect(@agent.last_web_request_at.to_i).to be_within(1).of(Time.now.to_i)
- end
- end
- context "when .receive_webhook is defined" do
- before do
- @agent = Agents::WebRequestReceiver.new(:name => "something")
- @agent.user = users(:bob)
- @agent.save!
- def @agent.receive_webhook(params)
- memory['last_webhook_request'] = params
- ['Ok!', 200]
- end
- end
- it "outputs a deprecation warning and calls .receive_webhook with the params" do
- request = ActionDispatch::Request.new({
- 'action_dispatch.request.request_parameters' => { :some_param => "some_value" },
- 'REQUEST_METHOD' => "POST",
- 'HTTP_ACCEPT' => 'text/html'
- })
- mock(Rails.logger).warn("DEPRECATED: The .receive_webhook method is deprecated, please switch your Agent to use .receive_web_request.")
- @agent.trigger_web_request(request)
- expect(@agent.reload.memory['last_webhook_request']).to eq({ "some_param" => "some_value" })
- expect(@agent.last_web_request_at.to_i).to be_within(1).of(Time.now.to_i)
- end
- end
- end
- describe "scopes" do
- describe "of_type" do
- it "should accept classes" do
- agents = Agent.of_type(Agents::WebsiteAgent)
- expect(agents).to include(agents(:bob_website_agent))
- expect(agents).to include(agents(:jane_website_agent))
- expect(agents).not_to include(agents(:bob_weather_agent))
- end
- it "should accept strings" do
- agents = Agent.of_type("Agents::WebsiteAgent")
- expect(agents).to include(agents(:bob_website_agent))
- expect(agents).to include(agents(:jane_website_agent))
- expect(agents).not_to include(agents(:bob_weather_agent))
- end
- it "should accept instances of an Agent" do
- agents = Agent.of_type(agents(:bob_website_agent))
- expect(agents).to include(agents(:bob_website_agent))
- expect(agents).to include(agents(:jane_website_agent))
- expect(agents).not_to include(agents(:bob_weather_agent))
- end
- end
- end
- describe "#create_event" do
- describe "when the agent has keep_events_for set" do
- before do
- expect(agents(:jane_weather_agent).keep_events_for).to be > 0
- end
- it "sets expires_at on created events" do
- event = agents(:jane_weather_agent).create_event :payload => { 'hi' => 'there' }
- expect(event.expires_at.to_i).to be_within(5).of(agents(:jane_weather_agent).keep_events_for.seconds.from_now.to_i)
- end
- end
- describe "when the agent does not have keep_events_for set" do
- before do
- expect(agents(:jane_website_agent).keep_events_for).to eq(0)
- end
- it "does not set expires_at on created events" do
- event = agents(:jane_website_agent).create_event :payload => { 'hi' => 'there' }
- expect(event.expires_at).to be_nil
- end
- end
- end
- describe '.last_checked_event_id' do
- it "should be updated by setting drop_pending_events to true" do
- agent = agents(:bob_rain_notifier_agent)
- agent.last_checked_event_id = nil
- agent.save!
- agent.update!(drop_pending_events: true)
- expect(agent.reload.last_checked_event_id).to eq(Event.maximum(:id))
- end
- it "should not affect a virtual attribute drop_pending_events" do
- agent = agents(:bob_rain_notifier_agent)
- agent.update!(drop_pending_events: true)
- expect(agent.reload.drop_pending_events).to eq(false)
- end
- end
- describe ".drop_pending_events" do
- before do
- stub_request(:any, /darksky/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/weather.json")), status: 200)
- end
- it "should drop pending events while the agent was disabled when set to true" do
- agent1 = agents(:bob_weather_agent)
- agent2 = agents(:bob_rain_notifier_agent)
- expect {
- expect {
- Agent.async_check(agent1.id)
- Agent.receive!
- }.to change { agent1.events.count }.by(1)
- }.to change { agent2.events.count }.by(0)
- agent2.disabled = true
- agent2.save!
- expect {
- expect {
- Agent.async_check(agent1.id)
- Agent.receive!
- }.to change { agent1.events.count }.by(1)
- }.not_to change { agent2.events.count }
- agent2.disabled = false
- agent2.drop_pending_events = true
- agent2.save!
- expect {
- Agent.receive!
- }.not_to change { agent2.events.count }
- end
- end
- end
- describe AgentDrop do
- def interpolate(string, agent)
- agent.interpolate_string(string, "agent" => agent)
- end
- before do
- @wsa1 = Agents::WebsiteAgent.new(
- name: 'XKCD',
- options: {
- expected_update_period_in_days: 2,
- type: 'html',
- url: 'http://xkcd.com/',
- mode: 'on_change',
- extract: {
- url: { css: '#comic img', value: '@src' },
- title: { css: '#comic img', value: '@alt' },
- },
- },
- schedule: 'every_1h',
- keep_events_for: 2.days)
- @wsa1.user = users(:bob)
- @wsa1.save!
- @wsa2 = Agents::WebsiteAgent.new(
- name: 'Dilbert',
- options: {
- expected_update_period_in_days: 2,
- type: 'html',
- url: 'http://dilbert.com/',
- mode: 'on_change',
- extract: {
- url: { css: '[id^=strip_enlarged_] img', value: '@src' },
- title: { css: '.STR_DateStrip', value: 'string(.)' },
- },
- },
- schedule: 'every_12h',
- keep_events_for: 2.days)
- @wsa2.user = users(:bob)
- @wsa2.save!
- @efa = Agents::EventFormattingAgent.new(
- name: 'Formatter',
- options: {
- instructions: {
- message: '{{agent.name}}: {{title}} {{url}}',
- agent: '{{agent.type}}',
- },
- mode: 'clean',
- matchers: [],
- skip_created_at: 'false',
- },
- keep_events_for: 2.days,
- propagate_immediately: true)
- @efa.user = users(:bob)
- @efa.sources << @wsa1 << @wsa2
- @efa.memory[:test] = 1
- @efa.save!
- @wsa1.reload
- @wsa2.reload
- end
- it 'should be created via Agent#to_liquid' do
- expect(@wsa1.to_liquid.class).to be(AgentDrop)
- expect(@wsa2.to_liquid.class).to be(AgentDrop)
- expect(@efa.to_liquid.class).to be(AgentDrop)
- end
- it 'should have .id, .type and .name' do
- t = '[{{agent.id}}]{{agent.type}}: {{agent.name}}'
- expect(interpolate(t, @wsa1)).to eq("[#{@wsa1.id}]WebsiteAgent: XKCD")
- expect(interpolate(t, @wsa2)).to eq("[#{@wsa2.id}]WebsiteAgent: Dilbert")
- expect(interpolate(t, @efa)).to eq("[#{@efa.id}]EventFormattingAgent: Formatter")
- end
- it 'should have .options' do
- t = '{{agent.options.url}}'
- expect(interpolate(t, @wsa1)).to eq('http://xkcd.com/')
- expect(interpolate(t, @wsa2)).to eq('http://dilbert.com/')
- expect(interpolate('{{agent.options.instructions.message}}',
- @efa)).to eq('{{agent.name}}: {{title}} {{url}}')
- end
- it 'should have .sources' do
- t = '{{agent.sources.size}}: {{agent.sources | map:"name" | join:", "}}'
- expect(interpolate(t, @wsa1)).to eq('0: ')
- expect(interpolate(t, @wsa2)).to eq('0: ')
- expect(interpolate(t, @efa)).to eq('2: XKCD, Dilbert')
- t = '{{agent.sources.first.name}}..{{agent.sources.last.name}}'
- expect(interpolate(t, @wsa1)).to eq('..')
- expect(interpolate(t, @wsa2)).to eq('..')
- expect(interpolate(t, @efa)).to eq('XKCD..Dilbert')
- t = '{{agent.sources[1].name}}'
- expect(interpolate(t, @efa)).to eq('Dilbert')
- end
- it 'should have .receivers' do
- t = '{{agent.receivers.size}}: {{agent.receivers | map:"name" | join:", "}}'
- expect(interpolate(t, @wsa1)).to eq('1: Formatter')
- expect(interpolate(t, @wsa2)).to eq('1: Formatter')
- expect(interpolate(t, @efa)).to eq('0: ')
- end
- it 'should have .working' do
- stub(@wsa1).working? { false }
- stub(@wsa2).working? { true }
- stub(@efa).working? { false }
- t = '{% if agent.working %}healthy{% else %}unhealthy{% endif %}'
- expect(interpolate(t, @wsa1)).to eq('unhealthy')
- expect(interpolate(t, @wsa2)).to eq('healthy')
- expect(interpolate(t, @efa)).to eq('unhealthy')
- end
- it 'should have .url' do
- t = '{{ agent.url }}'
- expect(interpolate(t, @wsa1)).to match(/http:\/\/localhost(?::\d+)?\/agents\/#{@wsa1.id}/)
- expect(interpolate(t, @wsa2)).to match(/http:\/\/localhost(?::\d+)?\/agents\/#{@wsa2.id}/)
- expect(interpolate(t, @efa)).to match(/http:\/\/localhost(?::\d+)?\/agents\/#{@efa.id}/)
- end
- end
|