123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- require 'rails_helper'
- describe Agents::TriggerAgent do
- before do
- @valid_params = {
- 'name' => "my trigger agent",
- 'options' => {
- 'expected_receive_period_in_days' => 2,
- 'rules' => [{
- 'type' => "regex",
- 'value' => "a\\db",
- 'path' => "foo.bar.baz",
- }],
- 'message' => "I saw '{{foo.bar.baz}}' from {{name}}"
- }
- }
- @checker = Agents::TriggerAgent.new(@valid_params)
- @checker.user = users(:bob)
- @checker.save!
- @event = Event.new
- @event.agent = agents(:bob_rain_notifier_agent)
- @event.payload = { 'foo' => { "bar" => { 'baz' => "a2b" } },
- 'name' => "Joe" }
- end
- describe "validation" do
- before do
- expect(@checker).to be_valid
- end
- it "should validate presence of message" do
- @checker.options['message'] = nil
- expect(@checker).not_to be_valid
- @checker.options['message'] = ''
- expect(@checker).not_to be_valid
- end
- it "should be valid without a message when 'keep_event' is set" do
- @checker.options['keep_event'] = 'true'
- @checker.options['message'] = ''
- expect(@checker).to be_valid
- end
- it "if present, 'keep_event' must equal true or false" do
- @checker.options['keep_event'] = 'true'
- expect(@checker).to be_valid
- @checker.options['keep_event'] = 'false'
- expect(@checker).to be_valid
- @checker.options['keep_event'] = ''
- expect(@checker).to be_valid
- @checker.options['keep_event'] = 'tralse'
- expect(@checker).not_to be_valid
- end
- it "validates that 'must_match' is a positive integer, not greater than the number of rules, if provided" do
- @checker.options['must_match'] = '1'
- expect(@checker).to be_valid
- @checker.options['must_match'] = '0'
- expect(@checker).not_to be_valid
- @checker.options['must_match'] = 'wrong'
- expect(@checker).not_to be_valid
- @checker.options['must_match'] = ''
- expect(@checker).to be_valid
- @checker.options.delete('must_match')
- expect(@checker).to be_valid
- @checker.options['must_match'] = '2'
- expect(@checker).not_to be_valid
- expect(@checker.errors[:base].first).to match(/equal to or less than the number of rules/)
- end
- it "should validate the three fields in each rule" do
- @checker.options['rules'] << { 'path' => "foo", 'type' => "fake", 'value' => "6" }
- expect(@checker).not_to be_valid
- @checker.options['rules'].last['type'] = "field!=value"
- expect(@checker).to be_valid
- @checker.options['rules'].last.delete('value')
- expect(@checker).not_to be_valid
- @checker.options['rules'].last['value'] = ''
- expect(@checker).to be_valid
- @checker.options['rules'].last['value'] = nil
- expect(@checker).to be_valid
- @checker.options['rules'].last.delete('value')
- expect(@checker).not_to be_valid
- @checker.options['rules'].last['value'] = ['a']
- expect(@checker).to be_valid
- @checker.options['rules'].last['path'] = ''
- expect(@checker).not_to be_valid
- end
- it "should validate non-hash rules" do
- @checker.options['rules'] << "{% if status == 'ok' %}true{% endif %}"
- expect(@checker).to be_valid
- @checker.options['rules'] << []
- expect(@checker).not_to be_valid
- end
- end
- describe "#working?" do
- it "checks to see if the Agent has received any events in the last 'expected_receive_period_in_days' days" do
- @event.save!
- expect(@checker).not_to be_working # no events have ever been received
- Agents::TriggerAgent.async_receive(@checker.id, [@event.id])
- expect(@checker.reload).to be_working # Events received
- three_days_from_now = 3.days.from_now
- allow(Time).to receive(:now) { three_days_from_now }
- expect(@checker.reload).not_to be_working # too much time has passed
- end
- end
- describe "#receive" do
- it "handles regex" do
- @event.payload['foo']['bar']['baz'] = "a222b"
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @event.payload['foo']['bar']['baz'] = "a2b"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "handles array of regex" do
- @event.payload['foo']['bar']['baz'] = "a222b"
- @checker.options['rules'][0] = {
- 'type' => "regex",
- 'value' => ["a\\db", "a\\Wb"],
- 'path' => "foo.bar.baz",
- }
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @event.payload['foo']['bar']['baz'] = "a2b"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- @event.payload['foo']['bar']['baz'] = "a b"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "handles negated regex" do
- @event.payload['foo']['bar']['baz'] = "a2b"
- @checker.options['rules'][0] = {
- 'type' => "!regex",
- 'value' => "a\\db",
- 'path' => "foo.bar.baz",
- }
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @event.payload['foo']['bar']['baz'] = "a22b"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "handles array of negated regex" do
- @event.payload['foo']['bar']['baz'] = "a2b"
- @checker.options['rules'][0] = {
- 'type' => "!regex",
- 'value' => ["a\\db", "a2b"],
- 'path' => "foo.bar.baz",
- }
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @event.payload['foo']['bar']['baz'] = "a3b"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "puts can extract values into the message based on paths" do
- @checker.receive([@event])
- expect(Event.last.payload['message']).to eq("I saw 'a2b' from Joe")
- end
- it "handles numerical comparisons" do
- @event.payload['foo']['bar']['baz'] = "5"
- @checker.options['rules'].first['value'] = 6
- @checker.options['rules'].first['type'] = "field<value"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- @checker.options['rules'].first['value'] = 3
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- end
- it "handles array of numerical comparisons" do
- @event.payload['foo']['bar']['baz'] = "5"
- @checker.options['rules'].first['value'] = [6, 3]
- @checker.options['rules'].first['type'] = "field<value"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- @checker.options['rules'].first['value'] = [4, 3]
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- end
- it "handles exact comparisons" do
- @event.payload['foo']['bar']['baz'] = "hello world"
- @checker.options['rules'].first['type'] = "field==value"
- @checker.options['rules'].first['value'] = "hello there"
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @checker.options['rules'].first['value'] = "hello world"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "handles array of exact comparisons" do
- @event.payload['foo']['bar']['baz'] = "hello world"
- @checker.options['rules'].first['type'] = "field==value"
- @checker.options['rules'].first['value'] = ["hello there", "hello universe"]
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @checker.options['rules'].first['value'] = ["hello world", "hello universe"]
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "handles negated comparisons" do
- @event.payload['foo']['bar']['baz'] = "hello world"
- @checker.options['rules'].first['type'] = "field!=value"
- @checker.options['rules'].first['value'] = "hello world"
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @checker.options['rules'].first['value'] = "hello there"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "handles array of negated comparisons" do
- @event.payload['foo']['bar']['baz'] = "hello world"
- @checker.options['rules'].first['type'] = "field!=value"
- @checker.options['rules'].first['value'] = ["hello world", "hello world"]
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @checker.options['rules'].first['value'] = ["hello there", "hello world"]
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "handles array of `not in` comparisons" do
- @event.payload['foo']['bar']['baz'] = "hello world"
- @checker.options['rules'].first['type'] = "not in"
- @checker.options['rules'].first['value'] = ["hello world", "hello world"]
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @checker.options['rules'].first['value'] = ["hello there", "hello world"]
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @checker.options['rules'].first['value'] = ["hello there", "hello here"]
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- end
- it "does fine without dots in the path" do
- @event.payload = { 'hello' => "world" }
- @checker.options['rules'].first['type'] = "field==value"
- @checker.options['rules'].first['path'] = "hello"
- @checker.options['rules'].first['value'] = "world"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- @checker.options['rules'].first['path'] = "foo"
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @checker.options['rules'].first['value'] = "hi"
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- end
- it "handles multiple events" do
- event2 = Event.new
- event2.agent = agents(:bob_weather_agent)
- event2.payload = { 'foo' => { 'bar' => { 'baz' => "a2b" } } }
- event3 = Event.new
- event3.agent = agents(:bob_weather_agent)
- event3.payload = { 'foo' => { 'bar' => { 'baz' => "a222b" } } }
- expect {
- @checker.receive([@event, event2, event3])
- }.to change { Event.count }.by(2)
- end
- it "handles Liquid rules" do
- event1 = Event.create!(
- agent: agents(:bob_rain_notifier_agent),
- payload: { 'hello' => 'world1', 'created_at' => '2019-03-27T08:54:12+09:00' }
- )
- event2 = Event.create!(
- agent: agents(:bob_rain_notifier_agent),
- payload: { 'hello' => 'world2', 'created_at' => '2019-03-27T09:17:01+09:00' }
- )
- @checker.options['message'] = '{{hello}}'
- @checker.options['rules'] = [
- "{% assign value = created_at | date: '%s' | plus: 0 %}{% assign threshold = 'now' | date: '%s' | minus: 86400 %}{% if value > threshold %}true{% endif %}"
- ]
- expect {
- travel_to(Time.parse('2019-03-28T00:00:00+00:00')) {
- @checker.receive([event1, event2])
- }
- }.to change { Event.count }.by(1)
- expect(Event.last.payload['message']).to eq("world2")
- end
- describe "with multiple rules" do
- before do
- @checker.options['rules'] << {
- 'type' => "field>=value",
- 'value' => "4",
- 'path' => "foo.bing"
- }
- end
- it "handles ANDing rules together" do
- @event.payload['foo']["bing"] = "5"
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- @event.payload['foo']["bing"] = "2"
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- end
- it "can accept a partial rule set match when 'must_match' is present and less than the total number of rules" do
- @checker.options['must_match'] = "1"
- @event.payload['foo']["bing"] = "5" # 5 > 4
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- @event.payload['foo']["bing"] = "2" # 2 !> 4
- expect {
- @checker.receive([@event])
- }.to(change { Event.count }) # but the first one matches
- @checker.options['must_match'] = "2"
- @event.payload['foo']["bing"] = "5" # 5 > 4
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- @event.payload['foo']["bing"] = "2" # 2 !> 4
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count }) # only 1 matches, we needed 2
- end
- end
- describe "when 'keep_event' is true" do
- before do
- @checker.options['keep_event'] = 'true'
- @event.payload['foo']['bar']['baz'] = "5"
- @checker.options['rules'].first['type'] = "field<value"
- end
- it "can re-emit the origin event" do
- @checker.options['rules'].first['value'] = 3
- @checker.options['message'] = ''
- @event.payload['message'] = 'hi there'
- expect {
- @checker.receive([@event])
- }.not_to(change { Event.count })
- @checker.options['rules'].first['value'] = 6
- expect {
- @checker.receive([@event])
- }.to change { Event.count }.by(1)
- expect(@checker.most_recent_event.payload).to eq(@event.payload)
- end
- it "merges 'message' into the original event when present" do
- @checker.options['rules'].first['value'] = 6
- @checker.receive([@event])
- expect(@checker.most_recent_event.payload).to eq(@event.payload.merge(message: "I saw '5' from Joe"))
- end
- end
- end
- end
|