mirror of
https://github.com/Fishwaldo/huginn.git
synced 2025-03-15 19:31:26 +00:00
Importing of Scenarios now works, creating any needed Agents by global guid
This commit is contained in:
parent
5c1fbdb997
commit
64564eb120
31 changed files with 342 additions and 71 deletions
|
@ -158,6 +158,7 @@ h2 .scenario, a span.label.scenario {
|
|||
}
|
||||
|
||||
// Bootstrappy color styles
|
||||
|
||||
.color-danger {
|
||||
color: #d9534f;
|
||||
}
|
||||
|
|
9
app/assets/stylesheets/scenarios.css.scss
Normal file
9
app/assets/stylesheets/scenarios.css.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
.scenario-import {
|
||||
.danger {
|
||||
color: red;
|
||||
font-weight: strong;
|
||||
border: 1px solid red;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ module AssignableTypes
|
|||
const_get(:TYPES).include?(type)
|
||||
end
|
||||
|
||||
def build_for_type(type, user, attributes)
|
||||
def build_for_type(type, user, attributes = {})
|
||||
attributes.delete(:type)
|
||||
|
||||
if valid_type?(type)
|
||||
|
|
13
app/concerns/has_guid.rb
Normal file
13
app/concerns/has_guid.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
module HasGuid
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_save :make_guid
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def make_guid
|
||||
self.guid = SecureRandom.hex unless guid.present?
|
||||
end
|
||||
end
|
|
@ -12,6 +12,7 @@ class Agent < ActiveRecord::Base
|
|||
include JSONSerializedField
|
||||
include RDBMSFunctions
|
||||
include WorkingHelpers
|
||||
include HasGuid
|
||||
|
||||
markdown_class_attributes :description, :event_description
|
||||
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
class Scenario < ActiveRecord::Base
|
||||
attr_accessible :name, :agent_ids, :description, :public
|
||||
include HasGuid
|
||||
|
||||
attr_accessible :name, :agent_ids, :description, :public, :source_url
|
||||
|
||||
belongs_to :user, :counter_cache => :scenario_count, :inverse_of => :scenarios
|
||||
has_many :scenario_memberships, :dependent => :destroy, :inverse_of => :scenario
|
||||
has_many :agents, :through => :scenario_memberships, :inverse_of => :scenarios
|
||||
|
||||
before_save :make_guid
|
||||
|
||||
validates_presence_of :name, :user
|
||||
|
||||
validate :agents_are_owned
|
||||
|
||||
protected
|
||||
|
||||
def make_guid
|
||||
self.guid = SecureRandom.hex unless guid.present?
|
||||
end
|
||||
|
||||
def agents_are_owned
|
||||
errors.add(:agents, "must be owned by you") unless agents.all? {|s| s.user == user }
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ class ScenarioImport
|
|||
include ActiveModel::Callbacks
|
||||
include ActiveModel::Validations::Callbacks
|
||||
|
||||
DANGEROUS_AGENT_TYPES = %w[Agents::ShellCommandAgent]
|
||||
URL_REGEX = /\Ahttps?:\/\//i
|
||||
|
||||
attr_accessor :file, :url, :data, :do_import
|
||||
|
@ -33,19 +34,54 @@ class ScenarioImport
|
|||
@existing_scenario ||= user.scenarios.find_by_guid(parsed_data["guid"])
|
||||
end
|
||||
|
||||
def dangerous?
|
||||
(parsed_data['agents'] || []).any? { |agent| DANGEROUS_AGENT_TYPES.include?(agent['type']) }
|
||||
end
|
||||
|
||||
def parsed_data
|
||||
@parsed_data
|
||||
@parsed_data ||= data && JSON.parse(data) rescue {}
|
||||
end
|
||||
|
||||
def do_import?
|
||||
do_import == "1"
|
||||
end
|
||||
|
||||
def import!
|
||||
def import!(options = {})
|
||||
guid = parsed_data['guid']
|
||||
description = parsed_data['description']
|
||||
name = parsed_data['name']
|
||||
agents = parsed_data['agents']
|
||||
links = parsed_data['links']
|
||||
source_url = parsed_data['source_url'].presence || nil
|
||||
@scenario = user.scenarios.where(:guid => guid).first_or_initialize
|
||||
@scenario.update_attributes!(:name => name, :description => description,
|
||||
:source_url => source_url, :public => false)
|
||||
|
||||
unless options[:skip_agents]
|
||||
created_agents = agents.map do |agent_data|
|
||||
agent = @scenario.agents.find_by(:guid => agent_data['guid']) || Agent.build_for_type(agent_data['type'], user)
|
||||
agent.guid = agent_data['guid']
|
||||
agent.attributes = { :name => agent_data['name'],
|
||||
:schedule => agent_data['schedule'],
|
||||
:keep_events_for => agent_data['keep_events_for'],
|
||||
:propagate_immediately => agent_data['propagate_immediately'],
|
||||
:disabled => agent_data['disabled'],
|
||||
:options => agent_data['options'],
|
||||
:scenario_ids => [@scenario.id] }
|
||||
agent.save!
|
||||
agent
|
||||
end
|
||||
|
||||
links.each do |link|
|
||||
receiver = created_agents[link['receiver']]
|
||||
source = created_agents[link['source']]
|
||||
receiver.sources << source unless receiver.sources.include?(source)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def scenario
|
||||
existing_scenario
|
||||
@scenario || @existing_scenario
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -65,10 +101,12 @@ class ScenarioImport
|
|||
def validate_data
|
||||
if data.present?
|
||||
@parsed_data = JSON.parse(data) rescue {}
|
||||
if (%w[name guid] - @parsed_data.keys).length > 0
|
||||
if (%w[name guid agents] - @parsed_data.keys).length > 0
|
||||
errors.add(:base, "The provided data does not appear to be a valid Scenario.")
|
||||
self.data = nil
|
||||
end
|
||||
else
|
||||
@parsed_data = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -32,11 +32,20 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<% if @scenario_import.dangerous? %>
|
||||
<div class="danger">
|
||||
This Scenario contains one or more potentially dangerous Agents.
|
||||
These may be able to run local commands or execute code.
|
||||
Please be sure that you understand the above Agent configurations before importing!
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @scenario_import.existing_scenario.present? %>
|
||||
<strong>
|
||||
This Scenario already exists on your Huginn.
|
||||
If you continue, the import will overwrite your existing <span class='label label-info scenario'><%= @scenario_import.existing_scenario.name %></span> Scenario.
|
||||
</strong>
|
||||
<div class="danger">
|
||||
This Scenario already exists in your system.
|
||||
If you continue, the import will overwrite your existing
|
||||
<span class='label label-info scenario'><%= @scenario_import.existing_scenario.name %></span> Scenario and the Agents in it.
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="checkbox">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class='container'>
|
||||
<div class='container scenario-import'>
|
||||
<div class='row'>
|
||||
<div class='col-md-12'>
|
||||
<% if @scenario_import.errors.any? %>
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Agents</th>
|
||||
<th>Public</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
|
@ -22,6 +23,7 @@
|
|||
<%= link_to(scenario.name, scenario, class: "label label-info") %>
|
||||
</td>
|
||||
<td><%= link_to pluralize(scenario.agents.count, "agent"), scenario %></td>
|
||||
<td><%= scenario.public? ? "yes" : "no" %></td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-xs" style="float: right">
|
||||
<%= link_to 'Show', scenario, class: "btn btn-default" %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class AddIndicesToScenarios < ActiveRecord::Migration
|
||||
def change
|
||||
add_index :scenarios, [:user_id, :guid]
|
||||
add_index :scenarios, [:user_id, :guid], :unique => true
|
||||
add_index :scenario_memberships, :agent_id
|
||||
add_index :scenario_memberships, :scenario_id
|
||||
end
|
||||
|
|
15
db/migrate/20140605032822_add_guid_to_agents.rb
Normal file
15
db/migrate/20140605032822_add_guid_to_agents.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class AddGuidToAgents < ActiveRecord::Migration
|
||||
class Agent < ActiveRecord::Base; end
|
||||
|
||||
def change
|
||||
add_column :agents, :guid, :string
|
||||
|
||||
Agent.find_each do |agent|
|
||||
agent.update_attribute :guid, SecureRandom.hex
|
||||
end
|
||||
|
||||
change_column_null :agents, :guid, false
|
||||
|
||||
add_index :agents, :guid
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20140602014917) do
|
||||
ActiveRecord::Schema.define(version: 20140605032822) do
|
||||
|
||||
create_table "agent_logs", force: true do |t|
|
||||
t.integer "agent_id", null: false
|
||||
|
@ -42,8 +42,10 @@ ActiveRecord::Schema.define(version: 20140602014917) do
|
|||
t.integer "keep_events_for", default: 0, null: false
|
||||
t.boolean "propagate_immediately", default: false, null: false
|
||||
t.boolean "disabled", default: false, null: false
|
||||
t.string "guid", null: false
|
||||
end
|
||||
|
||||
add_index "agents", ["guid"], name: "index_agents_on_guid", using: :btree
|
||||
add_index "agents", ["schedule"], name: "index_agents_on_schedule", using: :btree
|
||||
add_index "agents", ["type"], name: "index_agents_on_type", using: :btree
|
||||
add_index "agents", ["user_id", "created_at"], name: "index_agents_on_user_id_and_created_at", using: :btree
|
||||
|
@ -111,7 +113,7 @@ ActiveRecord::Schema.define(version: 20140602014917) do
|
|||
t.string "source_url"
|
||||
end
|
||||
|
||||
add_index "scenarios", ["user_id", "guid"], name: "index_scenarios_on_user_id_and_guid", using: :btree
|
||||
add_index "scenarios", ["user_id", "guid"], name: "index_scenarios_on_user_id_and_guid", unique: true, using: :btree
|
||||
|
||||
create_table "user_credentials", force: true do |t|
|
||||
t.integer "user_id", null: false
|
||||
|
|
|
@ -46,7 +46,7 @@ class AgentsExporter
|
|||
:keep_events_for => agent.keep_events_for,
|
||||
:propagate_immediately => agent.propagate_immediately,
|
||||
:disabled => agent.disabled,
|
||||
:source_system_agent_id => agent.id,
|
||||
:guid => agent.guid,
|
||||
:options => agent.options
|
||||
}
|
||||
end
|
||||
|
|
8
spec/fixtures/agents.yml
vendored
8
spec/fixtures/agents.yml
vendored
|
@ -4,6 +4,7 @@ jane_website_agent:
|
|||
events_count: 1
|
||||
schedule: "5pm"
|
||||
name: "ZKCD"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
options: <%= {
|
||||
:url => "http://trailers.apple.com/trailers/home/rss/newtrailers.rss",
|
||||
:expected_update_period_in_days => 2,
|
||||
|
@ -20,6 +21,7 @@ bob_website_agent:
|
|||
events_count: 1
|
||||
schedule: "midnight"
|
||||
name: "ZKCD"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
options: <%= {
|
||||
:url => "http://xkcd.com",
|
||||
:expected_update_period_in_days => 2,
|
||||
|
@ -35,6 +37,7 @@ bob_weather_agent:
|
|||
user: bob
|
||||
schedule: "midnight"
|
||||
name: "SF Weather"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
keep_events_for: 45
|
||||
options: <%= {
|
||||
:location => 94102,
|
||||
|
@ -48,6 +51,7 @@ jane_weather_agent:
|
|||
user: jane
|
||||
schedule: "midnight"
|
||||
name: "SF Weather"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
keep_events_for: 30
|
||||
options: <%= {
|
||||
:location => 94103,
|
||||
|
@ -60,6 +64,7 @@ jane_rain_notifier_agent:
|
|||
type: Agents::TriggerAgent
|
||||
user: jane
|
||||
name: "Jane's Rain Watcher"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
options: <%= {
|
||||
:expected_receive_period_in_days => "2",
|
||||
:rules => [{
|
||||
|
@ -74,6 +79,7 @@ bob_rain_notifier_agent:
|
|||
type: Agents::TriggerAgent
|
||||
user: bob
|
||||
name: "Bob's Rain Watcher"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
options: <%= {
|
||||
:expected_receive_period_in_days => "2",
|
||||
:rules => [{
|
||||
|
@ -88,6 +94,7 @@ bob_twitter_user_agent:
|
|||
type: Agents::TwitterUserAgent
|
||||
user: bob
|
||||
name: "Bob's Twitter User Watcher"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
options: <%= {
|
||||
:username => "tectonic",
|
||||
:expected_update_period_in_days => "2",
|
||||
|
@ -101,3 +108,4 @@ bob_manual_event_agent:
|
|||
type: Agents::ManualEventAgent
|
||||
user: bob
|
||||
name: "Bob's event testing agent"
|
||||
guid: <%= SecureRandom.hex %>
|
||||
|
|
|
@ -20,7 +20,7 @@ describe AgentsExporter do
|
|||
Time.parse(data[:exported_at]).should be_within(2).of(Time.now.utc)
|
||||
data[:links].should == [{ :source => 0, :receiver => 1 }]
|
||||
data[:agents].should == agent_list.map { |agent| exporter.agent_as_json(agent) }
|
||||
data[:agents].all? { |agent_json| agent_json[:source_system_agent_id] && agent_json[:type] && agent_json[:name] }.should be_true
|
||||
data[:agents].all? { |agent_json| agent_json[:guid].present? && agent_json[:type].present? && agent_json[:name].present? }.should be_true
|
||||
end
|
||||
|
||||
it "does not output links to other agents" do
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/working_helpers'
|
||||
|
||||
describe Agent do
|
||||
it_behaves_like WorkingHelpers
|
||||
|
@ -122,6 +121,14 @@ describe Agent do
|
|||
stub(Agents::CannotBeScheduled).valid_type?("Agents::CannotBeScheduled") { true }
|
||||
end
|
||||
|
||||
let(:new_instance) do
|
||||
agent = Agents::SomethingSource.new(:name => "some agent")
|
||||
agent.user = users(:bob)
|
||||
agent
|
||||
end
|
||||
|
||||
it_behaves_like HasGuid
|
||||
|
||||
describe ".default_schedule" do
|
||||
it "stores the default on the class" do
|
||||
Agents::SomethingSource.default_schedule.should == "2pm"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
describe Agents::DataOutputAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
describe Agents::HipchatAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
describe Agents::HumanTaskAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
describe Agents::JabberAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
describe Agents::PeakDetectorAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
describe Agents::PushbulletAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
describe Agents::SlackAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
|
||||
describe Agents::TranslationAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'spec_helper'
|
||||
require 'models/concerns/liquid_interpolatable'
|
||||
|
||||
describe Agents::TriggerAgent do
|
||||
it_behaves_like LiquidInterpolatable
|
||||
|
|
|
@ -1,6 +1,64 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ScenarioImport do
|
||||
let(:guid) { "somescenarioguid" }
|
||||
let(:description) { "This is a cool Huginn Scenario that does something useful!" }
|
||||
let(:name) { "A useful Scenario" }
|
||||
let(:source_url) { "http://example.com/scenarios/2/export.json" }
|
||||
let(:weather_agent_options) {
|
||||
{
|
||||
'api_key' => 'some-api-key',
|
||||
'location' => '12345'
|
||||
}
|
||||
}
|
||||
let(:trigger_agent_options) {
|
||||
{
|
||||
'expected_receive_period_in_days' => 2,
|
||||
'rules' => [{
|
||||
'type' => "regex",
|
||||
'value' => "rain|storm",
|
||||
'path' => "conditions",
|
||||
}],
|
||||
'message' => "Looks like rain!"
|
||||
}
|
||||
}
|
||||
let(:valid_parsed_data) do
|
||||
{
|
||||
:name => name,
|
||||
:description => description,
|
||||
:guid => guid,
|
||||
:source_url => source_url,
|
||||
:exported_at => 2.days.ago.utc.iso8601,
|
||||
:agents => [
|
||||
{
|
||||
:type => "Agents::WeatherAgent",
|
||||
:name => "a weather agent",
|
||||
:schedule => "5pm",
|
||||
:keep_events_for => 14,
|
||||
:propagate_immediately => false,
|
||||
:disabled => false,
|
||||
:guid => "a-weather-agent",
|
||||
:options => weather_agent_options
|
||||
},
|
||||
{
|
||||
:type => "Agents::TriggerAgent",
|
||||
:name => "listen for weather",
|
||||
:schedule => nil,
|
||||
:keep_events_for => 0,
|
||||
:propagate_immediately => true,
|
||||
:disabled => true,
|
||||
:guid => "a-trigger-agent",
|
||||
:options => trigger_agent_options
|
||||
}
|
||||
],
|
||||
:links => [
|
||||
{ :source => 0, :receiver => 1 }
|
||||
]
|
||||
}
|
||||
end
|
||||
let(:valid_data) { valid_parsed_data.to_json }
|
||||
let(:invalid_data) { { :name => "some scenario missing a guid" }.to_json }
|
||||
|
||||
describe "initialization" do
|
||||
it "is initialized with an attributes hash" do
|
||||
ScenarioImport.new(:url => "http://google.com").url.should == "http://google.com"
|
||||
|
@ -9,8 +67,6 @@ describe ScenarioImport do
|
|||
|
||||
describe "validations" do
|
||||
subject { ScenarioImport.new }
|
||||
let(:valid_json) { { :name => "some scenario", :guid => "someguid" }.to_json }
|
||||
let(:invalid_json) { { :name => "some scenario missing a guid" }.to_json }
|
||||
|
||||
it "is not valid when none of file, url, or data are present" do
|
||||
subject.should_not be_valid
|
||||
|
@ -20,7 +76,7 @@ describe ScenarioImport do
|
|||
|
||||
describe "data" do
|
||||
it "should be invalid with invalid data" do
|
||||
subject.data = invalid_json
|
||||
subject.data = invalid_data
|
||||
subject.should_not be_valid
|
||||
subject.should have(1).error_on(:base)
|
||||
|
||||
|
@ -33,7 +89,7 @@ describe ScenarioImport do
|
|||
end
|
||||
|
||||
it "should be valid with valid data" do
|
||||
subject.data = valid_json
|
||||
subject.data = valid_data
|
||||
subject.should be_valid
|
||||
end
|
||||
end
|
||||
|
@ -47,14 +103,14 @@ describe ScenarioImport do
|
|||
end
|
||||
|
||||
it "should be invalid when the referenced url doesn't contain a scenario" do
|
||||
stub_request(:get, "http://example.com/scenarios/1/export.json").to_return(:status => 200, :body => invalid_json)
|
||||
stub_request(:get, "http://example.com/scenarios/1/export.json").to_return(:status => 200, :body => invalid_data)
|
||||
subject.url = "http://example.com/scenarios/1/export.json"
|
||||
subject.should_not be_valid
|
||||
subject.errors[:base].should include("The provided data does not appear to be a valid Scenario.")
|
||||
end
|
||||
|
||||
it "should be valid when the url points to a valid scenario" do
|
||||
stub_request(:get, "http://example.com/scenarios/1/export.json").to_return(:status => 200, :body => valid_json)
|
||||
stub_request(:get, "http://example.com/scenarios/1/export.json").to_return(:status => 200, :body => valid_data)
|
||||
subject.url = "http://example.com/scenarios/1/export.json"
|
||||
subject.should be_valid
|
||||
end
|
||||
|
@ -66,15 +122,139 @@ describe ScenarioImport do
|
|||
subject.should_not be_valid
|
||||
subject.errors[:base].should include("The provided data does not appear to be a valid Scenario.")
|
||||
|
||||
subject.file = StringIO.new(invalid_json)
|
||||
subject.file = StringIO.new(invalid_data)
|
||||
subject.should_not be_valid
|
||||
subject.errors[:base].should include("The provided data does not appear to be a valid Scenario.")
|
||||
end
|
||||
|
||||
it "should be valid with a valid uploaded scenario" do
|
||||
subject.file = StringIO.new(valid_json)
|
||||
subject.file = StringIO.new(valid_data)
|
||||
subject.should be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#dangerous?" do
|
||||
it "returns false on most Agents" do
|
||||
ScenarioImport.new(:data => valid_data).should_not be_dangerous
|
||||
end
|
||||
|
||||
it "returns true if a ShellCommandAgent is present" do
|
||||
valid_parsed_data[:agents][0][:type] = "Agents::ShellCommandAgent"
|
||||
ScenarioImport.new(:data => valid_parsed_data.to_json).should be_dangerous
|
||||
end
|
||||
end
|
||||
|
||||
describe "#import!" do
|
||||
let(:scenario_import) do
|
||||
_import = ScenarioImport.new(:data => valid_data)
|
||||
_import.set_user users(:bob)
|
||||
_import
|
||||
end
|
||||
|
||||
context "when this scenario has never been seen before" do
|
||||
it "makes a new scenario" do
|
||||
lambda {
|
||||
scenario_import.import!(:skip_agents => true)
|
||||
}.should change { users(:bob).scenarios.count }.by(1)
|
||||
|
||||
scenario_import.scenario.name.should == name
|
||||
scenario_import.scenario.description.should == description
|
||||
scenario_import.scenario.guid.should == guid
|
||||
scenario_import.scenario.source_url.should == source_url
|
||||
scenario_import.scenario.public.should be_false
|
||||
end
|
||||
|
||||
it "creates the Agents" do
|
||||
lambda {
|
||||
scenario_import.import!
|
||||
}.should change { users(:bob).agents.count }.by(2)
|
||||
|
||||
weather_agent = scenario_import.scenario.agents.find_by(:guid => "a-weather-agent")
|
||||
trigger_agent = scenario_import.scenario.agents.find_by(:guid => "a-trigger-agent")
|
||||
|
||||
weather_agent.name.should == "a weather agent"
|
||||
weather_agent.schedule.should == "5pm"
|
||||
weather_agent.keep_events_for.should == 14
|
||||
weather_agent.propagate_immediately.should be_false
|
||||
weather_agent.should_not be_disabled
|
||||
weather_agent.memory.should be_empty
|
||||
weather_agent.options.should == weather_agent_options
|
||||
|
||||
trigger_agent.name.should == "listen for weather"
|
||||
trigger_agent.sources.should == [weather_agent]
|
||||
trigger_agent.schedule.should be_nil
|
||||
trigger_agent.keep_events_for.should == 0
|
||||
trigger_agent.propagate_immediately.should be_true
|
||||
trigger_agent.should be_disabled
|
||||
trigger_agent.memory.should be_empty
|
||||
trigger_agent.options.should == trigger_agent_options
|
||||
end
|
||||
|
||||
it "creates new Agents, even if one already exists with the given guid (so that we don't overwrite a user's work outside of the scenario)" do
|
||||
agents(:bob_weather_agent).update_attribute :guid, "a-weather-agent"
|
||||
|
||||
lambda {
|
||||
scenario_import.import!
|
||||
}.should change { users(:bob).agents.count }.by(2)
|
||||
end
|
||||
end
|
||||
|
||||
context "when an a scenario already exists with the given guid" do
|
||||
let!(:existing_scenario) {
|
||||
_existing_scenerio = users(:bob).scenarios.build(:name => "an existing scenario")
|
||||
_existing_scenerio.guid = guid
|
||||
_existing_scenerio.save!
|
||||
_existing_scenerio
|
||||
}
|
||||
|
||||
it "uses the existing scenario, updating it's data" do
|
||||
lambda {
|
||||
scenario_import.import!(:skip_agents => true)
|
||||
scenario_import.scenario.should == existing_scenario
|
||||
}.should_not change { users(:bob).scenarios.count }
|
||||
|
||||
existing_scenario.reload
|
||||
existing_scenario.guid.should == guid
|
||||
existing_scenario.description.should == description
|
||||
existing_scenario.name.should == name
|
||||
existing_scenario.source_url.should == source_url
|
||||
existing_scenario.public.should be_false
|
||||
end
|
||||
|
||||
it "updates any existing agents in the scenario, and makes new ones as needed" do
|
||||
agents(:bob_weather_agent).update_attribute :guid, "a-weather-agent"
|
||||
agents(:bob_weather_agent).scenarios << existing_scenario
|
||||
|
||||
lambda {
|
||||
# Shouldn't matter how many times we do it!
|
||||
scenario_import.import!
|
||||
scenario_import.import!
|
||||
scenario_import.import!
|
||||
}.should change { users(:bob).agents.count }.by(1)
|
||||
|
||||
weather_agent = existing_scenario.agents.find_by(:guid => "a-weather-agent")
|
||||
trigger_agent = existing_scenario.agents.find_by(:guid => "a-trigger-agent")
|
||||
|
||||
weather_agent.should == agents(:bob_weather_agent)
|
||||
|
||||
weather_agent.name.should == "a weather agent"
|
||||
weather_agent.schedule.should == "5pm"
|
||||
weather_agent.keep_events_for.should == 14
|
||||
weather_agent.propagate_immediately.should be_false
|
||||
weather_agent.should_not be_disabled
|
||||
weather_agent.memory.should be_empty
|
||||
weather_agent.options.should == weather_agent_options
|
||||
|
||||
trigger_agent.name.should == "listen for weather"
|
||||
trigger_agent.sources.should == [weather_agent]
|
||||
trigger_agent.schedule.should be_nil
|
||||
trigger_agent.keep_events_for.should == 0
|
||||
trigger_agent.propagate_immediately.should be_true
|
||||
trigger_agent.should be_disabled
|
||||
trigger_agent.memory.should be_empty
|
||||
trigger_agent.options.should == trigger_agent_options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,54 +1,42 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Scenario do
|
||||
let(:new_instance) { users(:bob).scenarios.build(:name => "some scenario") }
|
||||
|
||||
it_behaves_like HasGuid
|
||||
|
||||
describe "validations" do
|
||||
before do
|
||||
@scenario = users(:bob).scenarios.new(:name => "some scenario")
|
||||
@scenario.should be_valid
|
||||
new_instance.should be_valid
|
||||
end
|
||||
|
||||
it "validates the presence of name" do
|
||||
@scenario.name = ''
|
||||
@scenario.should_not be_valid
|
||||
new_instance.name = ''
|
||||
new_instance.should_not be_valid
|
||||
end
|
||||
|
||||
it "validates the presence of user" do
|
||||
@scenario.user = nil
|
||||
@scenario.should_not be_valid
|
||||
new_instance.user = nil
|
||||
new_instance.should_not be_valid
|
||||
end
|
||||
|
||||
it "only allows Agents owned by user" do
|
||||
@scenario.agent_ids = [agents(:bob_website_agent).id]
|
||||
@scenario.should be_valid
|
||||
new_instance.agent_ids = [agents(:bob_website_agent).id]
|
||||
new_instance.should be_valid
|
||||
|
||||
@scenario.agent_ids = [agents(:jane_website_agent).id]
|
||||
@scenario.should_not be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "guid" do
|
||||
it "gets created before_save, but only if it's not present" do
|
||||
scenario = users(:bob).scenarios.new(:name => "some scenario")
|
||||
scenario.guid.should be_nil
|
||||
scenario.save!
|
||||
scenario.guid.should_not be_nil
|
||||
|
||||
lambda { scenario.save! }.should_not change { scenario.reload.guid }
|
||||
new_instance.agent_ids = [agents(:jane_website_agent).id]
|
||||
new_instance.should_not be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "counters" do
|
||||
before do
|
||||
@scenario = users(:bob).scenarios.new(:name => "some scenario")
|
||||
end
|
||||
|
||||
it "maintains a counter cache on user" do
|
||||
lambda {
|
||||
@scenario.save!
|
||||
new_instance.save!
|
||||
}.should change { users(:bob).reload.scenario_count }.by(1)
|
||||
|
||||
lambda {
|
||||
@scenario.destroy
|
||||
new_instance.destroy
|
||||
}.should change { users(:bob).reload.scenario_count }.by(-1)
|
||||
end
|
||||
end
|
||||
|
|
12
spec/support/shared_examples/has_guid.rb
Normal file
12
spec/support/shared_examples/has_guid.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
require 'spec_helper'
|
||||
|
||||
shared_examples_for HasGuid do
|
||||
it "gets created before_save, but only if it's not present" do
|
||||
instance = new_instance
|
||||
instance.guid.should be_nil
|
||||
instance.save!
|
||||
instance.guid.should_not be_nil
|
||||
|
||||
lambda { instance.save! }.should_not change { instance.reload.guid }
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@ require 'spec_helper'
|
|||
shared_examples_for WorkingHelpers do
|
||||
describe "recent_error_logs?" do
|
||||
it "returns true if last_error_log_at is near last_event_at" do
|
||||
agent = Agent.new
|
||||
agent = described_class.new
|
||||
|
||||
agent.last_error_log_at = 10.minutes.ago
|
||||
agent.last_event_at = 10.minutes.ago
|
||||
|
@ -26,9 +26,10 @@ shared_examples_for WorkingHelpers do
|
|||
agent.recent_error_logs?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "received_event_without_error?" do
|
||||
before do
|
||||
@agent = Agent.new
|
||||
@agent = described_class.new
|
||||
end
|
||||
|
||||
it "should return false until the first event was received" do
|
||||
|
@ -49,5 +50,4 @@ shared_examples_for WorkingHelpers do
|
|||
@agent.received_event_without_error?.should == true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Add table
Reference in a new issue