70
+        delete :destroy, params: {:id => events(:jane_website_agent_event).to_param}
71 71
       }.to raise_error(ActiveRecord::RecordNotFound)
72 72
     end
73 73
   end

+ 5 - 5
spec/controllers/jobs_controller_spec.rb

@@ -37,11 +37,11 @@ describe JobsController do
37 37
     end
38 38
 
39 39
     it "destroy a job which is not running" do
40
-      expect { delete :destroy, id: @not_running.id }.to change(Delayed::Job, :count).by(-1)
40
+      expect { delete :destroy, params: {id: @not_running.id} }.to change(Delayed::Job, :count).by(-1)
41 41
     end
42 42
 
43 43
     it "does not destroy a running job" do
44
-      expect { delete :destroy, id: @running.id }.to change(Delayed::Job, :count).by(0)
44
+      expect { delete :destroy, params: {id: @running.id} }.to change(Delayed::Job, :count).by(0)
45 45
     end
46 46
   end
47 47
 
@@ -54,15 +54,15 @@ describe JobsController do
54 54
     end
55 55
 
56 56
     it "queue a job which is not running" do
57
-      expect { put :run, id: @not_running.id }.to change { @not_running.reload.run_at }
57
+      expect { put :run, params: {id: @not_running.id} }.to change { @not_running.reload.run_at }
58 58
     end
59 59
 
60 60
     it "queue a job that failed" do
61
-      expect { put :run, id: @failed.id }.to change { @failed.reload.run_at }
61
+      expect { put :run, params: {id: @failed.id} }.to change { @failed.reload.run_at }
62 62
     end
63 63
 
64 64
     it "not queue a running job" do
65
-      expect { put :run, id: @running.id }.not_to change { @not_running.reload.run_at }
65
+      expect { put :run, params: {id: @running.id} }.not_to change { @not_running.reload.run_at }
66 66
     end
67 67
   end
68 68
 

+ 4 - 4
spec/controllers/logs_controller_spec.rb

@@ -4,7 +4,7 @@ describe LogsController do
4 4
   describe "GET index" do
5 5
     it "can filter by Agent" do
6 6
       sign_in users(:bob)
7
-      get :index, :agent_id => agents(:bob_weather_agent).id
7
+      get :index, params: {:agent_id => agents(:bob_weather_agent).id}
8 8
       expect(assigns(:logs).length).to eq(agents(:bob_weather_agent).logs.length)
9 9
       expect(assigns(:logs).all? {|i| expect(i.agent).to eq(agents(:bob_weather_agent)) }).to be_truthy
10 10
     end
@@ -12,7 +12,7 @@ describe LogsController do
12 12
     it "only loads Agents owned by the current user" do
13 13
       sign_in users(:bob)
14 14
       expect {
15
-        get :index, :agent_id => agents(:jane_weather_agent).id
15
+        get :index, params: {:agent_id => agents(:jane_weather_agent).id}
16 16
       }.to raise_error(ActiveRecord::RecordNotFound)
17 17
     end
18 18
   end
@@ -22,7 +22,7 @@ describe LogsController do
22 22
       agents(:bob_weather_agent).last_error_log_at = 2.hours.ago
23 23
       sign_in users(:bob)
24 24
       expect {
25
-        delete :clear, :agent_id => agents(:bob_weather_agent).id
25
+        delete :clear, params: {:agent_id => agents(:bob_weather_agent).id}
26 26
       }.to change { AgentLog.count }.by(-1 * agents(:bob_weather_agent).logs.count)
27 27
       expect(assigns(:logs).length).to eq(0)
28 28
       expect(agents(:bob_weather_agent).reload.logs.count).to eq(0)
@@ -32,7 +32,7 @@ describe LogsController do
32 32
     it "only deletes logs for an Agent owned by the current user" do
33 33
       sign_in users(:bob)
34 34
       expect {
35
-        delete :clear, :agent_id => agents(:jane_weather_agent).id
35
+        delete :clear, params: {:agent_id => agents(:jane_weather_agent).id}
36 36
       }.to raise_error(ActiveRecord::RecordNotFound)
37 37
     end
38 38
   end

+ 9 - 8
spec/controllers/omniauth_callbacks_controller_spec.rb

@@ -5,22 +5,23 @@ describe OmniauthCallbacksController do
5 5
     sign_in users(:bob)
6 6
     OmniAuth.config.test_mode = true
7 7
     request.env["devise.mapping"] = Devise.mappings[:user]
8
-    request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json')))
9 8
   end
10 9
 
11 10
   describe "accepting a callback url" do
12 11
     it "should update the user's credentials" do
12
+      request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json')))
13 13
       expect {
14 14
         get :twitter
15 15
       }.to change { users(:bob).services.count }.by(1)
16 16
     end
17
+  end
17 18
 
18
-    # it "should work with an unknown provider (for now)" do
19
-    #   request.env["omniauth.auth"]['provider'] = 'unknown'
20
-    #   expect {
21
-    #     get :unknown
22
-    #   }.to change { users(:bob).services.count }.by(1)
23
-    #   expect(users(:bob).services.first.provider).to eq('unknown')
24
-    # end
19
+  describe "handling a provider with non-standard omniauth options" do
20
+    it "should update the user's credentials" do
21
+      request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/37signals.json')))
22
+      expect {
23
+        get "37signals"
24
+      }.to change { users(:bob).services.count }.by(1)
25
+    end
25 26
   end
26 27
 end

+ 1 - 1
spec/controllers/scenario_imports_controller_spec.rb

@@ -15,7 +15,7 @@ describe ScenarioImportsController do
15 15
 
16 16
   describe "POST create" do
17 17
     it "initializes a ScenarioImport for current_user, passing in params" do
18
-      post :create, :scenario_import => { :url => "bad url" }
18
+      post :create, params: {:scenario_import => { :url => "bad url" }}
19 19
       expect(assigns(:scenario_import).user).to eq(users(:bob))
20 20
       expect(assigns(:scenario_import).url).to eq("bad url")
21 21
       expect(assigns(:scenario_import)).not_to be_valid

+ 29 - 23
spec/controllers/scenarios_controller_spec.rb

@@ -18,34 +18,34 @@ describe ScenariosController do
18 18
 
19 19
   describe "GET show" do
20 20
     it "only shows Scenarios for the current user" do
21
-      get :show, :id => scenarios(:bob_weather).to_param
21
+      get :show, params: {:id => scenarios(:bob_weather).to_param}
22 22
       expect(assigns(:scenario)).to eq(scenarios(:bob_weather))
23 23
 
24 24
       expect {
25
-        get :show, :id => scenarios(:jane_weather).to_param
25
+        get :show, params: {:id => scenarios(:jane_weather).to_param}
26 26
       }.to raise_error(ActiveRecord::RecordNotFound)
27 27
     end
28 28
 
29 29
     it "loads Agents for the requested Scenario" do
30
-      get :show, :id => scenarios(:bob_weather).to_param
30
+      get :show, params: {:id => scenarios(:bob_weather).to_param}
31 31
       expect(assigns(:agents).pluck(:id).sort).to eq(scenarios(:bob_weather).agents.pluck(:id).sort)
32 32
     end
33 33
   end
34 34
 
35 35
   describe "GET share" do
36 36
     it "only displays Scenario share information for the current user" do
37
-      get :share, :id => scenarios(:bob_weather).to_param
37
+      get :share, params: {:id => scenarios(:bob_weather).to_param}
38 38
       expect(assigns(:scenario)).to eq(scenarios(:bob_weather))
39 39
 
40 40
       expect {
41
-        get :share, :id => scenarios(:jane_weather).to_param
41
+        get :share, params: {:id => scenarios(:jane_weather).to_param}
42 42
       }.to raise_error(ActiveRecord::RecordNotFound)
43 43
     end
44 44
   end
45 45
 
46 46
   describe "GET export" do
47 47
     it "returns a JSON file download from an instantiated AgentsExporter" do
48
-      get :export, :id => scenarios(:bob_weather).to_param
48
+      get :export, params: {:id => scenarios(:bob_weather).to_param}
49 49
       expect(assigns(:exporter).options[:name]).to eq(scenarios(:bob_weather).name)
50 50
       expect(assigns(:exporter).options[:description]).to eq(scenarios(:bob_weather).description)
51 51
       expect(assigns(:exporter).options[:agents]).to eq(scenarios(:bob_weather).agents)
@@ -59,11 +59,11 @@ describe ScenariosController do
59 59
     end
60 60
 
61 61
     it "only exports private Scenarios for the current user" do
62
-      get :export, :id => scenarios(:bob_weather).to_param
62
+      get :export, params: {:id => scenarios(:bob_weather).to_param}
63 63
       expect(assigns(:scenario)).to eq(scenarios(:bob_weather))
64 64
 
65 65
       expect {
66
-        get :export, :id => scenarios(:jane_weather).to_param
66
+        get :export, params: {:id => scenarios(:jane_weather).to_param}
67 67
       }.to raise_error(ActiveRecord::RecordNotFound)
68 68
     end
69 69
 
@@ -73,14 +73,14 @@ describe ScenariosController do
73 73
       end
74 74
 
75 75
       it "exports public scenarios for other users when logged in" do
76
-        get :export, :id => scenarios(:jane_weather).to_param
76
+        get :export, params: {:id => scenarios(:jane_weather).to_param}
77 77
         expect(assigns(:scenario)).to eq(scenarios(:jane_weather))
78 78
         expect(assigns(:exporter).options[:source_url]).to eq(export_scenario_url(scenarios(:jane_weather)))
79 79
       end
80 80
 
81 81
       it "exports public scenarios for other users when logged out" do
82 82
         sign_out :user
83
-        get :export, :id => scenarios(:jane_weather).to_param
83
+        get :export, params: {:id => scenarios(:jane_weather).to_param}
84 84
         expect(assigns(:scenario)).to eq(scenarios(:jane_weather))
85 85
         expect(assigns(:exporter).options[:source_url]).to eq(export_scenario_url(scenarios(:jane_weather)))
86 86
       end
@@ -89,11 +89,11 @@ describe ScenariosController do
89 89
 
90 90
   describe "GET edit" do
91 91
     it "only shows Scenarios for the current user" do
92
-      get :edit, :id => scenarios(:bob_weather).to_param
92
+      get :edit, params: {:id => scenarios(:bob_weather).to_param}
93 93
       expect(assigns(:scenario)).to eq(scenarios(:bob_weather))
94 94
 
95 95
       expect {
96
-        get :edit, :id => scenarios(:jane_weather).to_param
96
+        get :edit, params: {:id => scenarios(:jane_weather).to_param}
97 97
       }.to raise_error(ActiveRecord::RecordNotFound)
98 98
     end
99 99
   end
@@ -101,13 +101,13 @@ describe ScenariosController do
101 101
   describe "POST create" do
102 102
     it "creates Scenarios for the current user" do
103 103
       expect {
104
-        post :create, :scenario => valid_attributes
104
+        post :create, params: {:scenario => valid_attributes}
105 105
       }.to change { users(:bob).scenarios.count }.by(1)
106 106
     end
107 107
 
108 108
     it "shows errors" do
109 109
       expect {
110
-        post :create, :scenario => valid_attributes(:name => "")
110
+        post :create, params: {:scenario => valid_attributes(:name => "")}
111 111
       }.not_to change { users(:bob).scenarios.count }
112 112
       expect(assigns(:scenario)).to have(1).errors_on(:name)
113 113
       expect(response).to render_template("new")
@@ -115,35 +115,41 @@ describe ScenariosController do
115 115
 
116 116
     it "will not create Scenarios for other users" do
117 117
       expect {
118
-        post :create, :scenario => valid_attributes(:user_id => users(:jane).id)
119
-      }.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
118
+        post :create, params: {:scenario => valid_attributes(:user_id => users(:jane).id)}
119
+      }.to raise_error(ActionController::UnpermittedParameters)
120 120
     end
121 121
   end
122 122
 
123 123
   describe "PUT update" do
124 124
     it "updates attributes on Scenarios for the current user" do
125
-      post :update, :id => scenarios(:bob_weather).to_param, :scenario => { :name => "new_name", :public => "1" }
125
+      post :update, params: {:id => scenarios(:bob_weather).to_param, :scenario => { :name => "new_name", :public => "1" }}
126 126
       expect(response).to redirect_to(scenario_path(scenarios(:bob_weather)))
127 127
       expect(scenarios(:bob_weather).reload.name).to eq("new_name")
