liquid_output_agent_spec.rb 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. # encoding: utf-8
  2. require 'rails_helper'
  3. describe Agents::LiquidOutputAgent do
  4. let(:agent) do
  5. _agent = Agents::LiquidOutputAgent.new(name: 'My Data Output Agent')
  6. _agent.options = _agent.default_options.merge(
  7. 'secret' => 'a secret1',
  8. 'events_to_show' => 3,
  9. )
  10. _agent.user = users(:bob)
  11. _agent.sources << agents(:bob_website_agent)
  12. _agent.save!
  13. _agent
  14. end
  15. let(:event_struct) { Struct.new(:payload) }
  16. describe "#working?" do
  17. it "checks if events have been received within expected receive period" do
  18. expect(agent).not_to be_working
  19. Agents::LiquidOutputAgent.async_receive agent.id, [events(:bob_website_agent_event).id]
  20. expect(agent.reload).to be_working
  21. two_days_from_now = 2.days.from_now
  22. allow(Time).to receive(:now) { two_days_from_now }
  23. expect(agent.reload).not_to be_working
  24. end
  25. end
  26. describe "validation" do
  27. before do
  28. expect(agent).to be_valid
  29. end
  30. it "should validate presence and length of secret" do
  31. agent.options[:secret] = ""
  32. expect(agent).not_to be_valid
  33. agent.options[:secret] = "foo"
  34. expect(agent).to be_valid
  35. agent.options[:secret] = "foo/bar"
  36. expect(agent).not_to be_valid
  37. agent.options[:secret] = "foo.xml"
  38. expect(agent).not_to be_valid
  39. agent.options[:secret] = false
  40. expect(agent).not_to be_valid
  41. agent.options[:secret] = []
  42. expect(agent).not_to be_valid
  43. agent.options[:secret] = ["foo.xml"]
  44. expect(agent).not_to be_valid
  45. agent.options[:secret] = ["hello", true]
  46. expect(agent).not_to be_valid
  47. agent.options[:secret] = ["hello"]
  48. expect(agent).not_to be_valid
  49. agent.options[:secret] = ["hello", "world"]
  50. expect(agent).not_to be_valid
  51. end
  52. it "should validate presence of expected_receive_period_in_days" do
  53. agent.options[:expected_receive_period_in_days] = ""
  54. expect(agent).not_to be_valid
  55. agent.options[:expected_receive_period_in_days] = 0
  56. expect(agent).not_to be_valid
  57. agent.options[:expected_receive_period_in_days] = -1
  58. expect(agent).not_to be_valid
  59. end
  60. it "should validate the event_limit" do
  61. agent.options[:event_limit] = ""
  62. expect(agent).to be_valid
  63. agent.options[:event_limit] = "1"
  64. expect(agent).to be_valid
  65. agent.options[:event_limit] = "1001"
  66. expect(agent).not_to be_valid
  67. agent.options[:event_limit] = "10000"
  68. expect(agent).not_to be_valid
  69. end
  70. it "should validate the event_limit with relative time" do
  71. agent.options[:event_limit] = "15 minutes"
  72. expect(agent).to be_valid
  73. agent.options[:event_limit] = "1 century"
  74. expect(agent).not_to be_valid
  75. end
  76. it "should not allow non-integer event limits" do
  77. agent.options[:event_limit] = "abc1234"
  78. expect(agent).not_to be_valid
  79. end
  80. end
  81. describe "#receive?" do
  82. let(:key) { SecureRandom.uuid }
  83. let(:value) { SecureRandom.uuid }
  84. let(:incoming_events) do
  85. last_payload = { key => value }
  86. [event_struct.new( { key => SecureRandom.uuid } ),
  87. event_struct.new( { key => SecureRandom.uuid } ),
  88. event_struct.new(last_payload)]
  89. end
  90. describe "and the mode is last event in" do
  91. before { agent.options['mode'] = 'Last event in' }
  92. it "stores the last event in memory" do
  93. agent.receive incoming_events
  94. expect(agent.memory['last_event'][key]).to equal(value)
  95. end
  96. describe "but the casing is wrong" do
  97. before { agent.options['mode'] = 'LAST EVENT IN' }
  98. it "stores the last event in memory" do
  99. agent.receive incoming_events
  100. expect(agent.memory['last_event'][key]).to equal(value)
  101. end
  102. end
  103. end
  104. describe "but the mode is merge" do
  105. let(:second_key) { SecureRandom.uuid }
  106. let(:second_value) { SecureRandom.uuid }
  107. before { agent.options['mode'] = 'Merge events' }
  108. let(:incoming_events) do
  109. last_payload = { key => value }
  110. [event_struct.new( { key => SecureRandom.uuid, second_key => second_value } ),
  111. event_struct.new(last_payload)]
  112. end
  113. it "should merge all of the events passed to it" do
  114. agent.receive incoming_events
  115. expect(agent.memory['last_event'][key]).to equal(value)
  116. expect(agent.memory['last_event'][second_key]).to equal(second_value)
  117. end
  118. describe "but the casing on the mode is wrong" do
  119. before { agent.options['mode'] = 'MERGE EVENTS' }
  120. it "should merge all of the events passed to it" do
  121. agent.receive incoming_events
  122. expect(agent.memory['last_event'][key]).to equal(value)
  123. expect(agent.memory['last_event'][second_key]).to equal(second_value)
  124. end
  125. end
  126. end
  127. describe "but the mode is anything else" do
  128. before { agent.options['mode'] = SecureRandom.uuid }
  129. let(:incoming_events) do
  130. last_payload = { key => value }
  131. [event_struct.new(last_payload)]
  132. end
  133. it "should update nothing" do
  134. expect {
  135. agent.receive incoming_events
  136. }.not_to change { agent.reload.memory&.fetch("last_event", nil) }
  137. end
  138. end
  139. end
  140. describe "#count_limit" do
  141. it "should have a default of 1000" do
  142. agent.options['event_limit'] = nil
  143. expect(agent.send(:count_limit)).to eq(1000)
  144. agent.options['event_limit'] = ''
  145. expect(agent.send(:count_limit)).to eq(1000)
  146. agent.options['event_limit'] = ' '
  147. expect(agent.send(:count_limit)).to eq(1000)
  148. end
  149. it "should convert string count limits to integers" do
  150. agent.options['event_limit'] = '1'
  151. expect(agent.send(:count_limit)).to eq(1)
  152. agent.options['event_limit'] = '2'
  153. expect(agent.send(:count_limit)).to eq(2)
  154. agent.options['event_limit'] = 3
  155. expect(agent.send(:count_limit)).to eq(3)
  156. end
  157. it "should default to 1000 with invalid values" do
  158. agent.options['event_limit'] = SecureRandom.uuid
  159. expect(agent.send(:count_limit)).to eq(1000)
  160. agent.options['event_limit'] = 'John Galt'
  161. expect(agent.send(:count_limit)).to eq(1000)
  162. end
  163. it "should not allow event limits above 1000" do
  164. agent.options['event_limit'] = '1001'
  165. expect(agent.send(:count_limit)).to eq(1000)
  166. agent.options['event_limit'] = '5000'
  167. expect(agent.send(:count_limit)).to eq(1000)
  168. end
  169. end
  170. describe "#receive_web_request" do
  171. let(:secret) { SecureRandom.uuid }
  172. let(:headers) { {} }
  173. let(:params) { { 'secret' => secret } }
  174. let(:format) { :html }
  175. let(:request) {
  176. instance_double(
  177. ActionDispatch::Request,
  178. headers:,
  179. params:,
  180. format:,
  181. )
  182. }
  183. let(:mime_type) { SecureRandom.uuid }
  184. let(:content) { "The key is {{#{key}}}." }
  185. let(:key) { SecureRandom.uuid }
  186. let(:value) { SecureRandom.uuid }
  187. before do
  188. agent.options['secret'] = secret
  189. agent.options['mime_type'] = mime_type
  190. agent.options['content'] = content
  191. agent.memory['last_event'] = { key => value }
  192. agent.save!
  193. agents(:bob_website_agent).events.destroy_all
  194. end
  195. it "should output LF-terminated lines if line_break_is_lf is true" do
  196. agent.options["content"] = "hello\r\nworld\r\n"
  197. result = agent.receive_web_request request
  198. expect(result[0]).to eq "hello\r\nworld\r\n"
  199. agent.options["line_break_is_lf"] = "true"
  200. result = agent.receive_web_request request
  201. expect(result[0]).to eq "hello\nworld\n"
  202. agent.options["line_break_is_lf"] = "false"
  203. result = agent.receive_web_request request
  204. expect(result[0]).to eq "hello\r\nworld\r\n"
  205. end
  206. it 'should respond with custom response header if configured with `response_headers` option' do
  207. agent.options['response_headers'] = { "X-My-Custom-Header" => 'hello' }
  208. result = agent.receive_web_request request
  209. expect(result).to match([
  210. "The key is #{value}.",
  211. 200,
  212. mime_type,
  213. a_hash_including(
  214. "Cache-Control" => a_kind_of(String),
  215. "ETag" => a_kind_of(String),
  216. "Last-Modified" => a_kind_of(String),
  217. "X-My-Custom-Header" => "hello"
  218. )
  219. ])
  220. end
  221. it 'should allow the usage custom liquid tags' do
  222. agent.options['content'] = "{% credential aws_secret %}"
  223. result = agent.receive_web_request request
  224. expect(result).to match([
  225. "1111111111-bob",
  226. 200,
  227. mime_type,
  228. a_hash_including(
  229. "Cache-Control" => a_kind_of(String),
  230. "ETag" => a_kind_of(String),
  231. "Last-Modified" => a_kind_of(String),
  232. )
  233. ])
  234. end
  235. describe "when requested with or without the If-None-Match header" do
  236. let(:now) { Time.now }
  237. it "should conditionally return 304 responses whenever ETag matches" do
  238. travel_to now
  239. allow(agent).to receive(:liquified_content).and_call_original
  240. result = agent.receive_web_request request
  241. expect(result).to eq([
  242. "The key is #{value}.",
  243. 200,
  244. mime_type,
  245. {
  246. 'Cache-Control' => "max-age=#{agent.options['expected_receive_period_in_days'].to_i * 86400}",
  247. 'ETag' => agent.etag,
  248. 'Last-Modified' => agent.memory['last_modified_at'].to_time.httpdate,
  249. }
  250. ])
  251. expect(agent).to have_received(:liquified_content).once
  252. travel_to now + 1
  253. request.headers['If-None-Match'] = agent.etag
  254. result = agent.receive_web_request request
  255. expect(result).to eq([nil, 304, {}])
  256. expect(agent).to have_received(:liquified_content).once
  257. # Receiving an event will update the ETag and Last-Modified-At
  258. event = agents(:bob_website_agent).events.create!(payload: { key => 'latest' })
  259. AgentReceiveJob.perform_now agent.id, [event.id]
  260. agent.reload
  261. travel_to now + 2
  262. result = agent.receive_web_request request
  263. expect(result).to eq(["The key is latest.", 200, mime_type, {
  264. 'Cache-Control' => "max-age=#{agent.options['expected_receive_period_in_days'].to_i * 86400}",
  265. 'ETag' => agent.etag,
  266. 'Last-Modified' => agent.last_receive_at.httpdate,
  267. }])
  268. expect(agent).to have_received(:liquified_content).twice
  269. travel_to now + 3
  270. request.headers['If-None-Match'] = agent.etag
  271. result = agent.receive_web_request request
  272. expect(result).to eq([nil, 304, {}])
  273. expect(agent).to have_received(:liquified_content).twice
  274. # Changing options will update the ETag and Last-Modified-At
  275. agent.update!(options: agent.options.merge('content' => "The key is now {{#{key}}}."))
  276. agent.reload
  277. result = agent.receive_web_request request
  278. expect(result).to eq(["The key is now latest.", 200, mime_type, {
  279. 'Cache-Control' => "max-age=#{agent.options['expected_receive_period_in_days'].to_i * 86400}",
  280. 'ETag' => agent.etag,
  281. 'Last-Modified' => (now + 3).httpdate,
  282. }])
  283. expect(agent).to have_received(:liquified_content).exactly(3).times
  284. end
  285. end
  286. describe "and the mode is last event in" do
  287. before { agent.options['mode'] = 'Last event in' }
  288. it "should render the results as a liquid template from the last event in" do
  289. result = agent.receive_web_request request
  290. expect(result[0]).to eq("The key is #{value}.")
  291. expect(result[1]).to eq(200)
  292. expect(result[2]).to eq(mime_type)
  293. end
  294. describe "but the casing is wrong" do
  295. before { agent.options['mode'] = 'last event in' }
  296. it "should render the results as a liquid template from the last event in" do
  297. result = agent.receive_web_request request
  298. expect(result).to match(["The key is #{value}.", 200, mime_type, a_kind_of(Hash)])
  299. end
  300. end
  301. end
  302. describe "and the mode is merge events" do
  303. before { agent.options['mode'] = 'Merge events' }
  304. it "should render the results as a liquid template from the last event in" do
  305. result = agent.receive_web_request request
  306. expect(result).to match(["The key is #{value}.", 200, mime_type, a_kind_of(Hash)])
  307. end
  308. end
  309. describe "and the mode is last X events" do
  310. before do
  311. agent.options['mode'] = 'Last X events'
  312. agents(:bob_website_agent).create_event payload: {
  313. "name" => "Dagny Taggart",
  314. "book" => "Atlas Shrugged"
  315. }
  316. agents(:bob_website_agent).create_event payload: {
  317. "name" => "John Galt",
  318. "book" => "Atlas Shrugged"
  319. }
  320. agents(:bob_website_agent).create_event payload: {
  321. "name" => "Howard Roark",
  322. "book" => "The Fountainhead"
  323. }
  324. agent.options['content'] = <<EOF
  325. <table>
  326. {% for event in events %}
  327. <tr>
  328. <td>{{ event.name }}</td>
  329. <td>{{ event.book }}</td>
  330. </tr>
  331. {% endfor %}
  332. </table>
  333. EOF
  334. end
  335. it "should render the results as a liquid template from the last event in, limiting to 2" do
  336. agent.options['event_limit'] = 2
  337. result = agent.receive_web_request request
  338. expect(result[0]).to eq <<EOF
  339. <table>
  340. <tr>
  341. <td>Howard Roark</td>
  342. <td>The Fountainhead</td>
  343. </tr>
  344. <tr>
  345. <td>John Galt</td>
  346. <td>Atlas Shrugged</td>
  347. </tr>
  348. </table>
  349. EOF
  350. end
  351. it "should render the results as a liquid template from the last event in, limiting to 1" do
  352. agent.options['event_limit'] = 1
  353. result = agent.receive_web_request request
  354. expect(result[0]).to eq <<EOF
  355. <table>
  356. <tr>
  357. <td>Howard Roark</td>
  358. <td>The Fountainhead</td>
  359. </tr>
  360. </table>
  361. EOF
  362. end
  363. it "should render the results as a liquid template from the last event in, allowing no limit" do
  364. agent.options['event_limit'] = ''
  365. result = agent.receive_web_request request
  366. expect(result[0]).to eq <<EOF
  367. <table>
  368. <tr>
  369. <td>Howard Roark</td>
  370. <td>The Fountainhead</td>
  371. </tr>
  372. <tr>
  373. <td>John Galt</td>
  374. <td>Atlas Shrugged</td>
  375. </tr>
  376. <tr>
  377. <td>Dagny Taggart</td>
  378. <td>Atlas Shrugged</td>
  379. </tr>
  380. </table>
  381. EOF
  382. end
  383. it "should allow the limiting by time, as well" do
  384. one_event = agent.received_events.select { |x| x.payload['name'] == 'John Galt' }.first
  385. one_event.created_at = 2.days.ago
  386. one_event.save!
  387. agent.options['event_limit'] = '1 day'
  388. result = agent.receive_web_request request
  389. expect(result[0]).to eq <<EOF
  390. <table>
  391. <tr>
  392. <td>Howard Roark</td>
  393. <td>The Fountainhead</td>
  394. </tr>
  395. <tr>
  396. <td>Dagny Taggart</td>
  397. <td>Atlas Shrugged</td>
  398. </tr>
  399. </table>
  400. EOF
  401. end
  402. it "should not be case sensitive when limiting on time" do
  403. one_event = agent.received_events.select { |x| x.payload['name'] == 'John Galt' }.first
  404. one_event.created_at = 2.days.ago
  405. one_event.save!
  406. agent.options['event_limit'] = '1 DaY'
  407. result = agent.receive_web_request request
  408. expect(result[0]).to eq <<EOF
  409. <table>
  410. <tr>
  411. <td>Howard Roark</td>
  412. <td>The Fountainhead</td>
  413. </tr>
  414. <tr>
  415. <td>Dagny Taggart</td>
  416. <td>Atlas Shrugged</td>
  417. </tr>
  418. </table>
  419. EOF
  420. end
  421. it "it should continue to work when the event limit is wrong" do
  422. agent.options['event_limit'] = 'five days'
  423. result = agent.receive_web_request request
  424. expect(result[0]).to include("Howard Roark")
  425. expect(result[0]).to include("Dagny Taggart")
  426. expect(result[0]).to include("John Galt")
  427. agent.options['event_limit'] = '5 quibblequarks'
  428. result = agent.receive_web_request request
  429. expect(result[0]).to include("Howard Roark")
  430. expect(result[0]).to include("Dagny Taggart")
  431. expect(result[0]).to include("John Galt")
  432. end
  433. describe "but the mode was set to last X events with the wrong casing" do
  434. before { agent.options['mode'] = 'LAST X EVENTS' }
  435. it "should still work as last x events" do
  436. result = agent.receive_web_request request
  437. expect(result[0]).to include("Howard Roark")
  438. expect(result[0]).to include("Dagny Taggart")
  439. expect(result[0]).to include("John Galt")
  440. end
  441. end
  442. end
  443. describe "but the secret provided does not match" do
  444. before { params['secret'] = SecureRandom.uuid }
  445. it "should return a 401 response" do
  446. result = agent.receive_web_request request
  447. expect(result[0]).to eq("Not Authorized")
  448. expect(result[1]).to eq(401)
  449. end
  450. context 'if the format is json' do
  451. let(:format) { :json }
  452. it "should return a 401 json response " do
  453. result = agent.receive_web_request request
  454. expect(result[0][:error]).to eq("Not Authorized")
  455. expect(result[1]).to eq(401)
  456. end
  457. end
  458. end
  459. end
  460. end