@@ -24,6 +24,7 @@ gem 'coffee-rails', '~> 3.2.1' |
||
| 24 | 24 |
gem 'uglifier', '>= 1.0.3' |
| 25 | 25 |
gem 'select2-rails' |
| 26 | 26 |
gem 'jquery-rails' |
| 27 |
+gem 'ace-rails-ap' |
|
| 27 | 28 |
|
| 28 | 29 |
gem 'geokit-rails3' |
| 29 | 30 |
gem 'kramdown' |
@@ -37,6 +38,8 @@ gem 'twitter-stream', '>=0.1.16' |
||
| 37 | 38 |
gem 'em-http-request' |
| 38 | 39 |
gem 'weibo_2' |
| 39 | 40 |
|
| 41 |
+gem 'therubyracer' |
|
| 42 |
+ |
|
| 40 | 43 |
platforms :ruby_18 do |
| 41 | 44 |
gem 'system_timer' |
| 42 | 45 |
gem 'fastercsv' |
@@ -1,6 +1,7 @@ |
||
| 1 | 1 |
GEM |
| 2 | 2 |
remote: https://rubygems.org/ |
| 3 | 3 |
specs: |
| 4 |
+ ace-rails-ap (2.0.1) |
|
| 4 | 5 |
actionmailer (3.2.13) |
| 5 | 6 |
actionpack (= 3.2.13) |
| 6 | 7 |
mail (~> 2.5.3) |
@@ -124,6 +125,7 @@ GEM |
||
| 124 | 125 |
actionpack (>= 3.0.0) |
| 125 | 126 |
activesupport (>= 3.0.0) |
| 126 | 127 |
kramdown (1.1.0) |
| 128 |
+ libv8 (3.16.14.3) |
|
| 127 | 129 |
mail (2.5.4) |
| 128 | 130 |
mime-types (~> 1.16) |
| 129 | 131 |
treetop (~> 1.4.8) |
@@ -174,6 +176,7 @@ GEM |
||
| 174 | 176 |
rake (10.1.0) |
| 175 | 177 |
rdoc (3.12.2) |
| 176 | 178 |
json (~> 1.4) |
| 179 |
+ ref (1.0.5) |
|
| 177 | 180 |
rest-client (1.6.7) |
| 178 | 181 |
mime-types (>= 1.16) |
| 179 | 182 |
rr (1.1.2) |
@@ -224,6 +227,9 @@ GEM |
||
| 224 | 227 |
system_timer (1.2.4) |
| 225 | 228 |
term-ansicolor (1.2.2) |
| 226 | 229 |
tins (~> 0.8) |
| 230 |
+ therubyracer (0.12.0) |
|
| 231 |
+ libv8 (~> 3.16.14.0) |
|
| 232 |
+ ref |
|
| 227 | 233 |
thor (0.18.1) |
| 228 | 234 |
tilt (1.4.1) |
| 229 | 235 |
tins (0.13.1) |
@@ -267,6 +273,7 @@ PLATFORMS |
||
| 267 | 273 |
ruby |
| 268 | 274 |
|
| 269 | 275 |
DEPENDENCIES |
| 276 |
+ ace-rails-ap |
|
| 270 | 277 |
better_errors |
| 271 | 278 |
binding_of_caller |
| 272 | 279 |
bootstrap-kaminari-views |
@@ -300,6 +307,7 @@ DEPENDENCIES |
||
| 300 | 307 |
select2-rails |
| 301 | 308 |
shoulda-matchers |
| 302 | 309 |
system_timer |
| 310 |
+ therubyracer |
|
| 303 | 311 |
twilio-ruby |
| 304 | 312 |
|
| 305 | 313 |
twitter-stream (>= 0.1.16) |
@@ -0,0 +1,27 @@ |
||
| 1 |
+#= require ace/ace |
|
| 2 |
+#= require ace/mode-javascript.js |
|
| 3 |
+#= require ace/mode-markdown.js |
|
| 4 |
+#= require_self |
|
| 5 |
+ |
|
| 6 |
+$ -> |
|
| 7 |
+ editor = ace.edit("ace-credential-value")
|
|
| 8 |
+ editor.getSession().setTabSize(2) |
|
| 9 |
+ editor.getSession().setUseSoftTabs(true) |
|
| 10 |
+ editor.getSession().setUseWrapMode(false) |
|
| 11 |
+ editor.setTheme("ace/theme/chrome")
|
|
| 12 |
+ |
|
| 13 |
+ setMode = -> |
|
| 14 |
+ mode = $("#user_credential_mode").val()
|
|
| 15 |
+ if mode == 'java_script' |
|
| 16 |
+ editor.getSession().setMode("ace/mode/javascript")
|
|
| 17 |
+ else |
|
| 18 |
+ editor.getSession().setMode("ace/mode/text")
|
|
| 19 |
+ |
|
| 20 |
+ setMode() |
|
| 21 |
+ $("#user_credential_mode").on 'change', setMode
|
|
| 22 |
+ |
|
| 23 |
+ $textarea = $('#user_credential_credential_value').hide()
|
|
| 24 |
+ editor.getSession().setValue($textarea.val()) |
|
| 25 |
+ |
|
| 26 |
+ $textarea.closest('form').on 'submit', ->
|
|
| 27 |
+ $textarea.val(editor.getSession().getValue()) |
@@ -126,3 +126,11 @@ span.not-applicable:after {
|
||
| 126 | 126 |
#show-tabs li a.recent-errors {
|
| 127 | 127 |
font-weight: bold; |
| 128 | 128 |
} |
| 129 |
+ |
|
| 130 |
+// Credentials |
|
| 131 |
+ |
|
| 132 |
+#ace-credential-value {
|
|
| 133 |
+ position: relative; |
|
| 134 |
+ width: 940px; |
|
| 135 |
+ height: 400px; |
|
| 136 |
+} |
@@ -16,7 +16,7 @@ class Agent < ActiveRecord::Base |
||
| 16 | 16 |
load_types_in "Agents" |
| 17 | 17 |
|
| 18 | 18 |
SCHEDULES = %w[every_2m every_5m every_10m every_30m every_1h every_2h every_5h every_12h every_1d every_2d every_7d |
| 19 |
- midnight 1am 2am 3am 4am 5am 6am 7am 8am 9am 10am 11am noon 1pm 2pm 3pm 4pm 5pm 6pm 7pm 8pm 9pm 10pm 11pm] |
|
| 19 |
+ midnight 1am 2am 3am 4am 5am 6am 7am 8am 9am 10am 11am noon 1pm 2pm 3pm 4pm 5pm 6pm 7pm 8pm 9pm 10pm 11pm never] |
|
| 20 | 20 |
|
| 21 | 21 |
EVENT_RETENTION_SCHEDULES = [["Forever", 0], ["1 day", 1], *([2, 3, 4, 5, 7, 14, 21, 30, 45, 90, 180, 365].map {|n| ["#{n} days", n] })]
|
| 22 | 22 |
|
@@ -296,6 +296,7 @@ class Agent < ActiveRecord::Base |
||
| 296 | 296 |
# Given a schedule name, run `check` via `bulk_check` on all Agents with that schedule. |
| 297 | 297 |
# This is called by bin/schedule.rb for each schedule in `SCHEDULES`. |
| 298 | 298 |
def run_schedule(schedule) |
| 299 |
+ return if schedule == 'never' |
|
| 299 | 300 |
types = where(:schedule => schedule).group(:type).pluck(:type) |
| 300 | 301 |
types.each do |type| |
| 301 | 302 |
type.constantize.bulk_check(schedule) |
@@ -0,0 +1,186 @@ |
||
| 1 |
+require 'date' |
|
| 2 |
+require 'cgi' |
|
| 3 |
+ |
|
| 4 |
+module Agents |
|
| 5 |
+ class JavaScriptAgent < Agent |
|
| 6 |
+ default_schedule "never" |
|
| 7 |
+ |
|
| 8 |
+ description <<-MD |
|
| 9 |
+ This Agent allows you to write code in JavaScript that can create and receive events. If other Agents aren't meeting your needs, try this one! |
|
| 10 |
+ |
|
| 11 |
+ You can put code in the `code` option, or put your code in a Credential and reference it from `code` with `credential:<name>` (recommended). |
|
| 12 |
+ |
|
| 13 |
+ You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent in the JavaScript environment: |
|
| 14 |
+ |
|
| 15 |
+ * `this.createEvent(payload)` |
|
| 16 |
+ * `this.incomingEvents()` |
|
| 17 |
+ * `this.memory()` |
|
| 18 |
+ * `this.memory(key)` |
|
| 19 |
+ * `this.memory(keyToSet, valueToSet)` |
|
| 20 |
+ * `this.options()` |
|
| 21 |
+ * `this.options(key)` |
|
| 22 |
+ * `this.log(message)` |
|
| 23 |
+ * `this.error(message)` |
|
| 24 |
+ MD |
|
| 25 |
+ |
|
| 26 |
+ def validate_options |
|
| 27 |
+ cred_name = credential_referenced_by_code |
|
| 28 |
+ if cred_name |
|
| 29 |
+ errors.add(:base, "The credential '#{cred_name}' referenced by code cannot be found") unless credential(cred_name).present?
|
|
| 30 |
+ else |
|
| 31 |
+ errors.add(:base, "The 'code' option is required") unless options['code'].present? |
|
| 32 |
+ end |
|
| 33 |
+ end |
|
| 34 |
+ |
|
| 35 |
+ def working? |
|
| 36 |
+ return false if recent_error_logs? |
|
| 37 |
+ |
|
| 38 |
+ if options['expected_update_period_in_days'].present? |
|
| 39 |
+ return false unless event_created_within?(options['expected_update_period_in_days']) |
|
| 40 |
+ end |
|
| 41 |
+ |
|
| 42 |
+ if options['expected_receive_period_in_days'].present? |
|
| 43 |
+ return false unless last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago |
|
| 44 |
+ end |
|
| 45 |
+ |
|
| 46 |
+ true |
|
| 47 |
+ end |
|
| 48 |
+ |
|
| 49 |
+ def check |
|
| 50 |
+ log_errors do |
|
| 51 |
+ execute_js("check")
|
|
| 52 |
+ end |
|
| 53 |
+ end |
|
| 54 |
+ |
|
| 55 |
+ def receive(incoming_events) |
|
| 56 |
+ log_errors do |
|
| 57 |
+ execute_js("receive", incoming_events)
|
|
| 58 |
+ end |
|
| 59 |
+ end |
|
| 60 |
+ |
|
| 61 |
+ def default_options |
|
| 62 |
+ js_code = <<-JS |
|
| 63 |
+ Agent.check = function() {
|
|
| 64 |
+ if (this.options('make_event')) {
|
|
| 65 |
+ this.createEvent({ 'message': 'I made an event!' });
|
|
| 66 |
+ var callCount = this.memory('callCount') || 0;
|
|
| 67 |
+ this.memory('callCount', callCount + 1);
|
|
| 68 |
+ } |
|
| 69 |
+ }; |
|
| 70 |
+ |
|
| 71 |
+ Agent.receive = function() {
|
|
| 72 |
+ var events = this.incomingEvents(); |
|
| 73 |
+ for(var i = 0; i < events.length; i++) {
|
|
| 74 |
+ this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload });
|
|
| 75 |
+ } |
|
| 76 |
+ } |
|
| 77 |
+ JS |
|
| 78 |
+ |
|
| 79 |
+ {
|
|
| 80 |
+ "code" => js_code.gsub(/[\n\r\t]/, '').strip, |
|
| 81 |
+ 'expected_receive_period_in_days' => "2", |
|
| 82 |
+ 'expected_update_period_in_days' => "2" |
|
| 83 |
+ } |
|
| 84 |
+ end |
|
| 85 |
+ |
|
| 86 |
+ private |
|
| 87 |
+ |
|
| 88 |
+ def execute_js(js_function, incoming_events = []) |
|
| 89 |
+ js_function = js_function == "check" ? "check" : "receive" |
|
| 90 |
+ context = V8::Context.new |
|
| 91 |
+ context.eval(setup_javascript) |
|
| 92 |
+ |
|
| 93 |
+ context["doCreateEvent"] = lambda { |a, y| create_event(payload: clean_nans(JSON.parse(y))).payload.to_json }
|
|
| 94 |
+ context["getIncomingEvents"] = lambda { |a| incoming_events.to_json }
|
|
| 95 |
+ context["getOptions"] = lambda { |a, x| options.to_json }
|
|
| 96 |
+ context["doLog"] = lambda { |a, x| log x }
|
|
| 97 |
+ context["doError"] = lambda { |a, x| error x }
|
|
| 98 |
+ context["getMemory"] = lambda do |a, x, y| |
|
| 99 |
+ if x && y |
|
| 100 |
+ memory[x] = clean_nans(y) |
|
| 101 |
+ else |
|
| 102 |
+ memory.to_json |
|
| 103 |
+ end |
|
| 104 |
+ end |
|
| 105 |
+ |
|
| 106 |
+ context.eval(code) |
|
| 107 |
+ context.eval("Agent.#{js_function}();")
|
|
| 108 |
+ end |
|
| 109 |
+ |
|
| 110 |
+ def code |
|
| 111 |
+ cred = credential_referenced_by_code |
|
| 112 |
+ if cred |
|
| 113 |
+ credential(cred) || 'Agent.check = function() { this.error("Unable to find credential"); };'
|
|
| 114 |
+ else |
|
| 115 |
+ options['code'] |
|
| 116 |
+ end |
|
| 117 |
+ end |
|
| 118 |
+ |
|
| 119 |
+ def credential_referenced_by_code |
|
| 120 |
+ options['code'] =~ /\Acredential:(.*)\Z/ && $1 |
|
| 121 |
+ end |
|
| 122 |
+ |
|
| 123 |
+ def setup_javascript |
|
| 124 |
+ <<-JS |
|
| 125 |
+ function Agent() {};
|
|
| 126 |
+ |
|
| 127 |
+ Agent.createEvent = function(opts) {
|
|
| 128 |
+ return JSON.parse(doCreateEvent(JSON.stringify(opts))); |
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ Agent.incomingEvents = function() {
|
|
| 132 |
+ return JSON.parse(getIncomingEvents()); |
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ Agent.memory = function(key, value) {
|
|
| 136 |
+ if (typeof(key) !== "undefined" && typeof(value) !== "undefined") {
|
|
| 137 |
+ getMemory(key, value); |
|
| 138 |
+ } else if (typeof(key) !== "undefined") {
|
|
| 139 |
+ return JSON.parse(getMemory())[key]; |
|
| 140 |
+ } else {
|
|
| 141 |
+ return JSON.parse(getMemory()); |
|
| 142 |
+ } |
|
| 143 |
+ } |
|
| 144 |
+ |
|
| 145 |
+ Agent.options = function(key) {
|
|
| 146 |
+ if (typeof(key) !== "undefined") {
|
|
| 147 |
+ return JSON.parse(getOptions())[key]; |
|
| 148 |
+ } else {
|
|
| 149 |
+ return JSON.parse(getOptions()); |
|
| 150 |
+ } |
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ Agent.log = function(message) {
|
|
| 154 |
+ doLog(message); |
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 157 |
+ Agent.error = function(message) {
|
|
| 158 |
+ doError(message); |
|
| 159 |
+ } |
|
| 160 |
+ |
|
| 161 |
+ Agent.check = function(){};
|
|
| 162 |
+ Agent.receive = function(){};
|
|
| 163 |
+ JS |
|
| 164 |
+ end |
|
| 165 |
+ |
|
| 166 |
+ def log_errors |
|
| 167 |
+ begin |
|
| 168 |
+ yield |
|
| 169 |
+ rescue V8::Error => e |
|
| 170 |
+ error "JavaScript error: #{e.message}"
|
|
| 171 |
+ end |
|
| 172 |
+ end |
|
| 173 |
+ |
|
| 174 |
+ def clean_nans(input) |
|
| 175 |
+ if input.is_a?(Array) |
|
| 176 |
+ input.map {|v| clean_nans(v) }
|
|
| 177 |
+ elsif input.is_a?(Hash) |
|
| 178 |
+ input.inject({}) { |m, (k, v)| m[k] = clean_nans(v); m }
|
|
| 179 |
+ elsif input.is_a?(Float) && input.nan? |
|
| 180 |
+ 'NaN' |
|
| 181 |
+ else |
|
| 182 |
+ input |
|
| 183 |
+ end |
|
| 184 |
+ end |
|
| 185 |
+ end |
|
| 186 |
+end |
@@ -1,13 +1,17 @@ |
||
| 1 | 1 |
class UserCredential < ActiveRecord::Base |
| 2 |
- attr_accessible :credential_name, :credential_value |
|
| 2 |
+ MODES = %w[text java_script] |
|
| 3 |
+ |
|
| 4 |
+ attr_accessible :credential_name, :credential_value, :mode |
|
| 3 | 5 |
|
| 4 | 6 |
belongs_to :user |
| 5 | 7 |
|
| 6 | 8 |
validates_presence_of :credential_name |
| 7 | 9 |
validates_presence_of :credential_value |
| 10 |
+ validates_inclusion_of :mode, :in => MODES |
|
| 8 | 11 |
validates_presence_of :user_id |
| 9 | 12 |
validates_uniqueness_of :credential_name, :scope => :user_id |
| 10 | 13 |
|
| 14 |
+ before_validation :default_mode_to_text |
|
| 11 | 15 |
before_save :trim_fields |
| 12 | 16 |
|
| 13 | 17 |
protected |
@@ -16,4 +20,8 @@ class UserCredential < ActiveRecord::Base |
||
| 16 | 20 |
credential_name.strip! |
| 17 | 21 |
credential_value.strip! |
| 18 | 22 |
end |
| 23 |
+ |
|
| 24 |
+ def default_mode_to_text |
|
| 25 |
+ self.mode = 'text' unless mode.present? |
|
| 26 |
+ end |
|
| 19 | 27 |
end |
@@ -44,7 +44,6 @@ |
||
| 44 | 44 |
</div> |
| 45 | 45 |
</div> |
| 46 | 46 |
|
| 47 |
- |
|
| 48 | 47 |
<div class='event-related-region' data-can-create-events="<%= @agent.can_create_events? %>"> |
| 49 | 48 |
<div class="control-group"> |
| 50 | 49 |
<%= f.label :keep_events_for, "Keep events", :class => 'control-label' %> |
@@ -18,9 +18,17 @@ |
||
| 18 | 18 |
</div> |
| 19 | 19 |
|
| 20 | 20 |
<div class="control-group"> |
| 21 |
+ <%= f.label :mode, :class => 'control-label' %> |
|
| 22 |
+ <div class="controls"> |
|
| 23 |
+ <%= f.select :mode, options_for_select(UserCredential::MODES.map {|s| [s.classify, s] }, @user_credential.mode), {}, :class => 'span4' %>
|
|
| 24 |
+ </div> |
|
| 25 |
+ </div> |
|
| 26 |
+ |
|
| 27 |
+ <div class="control-group"> |
|
| 21 | 28 |
<%= f.label :credential_value, :class => 'control-label' %> |
| 22 | 29 |
<div class="controls"> |
| 23 | 30 |
<%= f.text_area :credential_value, :class => 'span8', :rows => 10 %> |
| 31 |
+ <div id="ace-credential-value"></div> |
|
| 24 | 32 |
</div> |
| 25 | 33 |
</div> |
| 26 | 34 |
|
@@ -28,3 +36,5 @@ |
||
| 28 | 36 |
<%= f.submit "Save Credential", :class => "btn btn-primary" %> |
| 29 | 37 |
</div> |
| 30 | 38 |
<% end %> |
| 39 |
+ |
|
| 40 |
+<%= javascript_include_tag "user_credentials" %> |
@@ -48,7 +48,7 @@ Huginn::Application.configure do |
||
| 48 | 48 |
end |
| 49 | 49 |
|
| 50 | 50 |
# Precompile additional assets (application.js.coffee.erb, application.css, and all non-JS/CSS are already added) |
| 51 |
- config.assets.precompile += %w( graphing.js ) |
|
| 51 |
+ config.assets.precompile += %w( graphing.js user_credentials.js ) |
|
| 52 | 52 |
|
| 53 | 53 |
# Enable threaded mode |
| 54 | 54 |
# config.threadsafe! |
@@ -0,0 +1,5 @@ |
||
| 1 |
+class AddModeToUserCredentials < ActiveRecord::Migration |
|
| 2 |
+ def change |
|
| 3 |
+ add_column :user_credentials, :mode, :string, :default => 'text', :null => false |
|
| 4 |
+ end |
|
| 5 |
+end |
@@ -11,7 +11,7 @@ |
||
| 11 | 11 |
# |
| 12 | 12 |
# It's strongly recommended to check this file into your version control system. |
| 13 | 13 |
|
| 14 |
-ActiveRecord::Schema.define(:version => 20140127164931) do |
|
| 14 |
+ActiveRecord::Schema.define(:version => 20140210062747) do |
|
| 15 | 15 |
|
| 16 | 16 |
create_table "agent_logs", :force => true do |t| |
| 17 | 17 |
t.integer "agent_id", :null => false |
@@ -96,11 +96,12 @@ ActiveRecord::Schema.define(:version => 20140127164931) do |
||
| 96 | 96 |
add_index "links", ["source_id", "receiver_id"], :name => "index_links_on_source_id_and_receiver_id" |
| 97 | 97 |
|
| 98 | 98 |
create_table "user_credentials", :force => true do |t| |
| 99 |
- t.integer "user_id", :null => false |
|
| 100 |
- t.string "credential_name", :null => false |
|
| 101 |
- t.text "credential_value", :null => false |
|
| 102 |
- t.datetime "created_at", :null => false |
|
| 103 |
- t.datetime "updated_at", :null => false |
|
| 99 |
+ t.integer "user_id", :null => false |
|
| 100 |
+ t.string "credential_name", :null => false |
|
| 101 |
+ t.text "credential_value", :null => false |
|
| 102 |
+ t.datetime "created_at", :null => false |
|
| 103 |
+ t.datetime "updated_at", :null => false |
|
| 104 |
+ t.string "mode", :default => "text", :null => false |
|
| 104 | 105 |
end |
| 105 | 106 |
|
| 106 | 107 |
add_index "user_credentials", ["user_id", "credential_name"], :name => "index_user_credentials_on_user_id_and_credential_name", :unique => true |
@@ -1,9 +1,9 @@ |
||
| 1 | 1 |
bob_website_agent_event: |
| 2 | 2 |
user: bob |
| 3 | 3 |
agent: bob_website_agent |
| 4 |
- payload: <%= [{ :title => "foo", :url => "http://foo.com" }].to_json.inspect %>
|
|
| 4 |
+ payload: <%= { :title => "foo", :url => "http://foo.com" }.to_json.inspect %>
|
|
| 5 | 5 |
|
| 6 | 6 |
jane_website_agent_event: |
| 7 | 7 |
user: jane |
| 8 | 8 |
agent: jane_website_agent |
| 9 |
- payload: <%= [{ :title => "foo", :url => "http://foo.com" }].to_json.inspect %>
|
|
| 9 |
+ payload: <%= { :title => "foo", :url => "http://foo.com" }.to_json.inspect %>
|
@@ -2,15 +2,19 @@ bob_aws_key: |
||
| 2 | 2 |
user: bob |
| 3 | 3 |
credential_name: aws_key |
| 4 | 4 |
credential_value: 2222222222-bob |
| 5 |
+ mode: text |
|
| 5 | 6 |
bob_aws_secret: |
| 6 | 7 |
user: bob |
| 7 | 8 |
credential_name: aws_secret |
| 8 | 9 |
credential_value: 1111111111-bob |
| 10 |
+ mode: text |
|
| 9 | 11 |
jane_aws_key: |
| 10 | 12 |
user: jane |
| 11 | 13 |
credential_name: aws_key |
| 12 | 14 |
credential_value: 2222222222-jane |
| 15 |
+ mode: text |
|
| 13 | 16 |
jane_aws_secret: |
| 14 | 17 |
user: jane |
| 15 | 18 |
credential_name: aws_secret |
| 16 | 19 |
credential_value: 1111111111-jabe |
| 20 |
+ mode: text |
@@ -25,6 +25,12 @@ describe Agent do |
||
| 25 | 25 |
do_not_allow(Agents::WebsiteAgent).async_check |
| 26 | 26 |
Agent.run_schedule("blah")
|
| 27 | 27 |
end |
| 28 |
+ |
|
| 29 |
+ it "will not run the 'never' schedule" do |
|
| 30 |
+ agents(:bob_weather_agent).update_attribute 'schedule', 'never' |
|
| 31 |
+ do_not_allow(Agents::WebsiteAgent).async_check |
|
| 32 |
+ Agent.run_schedule("never")
|
|
| 33 |
+ end |
|
| 28 | 34 |
end |
| 29 | 35 |
|
| 30 | 36 |
describe "credential" do |
@@ -0,0 +1,228 @@ |
||
| 1 |
+require 'spec_helper' |
|
| 2 |
+ |
|
| 3 |
+describe Agents::JavaScriptAgent do |
|
| 4 |
+ before do |
|
| 5 |
+ @valid_params = {
|
|
| 6 |
+ :name => "somename", |
|
| 7 |
+ :options => {
|
|
| 8 |
+ :code => "Agent.check = function() { this.createEvent({ 'message': 'hi' }); };",
|
|
| 9 |
+ } |
|
| 10 |
+ } |
|
| 11 |
+ |
|
| 12 |
+ @agent = Agents::JavaScriptAgent.new(@valid_params) |
|
| 13 |
+ @agent.user = users(:jane) |
|
| 14 |
+ @agent.save! |
|
| 15 |
+ end |
|
| 16 |
+ |
|
| 17 |
+ describe "validations" do |
|
| 18 |
+ it "requires 'code'" do |
|
| 19 |
+ @agent.should be_valid |
|
| 20 |
+ @agent.options['code'] = '' |
|
| 21 |
+ @agent.should_not be_valid |
|
| 22 |
+ @agent.options.delete('code')
|
|
| 23 |
+ @agent.should_not be_valid |
|
| 24 |
+ end |
|
| 25 |
+ |
|
| 26 |
+ it "accepts a credential, but it must exist" do |
|
| 27 |
+ @agent.should be_valid |
|
| 28 |
+ @agent.options['code'] = 'credential:foo' |
|
| 29 |
+ @agent.should_not be_valid |
|
| 30 |
+ users(:jane).user_credentials.create! :credential_name => "foo", :credential_value => "bar" |
|
| 31 |
+ @agent.reload.should be_valid |
|
| 32 |
+ end |
|
| 33 |
+ end |
|
| 34 |
+ |
|
| 35 |
+ describe "#working?" do |
|
| 36 |
+ describe "when expected_update_period_in_days is set" do |
|
| 37 |
+ it "returns false when more than expected_update_period_in_days have passed since the last event creation" do |
|
| 38 |
+ @agent.options['expected_update_period_in_days'] = 1 |
|
| 39 |
+ @agent.save! |
|
| 40 |
+ @agent.should_not be_working |
|
| 41 |
+ @agent.check |
|
| 42 |
+ @agent.reload.should be_working |
|
| 43 |
+ three_days_from_now = 3.days.from_now |
|
| 44 |
+ stub(Time).now { three_days_from_now }
|
|
| 45 |
+ @agent.should_not be_working |
|
| 46 |
+ end |
|
| 47 |
+ end |
|
| 48 |
+ |
|
| 49 |
+ describe "when expected_receive_period_in_days is set" do |
|
| 50 |
+ it "returns false when more than expected_receive_period_in_days have passed since the last event was received" do |
|
| 51 |
+ @agent.options['expected_receive_period_in_days'] = 1 |
|
| 52 |
+ @agent.save! |
|
| 53 |
+ @agent.should_not be_working |
|
| 54 |
+ Agents::JavaScriptAgent.async_receive @agent.id, [events(:bob_website_agent_event).id] |
|
| 55 |
+ @agent.reload.should be_working |
|
| 56 |
+ two_days_from_now = 2.days.from_now |
|
| 57 |
+ stub(Time).now { two_days_from_now }
|
|
| 58 |
+ @agent.reload.should_not be_working |
|
| 59 |
+ end |
|
| 60 |
+ end |
|
| 61 |
+ end |
|
| 62 |
+ |
|
| 63 |
+ describe "executing code" do |
|
| 64 |
+ it "works by default" do |
|
| 65 |
+ @agent.options = @agent.default_options |
|
| 66 |
+ @agent.options['make_event'] = true |
|
| 67 |
+ @agent.save! |
|
| 68 |
+ |
|
| 69 |
+ lambda {
|
|
| 70 |
+ lambda {
|
|
| 71 |
+ @agent.receive([events(:bob_website_agent_event)]) |
|
| 72 |
+ @agent.check |
|
| 73 |
+ }.should_not change { AgentLog.count }
|
|
| 74 |
+ }.should change { Event.count }.by(2)
|
|
| 75 |
+ end |
|
| 76 |
+ |
|
| 77 |
+ |
|
| 78 |
+ describe "using credentials as code" do |
|
| 79 |
+ before do |
|
| 80 |
+ @agent.user.user_credentials.create :credential_name => 'code-foo', :credential_value => 'Agent.check = function() { this.log("ran it"); };'
|
|
| 81 |
+ @agent.options['code'] = 'credential:code-foo' |
|
| 82 |
+ @agent.save! |
|
| 83 |
+ end |
|
| 84 |
+ |
|
| 85 |
+ it "accepts credentials" do |
|
| 86 |
+ @agent.check |
|
| 87 |
+ AgentLog.last.message.should == "ran it" |
|
| 88 |
+ end |
|
| 89 |
+ |
|
| 90 |
+ it "logs an error when the credential goes away" do |
|
| 91 |
+ @agent.user.user_credentials.delete_all |
|
| 92 |
+ @agent.reload.check |
|
| 93 |
+ AgentLog.last.message.should == "Unable to find credential" |
|
| 94 |
+ end |
|
| 95 |
+ end |
|
| 96 |
+ |
|
| 97 |
+ describe "error handling" do |
|
| 98 |
+ it "should log an error when V8 has issues" do |
|
| 99 |
+ @agent.options['code'] = 'syntax error!' |
|
| 100 |
+ @agent.save! |
|
| 101 |
+ lambda {
|
|
| 102 |
+ lambda {
|
|
| 103 |
+ @agent.check |
|
| 104 |
+ }.should_not raise_error |
|
| 105 |
+ }.should change { AgentLog.count }.by(1)
|
|
| 106 |
+ AgentLog.last.message.should =~ /Unexpected identifier/ |
|
| 107 |
+ AgentLog.last.level.should == 4 |
|
| 108 |
+ end |
|
| 109 |
+ |
|
| 110 |
+ it "should log an error when JavaScript throws" do |
|
| 111 |
+ @agent.options['code'] = 'Agent.check = function() { throw "oh no"; };'
|
|
| 112 |
+ @agent.save! |
|
| 113 |
+ lambda {
|
|
| 114 |
+ lambda {
|
|
| 115 |
+ @agent.check |
|
| 116 |
+ }.should_not raise_error |
|
| 117 |
+ }.should change { AgentLog.count }.by(1)
|
|
| 118 |
+ AgentLog.last.message.should =~ /oh no/ |
|
| 119 |
+ AgentLog.last.level.should == 4 |
|
| 120 |
+ end |
|
| 121 |
+ |
|
| 122 |
+ it "won't store NaNs" do |
|
| 123 |
+ @agent.options['code'] = 'Agent.check = function() { this.memory("foo", NaN); };'
|
|
| 124 |
+ @agent.save! |
|
| 125 |
+ @agent.check |
|
| 126 |
+ @agent.memory['foo'].should == 'NaN' # string |
|
| 127 |
+ @agent.save! |
|
| 128 |
+ lambda { @agent.reload.memory }.should_not raise_error
|
|
| 129 |
+ end |
|
| 130 |
+ end |
|
| 131 |
+ |
|
| 132 |
+ describe "creating events" do |
|
| 133 |
+ it "creates events with this.createEvent in the JavaScript environment" do |
|
| 134 |
+ @agent.options['code'] = 'Agent.check = function() { this.createEvent({ message: "This is an event!", stuff: { foo: 5 } }); };'
|
|
| 135 |
+ @agent.save! |
|
| 136 |
+ lambda {
|
|
| 137 |
+ lambda {
|
|
| 138 |
+ @agent.check |
|
| 139 |
+ }.should_not change { AgentLog.count }
|
|
| 140 |
+ }.should change { Event.count }.by(1)
|
|
| 141 |
+ created_event = @agent.events.last |
|
| 142 |
+ created_event.payload.should == { 'message' => "This is an event!", 'stuff' => { 'foo' => 5 } }
|
|
| 143 |
+ end |
|
| 144 |
+ end |
|
| 145 |
+ |
|
| 146 |
+ describe "logging" do |
|
| 147 |
+ it "can output AgentLogs with this.log and this.error in the JavaScript environment" do |
|
| 148 |
+ @agent.options['code'] = 'Agent.check = function() { this.log("woah"); this.error("WOAH!"); };'
|
|
| 149 |
+ @agent.save! |
|
| 150 |
+ lambda {
|
|
| 151 |
+ lambda {
|
|
| 152 |
+ @agent.check |
|
| 153 |
+ }.should_not raise_error |
|
| 154 |
+ }.should change { AgentLog.count }.by(2)
|
|
| 155 |
+ |
|
| 156 |
+ log1, log2 = AgentLog.last(2) |
|
| 157 |
+ |
|
| 158 |
+ log1.message.should == "woah" |
|
| 159 |
+ log1.level.should == 3 |
|
| 160 |
+ log2.message.should == "WOAH!" |
|
| 161 |
+ log2.level.should == 4 |
|
| 162 |
+ end |
|
| 163 |
+ end |
|
| 164 |
+ |
|
| 165 |
+ describe "getting incoming events" do |
|
| 166 |
+ it "can access incoming events in the JavaScript enviroment via this.incomingEvents" do |
|
| 167 |
+ event = Event.new |
|
| 168 |
+ event.agent = agents(:bob_rain_notifier_agent) |
|
| 169 |
+ event.payload = { :data => "Something you should know about" }
|
|
| 170 |
+ event.save! |
|
| 171 |
+ event.reload |
|
| 172 |
+ |
|
| 173 |
+ @agent.options['code'] = <<-JS |
|
| 174 |
+ Agent.receive = function() {
|
|
| 175 |
+ var events = this.incomingEvents(); |
|
| 176 |
+ for(var i = 0; i < events.length; i++) {
|
|
| 177 |
+ this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload });
|
|
| 178 |
+ } |
|
| 179 |
+ } |
|
| 180 |
+ JS |
|
| 181 |
+ |
|
| 182 |
+ @agent.save! |
|
| 183 |
+ lambda {
|
|
| 184 |
+ lambda {
|
|
| 185 |
+ @agent.receive([events(:bob_website_agent_event), event]) |
|
| 186 |
+ }.should_not change { AgentLog.count }
|
|
| 187 |
+ }.should change { Event.count }.by(2)
|
|
| 188 |
+ created_event = @agent.events.first |
|
| 189 |
+ created_event.payload.should == { 'message' => "I got an event!", 'event_was' => { 'data' => "Something you should know about" } }
|
|
| 190 |
+ end |
|
| 191 |
+ end |
|
| 192 |
+ |
|
| 193 |
+ describe "getting and setting memory, getting options" do |
|
| 194 |
+ it "can access options via this.options and work with memory via this.memory" do |
|
| 195 |
+ @agent.options['code'] = <<-JS |
|
| 196 |
+ Agent.check = function() {
|
|
| 197 |
+ if (this.options('make_event')) {
|
|
| 198 |
+ var callCount = this.memory('callCount') || 0;
|
|
| 199 |
+ this.memory('callCount', callCount + 1);
|
|
| 200 |
+ } |
|
| 201 |
+ }; |
|
| 202 |
+ JS |
|
| 203 |
+ |
|
| 204 |
+ @agent.save! |
|
| 205 |
+ |
|
| 206 |
+ lambda {
|
|
| 207 |
+ lambda {
|
|
| 208 |
+ |
|
| 209 |
+ @agent.check |
|
| 210 |
+ @agent.memory['callCount'].should_not be_present |
|
| 211 |
+ |
|
| 212 |
+ @agent.options['make_event'] = true |
|
| 213 |
+ @agent.check |
|
| 214 |
+ @agent.memory['callCount'].should == 1 |
|
| 215 |
+ |
|
| 216 |
+ @agent.check |
|
| 217 |
+ @agent.memory['callCount'].should == 2 |
|
| 218 |
+ |
|
| 219 |
+ @agent.memory['callCount'] = 20 |
|
| 220 |
+ @agent.check |
|
| 221 |
+ @agent.memory['callCount'].should == 21 |
|
| 222 |
+ |
|
| 223 |
+ }.should_not change { AgentLog.count }
|
|
| 224 |
+ }.should_not change { Event.count }
|
|
| 225 |
+ end |
|
| 226 |
+ end |
|
| 227 |
+ end |
|
| 228 |
+end |