128 128
       expect(scenarios(:bob_weather)).to be_public
129 129
 
130 130
       expect {
131
-        post :update, :id => scenarios(:jane_weather).to_param, :scenario => { :name => "new_name" }
131
+        post :update, params: {:id => scenarios(:jane_weather).to_param, :scenario => { :name => "new_name" }}
132 132
       }.to raise_error(ActiveRecord::RecordNotFound)
133 133
       expect(scenarios(:jane_weather).reload.name).not_to eq("new_name")
134 134
     end
135 135
 
136 136
     it "shows errors" do
137
-      post :update, :id => scenarios(:bob_weather).to_param, :scenario => { :name => "" }
137
+      post :update, params: {:id => scenarios(:bob_weather).to_param, :scenario => { :name => "" }}
138 138
       expect(assigns(:scenario)).to have(1).errors_on(:name)
139 139
       expect(response).to render_template("edit")
140 140
     end
141
+
142
+    it 'adds an agent to the scenario' do
143
+      expect {
144
+        post :update, params: {:id => scenarios(:bob_weather).to_param, :scenario => { :name => "new_name", :public => "1", agent_ids: scenarios(:bob_weather).agent_ids + [agents(:bob_website_agent).id] }}
145
+      }.to change { scenarios(:bob_weather).reload.agent_ids.length }.by(1)
146
+    end
141 147
   end
142 148
 
143 149
   describe 'PUT enable_or_disable_all_agents' do
144 150
     it 'updates disabled on all agents in a scenario for the current user' do
145 151
       @params = {"scenario"=>{"disabled"=>"true"}, "commit"=>"Yes", "id"=> scenarios(:bob_weather).id}
146
-      put :enable_or_disable_all_agents, @params
152
+      put :enable_or_disable_all_agents, params: @params
147 153
       expect(agents(:bob_rain_notifier_agent).disabled).to eq(true)
148 154
       expect(response).to redirect_to(scenario_path(scenarios(:bob_weather)))
149 155
     end
@@ -152,17 +158,17 @@ describe ScenariosController do
152 158
   describe "DELETE destroy" do
153 159
     it "destroys only Scenarios owned by the current user" do
154 160
       expect {
155
-        delete :destroy, :id => scenarios(:bob_weather).to_param
161
+        delete :destroy, params: {:id => scenarios(:bob_weather).to_param}
156 162
       }.to change(Scenario, :count).by(-1)
157 163
 
158 164
       expect {
159
-        delete :destroy, :id => scenarios(:jane_weather).to_param
165
+        delete :destroy, params: {:id => scenarios(:jane_weather).to_param}
160 166
       }.to raise_error(ActiveRecord::RecordNotFound)
161 167
     end
162 168
 
163 169
     it "passes the mode to the model" do
164 170
       expect {
165
-        delete :destroy, id: scenarios(:bob_weather).to_param, mode: 'all_agents'
171
+        delete :destroy, params: {id: scenarios(:bob_weather).to_param, mode: 'all_agents'}
166 172
       }.to change(Agent, :count).by(-2)
167 173
     end
168 174
   end

+ 4 - 4
spec/controllers/services_controller_spec.rb

@@ -14,14 +14,14 @@ describe ServicesController do
14 14
 
15 15
   describe "POST toggle_availability" do
16 16
     it "should work for service of the user" do
17
-      post :toggle_availability, :id => services(:generic).to_param
17
+      post :toggle_availability, params: {:id => services(:generic).to_param}
18 18
       expect(assigns(:service)).to eq(services(:generic))
19 19
       redirect_to(services_path)
20 20
     end
21 21
 
22 22
     it "should not work for a service of another user" do
23 23
       expect {
24
-        post :toggle_availability, :id => services(:global).to_param
24
+        post :toggle_availability, params: {:id => services(:global).to_param}
25 25
       }.to raise_error(ActiveRecord::RecordNotFound)
26 26
     end
27 27
   end
@@ -29,11 +29,11 @@ describe ServicesController do
29 29
   describe "DELETE destroy" do
30 30
     it "destroys only services owned by the current user" do
31 31
       expect {
32
-        delete :destroy, :id => services(:generic).to_param
32
+        delete :destroy, params: {:id => services(:generic).to_param}
33 33
       }.to change(Service, :count).by(-1)
34 34
 
35 35
       expect {
36
-        delete :destroy, :id => services(:global).to_param
36
+        delete :destroy, params: {:id => services(:global).to_param}
37 37
       }.to raise_error(ActiveRecord::RecordNotFound)
38 38
     end
39 39
   end

+ 14 - 14
spec/controllers/user_credentials_controller_spec.rb

@@ -22,30 +22,30 @@ describe UserCredentialsController do
22 22
 
23 23
   describe "GET edit" do
24 24
     it "only shows UserCredentials for the current user" do
25
-      get :edit, :id => user_credentials(:bob_aws_secret).to_param
25
+      get :edit, params: {:id => user_credentials(:bob_aws_secret).to_param}
26 26
       expect(assigns(:user_credential)).to eq(user_credentials(:bob_aws_secret))
27 27
 
28 28
       expect {
29
-        get :edit, :id => user_credentials(:jane_aws_secret).to_param
29
+        get :edit, params: {:id => user_credentials(:jane_aws_secret).to_param}
30 30
       }.to raise_error(ActiveRecord::RecordNotFound)
31 31
     end
32 32
   end
33 33
 
34 34
   describe "Post import" do
35 35
     it "asserts user credentials were created for current user only" do
36
-      post :import, :file => @file
36
+      post :import, params: {:file => @file}
37 37
       expect(controller.current_user.id).to eq(users(:bob).id)
38 38
       expect(controller.current_user.user_credentials).to eq(users(:bob).user_credentials)
39 39
     end
40 40
 
41 41
     it "asserts that primary id in json file is ignored" do
42
-      post :import, :file => @file
42
+      post :import, params: {:file => @file}
43 43
       expect(controller.current_user.user_credentials.last.id).not_to eq(24)
44 44
     end
45 45
 
46 46
     it "duplicate credential name shows an error that it is not saved" do
47 47
       file1 = fixture_file_upload('multiple_user_credentials.json')
48
-      post :import, :file => file1
48
+      post :import, params: {:file => file1}
49 49
       expect(flash[:notice]).to eq("One or more of the uploaded credentials was not imported due to an error. Perhaps an existing credential had the same name?")
50 50
       expect(response).to redirect_to(user_credentials_path)
51 51
     end
@@ -54,13 +54,13 @@ describe UserCredentialsController do
54 54
   describe "POST create" do
55 55
     it "creates UserCredentials for the current user" do
56 56
       expect {
57
-        post :create, :user_credential => valid_attributes
57
+        post :create, params: {:user_credential => valid_attributes}
58 58
       }.to change { users(:bob).user_credentials.count }.by(1)
59 59
     end
60 60
 
61 61
     it "shows errors" do
62 62
       expect {
63
-        post :create, :user_credential => valid_attributes(:credential_name => "")
63
+        post :create, params: {:user_credential => valid_attributes(:credential_name => "")}
64 64
       }.not_to change { users(:bob).user_credentials.count }
65 65
       expect(assigns(:user_credential)).to have(1).errors_on(:credential_name)
66 66
       expect(response).to render_template("new")
@@ -68,25 +68,25 @@ describe UserCredentialsController do
68 68
 
69 69
     it "will not create UserCredentials for other users" do
70 70
       expect {
71
-        post :create, :user_credential => valid_attributes(:user_id => users(:jane).id)
72
-      }.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
71
+        post :create, params: {:user_credential => valid_attributes(:user_id => users(:jane).id)}
72
+      }.to raise_error(ActionController::UnpermittedParameters)
73 73
     end
74 74
   end
75 75
 
76 76
   describe "PUT update" do
77 77
     it "updates attributes on UserCredentials for the current user" do
78
-      post :update, :id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "new_name" }
78
+      post :update, params: {:id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "new_name" }}
79 79
       expect(response).to redirect_to(user_credentials_path)
80 80
       expect(user_credentials(:bob_aws_key).reload.credential_name).to eq("new_name")
81 81
 
82 82
       expect {
83
-        post :update, :id => user_credentials(:jane_aws_key).to_param, :user_credential => { :credential_name => "new_name" }
83
+        post :update, params: {:id => user_credentials(:jane_aws_key).to_param, :user_credential => { :credential_name => "new_name" }}
84 84
       }.to raise_error(ActiveRecord::RecordNotFound)
85 85
       expect(user_credentials(:jane_aws_key).reload.credential_name).not_to eq("new_name")
86 86
     end
87 87
 
88 88
     it "shows errors" do
89
-      post :update, :id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "" }
89
+      post :update, params: {:id => user_credentials(:bob_aws_key).to_param, :user_credential => { :credential_name => "" }}
90 90
       expect(assigns(:user_credential)).to have(1).errors_on(:credential_name)
91 91
       expect(response).to render_template("edit")
92 92
     end
@@ -95,11 +95,11 @@ describe UserCredentialsController do
95 95
   describe "DELETE destroy" do
96 96
     it "destroys only UserCredentials owned by the current user" do
97 97
       expect {
98
-        delete :destroy, :id => user_credentials(:bob_aws_key).to_param
98
+        delete :destroy, params: {:id => user_credentials(:bob_aws_key).to_param}
99 99
       }.to change(UserCredential, :count).by(-1)
100 100
 
101 101
       expect {
102
-        delete :destroy, :id => user_credentials(:jane_aws_key).to_param
102
+        delete :destroy, params: {:id => user_credentials(:jane_aws_key).to_param}
103 103
       }.to raise_error(ActiveRecord::RecordNotFound)
104 104
     end
105 105
   end

+ 13 - 7
spec/controllers/users/registrations_controller_spec.rb

@@ -2,16 +2,19 @@ require 'rails_helper'
2 2
 
3 3
 module Users
4 4
   describe RegistrationsController do
5
-    include Devise::TestHelpers
6
-
7 5
     describe "POST create" do
6
+      before do
7
+        @request.env["devise.mapping"] = Devise.mappings[:user]
8
+      end
9
+
8 10
       context 'with valid params' do
9 11
         it "imports the default scenario for the new user" do
10 12
           mock(DefaultScenarioImporter).import(is_a(User))
11 13
 
12
-          @request.env["devise.mapping"] = Devise.mappings[:user]
13
-          post :create, :user => {username: 'jdoe', email: 'jdoe@example.com',
14
-            password: 's3cr3t55', password_confirmation: 's3cr3t55', admin: false, invitation_code: 'try-huginn'}
14
+          post :create, params: {
15
+            :user => {username: 'jdoe', email: 'jdoe@example.com',
16
+              password: 's3cr3t55', password_confirmation: 's3cr3t55', invitation_code: 'try-huginn'}
17
+          }
15 18
         end
16 19
       end
17 20
 
@@ -19,9 +22,12 @@ module Users
19 22
         it "does not import the default scenario" do
20 23
           stub(DefaultScenarioImporter).import(is_a(User)) { fail "Should not attempt import" }
21 24
 
22
-          @request.env["devise.mapping"] = Devise.mappings[:user]
23 25
           setup_controller_for_warden
24
-          post :create, :user => {}
26
+          post :create, params: {:user => {}}
27
+        end
28
+
29
+        it 'does not allow to set the admin flag' do
30
+          expect { post :create, params: {:user => {admin: 'true'}} }.to raise_error(ActionController::UnpermittedParameters)
25 31
         end
26 32
       end
27 33
     end

+ 14 - 14
spec/controllers/web_requests_controller_spec.rb

@@ -26,14 +26,14 @@ describe WebRequestsController do
26 26
 
27 27
   it "should not require login to receive a web request" do
28 28
     expect(@agent.last_web_request_at).to be_nil
29
-    post :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"
29
+    post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}
30 30
     expect(@agent.reload.last_web_request_at).to be_within(2).of(Time.now)
31 31
     expect(response.body).to eq("success")
32 32
     expect(response).to be_success
33 33
   end
34 34
 
35 35
   it "should call receive_web_request" do
36
-    post :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"
36
+    post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}
37 37
     @agent.reload
38 38
     expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" })
39 39
     expect(@agent.memory[:web_request_format]).to eq("text/html")
@@ -42,14 +42,14 @@ describe WebRequestsController do
42 42
     expect(response.headers['Content-Type']).to eq('text/plain; charset=utf-8')
43 43
     expect(response).to be_success
44 44
 
