agent_spec.rb 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. require 'spec_helper'
  2. describe Agent do
  3. describe ".run_schedule" do
  4. before do
  5. Agents::WeatherAgent.count.should > 0
  6. Agents::WebsiteAgent.count.should > 0
  7. end
  8. it "runs agents with the given schedule" do
  9. weather_agent_ids = [agents(:bob_weather_agent), agents(:jane_weather_agent)].map(&:id)
  10. stub(Agents::WeatherAgent).async_check(anything) {|agent_id| weather_agent_ids.delete(agent_id) }
  11. stub(Agents::WebsiteAgent).async_check(agents(:bob_website_agent).id)
  12. Agent.run_schedule("midnight")
  13. weather_agent_ids.should be_empty
  14. end
  15. it "groups agents by type" do
  16. mock(Agents::WeatherAgent).bulk_check("midnight").once
  17. mock(Agents::WebsiteAgent).bulk_check("midnight").once
  18. Agent.run_schedule("midnight")
  19. end
  20. it "only runs agents with the given schedule" do
  21. do_not_allow(Agents::WebsiteAgent).async_check
  22. Agent.run_schedule("blah")
  23. end
  24. end
  25. describe "changes to type" do
  26. it "validates types" do
  27. source = Agent.new
  28. source.type = "Agents::WeatherAgent"
  29. source.should have(0).errors_on(:type)
  30. source.type = "Agents::WebsiteAgent"
  31. source.should have(0).errors_on(:type)
  32. source.type = "Agents::Fake"
  33. source.should have(1).error_on(:type)
  34. end
  35. it "disallows changes to type once a record has been saved" do
  36. source = agents(:bob_website_agent)
  37. source.type = "Agents::WeatherAgent"
  38. source.should have(1).error_on(:type)
  39. end
  40. it "should know about available types" do
  41. Agent.types.should include(Agents::WeatherAgent, Agents::WebsiteAgent)
  42. end
  43. end
  44. describe "with an example Agent" do
  45. class Agents::SomethingSource < Agent
  46. default_schedule "2pm"
  47. def check
  48. create_event :payload => {}
  49. end
  50. def validate_options
  51. errors.add(:base, "bad is bad") if options[:bad]
  52. end
  53. end
  54. class Agents::CannotBeScheduled < Agent
  55. cannot_be_scheduled!
  56. def receive(events)
  57. events.each do |event|
  58. create_event :payload => { :events_received => 1 }
  59. end
  60. end
  61. end
  62. before do
  63. stub(Agents::SomethingSource).valid_type?("Agents::SomethingSource") { true }
  64. stub(Agents::CannotBeScheduled).valid_type?("Agents::CannotBeScheduled") { true }
  65. end
  66. describe ".default_schedule" do
  67. it "stores the default on the class" do
  68. Agents::SomethingSource.default_schedule.should == "2pm"
  69. Agents::SomethingSource.new.default_schedule.should == "2pm"
  70. end
  71. it "sets the default on new instances, allows setting new schedules, and prevents invalid schedules" do
  72. @checker = Agents::SomethingSource.new(:name => "something")
  73. @checker.user = users(:bob)
  74. @checker.schedule.should == "2pm"
  75. @checker.save!
  76. @checker.reload.schedule.should == "2pm"
  77. @checker.update_attribute :schedule, "5pm"
  78. @checker.reload.schedule.should == "5pm"
  79. @checker.reload.schedule.should == "5pm"
  80. @checker.schedule = "this_is_not_real"
  81. @checker.should have(1).errors_on(:schedule)
  82. end
  83. it "should have an empty schedule if it cannot_be_scheduled" do
  84. @checker = Agents::CannotBeScheduled.new(:name => "something")
  85. @checker.user = users(:bob)
  86. @checker.schedule.should be_nil
  87. @checker.should be_valid
  88. @checker.schedule = "5pm"
  89. @checker.save!
  90. @checker.schedule.should be_nil
  91. @checker.schedule = "5pm"
  92. @checker.should have(0).errors_on(:schedule)
  93. @checker.schedule.should be_nil
  94. end
  95. end
  96. describe "#create_event" do
  97. before do
  98. @checker = Agents::SomethingSource.new(:name => "something")
  99. @checker.user = users(:bob)
  100. @checker.save!
  101. end
  102. it "should use the checker's user" do
  103. @checker.check
  104. Event.last.user.should == @checker.user
  105. end
  106. it "should log an error if the Agent has been marked with 'cannot_create_events!'" do
  107. mock(@checker).can_create_events? { false }
  108. lambda {
  109. @checker.check
  110. }.should_not change { Event.count }
  111. @checker.logs.first.message.should =~ /cannot create events/i
  112. end
  113. end
  114. describe ".async_check" do
  115. before do
  116. @checker = Agents::SomethingSource.new(:name => "something")
  117. @checker.user = users(:bob)
  118. @checker.save!
  119. end
  120. it "records last_check_at and calls check on the given Agent" do
  121. mock(@checker).check.once {
  122. @checker.options[:new] = true
  123. }
  124. mock(Agent).find(@checker.id) { @checker }
  125. @checker.last_check_at.should be_nil
  126. Agents::SomethingSource.async_check(@checker.id)
  127. @checker.reload.last_check_at.should be_within(2).of(Time.now)
  128. @checker.reload.options[:new].should be_true # Show that we save options
  129. end
  130. it "should log exceptions" do
  131. mock(@checker).check.once {
  132. raise "foo"
  133. }
  134. mock(Agent).find(@checker.id) { @checker }
  135. lambda {
  136. Agents::SomethingSource.async_check(@checker.id)
  137. }.should raise_error
  138. log = @checker.logs.first
  139. log.message.should =~ /Exception/
  140. log.level.should == 4
  141. end
  142. end
  143. describe ".receive! and .async_receive" do
  144. before do
  145. stub_request(:any, /wunderground/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/weather.json")), :status => 200)
  146. stub.any_instance_of(Agents::WeatherAgent).is_tomorrow?(anything) { true }
  147. end
  148. it "should use available events" do
  149. mock.any_instance_of(Agents::TriggerAgent).receive(anything).once
  150. Agent.async_check(agents(:bob_weather_agent).id)
  151. Agent.receive!
  152. end
  153. it "should log exceptions" do
  154. mock.any_instance_of(Agents::TriggerAgent).receive(anything).once {
  155. raise "foo"
  156. }
  157. Agent.async_check(agents(:bob_weather_agent).id)
  158. lambda {
  159. Agent.async_receive(agents(:bob_rain_notifier_agent).id, [agents(:bob_weather_agent).events.last.id])
  160. }.should raise_error
  161. log = agents(:bob_rain_notifier_agent).logs.first
  162. log.message.should =~ /Exception/
  163. log.level.should == 4
  164. end
  165. it "should track when events have been seen and not received them again" do
  166. mock.any_instance_of(Agents::TriggerAgent).receive(anything).once
  167. Agent.async_check(agents(:bob_weather_agent).id)
  168. Agent.receive!
  169. Agent.receive!
  170. end
  171. it "should not run consumers that have nothing to do" do
  172. do_not_allow.any_instance_of(Agents::TriggerAgent).receive(anything)
  173. Agent.receive!
  174. end
  175. it "should group events" do
  176. mock.any_instance_of(Agents::TriggerAgent).receive(anything).twice { |events|
  177. events.map(&:user).map(&:username).uniq.length.should == 1
  178. }
  179. Agent.async_check(agents(:bob_weather_agent).id)
  180. Agent.async_check(agents(:jane_weather_agent).id)
  181. Agent.receive!
  182. end
  183. end
  184. describe "creating a new agent and then calling .receive!" do
  185. it "should not backfill events for a newly created agent" do
  186. Event.delete_all
  187. sender = Agents::SomethingSource.new(:name => "Sending Agent")
  188. sender.user = users(:bob)
  189. sender.save!
  190. sender.create_event :payload => {}
  191. sender.create_event :payload => {}
  192. sender.events.count.should == 2
  193. receiver = Agents::CannotBeScheduled.new(:name => "Receiving Agent")
  194. receiver.user = users(:bob)
  195. receiver.sources << sender
  196. receiver.save!
  197. receiver.events.count.should == 0
  198. Agent.receive!
  199. receiver.events.count.should == 0
  200. sender.create_event :payload => {}
  201. Agent.receive!
  202. receiver.events.count.should == 1
  203. end
  204. end
  205. describe "validations" do
  206. it "calls validate_options" do
  207. agent = Agents::SomethingSource.new(:name => "something")
  208. agent.user = users(:bob)
  209. agent.options[:bad] = true
  210. agent.should have(1).error_on(:base)
  211. agent.options[:bad] = false
  212. agent.should have(0).errors_on(:base)
  213. end
  214. it "makes options symbol-indifferent before validating" do
  215. agent = Agents::SomethingSource.new(:name => "something")
  216. agent.user = users(:bob)
  217. agent.options["bad"] = true
  218. agent.should have(1).error_on(:base)
  219. agent.options["bad"] = false
  220. agent.should have(0).errors_on(:base)
  221. end
  222. it "makes memory symbol-indifferent before validating" do
  223. agent = Agents::SomethingSource.new(:name => "something")
  224. agent.user = users(:bob)
  225. agent.memory["bad"] = 2
  226. agent.save
  227. agent.memory[:bad].should == 2
  228. end
  229. it "should work when assigned a hash or JSON string" do
  230. agent = Agents::SomethingSource.new(:name => "something")
  231. agent.memory = {}
  232. agent.memory.should == {}
  233. agent.memory["foo"].should be_nil
  234. agent.memory = ""
  235. agent.memory["foo"].should be_nil
  236. agent.memory.should == {}
  237. agent.memory = '{"hi": "there"}'
  238. agent.memory.should == { "hi" => "there" }
  239. agent.memory = '{invalid}'
  240. agent.memory.should == { "hi" => "there" }
  241. agent.should have(1).errors_on(:memory)
  242. agent.memory = "{}"
  243. agent.memory["foo"].should be_nil
  244. agent.memory.should == {}
  245. agent.should have(0).errors_on(:memory)
  246. agent.options = "{}"
  247. agent.options["foo"].should be_nil
  248. agent.options.should == {}
  249. agent.should have(0).errors_on(:options)
  250. agent.options = '{"hi": 2}'
  251. agent.options["hi"].should == 2
  252. agent.should have(0).errors_on(:options)
  253. agent.options = '{"hi": wut}'
  254. agent.options["hi"].should == 2
  255. agent.should have(1).errors_on(:options)
  256. agent.errors_on(:options).should include("was assigned invalid JSON")
  257. agent.options = 5
  258. agent.options["hi"].should == 2
  259. agent.should have(1).errors_on(:options)
  260. agent.errors_on(:options).should include("cannot be set to an instance of Fixnum")
  261. end
  262. it "should not allow agents owned by other people" do
  263. agent = Agents::SomethingSource.new(:name => "something")
  264. agent.user = users(:bob)
  265. agent.source_ids = [agents(:bob_weather_agent).id]
  266. agent.should have(0).errors_on(:sources)
  267. agent.source_ids = [agents(:jane_weather_agent).id]
  268. agent.should have(1).errors_on(:sources)
  269. agent.user = users(:jane)
  270. agent.should have(0).errors_on(:sources)
  271. end
  272. it "validates keep_events_for" do
  273. agent = Agents::SomethingSource.new(:name => "something")
  274. agent.user = users(:bob)
  275. agent.should be_valid
  276. agent.keep_events_for = nil
  277. agent.should have(1).errors_on(:keep_events_for)
  278. agent.keep_events_for = 1000
  279. agent.should have(1).errors_on(:keep_events_for)
  280. agent.keep_events_for = ""
  281. agent.should have(1).errors_on(:keep_events_for)
  282. agent.keep_events_for = 5
  283. agent.should be_valid
  284. agent.keep_events_for = 0
  285. agent.should be_valid
  286. agent.keep_events_for = 365
  287. agent.should be_valid
  288. # Rails seems to call to_i on the input. This guards against future changes to that behavior.
  289. agent.keep_events_for = "drop table;"
  290. agent.keep_events_for.should == 0
  291. end
  292. end
  293. describe "cleaning up now-expired events" do
  294. before do
  295. @agent = Agents::SomethingSource.new(:name => "something")
  296. @agent.keep_events_for = 5
  297. @agent.user = users(:bob)
  298. @agent.save!
  299. @event = @agent.create_event :payload => { "hello" => "world" }
  300. @event.expires_at.to_i.should be_within(2).of(5.days.from_now.to_i)
  301. end
  302. describe "when keep_events_for has not changed" do
  303. it "does nothing" do
  304. mock(@agent).update_event_expirations!.times(0)
  305. @agent.options[:foo] = "bar1"
  306. @agent.save!
  307. @agent.options[:foo] = "bar1"
  308. @agent.keep_events_for = 5
  309. @agent.save!
  310. end
  311. end
  312. describe "when keep_events_for is changed" do
  313. it "updates events' expires_at" do
  314. lambda {
  315. @agent.options[:foo] = "bar1"
  316. @agent.keep_events_for = 3
  317. @agent.save!
  318. }.should change { @event.reload.expires_at }
  319. @event.expires_at.to_i.should be_within(2).of(3.days.from_now.to_i)
  320. end
  321. it "updates events relative to their created_at" do
  322. @event.update_attribute :created_at, 2.days.ago
  323. @event.reload.created_at.to_i.should be_within(2).of(2.days.ago.to_i)
  324. lambda {
  325. @agent.options[:foo] = "bar2"
  326. @agent.keep_events_for = 3
  327. @agent.save!
  328. }.should change { @event.reload.expires_at }
  329. @event.expires_at.to_i.should be_within(2).of(1.days.from_now.to_i)
  330. end
  331. it "nulls out expires_at when keep_events_for is set to 0" do
  332. lambda {
  333. @agent.options[:foo] = "bar"
  334. @agent.keep_events_for = 0
  335. @agent.save!
  336. }.should change { @event.reload.expires_at }.to(nil)
  337. end
  338. end
  339. end
  340. end
  341. describe "recent_error_logs?" do
  342. it "returns true if last_error_log_at is near last_event_at" do
  343. agent = Agent.new
  344. agent.last_error_log_at = 10.minutes.ago
  345. agent.last_event_at = 10.minutes.ago
  346. agent.recent_error_logs?.should be_true
  347. agent.last_error_log_at = 11.minutes.ago
  348. agent.last_event_at = 10.minutes.ago
  349. agent.recent_error_logs?.should be_true
  350. agent.last_error_log_at = 5.minutes.ago
  351. agent.last_event_at = 10.minutes.ago
  352. agent.recent_error_logs?.should be_true
  353. agent.last_error_log_at = 15.minutes.ago
  354. agent.last_event_at = 10.minutes.ago
  355. agent.recent_error_logs?.should be_false
  356. agent.last_error_log_at = 2.days.ago
  357. agent.last_event_at = 10.minutes.ago
  358. agent.recent_error_logs?.should be_false
  359. end
  360. end
  361. describe "scopes" do
  362. describe "of_type" do
  363. it "should accept classes" do
  364. agents = Agent.of_type(Agents::WebsiteAgent)
  365. agents.should include(agents(:bob_website_agent))
  366. agents.should include(agents(:jane_website_agent))
  367. agents.should_not include(agents(:bob_weather_agent))
  368. end
  369. it "should accept strings" do
  370. agents = Agent.of_type("Agents::WebsiteAgent")
  371. agents.should include(agents(:bob_website_agent))
  372. agents.should include(agents(:jane_website_agent))
  373. agents.should_not include(agents(:bob_weather_agent))
  374. end
  375. it "should accept instances of an Agent" do
  376. agents = Agent.of_type(agents(:bob_website_agent))
  377. agents.should include(agents(:bob_website_agent))
  378. agents.should include(agents(:jane_website_agent))
  379. agents.should_not include(agents(:bob_weather_agent))
  380. end
  381. end
  382. end
  383. describe "#create_event" do
  384. describe "when the agent has keep_events_for set" do
  385. before do
  386. agents(:jane_weather_agent).keep_events_for.should > 0
  387. end
  388. it "sets expires_at on created events" do
  389. event = agents(:jane_weather_agent).create_event :payload => { 'hi' => 'there' }
  390. event.expires_at.to_i.should be_within(5).of(agents(:jane_weather_agent).keep_events_for.days.from_now.to_i)
  391. end
  392. end
  393. describe "when the agent does not have keep_events_for set" do
  394. before do
  395. agents(:jane_website_agent).keep_events_for.should == 0
  396. end
  397. it "does not set expires_at on created events" do
  398. event = agents(:jane_website_agent).create_event :payload => { 'hi' => 'there' }
  399. event.expires_at.should be_nil
  400. end
  401. end
  402. end
  403. end