agent_spec.rb 35 KB

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