45
-    post :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "not_my_secret", :no => "go"
45
+    post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "not_my_secret", :no => "go"}
46 46
     expect(@agent.reload.memory[:web_request_values]).not_to eq({ 'no' => "go" })
47 47
     expect(response.body).to eq("failure")
48 48
     expect(response).to be_missing
49 49
   end
50 50
 
51 51
   it "should accept gets" do
52
-    get :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"
52
+    get :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}
53 53
     @agent.reload
54 54
     expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" })
55 55
     expect(@agent.memory[:web_request_format]).to eq("text/html")
@@ -59,19 +59,19 @@ describe WebRequestsController do
59 59
   end
60 60
 
61 61
   it "should pass through the received format" do
62
-    get :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5", :format => :json
62
+    get :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}, :format => :json
63 63
     @agent.reload
64 64
     expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" })
65 65
     expect(@agent.memory[:web_request_format]).to eq("application/json")
66 66
     expect(@agent.memory[:web_request_method]).to eq("get")
67 67
 
68
-    post :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5", :format => :xml
68
+    post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}, :format => :xml
69 69
     @agent.reload
70 70
     expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" })
71 71
     expect(@agent.memory[:web_request_format]).to eq("application/xml")
72 72
     expect(@agent.memory[:web_request_method]).to eq("post")
73 73
 
74
-    put :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5", :format => :atom
74
+    put :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}, :format => :atom
75 75
     @agent.reload
76 76
     expect(@agent.memory[:web_request_values]).to eq({ 'key' => "value", 'another_key' => "5" })
77 77
     expect(@agent.memory[:web_request_format]).to eq("application/atom+xml")
@@ -81,17 +81,17 @@ describe WebRequestsController do
81 81
   it "can accept a content-type to return" do
82 82
     @agent.memory['content_type'] = 'application/json'
83 83
     @agent.save!
84
-    get :handle_request, :user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"
84
+    get :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => @agent.id, :secret => "my_secret", :key => "value", :another_key => "5"}
85 85
     expect(response.headers['Content-Type']).to eq('application/json; charset=utf-8')
86 86
   end
87 87
 
88 88
   it "should fail on incorrect users" do
89
-    post :handle_request, :user_id => users(:jane).to_param, :agent_id => @agent.id, :secret => "my_secret", :no => "go"
89
+    post :handle_request, params: {:user_id => users(:jane).to_param, :agent_id => @agent.id, :secret => "my_secret", :no => "go"}
90 90
     expect(response).to be_missing
91 91
   end
92 92
 
93 93
   it "should fail on incorrect agents" do
94
-    post :handle_request, :user_id => users(:bob).to_param, :agent_id => 454545, :secret => "my_secret", :no => "go"
94
+    post :handle_request, params: {:user_id => users(:bob).to_param, :agent_id => 454545, :secret => "my_secret", :no => "go"}
95 95
     expect(response).to be_missing
96 96
   end
97 97
 
@@ -102,7 +102,7 @@ describe WebRequestsController do
102 102
     end
103 103
 
104 104
     it "should create events without requiring login" do
105
-      post :update_location, user_id: users(:bob).to_param, secret: "my_secret", longitude: 123, latitude: 45, something: "else"
105
+      post :update_location, params: {user_id: users(:bob).to_param, secret: "my_secret", longitude: 123, latitude: 45, something: "else"}
106 106
       expect(@agent.events.last.payload).to eq({ 'longitude' => "123", 'latitude' => "45", 'something' => "else" })
107 107
       expect(@agent.events.last.lat).to eq(45)
108 108
       expect(@agent.events.last.lng).to eq(123)
@@ -112,13 +112,13 @@ describe WebRequestsController do
112 112
       @jane_agent = Agent.build_for_type("Agents::UserLocationAgent", users(:jane), name: "something", options: { secret: "my_secret" })
113 113
       @jane_agent.save!
114 114
 
115
-      post :update_location, user_id: users(:bob).to_param, secret: "my_secret", longitude: 123, latitude: 45, something: "else"
115
+      post :update_location, params: {user_id: users(:bob).to_param, secret: "my_secret", longitude: 123, latitude: 45, something: "else"}
116 116
       expect(@agent.events.last.payload).to eq({ 'longitude' => "123", 'latitude' => "45", 'something' => "else" })
117 117
       expect(@jane_agent.events).to be_empty
118 118
     end
119 119
 
120 120
     it "should raise a 404 error when given an invalid user id" do
121
-      post :update_location, user_id: "123", secret: "not_my_secret", longitude: 123, latitude: 45, something: "else"
121
+      post :update_location, params: {user_id: "123", secret: "not_my_secret", longitude: 123, latitude: 45, something: "else"}
122 122
       expect(response).to be_missing
123 123
     end
124 124
 
@@ -127,7 +127,7 @@ describe WebRequestsController do
127 127
       @agent2.save!
128 128
 
129 129
       expect {
130
-        post :update_location, user_id: users(:bob).to_param, secret: "my_secret2", longitude: 123, latitude: 45, something: "else"
130
+        post :update_location, params: {user_id: users(:bob).to_param, secret: "my_secret2", longitude: 123, latitude: 45, something: "else"}
131 131
         expect(@agent2.events.last.payload).to eq({ 'longitude' => "123", 'latitude' => "45", 'something' => "else" })
132 132
       }.not_to change { @agent.events.count }
133 133
     end

+ 2 - 2
spec/data_fixtures/onethingwell.atom

@@ -44,6 +44,7 @@
44 44
             <category>calendar</category>
45 45
             <category>menubar</category>
46 46
             <category>osx</category>
47
+            <enclosure url="http://c.1tw.org/images/2015/itsy.png" length="48249" type="image/png" />
47 48
         </item>
48 49
         <item>
49 50
             <title>Magic Wormhole</title>
@@ -208,8 +209,7 @@
208 209
         </item>
209 210
         <item>
210 211
             <title>Showgoers</title>
211
-            <description>&lt;a href="http://showgoers.tv/"&gt;Showgoers&lt;/a&gt;: &lt;blockquote&gt; &lt;p&gt;Showgoers is a Chrome browser extension to synchronize your Netflix player with someone else so that you can co-watch the same movie on different computers with no hassle. Syncing up your player is as easy as sharing a URL.&lt;/p&gt; &lt;/blockquote&gt;
212
-            </description>
212
+            <description>&lt;a href="http://showgoers.tv/" onmouseover="javascript:void(0)"&gt;Showgoers&lt;/a&gt;: &lt;blockquote&gt; &lt;p&gt;Showgoers is a Chrome browser extension to synchronize your Netflix player with someone else so that you can co-watch the same movie on different computers with no hassle. Syncing up your player is as easy as sharing a URL.&lt;/p&gt; &lt;/blockquote&gt;&lt;script&gt;some code&lt;/script&gt;</description>
213 213
             <link>http://onethingwell.org/post/125509667816</link>
214 214
             <guid>http://onethingwell.org/post/125509667816</guid>
215 215
             <pubDate>Fri, 31 Jul 2015 13:00:13 +0100</pubDate>

+ 2 - 1
spec/data_fixtures/urlTest.html

@@ -12,6 +12,7 @@
12 12
             <li><a href="https://www.google.ca/search?q=위키백과:대문">unicode param</a></li>
13 13
             <li><a href="http://ko.wikipedia.org/wiki/%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EB%8C%80%EB%AC%B8">percent encoded url</a></li>
14 14
             <li><a href="https://www.google.ca/search?q=%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EB%8C%80%EB%AC%B8">percent encoded param</a></li>
15
+            <li><a href="http://[::1]/path[]?query[]=foo">brackets</a></li>
15 16
         </ul>
16 17
     </body>
17
-</html>
18
+</html>

+ 76 - 0
spec/features/dry_running_spec.rb

@@ -0,0 +1,76 @@
1
+require 'rails_helper'
2
+
3
+describe "Dry running an Agent", js: true do
4
+  let(:agent)   { agents(:bob_website_agent) }
5
+  let(:formatting_agent) { agents(:bob_formatting_agent) }
6
+  let(:user)    { users(:bob) }
7
+  let(:emitter) { agents(:bob_weather_agent) }
8
+
9
+  before(:each) do
10
+    login_as(user)
11
+  end
12
+
13
+  def open_dry_run_modal(agent)
14
+    visit edit_agent_path(agent)
15
+    click_on("Dry Run")
16
+    expect(page).to have_text('Event to send')
17
+  end
18
+
19
+  context 'successful dry runs' do
20
+    before do
21
+      stub_request(:get, "http://xkcd.com/").
22
+        with(:headers => {'Accept-Encoding'=>'gzip,deflate', 'User-Agent'=>'Huginn - https://github.com/cantino/huginn'}).
23
+        to_return(:status => 200, :body => File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), :headers => {})
24
+    end
25
+
26
+    it 'shows the dry run pop up without previous events and selects the events tab when a event was created' do
27
+      open_dry_run_modal(agent)
28
+      click_on("Dry Run")
29
+      expect(page).to have_text('Biologists play reverse')
30
+      expect(page).to have_selector(:css, 'li[role="presentation"].active a[href="#tabEvents"]')
31
+    end
32
+
33
+    it 'shows the dry run pop up with previous events and allows use previously received event' do
34
+      emitter.events << Event.new(payload: {url: "http://xkcd.com/"})
35
+      agent.sources << emitter
36
+      agent.options.merge!('url' => '', 'url_from_event' => '{{url}}')
37
+      agent.save!
38
+
39
+      open_dry_run_modal(agent)
40
+      find('.dry-run-event-sample').click
41
+      within(:css, '.modal .builder') do
42
+        expect(page).to have_text('http://xkcd.com/')
43
+      end
44
+      click_on("Dry Run")
45
+      expect(page).to have_text('Biologists play reverse')
46
+      expect(page).to have_selector(:css, 'li[role="presentation"].active a[href="#tabEvents"]')
47
+    end
48
+
49
+    it 'sends escape characters correctly to the backend' do
50
+      emitter.events << Event.new(payload: {data: "Line 1\nLine 2\nLine 3"})
51
+      formatting_agent.sources << emitter
52
+      formatting_agent.options.merge!('instructions' => {'data' => "{{data | newline_to_br | strip_newlines | split: '<br />' | join: ','}}"})
53
+      formatting_agent.save!
54
+
55
+      open_dry_run_modal(formatting_agent)
56
+      find('.dry-run-event-sample').click
57
+      within(:css, '.modal .builder') do
58
+        expect(page).to have_text('Line 1\nLine 2\nLine 3')
59
+      end
60
+      click_on("Dry Run")
61
+      expect(page).to have_text('Line 1,Line 2,Line 3')
62
+      expect(page).to have_selector(:css, 'li[role="presentation"].active a[href="#tabEvents"]')
63
+    end
64
+  end
65
+
66
+  it 'shows the dry run pop up without previous events and selects the log tab when no event was created' do
67
+    stub_request(:get, "http://xkcd.com/").
68
+      with(:headers => {'Accept-Encoding'=>'gzip,deflate', 'User-Agent'=>'Huginn - https://github.com/cantino/huginn'}).
69
+      to_return(:status => 200, :body => "", :headers => {})
70
+
71
+    open_dry_run_modal(agent)
72
+    click_on("Dry Run")
73
+    expect(page).to have_text('Dry Run started')
74
+    expect(page).to have_selector(:css, 'li[role="presentation"].active a[href="#tabLog"]')
75
+  end
76
+end

+ 10 - 0
spec/features/form_configurable_feature_spec.rb

@@ -0,0 +1,10 @@
1
+require 'capybara_helper'
2
+
3
+describe "form configuring agents", js: true do
4
+  it 'completes fields with predefined array values' do
5
+    login_as(users(:bob))
6
+    visit edit_agent_path(agents(:bob_csv_agent))
7
+    check('Propagate immediately')
8
+    select2("serialize", from: "Mode")
9
+  end
10
+end

+ 46 - 0
spec/features/scenario_import_spec.rb

