agent_spec.rb 39 KB

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