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 "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