@@ -0,0 +1,46 @@
1
+require 'rails_helper'
2
+
3
+describe ScenarioImportsController do
4
+  let(:user) { users(:bob) }
5
+
6
+  before do
7
+    login_as(user)
8
+  end
9
+
10
+  it 'renders the import form' do
11
+    visit new_scenario_imports_path
12
+    expect(page).to have_text('Import a Public Scenario')
13
+  end
14
+
15
+  it 'requires a URL or file uplaod' do
16
+    visit new_scenario_imports_path
17
+    click_on 'Start Import'
18
+    expect(page).to have_text('Please provide either a Scenario JSON File or a Public Scenario URL.')
19
+  end
20
+
21
+  it 'imports a scenario that does not exist yet' do
22
+    visit new_scenario_imports_path
23
+    attach_file('Option 2: Upload a Scenario JSON File', File.join(Rails.root, 'data/default_scenario.json'))
24
+    click_on 'Start Import'
25
+    expect(page).to have_text('This scenario has a few agents to get you started. Feel free to change them or delete them as you see fit!')
26
+    expect(page).not_to have_text('This Scenario already exists in your system.')
27
+    check('I confirm that I want to import these Agents.')
28
+    click_on 'Finish Import'
29
+    expect(page).to have_text('Import successful!')
30
+  end
31
+
32
+  it 'asks to accept conflicts when the scenario was modified' do
33
+    DefaultScenarioImporter.seed(user)
34
+    agent = user.agents.where(name: 'Rain Notifier').first
35
+    agent.options['expected_receive_period_in_days'] = 9001
36
+    agent.save!
37
+    visit new_scenario_imports_path
38
+    attach_file('Option 2: Upload a Scenario JSON File', File.join(Rails.root, 'data/default_scenario.json'))
39
+    click_on 'Start Import'
40
+    expect(page).to have_text('This Scenario already exists in your system.')
41
+    expect(page).to have_text('9001')
42
+    check('I confirm that I want to import these Agents.')
43
+    click_on 'Finish Import'
44
+    expect(page).to have_text('Import successful!')
45
+  end
46
+end

+ 21 - 0
spec/features/undefined_agents_spec.rb

@@ -0,0 +1,21 @@
1
+require 'capybara_helper'
2
+
3
+describe "handling undefined agents" do
4
+  before do
5
+    login_as(users(:bob))
6
+    agent = agents(:bob_website_agent)
7
+    agent.update_attribute(:type, 'Agents::UndefinedAgent')
8
+  end
9
+
10
+  it 'renders the error page' do
11
+    visit agents_path
12
+    expect(page).to have_text("Error: Agent(s) are 'missing in action'")
13
+    expect(page).to have_text('Undefined Agent')
14
+  end
15
+
16
+  it 'deletes all undefined agents' do
17
+    visit agents_path
18
+    click_on('Delete Missing Agents')
19
+    expect(page).to have_text('Your Agents')
20
+  end
21
+end

+ 22 - 0
spec/fixtures/agents.yml

@@ -60,6 +60,14 @@ bob_weather_agent:
60 60
   keep_events_for: <%= 45.days %>
61 61
   options: <%= { :location => 94102, :lat => 37.779329, :lng => -122.41915, :api_key => 'test' }.to_json.inspect %>
62 62
 
63
+bob_formatting_agent:
64
+  type: Agents::EventFormattingAgent
65
+  user: bob
66
+  name: "Formatting Agent"
67
+  guid: <%= SecureRandom.hex %>
68
+  keep_events_for: <%= 45.days %>
69
+  options: <%= { instructions: {}, mode: 'clean' }.to_json.inspect %>
70
+
63 71
 jane_weather_agent:
64 72
   type: Agents::WeatherAgent
65 73
   user: jane
@@ -136,14 +144,28 @@ bob_manual_event_agent:
136 144
 bob_basecamp_agent:
137 145
   type: Agents::BasecampAgent
138 146
   user: bob
147
+  name: "bob basecamp agent"
139 148
   service: generic
140 149
   guid: <%= SecureRandom.hex %>
150
+  options: <%= {
151
+      :project_id => "12345",
152
+    }.to_json.inspect %>
153
+
154
+bob_csv_agent:
155
+  type: Agents::CsvAgent
156
+  user: bob
157
+  name: "Bob's CsvAgent"
158
+  guid: <%= SecureRandom.hex %>
141 159
 
142 160
 jane_basecamp_agent:
143 161
   type: Agents::BasecampAgent
144 162
   user: jane
163
+  name: "jane basecamp agent"
145 164
   service: generic
146 165
   guid: <%= SecureRandom.hex %>
166
+  options: <%= {
167
+      :project_id => "12345",
168
+    }.to_json.inspect %>
147 169
 
148 170
 
149 171
 bob_data_output_agent:

+ 5 - 5
spec/models/agents/boxcar_agent_spec.rb

@@ -44,17 +44,17 @@ describe Agents::BoxcarAgent do
44 44
 
45 45
     it "should raise error when invalid response arrives" do
46 46
       stub(HTTParty).post { {"blah" => "blah"} }
47
-      expect{@checker.send_notification}.to raise_error
47
+      expect { @checker.send_notification({}) }.to raise_error(StandardError, /Invalid response from Boxcar:/)
48 48
     end
49 49
 
50 50
     it "should raise error when response says unauthorized" do
51
-      stub(HTTParty).post '{"Response":"Not authorized"}'
52
-      expect{@checker.send_notification}.to raise_error
51
+      stub(HTTParty).post { {"Response" => "Not authorized"} }
52
+      expect { @checker.send_notification({}) }.to raise_error(StandardError, /Not authorized/)
53 53
     end
54 54
 
55 55
     it "should raise error when response has an error" do
56
-      stub(HTTParty).post '{"error": {"message": "Sample error"}}'
57
-      expect{@checker.send_notification}.to raise_error
56
+      stub(HTTParty).post { {"error" => {"message" => "Sample error"}} }
57
+      expect { @checker.send_notification({}) }.to raise_error(StandardError, /Sample error/)
58 58
     end
59 59
   end
60 60
 end

+ 52 - 6
spec/models/agents/data_output_agent_spec.rb

@@ -142,7 +142,7 @@ describe Agents::DataOutputAgent do
142 142
           "url" => "http://imgs.xkcd.com/comics/evolving0.png",
143 143
           "title" => "Evolving yet again with a past date",
144 144
           "date" => '2014/05/05',
145
-          "hovertext" => "Something else"
145
+          "hovertext" => "A small text"
146 146
         }
147 147
       end
148 148
 
@@ -166,7 +166,7 @@ describe Agents::DataOutputAgent do
166 166
 
167 167
            <item>
168 168
             <title>Evolving yet again with a past date</title>
169
-            <description>Secret hovertext: Something else</description>
169
+            <description>Secret hovertext: A small text</description>
170 170
             <link>http://imgs.xkcd.com/comics/evolving0.png</link>
171 171
             <pubDate>#{Time.zone.parse(event3.payload['date']).rfc2822}</pubDate>
172 172
             <guid isPermaLink="false">#{event3.id}</guid>
@@ -216,7 +216,7 @@ describe Agents::DataOutputAgent do
216 216
           'items' => [
217 217
             {
218 218
               'title' => 'Evolving yet again with a past date',
219
-              'description' => 'Secret hovertext: Something else',
219
+              'description' => 'Secret hovertext: A small text',
220 220
               'link' => 'http://imgs.xkcd.com/comics/evolving0.png',
221 221
               'guid' => {"contents" => event3.id, "isPermaLink" => "false"},
222 222
               'pubDate' => Time.zone.parse(event3.payload['date']).rfc2822,
@@ -242,16 +242,62 @@ describe Agents::DataOutputAgent do
242 242
         })
243 243
       end
244 244
 
245
+      context 'with more events' do
246
+        let!(:event4) do
247
+          agents(:bob_website_agent).create_event payload: {
248
+            'site_title' => 'XKCD',
249
+            'url' => 'http://imgs.xkcd.com/comics/comic1.png',
250
+            'title' => 'Comic 1',
251
+            'date' => '',
252
+            'hovertext' => 'Hovertext for Comic 1'
253
+          }
254
+        end
255
+
256
+        let!(:event5) do
257
+          agents(:bob_website_agent).create_event payload: {
258
+            'site_title' => 'XKCD',
259
+            'url' => 'http://imgs.xkcd.com/comics/comic2.png',
260
+            'title' => 'Comic 2',
261
+            'date' => '',
262
+            'hovertext' => 'Hovertext for Comic 2'
263
+          }
264
+        end
265
+
266
+        let!(:event6) do
267
+          agents(:bob_website_agent).create_event payload: {
268
+            'site_title' => 'XKCD',
269
+            'url' => 'http://imgs.xkcd.com/comics/comic3.png',
270
+            'title' => 'Comic 3',
271
+            'date' => '',
272
+            'hovertext' => 'Hovertext for Comic 3'
273
+          }
274
+        end
275
+
276
+        describe 'limiting' do
277
+          it 'can select the last `events_to_show` events' do
278
+            agent.options['events_to_show'] = 2
279
+            content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
280
+            expect(content['items'].map {|i| i["title"] }).to eq(["Comic 3", "Comic 2"])
281
+          end
282
+        end
283
+      end
284
+
245 285
       describe 'ordering' do
246 286
         before do
247
-          agent.options['events_order'] = ['{{title}}']
287
+          agent.options['events_order'] = ['{{hovertext}}']
288
+          agent.options['events_list_order'] = ['{{title}}']
248 289
         end
249 290
 
250
-        it 'can reorder the events_to_show last events based on a Liquid expression' do
291
+        it 'can reorder the last `events_to_show` events based on a Liquid expression' do
292
+          agent.options['events_to_show'] = 2
293
+          asc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
294
+          expect(asc_content['items'].map {|i| i["title"] }).to eq(["Evolving", "Evolving again"])
295
+
296
+          agent.options['events_to_show'] = 40
251 297
           asc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
252 298
           expect(asc_content['items'].map {|i| i["title"] }).to eq(["Evolving", "Evolving again", "Evolving yet again with a past date"])
253 299
 
254
-          agent.options['events_order'] = [['{{title}}', 'string', true]]
300
+          agent.options['events_list_order'] = [['{{title}}', 'string', true]]
255 301
 
256 302
           desc_content, _status, _content_type = agent.receive_web_request({ 'secret' => 'secret2' }, 'get', 'application/json')
257 303
           expect(desc_content['items']).to eq(asc_content['items'].reverse)

+ 28 - 25
spec/models/agents/email_digest_agent_spec.rb

@@ -8,11 +8,11 @@ describe Agents::EmailDigestAgent do
8 8
   end
9 9
 
10 10
   before do
11
-    @checker = Agents::EmailDigestAgent.new(:name => "something", :options => { :expected_receive_period_in_days => "2", :subject => "something interesting" })
11
+    @checker = Agents::EmailDigestAgent.new(:name => "something", :options => {:expected_receive_period_in_days => "2", :subject => "something interesting"})
12 12
     @checker.user = users(:bob)
13 13
     @checker.save!
14 14
 
15
-    @checker1 = Agents::EmailDigestAgent.new(:name => "something", :options => { :expected_receive_period_in_days => "2", :subject => "something interesting", :content_type => "text/plain" })
15
+    @checker1 = Agents::EmailDigestAgent.new(:name => "something", :options => {:expected_receive_period_in_days => "2", :subject => "something interesting", :content_type => "text/plain"})
16 16
     @checker1.user = users(:bob)
17 17
     @checker1.save!
18 18
   end
@@ -25,16 +25,16 @@ describe Agents::EmailDigestAgent do
25 25
     it "queues any payloads it receives" do
26 26
       event1 = Event.new
27 27
       event1.agent = agents(:bob_rain_notifier_agent)
28
-      event1.payload = { :data => "Something you should know about" }
28
+      event1.payload = {:data => "Something you should know about"}
29 29
       event1.save!
30 30
 
31 31
       event2 = Event.new
32 32
       event2.agent = agents(:bob_weather_agent)
33
-      event2.payload = { :data => "Something else you should know about" }
33
+      event2.payload = {:data => "Something else you should know about"}
34 34
       event2.save!
35 35
 
36 36
       Agents::EmailDigestAgent.async_receive(@checker.id, [event1.id, event2.id])
37
-      expect(@checker.reload.memory[:queue]).to eq([{ 'data' => "Something you should know about" }, { 'data' => "Something else you should know about" }])
37
+      expect(@checker.reload.memory['events']).to match([event1.id, event2.id])
38 38
     end
39 39
   end
40 40
 
@@ -44,25 +44,34 @@ describe Agents::EmailDigestAgent do
44 44
       Agents::EmailDigestAgent.async_check(@checker.id)
45 45
       expect(ActionMailer::Base.deliveries).to eq([])
46 46
 
