agent_spec.rb 18KB

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