Add ChangeDetectorAgent, test

New agent will watch the property of an inbound event stream. When that
property changes, it will emit the inbound event.

Michael Cramm 10 年 前
コミット
22bee753cd
共有2 個のファイルを変更した175 個の追加0 個の削除を含む
  1. 76 0
      app/models/agents/change_detector_agent.rb
  2. 99 0
      spec/models/agents/change_detector_agent_spec.rb

+ 76 - 0
app/models/agents/change_detector_agent.rb

@@ -0,0 +1,76 @@
1
+module Agents
2
+  class ChangeDetectorAgent < Agent
3
+    cannot_be_scheduled!
4
+
5
+    description <<-MD
6
+      The ChangeDetectorAgent receives a stream of events and emits a new event when a property of the received event changes.
7
+
8
+      `property` specifies the property to be watched.
9
+
10
+      `expected_update_period_in_days` is used to determine if the Agent is working.
11
+
12
+      The resulting event will be a copy of the received event.
13
+    MD
14
+
15
+    event_description <<-MD
16
+    This will change based on the source event. If you were event from the ShellCommandAgent, your outbound event might look like:
17
+
18
+      {
19
+        'command' => 'pwd',
20
+        'path' => '/home/Huginn',
21
+        'exit_status' => '0',
22
+        'errors' => '',
23
+        'output' => '/home/Huginn'
24
+      }
25
+    MD
26
+
27
+    def default_options
28
+      {
29
+          'property' => 'output',
30
+          'expected_update_period_in_days' => 1
31
+      }
32
+    end
33
+
34
+    def validate_options
35
+      unless options['property'].present? && options['expected_update_period_in_days'].present?
36
+        errors.add(:base, "The property and expected_update_period_in_days fields are all required.")
37
+      end
38
+    end
39
+
40
+    def working?
41
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
42
+    end
43
+
44
+    def receive(incoming_events)
45
+      incoming_events.each do |event|
46
+        handle(interpolated(event), event)
47
+      end
48
+    end
49
+
50
+    private
51
+
52
+    def handle(opts, event = nil)
53
+      property = opts['property']
54
+      if has_changed?(property)
55
+        created_event = create_event :payload => event.payload
56
+
57
+        log("Propagating new event as property has changed to #{property} from #{last_property}", :outbound_event => created_event, :inbound_event => event )
58
+        update_memory(property)
59
+      else
60
+        log("Not propagating as incoming event has not changed from #{last_property}.", :inbound_event => event )
61
+      end
62
+    end
63
+
64
+    def has_changed?(property)
65
+      property != last_property
66
+    end
67
+
68
+    def last_property
69
+      self.memory['last_property']
70
+    end
71
+
72
+    def update_memory(property)
73
+      self.memory['last_property'] = property
74
+    end
75
+  end
76
+end

+ 99 - 0
spec/models/agents/change_detector_agent_spec.rb

@@ -0,0 +1,99 @@
1
+require 'spec_helper'
2
+
3
+describe Agents::ChangeDetectorAgent do
4
+  def create_event(output=nil)
5
+    event = Event.new
6
+    event.agent = agents(:jane_weather_agent)
7
+    event.payload = {
8
+      :command => 'some-command',
9
+      :output => output
10
+    }
11
+    event.save!
12
+
13
+    event
14
+  end
15
+
16
+  before do
17
+    @valid_params = {
18
+        :property  => "{{output}}",
19
+        :expected_update_period_in_days => "1",
20
+      }
21
+
22
+    @checker = Agents::ChangeDetectorAgent.new(:name => "somename", :options => @valid_params)
23
+    @checker.user = users(:jane)
24
+    @checker.save!
25
+  end
26
+
27
+  describe "validation" do
28
+    before do
29
+      @checker.should be_valid
30
+    end
31
+
32
+    it "should validate presence of property" do
33
+      @checker.options[:property] = nil
34
+      @checker.should_not be_valid
35
+    end
36
+
37
+    it "should validate presence of property" do
38
+      @checker.options[:expected_update_period_in_days] = nil
39
+      @checker.should_not be_valid
40
+    end
41
+  end
42
+
43
+  describe "#working?" do
44
+    before :each do
45
+      # Need to create an event otherwise event_created_within? returns nil
46
+      event = create_event
47
+      @checker.receive([event])
48
+    end
49
+
50
+    it "is when event created within :expected_update_period_in_days" do
51
+      @checker.options[:expected_update_period_in_days] = 2
52
+      three_days_from_now = 1.days.from_now
53
+      @checker.should be_working
54
+    end
55
+
56
+    it "isnt when event created outside :expected_update_period_in_days" do
57
+      @checker.options[:expected_update_period_in_days] = 2
58
+      three_days_from_now = 2.days.from_now
59
+      stub(Time).now { three_days_from_now }
60
+
61
+      @checker.should_not be_working
62
+    end
63
+  end
64
+
65
+  describe "#receive" do
66
+    before :each do
67
+      @event = create_event("2014-07-01")
68
+    end
69
+
70
+    it "creates events when memory is empty" do
71
+      @event.payload[:output] = "2014-07-01"
72
+      expect {
73
+        @checker.receive([@event])
74
+      }.to change(Event, :count).by(1)
75
+      Event.last.payload[:command].should == @event.payload[:command]
76
+      Event.last.payload[:output].should == @event.payload[:output]
77
+    end
78
+
79
+    it "creates events when new event changed" do
80
+      @event.payload[:output] = "2014-07-01"
81
+      @checker.receive([@event])
82
+
83
+      event = create_event("2014-08-01")
84
+
85
+      expect {
86
+        @checker.receive([event])
87
+      }.to change(Event, :count).by(1)
88
+    end
89
+
90
+    it "does not create event when no change" do
91
+      @event.payload[:output] = "2014-07-01"
92
+      @checker.receive([@event])
93
+
94
+      expect {
95
+        @checker.receive([@event])
96
+      }.to change(Event, :count).by(0)
97
+    end
98
+  end
99
+end