47
-      @checker.memory[:queue] = [{ :data => "Something you should know about" },
48
-                                 { :title => "Foo", :url => "http://google.com", :bar => 2 },
49
-                                 { "message" => "hi", :woah => "there" },
50
-                                 { "test" => 2 }]
51
-      @checker.memory[:events] = [1,2,3,4]
52
-      @checker.save!
53
-
54
-      Agents::EmailDigestAgent.async_check(@checker.id)
47
+      payloads = [
48
+        {:data => "Something you should know about"},
49
+        {:title => "Foo", :url => "http://google.com", :bar => 2},
50
+        {"message" => "hi", :woah => "there"},
51
+        {"test" => 2}
52
+      ]
53
+
54
+      events = payloads.map do |payload|
55
+        Event.new.tap do |event|
56
+          event.agent = agents(:bob_weather_agent)
57
+          event.payload = payload
58
+          event.save!
59
+        end
60
+      end
61
+
62
+      Agents::DigestAgent.async_receive(@checker.id, events.map(&:id))
63
+      @checker.sources << agents(:bob_weather_agent)
64
+      Agents::DigestAgent.async_check(@checker.id)
55 65
 
56 66
       expect(ActionMailer::Base.deliveries.last.to).to eq(["bob@example.com"])
57 67
       expect(ActionMailer::Base.deliveries.last.subject).to eq("something interesting")
58 68
       expect(get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip).to eq("Event\n  data: Something you should know about\n\nFoo\n  bar: 2\n  url: http://google.com\n\nhi\n  woah: there\n\nEvent\n  test: 2")
59
-      expect(@checker.reload.memory[:queue]).to be_empty
69
+      expect(@checker.reload.memory[:events]).to be_empty
60 70
     end
61 71
 
62 72
     it "logs and re-raises mailer errors" do
63 73
       mock(SystemMailer).send_message(anything) { raise Net::SMTPAuthenticationError.new("Wrong password") }
64 74
 
65
-      @checker.memory[:queue] = [{ :data => "Something you should know about" }]
66 75
       @checker.memory[:events] = [1]
67 76
       @checker.save!
68 77
 
@@ -71,8 +80,6 @@ describe Agents::EmailDigestAgent do
71 80
       }.to raise_error(/Wrong password/)
72 81
 
73 82
       expect(@checker.reload.memory[:events]).not_to be_empty
74
-      expect(@checker.reload.memory[:queue]).not_to be_empty
75
-
76 83
       expect(@checker.logs.last.message).to match(/Error sending digest mail .* Wrong password/)
77 84
     end
78 85
 
@@ -84,7 +91,7 @@ describe Agents::EmailDigestAgent do
84 91
       Agent.async_check(agents(:bob_weather_agent).id)
85 92
 
86 93
       Agent.receive!
87
-      expect(@checker.reload.memory[:queue]).not_to be_empty
94
+      expect(@checker.reload.memory[:events]).not_to be_empty
88 95
 
89 96
       Agents::EmailDigestAgent.async_check(@checker.id)
90 97
 
@@ -94,18 +101,14 @@ describe Agents::EmailDigestAgent do
94 101
       expect(plain_email_text).to match(/avehumidity/)
95 102
       expect(html_email_text).to match(/avehumidity/)
96 103
 
97
-      expect(@checker.reload.memory[:queue]).to be_empty
104
+      expect(@checker.reload.memory[:events]).to be_empty
98 105
     end
99
-    
106
+
100 107
     it "should send email with correct content type" do
101 108
       Agents::EmailDigestAgent.async_check(@checker1.id)
102 109
       expect(ActionMailer::Base.deliveries).to eq([])
103 110
 
104
-      @checker1.memory[:queue] = [{ :data => "Something you should know about" },
105
-                                 { :title => "Foo", :url => "http://google.com", :bar => 2 },
106
-                                 { "message" => "hi", :woah => "there" },
107
-                                 { "test" => 2 }]
108
-      @checker1.memory[:events] = [1,2,3,4]
111
+      @checker1.memory[:events] = [1, 2, 3, 4]
109 112
       @checker1.save!
110 113
 
111 114
       Agents::EmailDigestAgent.async_check(@checker1.id)

+ 18 - 0
spec/models/agents/post_agent_spec.rb

@@ -57,6 +57,11 @@ describe Agents::PostAgent do
57 57
   end
58 58
 
59 59
   it_behaves_like WebRequestConcern
60
+  it_behaves_like 'FileHandlingConsumer'
61
+
62
+  it 'renders the description markdown without errors' do
63
+    expect { @checker.description }.not_to raise_error
64
+  end
60 65
 
61 66
   describe "making requests" do
62 67
     it "can make requests of each type" do
@@ -149,6 +154,19 @@ describe Agents::PostAgent do
149 154
       headers = @sent_requests[:post].first.headers
150 155
       expect(headers["Foo"]).to eq("a_variable")
151 156
     end
157
+
158
+    it 'makes a multipart request when receiving a file_pointer' do
159
+      WebMock.reset!
160
+      stub_request(:post, "http://www.example.com/").
161
+        with(:body => "-------------RubyMultipartPost\r\nContent-Disposition: form-data; name=\"default\"\r\n\r\nvalue\r\n-------------RubyMultipartPost\r\nContent-Disposition: form-data; name=\"file\"; filename=\"local.path\"\r\nContent-Length: 8\r\nContent-Type: \r\nContent-Transfer-Encoding: binary\r\n\r\ntestdata\r\n-------------RubyMultipartPost--\r\n\r\n",
162
+             :headers => {'Accept-Encoding'=>'gzip,deflate', 'Content-Length'=>'307', 'Content-Type'=>'multipart/form-data; boundary=-----------RubyMultipartPost', 'User-Agent'=>'Huginn - https://github.com/cantino/huginn'}).
163
+        to_return(:status => 200, :body => "", :headers => {})
164
+      event = Event.new(payload: {file_pointer: {agent_id: 111, file: 'test'}})
165
+      io_mock = mock()
166
+      mock(@checker).get_io(event) { StringIO.new("testdata") }
167
+      @checker.options['no_merge'] = true
168
+      @checker.receive([event])
169
+    end
152 170
   end
153 171
 
154 172
   describe "#check" do

+ 34 - 32
spec/models/agents/pushover_agent_spec.rb

@@ -2,27 +2,29 @@ require 'rails_helper'
2 2
 
3 3
 describe Agents::PushoverAgent do
4 4
   before do
5
-    @checker = Agents::PushoverAgent.new(:name => 'Some Name',
6
-                                       :options => { :token => 'x',
7
-                                                :user => 'x',
8
-                                                :message => 'Some Message',
9
-                                                :device => 'Some Device',
10
-                                                :title => 'Some Message Title',
11
-                                                :url => 'http://someurl.com',
12
-                                                :url_title => 'Some Url Title',
13
-                                                :priority => 0,
14
-                                                :timestamp => 'false',
15
-                                                :sound => 'pushover',
16
-                                                :retry => 0,
17
-                                                :expire => 0,
18
-                                                :expected_receive_period_in_days => '1'})
19
-     
5
+    @checker = Agents::PushoverAgent.new(name: 'Some Name',
6
+                                         options: {
7
+                                           token: 'x',
8
+                                           user: 'x',
9
+                                           message: "{{ message | default: text | default: 'Some Message' }}",
10
+                                           device: "{{ device | default: 'Some Device' }}",
11
+                                           title: "{{ title | default: 'Some Message Title' }}",
12
+                                           url: "{{ url | default: 'http://someurl.com' }}",
13
+                                           url_title: "{{ url_title | default: 'Some Url Title' }}",
14
+                                           priority: "{{ priority | default: 0 }}",
15
+                                           timestamp: "{{ timestamp | default: 'false' }}",
16
+                                           sound: "{{ sound | default: 'pushover' }}",
17
+                                           retry: "{{ retry | default: 0 }}",
18
+                                           expire: "{{ expire | default: 0 }}",
19
+                                           expected_receive_period_in_days: '1'
20
+                                         })
21
+
20 22
     @checker.user = users(:bob)
21 23
     @checker.save!
22 24
 
23 25
     @event = Event.new
24 26
     @event.agent = agents(:bob_weather_agent)
25
-    @event.payload = { :message => 'Looks like its going to rain' }
27
+    @event.payload = { message: 'Looks like its going to rain' }
26 28
     @event.save!
27 29
 
28 30
     @sent_notifications = []
@@ -33,12 +35,12 @@ describe Agents::PushoverAgent do
33 35
     it 'should make sure multiple events are being received' do
34 36
       event1 = Event.new
35 37
       event1.agent = agents(:bob_rain_notifier_agent)
36
-      event1.payload = { :message => 'Some message' }
38
+      event1.payload = { message: 'Some message' }
37 39
       event1.save!
38 40
 
39 41
       event2 = Event.new
40 42
       event2.agent = agents(:bob_weather_agent)
41
-      event2.payload = { :message => 'Some other message' }
43
+      event2.payload = { message: 'Some other message' }
42 44
       event2.save!
43 45
 
44 46
       @checker.receive([@event,event1,event2])
@@ -50,7 +52,7 @@ describe Agents::PushoverAgent do
50 52
     it 'should make sure event message overrides default message' do
51 53
       event = Event.new
52 54
       event.agent = agents(:bob_rain_notifier_agent)
53
-      event.payload = { :message => 'Some new message'}
55
+      event.payload = { message: 'Some new message'}
54 56
       event.save!
55 57
 
56 58
       @checker.receive([event])
@@ -60,7 +62,7 @@ describe Agents::PushoverAgent do
60 62
     it 'should make sure event text overrides default message' do
61 63
       event = Event.new
62 64
       event.agent = agents(:bob_rain_notifier_agent)
63
-      event.payload = { :text => 'Some new text'}
65
+      event.payload = { text: 'Some new text'}
64 66
       event.save!
65 67
 
66 68
       @checker.receive([event])
@@ -70,7 +72,7 @@ describe Agents::PushoverAgent do
70 72
     it 'should make sure event title overrides default title' do
71 73
       event = Event.new
72 74
       event.agent = agents(:bob_rain_notifier_agent)
73
-      event.payload = { :message => 'Some message', :title => 'Some new title' }
75
+      event.payload = { message: 'Some message', title: 'Some new title' }
74 76
       event.save!
75 77
 
76 78
       @checker.receive([event])
@@ -80,7 +82,7 @@ describe Agents::PushoverAgent do
80 82
     it 'should make sure event url overrides default url' do
81 83
       event = Event.new
82 84
       event.agent = agents(:bob_rain_notifier_agent)
83
-      event.payload = { :message => 'Some message', :url => 'Some new url' }
85
+      event.payload = { message: 'Some message', url: 'Some new url' }
84 86
       event.save!
85 87
 
86 88
       @checker.receive([event])
@@ -90,7 +92,7 @@ describe Agents::PushoverAgent do
90 92
     it 'should make sure event url_title overrides default url_title' do
91 93
       event = Event.new
92 94
       event.agent = agents(:bob_rain_notifier_agent)
93
-      event.payload = { :message => 'Some message', :url_title => 'Some new url_title' }
95
+      event.payload = { message: 'Some message', url_title: 'Some new url_title' }
94 96
       event.save!
95 97
 
96 98
       @checker.receive([event])
@@ -100,17 +102,17 @@ describe Agents::PushoverAgent do
100 102
     it 'should make sure event priority overrides default priority' do
101 103
       event = Event.new
102 104
       event.agent = agents(:bob_rain_notifier_agent)
103
-      event.payload = { :message => 'Some message', :priority => 1 }
105
+      event.payload = { message: 'Some message', priority: '1' }
104 106
       event.save!
105 107
 
106 108
       @checker.receive([event])
107
-      expect(@sent_notifications[0]['priority']).to eq(1)
109
+      expect(@sent_notifications[0]['priority']).to eq('1')
108 110
     end
109 111
 
110 112
     it 'should make sure event timestamp overrides default timestamp' do
111 113
       event = Event.new
112 114
       event.agent = agents(:bob_rain_notifier_agent)
113
-      event.payload = { :message => 'Some message', :timestamp => 'false' }
115
+      event.payload = { message: 'Some message', timestamp: 'false' }
114 116
       event.save!
115 117
 
116 118
       @checker.receive([event])
@@ -120,7 +122,7 @@ describe Agents::PushoverAgent do
120 122
     it 'should make sure event sound overrides default sound' do
121 123
       event = Event.new
122 124
       event.agent = agents(:bob_rain_notifier_agent)
