agent_spec.rb 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. require 'rails_helper'
  2. describe Agent do
  3. it_behaves_like WorkingHelpers
  4. describe '.active/inactive' do
  5. let(:agent) { agents(:jane_website_agent) }
  6. it 'is active per default' do
  7. expect(Agent.active).to include(agent)
  8. expect(Agent.inactive).not_to include(agent)
  9. end
  10. it 'is not active when disabled' do
  11. agent.update_attribute(:disabled, true)
  12. expect(Agent.active).not_to include(agent)
  13. expect(Agent.inactive).to include(agent)
  14. end
  15. it 'is not active when deactivated' do
  16. agent.update_attribute(:deactivated, true)
  17. expect(Agent.active).not_to include(agent)
  18. expect(Agent.inactive).to include(agent)
  19. end
  20. it 'is not active when disabled and deactivated' do
  21. agent.update_attribute(:disabled, true)
  22. agent.update_attribute(:deactivated, true)
  23. expect(Agent.active).not_to include(agent)
  24. expect(Agent.inactive).to include(agent)
  25. end
  26. end
  27. describe ".bulk_check" do
  28. before do
  29. @weather_agent_count = Agents::WeatherAgent.where(:schedule => "midnight", :disabled => false).count
  30. end
  31. it "should run all Agents with the given schedule" do
  32. mock(Agents::WeatherAgent).async_check(anything).times(@weather_agent_count)
  33. Agents::WeatherAgent.bulk_check("midnight")
  34. end
  35. it "should skip disabled Agents" do
  36. agents(:bob_weather_agent).update_attribute :disabled, true
  37. mock(Agents::WeatherAgent).async_check(anything).times(@weather_agent_count - 1)
  38. Agents::WeatherAgent.bulk_check("midnight")
  39. end
  40. it "should skip agents of deactivated accounts" do
  41. agents(:bob_weather_agent).user.deactivate!
  42. mock(Agents::WeatherAgent).async_check(anything).times(@weather_agent_count - 1)
  43. Agents::WeatherAgent.bulk_check("midnight")
  44. end
  45. end
  46. describe ".run_schedule" do
  47. before do
  48. expect(Agents::WeatherAgent.count).to be > 0
  49. expect(Agents::WebsiteAgent.count).to be > 0
  50. end
  51. it "runs agents with the given schedule" do
  52. weather_agent_ids = [agents(:bob_weather_agent), agents(:jane_weather_agent)].map(&:id)
  53. stub(Agents::WeatherAgent).async_check(anything) {|agent_id| weather_agent_ids.delete(agent_id) }
  54. stub(Agents::WebsiteAgent).async_check(agents(:bob_website_agent).id)
  55. Agent.run_schedule("midnight")
  56. expect(weather_agent_ids).to be_empty
  57. end
  58. it "groups agents by type" do
  59. mock(Agents::WeatherAgent).bulk_check("midnight").once
  60. mock(Agents::WebsiteAgent).bulk_check("midnight").once
  61. Agent.run_schedule("midnight")
  62. end
  63. it "ignores unknown types" do
  64. Agent.where(id: agents(:bob_weather_agent).id).update_all type: 'UnknownTypeAgent'
  65. mock(Agents::WeatherAgent).bulk_check("midnight").once
  66. mock(Agents::WebsiteAgent).bulk_check("midnight").once
  67. Agent.run_schedule("midnight")
  68. end
  69. it "only runs agents with the given schedule" do
  70. do_not_allow(Agents::WebsiteAgent).async_check
  71. Agent.run_schedule("blah")
  72. end
  73. it "will not run the 'never' schedule" do
  74. agents(:bob_weather_agent).update_attribute 'schedule', 'never'
  75. do_not_allow(Agents::WebsiteAgent).async_check
  76. Agent.run_schedule("never")
  77. end
  78. end
  79. describe "credential" do
  80. it "should return the value of the credential when credential is present" do
  81. expect(agents(:bob_weather_agent).credential("aws_secret")).to eq(user_credentials(:bob_aws_secret).credential_value)
  82. end
  83. it "should return nil when credential is not present" do
  84. expect(agents(:bob_weather_agent).credential("non_existing_credential")).to eq(nil)
  85. end
  86. it "should memoize the load" do
  87. mock.any_instance_of(UserCredential).credential_value.twice { "foo" }
  88. expect(agents(:bob_weather_agent).credential("aws_secret")).to eq("foo")
  89. expect(agents(:bob_weather_agent).credential("aws_secret")).to eq("foo")
  90. agents(:bob_weather_agent).reload
  91. expect(agents(:bob_weather_agent).credential("aws_secret")).to eq("foo")
  92. expect(agents(:bob_weather_agent).credential("aws_secret")).to eq("foo")
  93. end
  94. end
  95. describe "changes to type" do
  96. it "validates types" do
  97. source = Agent.new
  98. source.type = "Agents::WeatherAgent"
  99. expect(source).to have(0).errors_on(:type)
  100. source.type = "Agents::WebsiteAgent"
  101. expect(source).to have(0).errors_on(:type)
  102. source.type = "Agents::Fake"
  103. expect(source).to have(1).error_on(:type)
  104. end
  105. it "disallows changes to type once a record has been saved" do
  106. source = agents(:bob_website_agent)
  107. source.type = "Agents::WeatherAgent"
  108. expect(source).to have(1).error_on(:type)
  109. end
  110. it "should know about available types" do
  111. expect(Agent.types).to include(Agents::WeatherAgent, Agents::WebsiteAgent)
  112. end
  113. end
  114. describe "with an example Agent" do
  115. class Agents::SomethingSource < Agent
  116. default_schedule "2pm"
  117. def check
  118. create_event :payload => {}
  119. end
  120. def validate_options
  121. errors.add(:base, "bad is bad") if options[:bad]
  122. end
  123. end
  124. class Agents::CannotBeScheduled < Agent
  125. cannot_be_scheduled!
  126. def receive(events)
  127. events.each do |event|
  128. create_event :payload => { :events_received => 1 }
  129. end
  130. end
  131. end
  132. before do
  133. stub(Agents::SomethingSource).valid_type?("Agents::SomethingSource") { true }
  134. stub(Agents::CannotBeScheduled).valid_type?("Agents::CannotBeScheduled") { true }
  135. end
  136. describe Agents::SomethingSource do
  137. let(:new_instance) do
  138. agent = Agents::SomethingSource.new(:name => "some agent")
  139. agent.user = users(:bob)
  140. agent
  141. end
  142. it_behaves_like LiquidInterpolatable
  143. it_behaves_like HasGuid
  144. end
  145. describe ".short_type" do
  146. it "returns a short name without 'Agents::'" do
  147. expect(Agents::SomethingSource.new.short_type).to eq("SomethingSource")
  148. expect(Agents::CannotBeScheduled.new.short_type).to eq("CannotBeScheduled")
  149. end
  150. end
  151. describe ".default_schedule" do
  152. it "stores the default on the class" do
  153. expect(Agents::SomethingSource.default_schedule).to eq("2pm")
  154. expect(Agents::SomethingSource.new.default_schedule).to eq("2pm")
  155. end
  156. it "sets the default on new instances, allows setting new schedules, and prevents invalid schedules" do
  157. @checker = Agents::SomethingSource.new(:name => "something")
  158. @checker.user = users(:bob)
  159. expect(@checker.schedule).to eq("2pm")
  160. @checker.save!
  161. expect(@checker.reload.schedule).to eq("2pm")
  162. @checker.update_attribute :schedule, "5pm"
  163. expect(@checker.reload.schedule).to eq("5pm")
  164. expect(@checker.reload.schedule).to eq("5pm")
  165. @checker.schedule = "this_is_not_real"
  166. expect(@checker).to have(1).errors_on(:schedule)
  167. end
  168. it "should have an empty schedule if it cannot_be_scheduled" do
  169. @checker = Agents::CannotBeScheduled.new(:name => "something")
  170. @checker.user = users(:bob)
  171. expect(@checker.schedule).to be_nil
  172. expect(@checker).to be_valid
  173. @checker.schedule = "5pm"
  174. @checker.save!
  175. expect(@checker.schedule).to be_nil
  176. @checker.schedule = "5pm"
  177. expect(@checker).to have(0).errors_on(:schedule)
  178. expect(@checker.schedule).to be_nil
  179. end
  180. end
  181. describe "#create_event" do
  182. before do
  183. @checker = Agents::SomethingSource.new(:name => "something")
  184. @checker.user = users(:bob)
  185. @checker.save!
  186. end
  187. it "should use the checker's user" do
  188. @checker.check
  189. expect(Event.last.user).to eq(@checker.user)
  190. end
  191. it "should log an error if the Agent has been marked with 'cannot_create_events!'" do
  192. mock(@checker).can_create_events? { false }
  193. expect {
  194. @checker.check
  195. }.not_to change { Event.count }
  196. expect(@checker.logs.first.message).to match(/cannot create events/i)
  197. end
  198. end
  199. describe ".async_check" do
  200. before do
  201. @checker = Agents::SomethingSource.new(:name => "something")
  202. @checker.user = users(:bob)
  203. @checker.save!
  204. end
  205. it "records last_check_at and calls check on the given Agent" do
  206. mock(@checker).check.once {
  207. @checker.options[:new] = true
  208. }
  209. mock(Agent).find(@checker.id) { @checker }
  210. expect(@checker.last_check_at).to be_nil
  211. Agents::SomethingSource.async_check(@checker.id)
  212. expect(@checker.reload.last_check_at).to be_within(2).of(Time.now)
  213. expect(@checker.reload.options[:new]).to be_truthy # Show that we save options
  214. end
  215. it "should log exceptions" do
  216. mock(@checker).check.once {
  217. raise "foo"
  218. }
  219. mock(Agent).find(@checker.id) { @checker }
  220. expect {
  221. Agents::SomethingSource.async_check(@checker.id)
  222. }.to raise_error(RuntimeError)
  223. log = @checker.logs.first
  224. expect(log.message).to match(/Exception/)
  225. expect(log.level).to eq(4)
  226. end
  227. it "should not run disabled Agents" do
  228. mock(Agent).find(agents(:bob_weather_agent).id) { agents(:bob_weather_agent) }
  229. do_not_allow(agents(:bob_weather_agent)).check
  230. agents(:bob_weather_agent).update_attribute :disabled, true
  231. Agent.async_check(agents(:bob_weather_agent).id)
  232. end
  233. end
  234. describe ".receive!" do
  235. before do
  236. stub_request(:any, /darksky/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/weather.json")), :status => 200)
  237. stub.any_instance_of(Agents::WeatherAgent).is_tomorrow?(anything) { true }
  238. end
  239. it "should use available events" do
  240. Agent.async_check(agents(:bob_weather_agent).id)
  241. mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(1)
  242. Agent.receive!
  243. end
  244. it "should not propagate to disabled Agents" do
  245. Agent.async_check(agents(:bob_weather_agent).id)
  246. agents(:bob_rain_notifier_agent).update_attribute :disabled, true
  247. mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
  248. Agent.receive!
  249. end
  250. it "should not propagate to Agents with unknown types" do
  251. Agent.async_check(agents(:jane_weather_agent).id)
  252. Agent.async_check(agents(:bob_weather_agent).id)
  253. Agent.where(id: agents(:bob_rain_notifier_agent).id).update_all type: 'UnknownTypeAgent'
  254. mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
  255. mock(Agent).async_receive(agents(:jane_rain_notifier_agent).id, anything).times(1)
  256. Agent.receive!
  257. end
  258. it "should not propagate from Agents with unknown types" do
  259. Agent.async_check(agents(:jane_weather_agent).id)
  260. Agent.async_check(agents(:bob_weather_agent).id)
  261. Agent.where(id: agents(:bob_weather_agent).id).update_all type: 'UnknownTypeAgent'
  262. mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
  263. mock(Agent).async_receive(agents(:jane_rain_notifier_agent).id, anything).times(1)
  264. Agent.receive!
  265. end
  266. it "should log exceptions" do
  267. mock.any_instance_of(Agents::TriggerAgent).receive(anything).once {
  268. raise "foo"
  269. }
  270. Agent.async_check(agents(:bob_weather_agent).id)
  271. expect {
  272. Agent.async_receive(agents(:bob_rain_notifier_agent).id, [agents(:bob_weather_agent).events.last.id])
  273. }.to raise_error(RuntimeError)
  274. log = agents(:bob_rain_notifier_agent).logs.first
  275. expect(log.message).to match(/Exception/)
  276. expect(log.level).to eq(4)
  277. end
  278. it "should track when events have been seen and not received them again" do
  279. mock.any_instance_of(Agents::TriggerAgent).receive(anything).once
  280. Agent.async_check(agents(:bob_weather_agent).id)
  281. expect {
  282. Agent.receive!
  283. }.to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
  284. expect {
  285. Agent.receive!
  286. }.not_to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
  287. end
  288. it "should not run consumers that have nothing to do" do
  289. do_not_allow.any_instance_of(Agents::TriggerAgent).receive(anything)
  290. Agent.receive!
  291. end
  292. it "should group events" do
  293. mock.any_instance_of(Agents::TriggerAgent).receive(anything).twice { |events|
  294. expect(events.map(&:user).map(&:username).uniq.length).to eq(1)
  295. }
  296. Agent.async_check(agents(:bob_weather_agent).id)
  297. Agent.async_check(agents(:jane_weather_agent).id)
  298. Agent.receive!
  299. end
  300. it "should call receive for each event when no_bulk_receive! is used" do
  301. mock.any_instance_of(Agents::TriggerAgent).receive(anything).twice
  302. stub(Agents::TriggerAgent).no_bulk_receive? { true }
  303. Agent.async_check(agents(:bob_weather_agent).id)
  304. Agent.async_check(agents(:bob_weather_agent).id)
  305. Agent.receive!
  306. end
  307. it "should ignore events that were created before a particular Link" do
  308. agent2 = Agents::SomethingSource.new(:name => "something")
  309. agent2.user = users(:bob)
  310. agent2.save!
  311. agent2.check
  312. mock.any_instance_of(Agents::TriggerAgent).receive(anything).twice
  313. agents(:bob_weather_agent).check # bob_weather_agent makes an event
  314. expect {
  315. Agent.receive! # event gets propagated
  316. }.to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
  317. # This agent creates a few events before we link to it, but after our last check.
  318. agent2.check
  319. agent2.check
  320. # Now we link to it.
  321. agents(:bob_rain_notifier_agent).sources << agent2
  322. expect(agent2.links_as_source.first.event_id_at_creation).to eq(agent2.events.reorder("events.id desc").first.id)
  323. expect {
  324. Agent.receive! # but we don't receive those events because they're too old
  325. }.not_to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
  326. # Now a new event is created by agent2
  327. agent2.check
  328. expect {
  329. Agent.receive! # and we receive it
  330. }.to change { agents(:bob_rain_notifier_agent).reload.last_checked_event_id }
  331. end
  332. it "should not run agents of deactivated accounts" do
  333. agents(:bob_weather_agent).user.deactivate!
  334. Agent.async_check(agents(:bob_weather_agent).id)
  335. mock(Agent).async_receive(agents(:bob_rain_notifier_agent).id, anything).times(0)
  336. Agent.receive!
  337. end
  338. end
  339. describe ".async_receive" do
  340. it "should not run disabled Agents" do
  341. mock(Agent).find(agents(:bob_rain_notifier_agent).id) { agents(:bob_rain_notifier_agent) }
  342. do_not_allow(agents(:bob_rain_notifier_agent)).receive
  343. agents(:bob_rain_notifier_agent).update_attribute :disabled, true
  344. Agent.async_receive(agents(:bob_rain_notifier_agent).id, [1, 2, 3])
  345. end
  346. end
  347. describe "creating a new agent and then calling .receive!" do
  348. it "should not backfill events for a newly created agent" do
  349. Event.delete_all
  350. sender = Agents::SomethingSource.new(:name => "Sending Agent")
  351. sender.user = users(:bob)
  352. sender.save!
  353. sender.create_event :payload => {}
  354. sender.create_event :payload => {}
  355. expect(sender.events.count).to eq(2)
  356. receiver = Agents::CannotBeScheduled.new(:name => "Receiving Agent")
  357. receiver.user = users(:bob)
  358. receiver.sources << sender
  359. receiver.save!
  360. expect(receiver.events.count).to eq(0)
  361. Agent.receive!
  362. expect(receiver.events.count).to eq(0)
  363. sender.create_event :payload => {}
  364. Agent.receive!
  365. expect(receiver.events.count).to eq(1)
  366. end
  367. end
  368. describe "creating agents with propagate_immediately = true" do
  369. it "should schedule subagent events immediately" do
  370. Event.delete_all
  371. sender = Agents::SomethingSource.new(:name => "Sending Agent")
  372. sender.user = users(:bob)
  373. sender.save!
  374. receiver = Agents::CannotBeScheduled.new(
  375. :name => "Receiving Agent",
  376. )
  377. receiver.propagate_immediately = true
  378. receiver.user = users(:bob)
  379. receiver.sources << sender
  380. receiver.save!
  381. sender.create_event :payload => {"message" => "new payload"}
  382. expect(sender.events.count).to eq(1)
  383. expect(receiver.events.count).to eq(1)
  384. #should be true without calling Agent.receive!
  385. end
  386. it "should only schedule receiving agents that are set to propagate_immediately" do
  387. Event.delete_all
  388. sender = Agents::SomethingSource.new(:name => "Sending Agent")
  389. sender.user = users(:bob)
  390. sender.save!
  391. im_receiver = Agents::CannotBeScheduled.new(
  392. :name => "Immediate Receiving Agent",
  393. )
  394. im_receiver.propagate_immediately = true
  395. im_receiver.user = users(:bob)
  396. im_receiver.sources << sender
  397. im_receiver.save!
  398. slow_receiver = Agents::CannotBeScheduled.new(
  399. :name => "Slow Receiving Agent",
  400. )
  401. slow_receiver.user = users(:bob)
  402. slow_receiver.sources << sender
  403. slow_receiver.save!
  404. sender.create_event :payload => {"message" => "new payload"}
  405. expect(sender.events.count).to eq(1)
  406. expect(im_receiver.events.count).to eq(1)
  407. #we should get the quick one
  408. #but not the slow one
  409. expect(slow_receiver.events.count).to eq(0)
  410. Agent.receive!
  411. #now we should have one in both
  412. expect(im_receiver.events.count).to eq(1)
  413. expect(slow_receiver.events.count).to eq(1)
  414. end
  415. end
  416. describe "validations" do
  417. it "calls validate_options" do
  418. agent = Agents::SomethingSource.new(:name => "something")
  419. agent.user = users(:bob)
  420. agent.options[:bad] = true
  421. expect(agent).to have(1).error_on(:base)
  422. agent.options[:bad] = false
  423. expect(agent).to have(0).errors_on(:base)
  424. end
  425. it "makes options symbol-indifferent before validating" do
  426. agent = Agents::SomethingSource.new(:name => "something")
  427. agent.user = users(:bob)
  428. agent.options["bad"] = true
  429. expect(agent).to have(1).error_on(:base)
  430. agent.options["bad"] = false
  431. expect(agent).to have(0).errors_on(:base)
  432. end
  433. it "makes memory symbol-indifferent before validating" do
  434. agent = Agents::SomethingSource.new(:name => "something")
  435. agent.user = users(:bob)
  436. agent.memory["bad"] = 2
  437. agent.save
  438. expect(agent.memory[:bad]).to eq(2)
  439. end
  440. it "should work when assigned a hash or JSON string" do
  441. agent = Agents::SomethingSource.new(:name => "something")
  442. agent.memory = {}
  443. expect(agent.memory).to eq({})
  444. expect(agent.memory["foo"]).to be_nil
  445. agent.memory = ""
  446. expect(agent.memory["foo"]).to be_nil
  447. expect(agent.memory).to eq({})
  448. agent.memory = '{"hi": "there"}'
  449. expect(agent.memory).to eq({ "hi" => "there" })
  450. agent.memory = '{invalid}'
  451. expect(agent.memory).to eq({ "hi" => "there" })
  452. expect(agent).to have(1).errors_on(:memory)
  453. agent.memory = "{}"
  454. expect(agent.memory["foo"]).to be_nil
  455. expect(agent.memory).to eq({})
  456. expect(agent).to have(0).errors_on(:memory)
  457. agent.options = "{}"
  458. expect(agent.options["foo"]).to be_nil
  459. expect(agent.options).to eq({})
  460. expect(agent).to have(0).errors_on(:options)
  461. agent.options = '{"hi": 2}'
  462. expect(agent.options["hi"]).to eq(2)
  463. expect(agent).to have(0).errors_on(:options)
  464. agent.options = '{"hi": wut}'
  465. expect(agent.options["hi"]).to eq(2)
  466. expect(agent).to have(1).errors_on(:options)
  467. expect(agent.errors_on(:options)).to include("was assigned invalid JSON")
  468. agent.options = 5
  469. expect(agent.options["hi"]).to eq(2)
  470. expect(agent).to have(1).errors_on(:options)
  471. 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)
  472. end
  473. it "should not allow source agents owned by other people" do
  474. agent = Agents::SomethingSource.new(:name => "something")
  475. agent.user = users(:bob)
  476. agent.source_ids = [agents(:bob_weather_agent).id]
  477. expect(agent).to have(0).errors_on(:sources)
  478. agent.source_ids = [agents(:jane_weather_agent).id]
  479. expect(agent).to have(1).errors_on(:sources)
  480. agent.user = users(:jane)
  481. expect(agent).to have(0).errors_on(:sources)
  482. end
  483. it "should not allow target agents owned by other people" do
  484. agent = Agents::SomethingSource.new(:name => "something")
  485. agent.user = users(:bob)
  486. agent.receiver_ids = [agents(:bob_weather_agent).id]
  487. expect(agent).to have(0).errors_on(:receivers)
  488. agent.receiver_ids = [agents(:jane_weather_agent).id]
  489. expect(agent).to have(1).errors_on(:receivers)
  490. agent.user = users(:jane)
  491. expect(agent).to have(0).errors_on(:receivers)
  492. end
  493. it "should not allow controller agents owned by other people" do
  494. agent = Agents::SomethingSource.new(:name => "something")
  495. agent.user = users(:bob)
  496. agent.controller_ids = [agents(:bob_weather_agent).id]
  497. expect(agent).to have(0).errors_on(:controllers)
  498. agent.controller_ids = [agents(:jane_weather_agent).id]
  499. expect(agent).to have(1).errors_on(:controllers)
  500. agent.user = users(:jane)
  501. expect(agent).to have(0).errors_on(:controllers)
  502. end
  503. it "should not allow control target agents owned by other people" do
  504. agent = Agents::CannotBeScheduled.new(:name => "something")
  505. agent.user = users(:bob)
  506. agent.control_target_ids = [agents(:bob_weather_agent).id]
  507. expect(agent).to have(0).errors_on(:control_targets)
  508. agent.control_target_ids = [agents(:jane_weather_agent).id]
  509. expect(agent).to have(1).errors_on(:control_targets)
  510. agent.user = users(:jane)
  511. expect(agent).to have(0).errors_on(:control_targets)
  512. end
  513. it "should not allow scenarios owned by other people" do
  514. agent = Agents::SomethingSource.new(:name => "something")
  515. agent.user = users(:bob)
  516. agent.scenario_ids = [scenarios(:bob_weather).id]
  517. expect(agent).to have(0).errors_on(:scenarios)
  518. agent.scenario_ids = [scenarios(:bob_weather).id, scenarios(:jane_weather).id]
  519. expect(agent).to have(1).errors_on(:scenarios)
  520. agent.scenario_ids = [scenarios(:jane_weather).id]
  521. expect(agent).to have(1).errors_on(:scenarios)
  522. agent.user = users(:jane)
  523. expect(agent).to have(0).errors_on(:scenarios)
  524. end
  525. it "validates keep_events_for" do
  526. agent = Agents::SomethingSource.new(:name => "something")
  527. agent.user = users(:bob)
  528. expect(agent).to be_valid
  529. agent.keep_events_for = nil
  530. expect(agent).to have(1).errors_on(:keep_events_for)
  531. agent.keep_events_for = 1000
  532. expect(agent).to have(1).errors_on(:keep_events_for)
  533. agent.keep_events_for = ""
  534. expect(agent).to have(1).errors_on(:keep_events_for)
  535. agent.keep_events_for = 5.days.to_i
  536. expect(agent).to be_valid
  537. agent.keep_events_for = 0
  538. expect(agent).to be_valid
  539. agent.keep_events_for = 365.days.to_i
  540. expect(agent).to be_valid
  541. # Rails seems to call to_i on the input. This guards against future changes to that behavior.
  542. agent.keep_events_for = "drop table;"
  543. expect(agent.keep_events_for).to eq(0)
  544. end
  545. end
  546. describe "cleaning up now-expired events" do
  547. before do
  548. @time = "2014-01-01 01:00:00 +00:00"
  549. travel_to @time do
  550. @agent = Agents::SomethingSource.new(:name => "something")
  551. @agent.keep_events_for = 5.days
  552. @agent.user = users(:bob)
  553. @agent.save!
  554. @event = @agent.create_event :payload => { "hello" => "world" }
  555. expect(@event.expires_at.to_i).to be_within(2).of(5.days.from_now.to_i)
  556. end
  557. end
  558. describe "when keep_events_for has not changed" do
  559. it "does nothing" do
  560. mock(@agent).update_event_expirations!.times(0)
  561. @agent.options[:foo] = "bar1"
  562. @agent.save!
  563. @agent.options[:foo] = "bar1"
  564. @agent.keep_events_for = 5.days
  565. @agent.save!
  566. end
  567. end
  568. describe "when keep_events_for is changed" do
  569. it "updates events' expires_at" do
  570. travel_to @time do
  571. expect {
  572. @agent.options[:foo] = "bar1"
  573. @agent.keep_events_for = 3.days
  574. @agent.save!
  575. }.to change { @event.reload.expires_at }
  576. expect(@event.expires_at.to_i).to be_within(2).of(3.days.from_now.to_i)
  577. end
  578. end
  579. it "updates events relative to their created_at" do
  580. @event.update_attribute :created_at, 2.days.ago
  581. expect(@event.reload.created_at.to_i).to be_within(2).of(2.days.ago.to_i)
  582. expect {
  583. @agent.options[:foo] = "bar2"
  584. @agent.keep_events_for = 3.days
  585. @agent.save!
  586. }.to change { @event.reload.expires_at }
  587. 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
  588. end
  589. it "nulls out expires_at when keep_events_for is set to 0" do
  590. expect {
  591. @agent.options[:foo] = "bar"
  592. @agent.keep_events_for = 0
  593. @agent.save!
  594. }.to change { @event.reload.expires_at }.to(nil)
  595. end
  596. end
  597. end
  598. describe "Agent.build_clone" do
  599. before do
  600. Event.delete_all
  601. @sender = Agents::SomethingSource.new(
  602. name: 'Agent (2)',
  603. options: { foo: 'bar2' },
  604. schedule: '5pm')
  605. @sender.user = users(:bob)
  606. @sender.save!
  607. @sender.create_event :payload => {}
  608. @sender.create_event :payload => {}
  609. expect(@sender.events.count).to eq(2)
  610. @receiver = Agents::CannotBeScheduled.new(
  611. name: 'Agent',
  612. options: { foo: 'bar3' },
  613. keep_events_for: 3.days,
  614. propagate_immediately: true)
  615. @receiver.user = users(:bob)
  616. @receiver.sources << @sender
  617. @receiver.memory[:test] = 1
  618. @receiver.save!
  619. end
  620. it "should create a clone of a given agent for editing" do
  621. sender_clone = users(:bob).agents.build_clone(@sender)
  622. expect(sender_clone.attributes).to eq(Agent.new.attributes.
  623. update(@sender.slice(:user_id, :type,
  624. :options, :schedule, :keep_events_for, :propagate_immediately)).
  625. update('name' => 'Agent (2) (2)', 'options' => { 'foo' => 'bar2' }))
  626. expect(sender_clone.source_ids).to eq([])
  627. receiver_clone = users(:bob).agents.build_clone(@receiver)
  628. expect(receiver_clone.attributes).to eq(Agent.new.attributes.
  629. update(@receiver.slice(:user_id, :type,
  630. :options, :schedule, :keep_events_for, :propagate_immediately)).
  631. update('name' => 'Agent (3)', 'options' => { 'foo' => 'bar3' }))
  632. expect(receiver_clone.source_ids).to eq([@sender.id])
  633. end
  634. end
  635. end
  636. describe ".trigger_web_request" do
  637. class Agents::WebRequestReceiver < Agent
  638. cannot_be_scheduled!
  639. end
  640. before do
  641. stub(Agents::WebRequestReceiver).valid_type?("Agents::WebRequestReceiver") { true }
  642. end
  643. context "when .receive_web_request is defined" do
  644. before do
  645. @agent = Agents::WebRequestReceiver.new(:name => "something")
  646. @agent.user = users(:bob)
  647. @agent.save!
  648. def @agent.receive_web_request(params, method, format)
  649. memory['last_request'] = [params, method, format]
  650. ['Ok!', 200]
  651. end
  652. end
  653. it "calls the .receive_web_request hook, updates last_web_request_at, and saves" do
  654. request = ActionDispatch::Request.new({
  655. 'action_dispatch.request.request_parameters' => { :some_param => "some_value" },
  656. 'REQUEST_METHOD' => "POST",
  657. 'HTTP_ACCEPT' => 'text/html'
  658. })
  659. @agent.trigger_web_request(request)
  660. expect(@agent.reload.memory['last_request']).to eq([ { "some_param" => "some_value" }, "post", "text/html" ])
  661. expect(@agent.last_web_request_at.to_i).to be_within(1).of(Time.now.to_i)
  662. end
  663. end
  664. context "when .receive_web_request is defined with just request" do
  665. before do
  666. @agent = Agents::WebRequestReceiver.new(:name => "something")
  667. @agent.user = users(:bob)
  668. @agent.save!
  669. def @agent.receive_web_request(request)
  670. memory['last_request'] = [request.params, request.method_symbol.to_s, request.format, {'HTTP_X_CUSTOM_HEADER' => request.headers['HTTP_X_CUSTOM_HEADER']}]
  671. ['Ok!', 200]
  672. end
  673. end
  674. it "calls the .trigger_web_request with headers, and they get passed to .receive_web_request" do
  675. request = ActionDispatch::Request.new({
  676. 'action_dispatch.request.request_parameters' => { :some_param => "some_value" },
  677. 'REQUEST_METHOD' => "POST",
  678. 'HTTP_ACCEPT' => 'text/html',
  679. 'HTTP_X_CUSTOM_HEADER' => "foo"
  680. })
  681. @agent.trigger_web_request(request)
  682. expect(@agent.reload.memory['last_request']).to eq([ { "some_param" => "some_value" }, "post", "text/html", {'HTTP_X_CUSTOM_HEADER' => "foo"} ])
  683. expect(@agent.last_web_request_at.to_i).to be_within(1).of(Time.now.to_i)
  684. end
  685. end
  686. context "when .receive_webhook is defined" do
  687. before do
  688. @agent = Agents::WebRequestReceiver.new(:name => "something")
  689. @agent.user = users(:bob)
  690. @agent.save!
  691. def @agent.receive_webhook(params)
  692. memory['last_webhook_request'] = params
  693. ['Ok!', 200]
  694. end
  695. end
  696. it "outputs a deprecation warning and calls .receive_webhook with the params" do
  697. request = ActionDispatch::Request.new({
  698. 'action_dispatch.request.request_parameters' => { :some_param => "some_value" },
  699. 'REQUEST_METHOD' => "POST",
  700. 'HTTP_ACCEPT' => 'text/html'
  701. })
  702. mock(Rails.logger).warn("DEPRECATED: The .receive_webhook method is deprecated, please switch your Agent to use .receive_web_request.")
  703. @agent.trigger_web_request(request)
  704. expect(@agent.reload.memory['last_webhook_request']).to eq({ "some_param" => "some_value" })
  705. expect(@agent.last_web_request_at.to_i).to be_within(1).of(Time.now.to_i)
  706. end
  707. end
  708. end
  709. describe "scopes" do
  710. describe "of_type" do
  711. it "should accept classes" do
  712. agents = Agent.of_type(Agents::WebsiteAgent)
  713. expect(agents).to include(agents(:bob_website_agent))
  714. expect(agents).to include(agents(:jane_website_agent))
  715. expect(agents).not_to include(agents(:bob_weather_agent))
  716. end
  717. it "should accept strings" do
  718. agents = Agent.of_type("Agents::WebsiteAgent")
  719. expect(agents).to include(agents(:bob_website_agent))
  720. expect(agents).to include(agents(:jane_website_agent))
  721. expect(agents).not_to include(agents(:bob_weather_agent))
  722. end
  723. it "should accept instances of an Agent" do
  724. agents = Agent.of_type(agents(:bob_website_agent))
  725. expect(agents).to include(agents(:bob_website_agent))
  726. expect(agents).to include(agents(:jane_website_agent))
  727. expect(agents).not_to include(agents(:bob_weather_agent))
  728. end
  729. end
  730. end
  731. describe "#create_event" do
  732. describe "when the agent has keep_events_for set" do
  733. before do
  734. expect(agents(:jane_weather_agent).keep_events_for).to be > 0
  735. end
  736. it "sets expires_at on created events" do
  737. event = agents(:jane_weather_agent).create_event :payload => { 'hi' => 'there' }
  738. expect(event.expires_at.to_i).to be_within(5).of(agents(:jane_weather_agent).keep_events_for.seconds.from_now.to_i)
  739. end
  740. end
  741. describe "when the agent does not have keep_events_for set" do
  742. before do
  743. expect(agents(:jane_website_agent).keep_events_for).to eq(0)
  744. end
  745. it "does not set expires_at on created events" do
  746. event = agents(:jane_website_agent).create_event :payload => { 'hi' => 'there' }
  747. expect(event.expires_at).to be_nil
  748. end
  749. end
  750. end
  751. describe '.last_checked_event_id' do
  752. it "should be updated by setting drop_pending_events to true" do
  753. agent = agents(:bob_rain_notifier_agent)
  754. agent.last_checked_event_id = nil
  755. agent.save!
  756. agent.update!(drop_pending_events: true)
  757. expect(agent.reload.last_checked_event_id).to eq(Event.maximum(:id))
  758. end
  759. it "should not affect a virtual attribute drop_pending_events" do
  760. agent = agents(:bob_rain_notifier_agent)
  761. agent.update!(drop_pending_events: true)
  762. expect(agent.reload.drop_pending_events).to eq(false)
  763. end
  764. end
  765. describe ".drop_pending_events" do
  766. before do
  767. stub_request(:any, /darksky/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/weather.json")), status: 200)
  768. end
  769. it "should drop pending events while the agent was disabled when set to true" do
  770. agent1 = agents(:bob_weather_agent)
  771. agent2 = agents(:bob_rain_notifier_agent)
  772. expect {
  773. expect {
  774. Agent.async_check(agent1.id)
  775. Agent.receive!
  776. }.to change { agent1.events.count }.by(1)
  777. }.to change { agent2.events.count }.by(0)
  778. agent2.disabled = true
  779. agent2.save!
  780. expect {
  781. expect {
  782. Agent.async_check(agent1.id)
  783. Agent.receive!
  784. }.to change { agent1.events.count }.by(1)
  785. }.not_to change { agent2.events.count }
  786. agent2.disabled = false
  787. agent2.drop_pending_events = true
  788. agent2.save!
  789. expect {
  790. Agent.receive!
  791. }.not_to change { agent2.events.count }
  792. end
  793. end
  794. end
  795. describe AgentDrop do
  796. def interpolate(string, agent)
  797. agent.interpolate_string(string, "agent" => agent)
  798. end
  799. before do
  800. @wsa1 = Agents::WebsiteAgent.new(
  801. name: 'XKCD',
  802. options: {
  803. expected_update_period_in_days: 2,
  804. type: 'html',
  805. url: 'http://xkcd.com/',
  806. mode: 'on_change',
  807. extract: {
  808. url: { css: '#comic img', value: '@src' },
  809. title: { css: '#comic img', value: '@alt' },
  810. },
  811. },
  812. schedule: 'every_1h',
  813. keep_events_for: 2.days)
  814. @wsa1.user = users(:bob)
  815. @wsa1.save!
  816. @wsa2 = Agents::WebsiteAgent.new(
  817. name: 'Dilbert',
  818. options: {
  819. expected_update_period_in_days: 2,
  820. type: 'html',
  821. url: 'http://dilbert.com/',
  822. mode: 'on_change',
  823. extract: {
  824. url: { css: '[id^=strip_enlarged_] img', value: '@src' },
  825. title: { css: '.STR_DateStrip', value: 'string(.)' },
  826. },
  827. },
  828. schedule: 'every_12h',
  829. keep_events_for: 2.days)
  830. @wsa2.user = users(:bob)
  831. @wsa2.save!
  832. @efa = Agents::EventFormattingAgent.new(
  833. name: 'Formatter',
  834. options: {
  835. instructions: {
  836. message: '{{agent.name}}: {{title}} {{url}}',
  837. agent: '{{agent.type}}',
  838. },
  839. mode: 'clean',
  840. matchers: [],
  841. skip_created_at: 'false',
  842. },
  843. keep_events_for: 2.days,
  844. propagate_immediately: true)
  845. @efa.user = users(:bob)
  846. @efa.sources << @wsa1 << @wsa2
  847. @efa.memory[:test] = 1
  848. @efa.save!
  849. @wsa1.reload
  850. @wsa2.reload
  851. end
  852. it 'should be created via Agent#to_liquid' do
  853. expect(@wsa1.to_liquid.class).to be(AgentDrop)
  854. expect(@wsa2.to_liquid.class).to be(AgentDrop)
  855. expect(@efa.to_liquid.class).to be(AgentDrop)
  856. end
  857. it 'should have .id, .type and .name' do
  858. t = '[{{agent.id}}]{{agent.type}}: {{agent.name}}'
  859. expect(interpolate(t, @wsa1)).to eq("[#{@wsa1.id}]WebsiteAgent: XKCD")
  860. expect(interpolate(t, @wsa2)).to eq("[#{@wsa2.id}]WebsiteAgent: Dilbert")
  861. expect(interpolate(t, @efa)).to eq("[#{@efa.id}]EventFormattingAgent: Formatter")
  862. end
  863. it 'should have .options' do
  864. t = '{{agent.options.url}}'
  865. expect(interpolate(t, @wsa1)).to eq('http://xkcd.com/')
  866. expect(interpolate(t, @wsa2)).to eq('http://dilbert.com/')
  867. expect(interpolate('{{agent.options.instructions.message}}',
  868. @efa)).to eq('{{agent.name}}: {{title}} {{url}}')
  869. end
  870. it 'should have .sources' do
  871. t = '{{agent.sources.size}}: {{agent.sources | map:"name" | join:", "}}'
  872. expect(interpolate(t, @wsa1)).to eq('0: ')
  873. expect(interpolate(t, @wsa2)).to eq('0: ')
  874. expect(interpolate(t, @efa)).to eq('2: XKCD, Dilbert')
  875. t = '{{agent.sources.first.name}}..{{agent.sources.last.name}}'
  876. expect(interpolate(t, @wsa1)).to eq('..')
  877. expect(interpolate(t, @wsa2)).to eq('..')
  878. expect(interpolate(t, @efa)).to eq('XKCD..Dilbert')
  879. t = '{{agent.sources[1].name}}'
  880. expect(interpolate(t, @efa)).to eq('Dilbert')
  881. end
  882. it 'should have .receivers' do
  883. t = '{{agent.receivers.size}}: {{agent.receivers | map:"name" | join:", "}}'
  884. expect(interpolate(t, @wsa1)).to eq('1: Formatter')
  885. expect(interpolate(t, @wsa2)).to eq('1: Formatter')
  886. expect(interpolate(t, @efa)).to eq('0: ')
  887. end
  888. it 'should have .working' do
  889. stub(@wsa1).working? { false }
  890. stub(@wsa2).working? { true }
  891. stub(@efa).working? { false }
  892. t = '{% if agent.working %}healthy{% else %}unhealthy{% endif %}'
  893. expect(interpolate(t, @wsa1)).to eq('unhealthy')
  894. expect(interpolate(t, @wsa2)).to eq('healthy')
  895. expect(interpolate(t, @efa)).to eq('unhealthy')
  896. end
  897. it 'should have .url' do
  898. t = '{{ agent.url }}'
  899. expect(interpolate(t, @wsa1)).to match(/http:\/\/localhost(?::\d+)?\/agents\/#{@wsa1.id}/)
  900. expect(interpolate(t, @wsa2)).to match(/http:\/\/localhost(?::\d+)?\/agents\/#{@wsa2.id}/)
  901. expect(interpolate(t, @efa)).to match(/http:\/\/localhost(?::\d+)?\/agents\/#{@efa.id}/)
  902. end
  903. end