123
-      event.payload = { :message => 'Some message', :sound => 'Some new sound' }
125
+      event.payload = { message: 'Some message', sound: 'Some new sound' }
124 126
       event.save!
125 127
 
126 128
       @checker.receive([event])
@@ -130,21 +132,21 @@ describe Agents::PushoverAgent do
130 132
     it 'should make sure event retry overrides default retry' do
131 133
       event = Event.new
132 134
       event.agent = agents(:bob_rain_notifier_agent)
133
-      event.payload = { :message => 'Some message', :retry => 1 }
135
+      event.payload = { message: 'Some message', retry: 1 }
134 136
       event.save!
135 137
 
136 138
       @checker.receive([event])
137
-      expect(@sent_notifications[0]['retry']).to eq(1)
139
+      expect(@sent_notifications[0]['retry']).to eq('1')
138 140
     end
139 141
 
140 142
     it 'should make sure event expire overrides default expire' do
141 143
       event = Event.new
142 144
       event.agent = agents(:bob_rain_notifier_agent)
143
-      event.payload = { :message => 'Some message', :expire => 60 }
145
+      event.payload = { message: 'Some message', expire: '60' }
144 146
       event.save!
145 147
 
146 148
       @checker.receive([event])
147
-      expect(@sent_notifications[0]['expire']).to eq(60)
149
+      expect(@sent_notifications[0]['expire']).to eq('60')
148 150
     end
149 151
   end
150 152
 
@@ -158,7 +160,7 @@ describe Agents::PushoverAgent do
158 160
       expect(@checker.reload).to be_working
159 161
       two_days_from_now = 2.days.from_now
160 162
       stub(Time).now { two_days_from_now }
161
-      
163
+
162 164
       # More time has passed than the expected receive period without any new events
163 165
       expect(@checker.reload).not_to be_working
164 166
     end

+ 92 - 2
spec/models/agents/rss_agent_spec.rb

@@ -55,16 +55,57 @@ describe Agents::RssAgent do
55 55
   end
56 56
 
57 57
   describe "emitting RSS events" do
58
-    it "should emit items as events" do
58
+    it "should emit items as events for an Atom feed" do
59
+      agent.options['include_feed_info'] = true
60
+
59 61
       expect {
60 62
         agent.check
61 63
       }.to change { agent.events.count }.by(20)
62 64
 
63 65
       first, *, last = agent.events.last(20)
66
+      [first, last].each do |event|
67
+        expect(first.payload['feed']).to include({
68
+                                                   "type" => "atom",
69
+                                                   "title" => "Recent Commits to huginn:master",
70
+                                                   "url" => "https://github.com/cantino/huginn/commits/master",
71
+                                                   "links" => [
72
+                                                     {
73
+                                                       "type" => "text/html",
74
+                                                       "rel" => "alternate",
75
+                                                       "href" => "https://github.com/cantino/huginn/commits/master",
76
+                                                     },
77
+                                                     {
78
+                                                       "type" => "application/atom+xml",
79
+                                                       "rel" => "self",
80
+                                                       "href" => "https://github.com/cantino/huginn/commits/master.atom",
81
+                                                     },
82
+                                                   ],
83
+                                                 })
84
+      end
64 85
       expect(first.payload['url']).to eq("https://github.com/cantino/huginn/commit/d0a844662846cf3c83b94c637c1803f03db5a5b0")
65 86
       expect(first.payload['urls']).to eq(["https://github.com/cantino/huginn/commit/d0a844662846cf3c83b94c637c1803f03db5a5b0"])
87
+      expect(first.payload['links']).to eq([
88
+                                             {
89
+                                               "href" => "https://github.com/cantino/huginn/commit/d0a844662846cf3c83b94c637c1803f03db5a5b0",
90
+                                               "rel" => "alternate",
91
+                                               "type" => "text/html",
92
+                                             }
93
+                                          ])
94
+      expect(first.payload['authors']).to eq(["cantino (https://github.com/cantino)"])
95
+      expect(first.payload['date_published']).to be_nil
96
+      expect(first.payload['last_updated']).to eq("2014-07-16T22:26:22-07:00")
66 97
       expect(last.payload['url']).to eq("https://github.com/cantino/huginn/commit/d465158f77dcd9078697e6167b50abbfdfa8b1af")
67 98
       expect(last.payload['urls']).to eq(["https://github.com/cantino/huginn/commit/d465158f77dcd9078697e6167b50abbfdfa8b1af"])
99
+      expect(last.payload['links']).to eq([
100
+                                              {
101
+                                                "href" => "https://github.com/cantino/huginn/commit/d465158f77dcd9078697e6167b50abbfdfa8b1af",
102
+                                                "rel" => "alternate",
103
+                                                "type" => "text/html",
104
+                                              }
105
+                                          ])
106
+      expect(last.payload['authors']).to eq(["CloCkWeRX (https://github.com/CloCkWeRX)"])
107
+      expect(last.payload['date_published']).to be_nil
108
+      expect(last.payload['last_updated']).to eq("2014-07-01T16:37:47+09:30")
68 109
     end
69 110
 
70 111
     it "should emit items as events in the order specified in the events_order option" do
@@ -82,6 +123,33 @@ describe Agents::RssAgent do
82 123
       expect(last.payload['urls']).to eq(["https://github.com/cantino/huginn/commit/0e80f5341587aace2c023b06eb9265b776ac4535"])
83 124
     end
84 125
 
126
+    it "should emit items as events for a FeedBurner RSS 2.0 feed" do
127
+      agent.options['url'] = "http://feeds.feedburner.com/SlickdealsnetFP?format=atom" # This is actually RSS 2.0 w/ Atom extension
128
+      agent.options['include_feed_info'] = true
129
+      agent.save!
130
+
131
+      expect {
132
+        agent.check
133
+      }.to change { agent.events.count }.by(79)
134
+
135
+      first, *, last = agent.events.last(79)
136
+      expect(first.payload['feed']).to include({
137
+                                                 "type" => "rss",
138
+                                                 "title" => "SlickDeals.net",
139
+                                                 "description" => "Slick online shopping deals.",
140
+                                                 "url" => "http://slickdeals.net/",
141
+                                               })
142
+      # Feedjira extracts feedburner:origLink
143
+      expect(first.payload['url']).to eq("http://slickdeals.net/permadeal/130160/green-man-gaming---pc-games-tomb-raider-game-of-the-year-6-hitman-absolution-elite-edition")
144
+      expect(last.payload['feed']).to include({
145
+                                                "type" => "rss",
146
+                                                "title" => "SlickDeals.net",
147
+                                                "description" => "Slick online shopping deals.",
148
+                                                "url" => "http://slickdeals.net/",
149
+                                              })
150
+      expect(last.payload['url']).to eq("http://slickdeals.net/permadeal/129980/amazon---rearth-ringke-fusion-bumper-hybrid-case-for-iphone-6")
151
+    end
152
+
85 153
     it "should track ids and not re-emit the same item when seen again" do
86 154
       agent.check
87 155
       expect(agent.memory['seen_ids']).to eq(agent.events.map {|e| e.payload['id'] })
@@ -155,17 +223,39 @@ describe Agents::RssAgent do
155 223
       @valid_options['url'] = 'http://onethingwell.org/rss'
156 224
     end
157 225
 
226
+    it "captures timestamps normalized in the ISO 8601 format" do
227
+      agent.check
228
+      first, *, third = agent.events.take(3)
229
+      expect(first.payload['date_published']).to eq('2015-08-20T17:00:10+01:00')
230
+      expect(third.payload['date_published']).to eq('2015-08-20T13:00:07+01:00')
231
+    end
232
+
158 233
     it "captures multiple categories" do
159 234
       agent.check
160 235
       first, *, third = agent.events.take(3)
161 236
       expect(first.payload['categories']).to eq(["csv", "crossplatform", "utilities"])
162 237
       expect(third.payload['categories']).to eq(["web"])
163 238
     end
239
+
240
+    it "sanitizes HTML content" do
241
+      agent.options['clean'] = true
242
+      agent.check
243
+      event = agent.events.last
244
+      expect(event.payload['content']).to eq('<a href="http://showgoers.tv/">Showgoers</a>: <blockquote> <p>Showgoers is a Chrome browser extension to synchronize your Netflix player with someone else so that you can co-watch the same movie on different computers with no hassle. Syncing up your player is as easy as sharing a URL.</p> </blockquote>')
245
+      expect(event.payload['description']).to eq('<a href="http://showgoers.tv/">Showgoers</a>: <blockquote> <p>Showgoers is a Chrome browser extension to synchronize your Netflix player with someone else so that you can co-watch the same movie on different computers with no hassle. Syncing up your player is as easy as sharing a URL.</p> </blockquote>')
246
+    end
247
+
248
+    it "captures an enclosure" do
249
+      agent.check
250
+      event = agent.events.fourth
251
+      expect(event.payload['enclosure']).to eq({ "url" => "http://c.1tw.org/images/2015/itsy.png", "type" => "image/png", "length" => "48249" })
252
+      expect(event.payload['image']).to eq("http://c.1tw.org/images/2015/itsy.png")
253
+    end
164 254
   end
165 255
 
166 256
   describe 'logging errors with the feed url' do
167 257
     it 'includes the feed URL when an exception is raised' do
168
-      mock(FeedNormalizer::FeedNormalizer).parse(anything, loose: true) { raise StandardError.new("Some error!") }
258
+      mock(Feedjira::Feed).parse(anything) { raise StandardError.new("Some error!") }
169 259
       expect(lambda {
170 260
         agent.check
171 261
       }).not_to raise_error

+ 43 - 12
spec/models/agents/twitter_action_agent_spec.rb

@@ -24,11 +24,11 @@ describe Agents::TwitterActionAgent do
24 24
 
25 25
     context 'when set up to retweet' do
26 26
       before do
27
-        @agent = build_agent({
28
-          'expected_receive_period_in_days' => '2',
27
+        @agent = build_agent(
29 28
           'favorite' => 'false',
30 29
           'retweet' => 'true',
31
-        })
30
+          'emit_error_events' => 'true'
31
+        )
32 32
         @agent.save!
33 33
       end
34 34
 
@@ -68,9 +68,9 @@ describe Agents::TwitterActionAgent do
68 68
     context 'when set up to favorite' do
69 69
       before do
70 70
         @agent = build_agent(
71
-          'expected_receive_period_in_days' => '2',
72 71
           'favorite' => 'true',
73 72
           'retweet' => 'false',
73
+          'emit_error_events' => 'true'
74 74
         )
75 75
         @agent.save!
76 76
       end
@@ -107,13 +107,48 @@ describe Agents::TwitterActionAgent do
107 107
         end
108 108
       end
109 109
     end
110
+
111
+    context 'with emit_error_events set to false' do
112
+      it 'does re-raises the exception on failure' do
113
+        agent = build_agent
114
+
115
+        stub(agent.twitter).retweet(anything) {
116
+          raise Twitter::Error.new('uh oh')
117
+        }
118
+
119
+       expect { agent.receive([@event1]) }.to raise_error(StandardError, /uh oh/)
120
+
121
+      end
122
+    end
110 123
   end
111 124
 
112 125
   describe "#validate_options" do
126
+    it 'the default options are valid' do
127
+      agent = build_agent(described_class.new.default_options)
128
+
129
+      expect(agent).to be_valid
130
+    end
131
+
132
+    context 'emit_error_events' do
133
+      it 'can be set to true' do
134
+        agent = build_agent(described_class.new.default_options.merge('emit_error_events' => 'true'))
135
+        expect(agent).to be_valid
136
+      end
137
+
138
+      it 'must be a boolean' do
139
+        agent = build_agent(described_class.new.default_options.merge('emit_error_events' => 'notbolean'))
140
+        expect(agent).not_to be_valid
141
+      end
142
+    end
143
+
144
+    it 'expected_receive_period_in_days must be set' do
145
+      agent = build_agent(described_class.new.default_options.merge('expected_receive_period_in_days' => ''))
146
+      expect(agent).not_to be_valid
147
+    end
148
+
113 149
     context 'when set up to neither favorite or retweet' do
114 150
       it 'is invalid' do
115 151
         agent = build_agent(
116
-          'expected_receive_period_in_days' => '2',
117 152
           'favorite' => 'false',
118 153
           'retweet' => 'false',
119 154
         )
@@ -129,11 +164,7 @@ describe Agents::TwitterActionAgent do
129 164
     end
130 165
 
131 166
     it 'checks if events have been received within the expected time period' do
132
-      agent = build_agent(
133
-        'expected_receive_period_in_days' => '2',
134
-        'favorite' => 'false',
135
-        'retweet' => 'true',
136
-      )
167
+      agent = build_agent
137 168
       agent.save!
138 169
 
139 170
       expect(agent).not_to be_working # No events received
@@ -147,10 +178,10 @@ describe Agents::TwitterActionAgent do
147 178
     end
148 179
   end
149 180
 
150
-  def build_agent(options)
181
+  def build_agent(options = {})
151 182
     described_class.new do |agent|
152 183
       agent.name = 'twitter stuff'
153
-      agent.options = options
184
+      agent.options = agent.default_options.merge(options)
154 185
       agent.service = services(:generic)
155 186
       agent.user = users(:bob)
156 187
     end

+ 10 - 0
spec/models/agents/weather_agent_spec.rb

@@ -19,6 +19,16 @@ describe Agents::WeatherAgent do
19 19
   it "creates a valid agent" do
20 20
     expect(agent).to be_valid
21 21
   end
22
+
23
+  it "is valid with put-your-key-here or your-key" do
24
+    agent.options['api_key'] = 'put-your-key-here'
25
+    expect(agent).to be_valid
26
+    expect(agent.working?).to be_falsey
27
+
28
+    agent.options['api_key'] = 'your-key'
29
+    expect(agent).to be_valid
30
+    expect(agent.working?).to be_falsey
31
+  end
22 32
   
23 33
   describe "#service" do
24 34
     it "doesn't have a Service object attached" do

+ 26 - 0
spec/models/agents/webhook_agent_spec.rb

@@ -49,6 +49,12 @@ describe Agents::WebhookAgent do
49 49
       expect(out).to eq(['', 201])
50 50
     end
51 51
 
52
+    it 'should respond with interpolated response message if configured with `response` option' do
53
+      agent.options['response'] = '{{some_key.people[1].name}}'
54
+      out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html")
55
+      expect(out).to eq(['jon', 201])
56
+    end
57
+
52 58
     it 'should respond with `Event Created` if the response option is nil or missing' do
53 59
       agent.options['response'] = nil
54 60
       out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html")
@@ -59,6 +65,26 @@ describe Agents::WebhookAgent do
59 65
       expect(out).to eq(['Event Created', 201])
60 66
     end
61 67
 
68
+    it 'should respond with customized response code if configured with `code` option' do
69
+      agent.options['code'] = '200'
70
+      out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html")
71
+      expect(out).to eq(['Event Created', 200])
72
+    end
73
+
74
+    it 'should respond with `201` if the code option is empty, nil or missing' do
75
+      agent.options['code'] = ''
76
+      out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html")
77
+      expect(out).to eq(['Event Created', 201])
78
+      
79
+      agent.options['code'] = nil
80
+      out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html")
81
+      expect(out).to eq(['Event Created', 201])
82
+
83
+      agent.options.delete('code')
84
+      out = agent.receive_web_request({ 'secret' => 'foobar', 'some_key' => payload }, "post", "text/html")
85
+      expect(out).to eq(['Event Created', 201])
86
+    end
87
+
62 88
     describe "receiving events" do
63 89
 
64 90
       context "default settings" do

+ 7 - 2
spec/models/agents/website_agent_spec.rb

@@ -1105,8 +1105,8 @@ fire: hot
1105 1105
 
1106 1106
     describe "#check" do
1107 1107
       before do
1108
-        expect { @checker.check }.to change { Event.count }.by(7)
1109
-        @events = Event.last(7)
1108
+        expect { @checker.check }.to change { Event.count }.by(8)
1109
+        @events = Event.last(8)
1110 1110
       end
1111 1111
 
1112 1112
       it "should check hostname" do
@@ -1143,6 +1143,11 @@ fire: hot
1143 1143
         event = @events[6]
1144 1144
         expect(event.payload['url']).to eq("https://www.google.ca/search?q=%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EB%8C%80%EB%AC%B8")
1145 1145
       end
1146
+
1147
+      it "should check url with unescaped brackets in the path component" do
1148
+        event = @events[7]
1149
+        expect(event.payload['url']).to eq("http://[::1]/path%5B%5D?query[]=foo")
1150
+      end
1146 1151
     end
1147 1152
   end
1148 1153
 end

+ 25 - 0
spec/models/service_spec.rb

@@ -98,6 +98,7 @@ describe Service do
98 98
       expect(service.token).to eq('a1b2c3d4...')
99 99
       expect(service.secret).to eq('abcdef1234')
100 100
     end
101
+
101 102
     it "should work with 37signals services" do
102 103
       signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/37signals.json')))
103 104
       expect {
@@ -113,6 +114,7 @@ describe Service do
113 114
       expect(service.options[:user_id]).to eq(12345)
114 115
       service.expires_at = Time.at(1401554352)
115 116
     end
117
+
116 118
     it "should work with github services" do
117 119
       signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/github.json')))
118 120
       expect {
@@ -126,4 +128,27 @@ describe Service do
126 128
       expect(service.token).to eq('agithubtoken')
127 129
     end
128 130
   end
131
+
132
+  describe 'omniauth options provider registry for non-conforming omniauth responses' do
133
+    describe '.register_options_provider' do
134
+      before do
135
+        Service.register_options_provider('test-omniauth-provider') do |omniauth|
136
+          { name: omniauth['special_field'] }
137
+        end
138
+      end
139
+
140
+      after do
141
+        Service.option_providers.delete('test-omniauth-provider')
142
+      end
143
+
144
+      it 'allows gem developers to add their own options provider to the registry' do
145
+        actual_options = Service.get_options({
146
+          'provider' => 'test-omniauth-provider',
147
+          'special_field' => 'A Great Name'
148
+        })
149
+
150
+        expect(actual_options[:name]).to eq('A Great Name')
151
+      end
152
+    end
153
+  end
129 154
 end

+ 0 - 8
spec/models/user_credential_spec.rb

@@ -8,14 +8,6 @@ describe UserCredential do
8 8
     it { should validate_presence_of(:user_id) }
9 9
   end
10 10
 
11
-  describe "mass assignment" do
12
-    it { should allow_mass_assignment_of :credential_name }
13
-
14
-    it { should allow_mass_assignment_of :credential_value }
15
-
16
-    it { should_not allow_mass_assignment_of :user_id }
17
-  end
18
-
19 11
   describe "cleaning fields" do
20 12
     it "should trim whitespace" do
21 13
       user_credential = user_credentials(:bob_aws_key)

+ 27 - 0
spec/models/users_spec.rb

@@ -1,6 +1,8 @@
1 1
 require 'rails_helper'
2 2
 
3 3
 describe User do
4
+  let(:bob) { users(:bob) }
5
+
4 6
   describe "validations" do
5 7
     describe "invitation_code" do
6 8
       context "when configured to use invitation codes" do
@@ -64,4 +66,29 @@ describe User do
64 66
       expect(users(:bob).deactivated_at).to be_nil
65 67
     end
66 68
   end
69
+
70
+  context '#undefined_agent_types' do
71
+    it 'returns an empty array when no agents are undefined' do
72
+      expect(bob.undefined_agent_types).to be_empty
73
+    end
74
+
75
+    it 'returns the undefined agent types' do
76
+      agent = agents(:bob_website_agent)
77
+      agent.update_attribute(:type, 'Agents::UndefinedAgent')
78
+      expect(bob.undefined_agent_types).to match_array(['Agents::UndefinedAgent'])
79
+    end
80
+  end
81
+
82
+  context '#undefined_agents' do
83
+    it 'returns an empty array when no agents are undefined' do
84
+      expect(bob.undefined_agents).to be_empty
85
+    end
86
+
87
+    it 'returns the undefined agent types' do
88
+      agent = agents(:bob_website_agent)
89
+      agent.update_attribute(:type, 'Agents::UndefinedAgent')
90
+      expect(bob.undefined_agents).not_to be_empty
91
+      expect(bob.undefined_agents.first).to be_a(Agent)
92
+    end
93
+  end
67 94
 end

+ 2 - 2
spec/rails_helper.rb

@@ -13,7 +13,7 @@ require 'rspec/rails'
13 13
 require 'rr'
14 14
 require 'webmock/rspec'
15 15
 
16
-WebMock.disable_net_connect!
16
+WebMock.disable_net_connect!(allow_localhost: true)
17 17
 
18 18
 # Requires supporting ruby files with custom matchers and macros, etc,
19 19
 # in spec/support/ and its subdirectories.
@@ -66,7 +66,7 @@ RSpec.configure do |config|
66 66
 
67 67
   config.render_views
68 68
 
69
-  config.include Devise::TestHelpers, type: :controller
69
+  config.include Devise::Test::ControllerHelpers, type: :controller
70 70
   config.include SpecHelpers
71 71
   config.include Delorean
72 72
 end

+ 23 - 3
spec/support/shared_examples/file_handling_consumer.rb

@@ -1,6 +1,8 @@
1 1
 require 'rails_helper'
2 2
 
3 3
 shared_examples_for 'FileHandlingConsumer' do
4
+  let(:event) { Event.new(user: @checker.user, payload: {'file_pointer' => {'file' => 'text.txt', 'agent_id' => @checker.id}}) }
5
+
4 6
   it 'returns a file pointer' do
5 7
     expect(@checker.get_file_pointer('testfile')).to eq(file_pointer: { file: "testfile", agent_id: @checker.id})
6 8
   end
@@ -9,8 +11,26 @@ shared_examples_for 'FileHandlingConsumer' do
9 11
     @checker2 = @checker.dup
10 12
     @checker2.user = users(:bob)
11 13
     @checker2.save!
12
-    expect(@checker2.user.id).not_to eq(@checker.user.id)
13
-    event = Event.new(user: @checker.user, payload: {'file_pointer' => {'file' => 'test', 'agent_id' => @checker2.id}})
14
+    event.payload['file_pointer']['agent_id'] = @checker2.id
14 15
     expect { @checker.get_io(event) }.to raise_error(ActiveRecord::RecordNotFound)
15 16
   end
16
-end
17
+
18
+  context '#has_file_pointer?' do
19
+    it 'returns true if the event contains a file pointer' do
20
+      expect(@checker.has_file_pointer?(event)).to be_truthy
21
+    end
22
+
23
+    it 'returns false if the event does not contain a file pointer' do
24
+      expect(@checker.has_file_pointer?(Event.new)).to be_falsy
25
+    end
26
+  end
27
+
28
+  it '#get_upload_io returns a Faraday::UploadIO instance' do
29
+    io_mock = mock()
30
+    mock(@checker).get_io(event) { StringIO.new("testdata") }
31
+
32
+    upload_io = @checker.get_upload_io(event)
33
+    expect(upload_io).to be_a(Faraday::UploadIO)
34
+    expect(upload_io.content_type).to eq('text/plain')
35
+  end
36
+end

+ 142 - 36
vendor/assets/javascripts/jquery.serializeObject.js

@@ -1,40 +1,146 @@
1
-//
2
-// Use internal $.serializeArray to get list of form elements which is
3
-// consistent with $.serialize
4
-//
5
-// From version 2.0.0, $.serializeObject will stop converting [name] values
6
-// to camelCase format. This is *consistent* with other serialize methods:
7
-//
8
-//   - $.serialize
9
-//   - $.serializeArray
10
-//
11
-// If you require camel casing, you can either download version 1.0.4 or map
12
-// them yourself.
13
-//
14
-
15
-(function($){
16
-  $.fn.serializeObject = function () {
17
-    "use strict";
18
-
19
-    var result = {};
20
-    var extend = function (i, element) {
21
-      var node = result[element.name];
22
-
23
-  // If node with same name exists already, need to convert it to an array as it
24
-  // is a multi-value field (i.e., checkboxes)
25
-
26
-      if ('undefined' !== typeof node && node !== null) {
27
-        if ($.isArray(node)) {
28
-          node.push(element.value);
29
-        } else {
30
-          result[element.name] = [node, element.value];
1
+/**
2
+ * jQuery serializeObject
3
+ * @copyright 2014, macek <paulmacek@gmail.com>
4
+ * @link https://github.com/macek/jquery-serialize-object
5
+ * @license BSD
6
+ * @version 2.5.0
7
+ */
8
+(function(root, factory) {
9
+
10
+  // AMD
11
+  if (typeof define === "function" && define.amd) {
12
+    define(["exports", "jquery"], function(exports, $) {
13
+      return factory(exports, $);
14
+    });
15
+  }
16
+
17
+  // CommonJS
18
+  else if (typeof exports !== "undefined") {
19
+    var $ = require("jquery");
20
+    factory(exports, $);
21
+  }
22
+
23
+  // Browser
24
+  else {
25
+    factory(root, (root.jQuery || root.Zepto || root.ender || root.$));
26
+  }
27
+
28
+}(this, function(exports, $) {
29
+
30
+  var patterns = {
31
+    validate: /^[a-z_][a-z0-9_]*(?:\[(?:\d*|[a-z0-9_]+)\])*$/i,
32
+    key:      /[a-z0-9_]+|(?=\[\])/gi,
33
+    push:     /^$/,
34
+    fixed:    /^\d+$/,
35
+    named:    /^[a-z0-9_]+$/i
36
+  };
37
+
38
+  function FormSerializer(helper, $form) {
39
+
40
+    // private variables
41
+    var data     = {},
42
+        pushes   = {};
43
+
44
+    // private API
45
+    function build(base, key, value) {
46
+      base[key] = value;
47
+      return base;
48
+    }
49
+
50
+    function makeObject(root, value) {
51
+
52
+      var keys = root.match(patterns.key), k;
53
+
54
+      // nest, nest, ..., nest
55
+      while ((k = keys.pop()) !== undefined) {
56
+        // foo[]
57
+        if (patterns.push.test(k)) {
58
+          var idx = incrementPush(root.replace(/\[\]$/, ''));
59
+          value = build([], idx, value);
60
+        }
61
+
62
+        // foo[n]
63
+        else if (patterns.fixed.test(k)) {
64
+          value = build([], k, value);
65
+        }
66
+
67
+        // foo; foo[bar]
68
+        else if (patterns.named.test(k)) {
69
+          value = build({}, k, value);
31 70
         }
32
-      } else {
33
-        result[element.name] = element.value;
34 71
       }
35
-    };
36 72
 
37
-    $.each(this.serializeArray(), extend);
38
-    return result;
73
+      return value;
74
+    }
75
+
76
+    function incrementPush(key) {
77
+      if (pushes[key] === undefined) {
78
+        pushes[key] = 0;
79
+      }
80
+      return pushes[key]++;
81
+    }
82
+
83
+    function encode(pair) {
84
+      switch ($('[name="' + pair.name + '"]', $form).attr("type")) {
85
+        case "checkbox":
86
+          return pair.value === "on" ? true : pair.value;
87
+        default:
88
+          return pair.value;
89
+      }
90
+    }
91
+
92
+    function addPair(pair) {
93
+      if (!patterns.validate.test(pair.name)) return this;
94
+      var obj = makeObject(pair.name, encode(pair));
95
+      data = helper.extend(true, data, obj);
96
+      return this;
97
+    }
98
+
99
+    function addPairs(pairs) {
100
+      if (!helper.isArray(pairs)) {
101
+        throw new Error("formSerializer.addPairs expects an Array");
102
+      }
103
+      for (var i=0, len=pairs.length; i<len; i++) {
104
+        this.addPair(pairs[i]);
105
+      }
106
+      return this;
107
+    }
108
+
109
+    function serialize() {
110
+      return data;
111
+    }
112
+
113
+    function serializeJSON() {
114
+      return JSON.stringify(serialize());
115
+    }
116
+
117
+    // public API
118
+    this.addPair = addPair;
119
+    this.addPairs = addPairs;
120
+    this.serialize = serialize;
121
+    this.serializeJSON = serializeJSON;
122
+  }
123
+
124
+  FormSerializer.patterns = patterns;
125
+
126
+  FormSerializer.serializeObject = function serializeObject() {
127
+    return new FormSerializer($, this).
128
+      addPairs(this.serializeArray()).
129
+      serialize();
130
+  };
131
+
132
+  FormSerializer.serializeJSON = function serializeJSON() {
133
+    return new FormSerializer($, this).
134
+      addPairs(this.serializeArray()).
135
+      serializeJSON();
39 136
   };
40
-})(jQuery);
137
+
138
+  if (typeof $.fn !== "undefined") {
139
+    $.fn.serializeObject = FormSerializer.serializeObject;
140
+    $.fn.serializeJSON   = FormSerializer.serializeJSON;
141
+  }
142
+
143
+  exports.FormSerializer = FormSerializer;
144
+
145
+  return FormSerializer;
146
+}));

huginn - Gogs J1X

java_script_agent_spec.rb 10.0KB

    require 'rails_helper' describe Agents::JavaScriptAgent do before do @valid_params = { :name => "somename", :options => { :code => "Agent.check = function() { this.createEvent({ 'message': 'hi' }); };", } } @agent = Agents::JavaScriptAgent.new(@valid_params) @agent.user = users(:jane) @agent.save! end describe "validations" do it "requires 'code'" do expect(@agent).to be_valid @agent.options['code'] = '' expect(@agent).not_to be_valid @agent.options.delete('code') expect(@agent).not_to be_valid end it "checks for a valid 'language', but allows nil" do expect(@agent).to be_valid @agent.options['language'] = '' expect(@agent).to be_valid @agent.options.delete('language') expect(@agent).to be_valid @agent.options['language'] = 'foo' expect(@agent).not_to be_valid %w[javascript JavaScript coffeescript CoffeeScript].each do |valid_language| @agent.options['language'] = valid_language expect(@agent).to be_valid end end it "accepts a credential, but it must exist" do expect(@agent).to be_valid @agent.options['code'] = 'credential:foo' expect(@agent).not_to be_valid users(:jane).user_credentials.create! :credential_name => "foo", :credential_value => "bar" expect(@agent.reload).to be_valid end end describe "#working?" do describe "when expected_update_period_in_days is set" do it "returns false when more than expected_update_period_in_days have passed since the last event creation" do @agent.options['expected_update_period_in_days'] = 1 @agent.save! expect(@agent).not_to be_working @agent.check expect(@agent.reload).to be_working three_days_from_now = 3.days.from_now stub(Time).now { three_days_from_now } expect(@agent).not_to be_working end end describe "when expected_receive_period_in_days is set" do it "returns false when more than expected_receive_period_in_days have passed since the last event was received" do @agent.options['expected_receive_period_in_days'] = 1 @agent.save! expect(@agent).not_to be_working Agents::JavaScriptAgent.async_receive @agent.id, [events(:bob_website_agent_event).id] expect(@agent.reload).to be_working two_days_from_now = 2.days.from_now stub(Time).now { two_days_from_now } expect(@agent.reload).not_to be_working end end end describe "executing code" do it "works by default" do @agent.options = @agent.default_options @agent.options['make_event'] = true @agent.save! expect { expect { @agent.receive([events(:bob_website_agent_event)]) @agent.check }.not_to change { AgentLog.count } }.to change { Event.count }.by(2) end describe "using credentials as code" do before do @agent.user.user_credentials.create :credential_name => 'code-foo', :credential_value => 'Agent.check = function() { this.log("ran it"); };' @agent.options['code'] = "credential:code-foo\n\n" @agent.save! end it "accepts credentials" do @agent.check expect(AgentLog.last.message).to eq("ran it") end it "logs an error when the credential goes away" do @agent.user.user_credentials.delete_all @agent.reload.check expect(AgentLog.last.message).to eq("Unable to find credential") end end describe "error handling" do it "should log an error when V8 has issues" do @agent.options['code'] = 'syntax error!' @agent.save! expect { expect { @agent.check }.not_to raise_error }.to change { AgentLog.count }.by(1) expect(AgentLog.last.message).to match(/Unexpected identifier/) expect(AgentLog.last.level).to eq(4) end it "should log an error when JavaScript throws" do @agent.options['code'] = 'Agent.check = function() { throw "oh no"; };' @agent.save! expect { expect { @agent.check }.not_to raise_error }.to change { AgentLog.count }.by(1) expect(AgentLog.last.message).to match(/oh no/) expect(AgentLog.last.level).to eq(4) end it "won't store NaNs" do @agent.options['code'] = 'Agent.check = function() { this.memory("foo", NaN); };' @agent.save! @agent.check expect(@agent.memory['foo']).to eq('NaN') # string @agent.save! expect { @agent.reload.memory }.not_to raise_error end end describe "creating events" do it "creates events with this.createEvent in the JavaScript environment" do @agent.options['code'] = 'Agent.check = function() { this.createEvent({ message: "This is an event!", stuff: { foo: 5 } }); };' @agent.save! expect { expect { @agent.check }.not_to change { AgentLog.count } }.to change { Event.count }.by(1) created_event = @agent.events.last expect(created_event.payload).to eq({ 'message' => "This is an event!", 'stuff' => { 'foo' => 5 } }) end end describe "logging" do it "can output AgentLogs with this.log and this.error in the JavaScript environment" do @agent.options['code'] = 'Agent.check = function() { this.log("woah"); this.error("WOAH!"); };' @agent.save! expect { expect { @agent.check }.not_to raise_error }.to change { AgentLog.count }.by(2) log1, log2 = AgentLog.last(2) expect(log1.message).to eq("woah") expect(log1.level).to eq(3) expect(log2.message).to eq("WOAH!") expect(log2.level).to eq(4) end end describe "escaping and unescaping HTML" do it "can escape and unescape html with this.escapeHtml and this.unescapeHtml in the javascript environment" do @agent.options['code'] = 'Agent.check = function() { this.createEvent({ escaped: this.escapeHtml(\'test \"escaping\" <characters>\'), unescaped: this.unescapeHtml(\'test &quot;unescaping&quot; &lt;characters&gt;\')}); };' @agent.save! expect { expect { @agent.check }.not_to change { AgentLog.count } }.to change { Event.count}.by(1) created_event = @agent.events.last expect(created_event.payload).to eq({ 'escaped' => 'test &quot;escaping&quot; &lt;characters&gt;', 'unescaped' => 'test "unescaping" <characters>'}) end end describe "getting incoming events" do it "can access incoming events in the JavaScript enviroment via this.incomingEvents" do event = Event.new event.agent = agents(:bob_rain_notifier_agent) event.payload = { :data => "Something you should know about" } event.save! event.reload @agent.options['code'] = <<-JS Agent.receive = function() { var events = this.incomingEvents(); for(var i = 0; i < events.length; i++) { this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload }); } } JS @agent.save! expect { expect { @agent.receive([events(:bob_website_agent_event), event]) }.not_to change { AgentLog.count } }.to change { Event.count }.by(2) created_event = @agent.events.first expect(created_event.payload).to eq({ 'message' => "I got an event!", 'event_was' => { 'data' => "Something you should know about" } }) end end describe "getting and setting memory, getting options" do it "can access options via this.options and work with memory via this.memory" do @agent.options['code'] = <<-JS Agent.check = function() { if (this.options('make_event')) { var callCount = this.memory('callCount') || 0; this.memory('callCount', callCount + 1); } }; JS @agent.save! expect { expect { @agent.check expect(@agent.memory['callCount']).not_to be_present @agent.options['make_event'] = true @agent.check expect(@agent.memory['callCount']).to eq(1) @agent.check expect(@agent.memory['callCount']).to eq(2) @agent.memory['callCount'] = 20 @agent.check expect(@agent.memory['callCount']).to eq(21) }.not_to change { AgentLog.count } }.not_to change { Event.count } end end describe "using CoffeeScript" do it "will accept a 'language' of 'CoffeeScript'" do @agent.options['code'] = 'Agent.check = -> this.log("hello from coffeescript")' @agent.options['language'] = 'CoffeeScript' @agent.save! expect { @agent.check }.not_to raise_error expect(AgentLog.last.message).to eq("hello from coffeescript") end end describe "user credentials" do it "can access an existing credential" do @agent.send(:set_credential, 'test', 'hello') @agent.options['code'] = 'Agent.check = function() { this.log(this.credential("test")); };' @agent.save! @agent.check expect(AgentLog.last.message).to eq("hello") end it "will create a new credential" do @agent.options['code'] = 'Agent.check = function() { this.credential("test","1234"); };' @agent.save! expect { @agent.check }.to change(UserCredential, :count).by(1) end it "updates an existing credential" do @agent.send(:set_credential, 'test', 1234) @agent.options['code'] = 'Agent.check = function() { this.credential("test","12345"); };' @agent.save! expect { @agent.check }.to change(UserCredential, :count).by(0) expect(@agent.user.user_credentials.last.credential_value).to eq('12345') end end end end