houston-core 0.7.0.beta3 → 0.7.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +56 -56
  3. data/app/assets/javascripts/houston/app/infinite_scroll.coffee +6 -2
  4. data/app/assets/javascripts/houston/app/models/ticket.coffee +0 -42
  5. data/app/assets/javascripts/houston/app/ticket_tracker_refresh.coffee +0 -2
  6. data/app/assets/javascripts/houston/app/views/keyboard_shortcuts_modal.coffee +0 -6
  7. data/app/assets/javascripts/houston/core/handlebars_helpers.coffee +0 -5
  8. data/app/assets/stylesheets/houston/application/actions.scss +15 -0
  9. data/app/assets/stylesheets/houston/application/layout.scss +3 -0
  10. data/app/assets/stylesheets/houston/application/markdown.scss +1 -1
  11. data/app/assets/stylesheets/houston/application/navigation.scss +3 -1
  12. data/app/assets/stylesheets/houston/application/project_banner_buttons.scss +2 -0
  13. data/app/assets/stylesheets/houston/application/tables.scss +0 -1
  14. data/app/assets/stylesheets/houston/application/timeline.scss +1 -1
  15. data/app/assets/stylesheets/houston/core/overrides.scss +2 -1
  16. data/app/concerns/houston/props.rb +11 -4
  17. data/app/concerns/project_adapter.rb +1 -1
  18. data/app/concerns/unique_add.rb +1 -1
  19. data/app/controllers/actions_controller.rb +39 -0
  20. data/app/controllers/commits_controller.rb +1 -1
  21. data/app/controllers/errors_controller.rb +10 -0
  22. data/app/controllers/hooks_controller.rb +1 -1
  23. data/app/controllers/omnibar_controller.rb +1 -11
  24. data/app/controllers/project_hooks_controller.rb +2 -2
  25. data/app/controllers/projects_controller.rb +2 -0
  26. data/app/controllers/test_runs_controller.rb +14 -3
  27. data/app/controllers/triggers_controller.rb +8 -0
  28. data/app/helpers/actions_helper.rb +7 -0
  29. data/app/helpers/application_helper.rb +1 -1
  30. data/app/interactors/cache_key_dependencies.rb +1 -1
  31. data/app/interactors/test_run_comparer.rb +1 -1
  32. data/app/mailers/project_notification.rb +0 -31
  33. data/app/models/ability.rb +0 -11
  34. data/app/models/{job.rb → action.rb} +19 -7
  35. data/app/models/commit.rb +2 -2
  36. data/app/models/deploy.rb +3 -3
  37. data/app/models/github/comment_event.rb +9 -3
  38. data/app/models/github/post_receive_event.rb +1 -1
  39. data/app/models/github/pull_request.rb +5 -5
  40. data/app/models/persistent_trigger.rb +46 -0
  41. data/app/models/project.rb +0 -1
  42. data/app/models/release.rb +1 -1
  43. data/app/models/run_tests_on_post_receive.rb +17 -17
  44. data/app/models/task.rb +4 -4
  45. data/app/models/test_run.rb +18 -5
  46. data/app/models/ticket.rb +2 -21
  47. data/app/models/ticket_antecedent.rb +3 -3
  48. data/app/models/user.rb +0 -1
  49. data/app/views/actions/index.html.erb +69 -0
  50. data/app/views/actions/show.html.erb +45 -0
  51. data/app/views/commits/show.html.erb +7 -8
  52. data/app/views/errors/_actions.html.erb +11 -0
  53. data/app/views/errors/index.html.erb +39 -0
  54. data/app/views/layouts/_mobile_navigation.html.erb +1 -9
  55. data/app/views/layouts/_navigation.html.erb +14 -8
  56. data/app/views/layouts/application.html.erb +1 -3
  57. data/app/views/project_notification/test_run.html.erb +13 -3
  58. data/app/views/projects/_form.html.erb +13 -7
  59. data/app/views/triggers/index.html.erb +39 -0
  60. data/config/initializers/add_navigation_renderers.rb +0 -6
  61. data/config/initializers/houston_async.rb +4 -2
  62. data/config/initializers/houston_scheduler_daemon.rb +6 -0
  63. data/config/initializers/load_persistent_triggers.rb +7 -0
  64. data/config/initializers/requirements.rb +2 -1
  65. data/config/initializers/sync_commits_on_post_receive.rb +2 -2
  66. data/config/routes.rb +17 -15
  67. data/db/migrate/20160711170921_rename_jobs_to_actions.rb +5 -0
  68. data/db/migrate/20160713204605_add_trigger_and_params_to_actions.rb +6 -0
  69. data/db/migrate/20160715173039_create_persistent_triggers.rb +10 -0
  70. data/db/structure.sql +197 -221
  71. data/houston-core.gemspec +1 -1
  72. data/lib/houston/boot/actions.rb +105 -0
  73. data/lib/houston/boot/active_record_serializer.rb +24 -0
  74. data/lib/houston/boot/configuration.rb +118 -49
  75. data/lib/houston/boot/events.rb +46 -0
  76. data/lib/houston/boot/extensions.rb +118 -14
  77. data/lib/houston/boot/observer.rb +122 -24
  78. data/lib/houston/boot/readonly_hash_serializer.rb +15 -0
  79. data/lib/houston/boot/serializer.rb +83 -0
  80. data/lib/houston/boot/ticket_antecedent_serializer.rb +21 -0
  81. data/lib/houston/boot/timer.rb +45 -0
  82. data/lib/houston/boot/triggers.rb +75 -0
  83. data/lib/houston/boot.rb +5 -0
  84. data/lib/houston/version.rb +1 -1
  85. data/lib/params_serializer.rb +18 -0
  86. data/lib/tasks/actions.rake +12 -0
  87. data/lib/tasks/events.rake +11 -0
  88. data/templates/new-instance/config/abilities.rb +0 -8
  89. data/templates/new-instance/config/{triggers → events}/alerts/slack_when_assigned.rb +1 -1
  90. data/templates/new-instance/config/{triggers → events}/alerts/slack_when_opened.rb +1 -1
  91. data/templates/new-instance/config/{triggers → events}/daemons/health.rb +6 -6
  92. data/templates/new-instance/config/{triggers → events}/deploy/autoresolve_errs.rb +1 -1
  93. data/templates/new-instance/config/{triggers → events}/deploy/checkout_mentioned_alerts.rb +1 -1
  94. data/templates/new-instance/config/{triggers → events}/deploy/notify_deployer_when_finished.rb +2 -2
  95. data/templates/new-instance/config/{triggers → events}/github/publish_comments_on_slack.rb +9 -9
  96. data/templates/new-instance/config/{triggers → events}/tests/slack_when_analyzed.rb +1 -1
  97. data/templates/new-instance/config/{triggers → events}/tests/slack_when_completed.rb +1 -1
  98. data/templates/new-instance/config/{triggers → events}/tickets/mark_tasks_completed_on_commit.rb +1 -1
  99. data/templates/new-instance/config/main.rb +8 -35
  100. data/templates/new-instance/config/{jobs → timers}/cache_key_dependencies.rb +0 -0
  101. data/templates/new-instance/config/{jobs → timers}/email_about_open_alerts.rb +0 -0
  102. data/templates/new-instance/config/{jobs → timers}/purge_jobs.rb +0 -0
  103. data/templates/new-instance/config/{jobs → timers}/slack_reminders_about_alerts.rb +0 -0
  104. data/templates/new-instance/config/{jobs → timers}/sync_commits.rb +0 -0
  105. data/templates/new-instance/config/{jobs → timers}/sync_pull_requests.rb +0 -0
  106. data/templates/new-instance/config/{jobs → timers}/sync_tickets.rb +0 -0
  107. data/templates/new-module/lib/houston/%name%.rb +13 -0
  108. data/test/integration/ci_integration_test.rb +5 -5
  109. data/test/integration/web_hook_test.rb +1 -1
  110. data/test/test_helper.rb +14 -0
  111. data/test/unit/controllers/hooks_controller_test.rb +2 -2
  112. data/test/unit/initializers/sync_commits_on_post_receive_test.rb +1 -1
  113. data/test/unit/models/actions_test.rb +107 -0
  114. data/test/unit/models/configuration_test.rb +108 -0
  115. data/test/unit/models/observer_test.rb +87 -3
  116. data/test/unit/models/persistent_trigger_test.rb +94 -0
  117. data/test/unit/models/serializer_test.rb +80 -0
  118. data/test/unit/models/triggers_test.rb +53 -0
  119. metadata +60 -60
  120. data/app/assets/javascripts/houston/app/models/testing_note.coffee +0 -18
  121. data/app/assets/javascripts/houston/app/views/commit_view.coffee +0 -13
  122. data/app/assets/javascripts/houston/app/views/testing_note_view.coffee +0 -85
  123. data/app/assets/javascripts/houston/app/views/testing_report_view.coffee +0 -29
  124. data/app/assets/javascripts/houston/app/views/testing_ticket_view.coffee +0 -203
  125. data/app/assets/stylesheets/houston/application/jobs.scss +0 -5
  126. data/app/assets/stylesheets/houston/application/testing_report.scss +0 -279
  127. data/app/assets/templates/commit.hbs +0 -9
  128. data/app/assets/templates/testing_notes/edit.hbs +0 -20
  129. data/app/assets/templates/testing_notes/new.hbs +0 -27
  130. data/app/assets/templates/testing_notes/show.hbs +0 -11
  131. data/app/assets/templates/testing_report/description.hbs +0 -12
  132. data/app/assets/templates/testing_report/ticket.hbs +0 -21
  133. data/app/assets/templates/testing_report/verdict.hbs +0 -4
  134. data/app/controllers/jobs_controller.rb +0 -42
  135. data/app/controllers/testing_notes_controller.rb +0 -50
  136. data/app/controllers/testing_report_controller.rb +0 -38
  137. data/app/models/testing_note.rb +0 -64
  138. data/app/presenters/testing_note_presenter.rb +0 -27
  139. data/app/presenters/testing_report_ticket_presenter.rb +0 -71
  140. data/app/views/jobs/index.html.erb +0 -72
  141. data/app/views/jobs/show.html.erb +0 -41
  142. data/app/views/project_notification/testing_note.html.erb +0 -9
  143. data/app/views/testing_report/_scripts.html.erb +0 -12
  144. data/app/views/testing_report/index.html.erb +0 -31
  145. data/app/views/testing_report/show.html.erb +0 -29
  146. data/config/initializers/houston_scheduler.rb +0 -23
  147. data/db/migrate/20120424212706_create_testing_notes.rb +0 -14
  148. data/db/migrate/20120501231817_add_expires_at_to_testing_notes.rb +0 -5
  149. data/db/migrate/20120501231948_add_unfuddle_id_to_testing_notes.rb +0 -5
  150. data/db/migrate/20120715230526_change_testing_notes_comment_to_text.rb +0 -9
  151. data/db/migrate/20130211015046_add_min_passing_verdicts_to_projects.rb +0 -5
  152. data/db/migrate/20130407220039_add_project_id_to_testing_notes.rb +0 -26
  153. data/db/migrate/20140511024021_rename_testing_notes_unfuddle_id_to_remote_id.rb +0 -5
  154. data/templates/new-instance/config/triggers/tickets/email_testing_notes.rb +0 -7
  155. data/templates/new-instance/log/development.log +0 -41253
  156. data/templates/new-instance/log/test.log +0 -545
  157. data/templates/new-module/config/initializers/add_navigation_renderer.rb +0 -3
@@ -249,45 +249,18 @@ Houston.config do
249
249
  # ---------------------------------------------------------------------------
250
250
  #
251
251
  # Configure Houston to execute block of code when an event is triggered.
252
- #
253
- # Here is a list of many of Houston's events:
254
- #
255
- # * antecedent:*:released When a Ticket has been released, for each antecedent
256
- # * antecedent:*:resolved When a Ticket has been resolved, for each antecedent
257
- # * antecedent:*:closed When a Ticket has been closed, for each antecedent
258
- # * daemon:*:start When a background thread (like the Scheduler or Slack) starts
259
- # * daemon:*:restart When a background thread (like the Scheduler or Slack) is restarted
260
- # * daemon:*:stop When a background thread (like the Scheduler or Slack) dies
261
- # * deploy:completed When a deploy has been recorded
262
- # * hooks:* When a Web Hook as been triggered
263
- # * release:create When a new Release has been created
264
- # * test_run:start When the CI server has begun a build
265
- # * test_run:complete When the CI server has completed a build
266
- # * testing_note:create When a Testing Note has been created
267
- # * testing_note:update When a Testing Note has been updated
268
- # * testing_note:save When a Testing Note has been created or updated
269
- # * ticket:release When a Ticket is mentioned in a Release
270
- # * task:released When a commit mentioning a Task is released
271
- # * task:committed When a commit mentioning a Task is pushed
272
- # * task:completed When a Task is marked completed
273
- # * task:reopened When a Task is marked reopened
274
- # * alert:create When an Alert is created
275
- # * alert:*:create When an Alert of a particular type is created
276
- # * alert:assign When an Alert is assigned
277
- # * alert:*:assign When an Alert of a particular type is assigned
278
- # * alert:deployed When an Alert is deployed
279
- # * alert:*:deployed When an Alert of a particular type is deployed
280
-
281
- load "triggers/**/*"
282
-
283
-
284
-
285
- # Background Jobs
252
+ # To see a complete list of events run `rake houston:events` at the command line.
253
+
254
+ load "events/**/*"
255
+
256
+
257
+
258
+ # Timers
286
259
  # ---------------------------------------------------------------------------
287
260
  #
288
261
  # Houston can be configured to run jobs at a variety of intervals.
289
262
 
290
- load "jobs/**/*"
263
+ load "timers/**/*"
291
264
 
292
265
 
293
266
 
@@ -12,4 +12,17 @@ module Houston
12
12
  end
13
13
 
14
14
  end
15
+
16
+
17
+ # Register events that will be raised by this module
18
+ # register_events {{
19
+ # "<%= name %>:create" => params("<%= name %>").desc("<%= camelized %> was created"),
20
+ # "<%= name %>:update" => params("<%= name %>").desc("<%= camelized %> was updated")
21
+ # }}
22
+
23
+
24
+ # Add a link to Houston's global navigation
25
+ # add_navigation_renderer :<%= name %> do
26
+ # render_nav_link "<%= camelized %>", Houston::<%= camelized %>::Engine.routes.url_helpers.<%= name %>_path, icon: "fa-thumbs-up"
27
+ # end
15
28
  end
@@ -8,7 +8,7 @@ class CIIntegrationTest < ActionDispatch::IntegrationTest
8
8
 
9
9
 
10
10
  context "Houston" do
11
- should "trigger a build when the hooks:post_receive event is fired for a project that uses a CI server" do
11
+ should "trigger a build when the hooks:project:post_receive event is fired for a project that uses a CI server" do
12
12
  @project = create(:project, ci_server_name: "Mock")
13
13
 
14
14
  stub.instance_of(PostReceivePayload).commit { "63cd1ef" }
@@ -20,7 +20,7 @@ class CIIntegrationTest < ActionDispatch::IntegrationTest
20
20
  end
21
21
  end
22
22
 
23
- should "do nothing when the hooks:post_receive event is fired for a project that does not use a CI server" do
23
+ should "do nothing when the hooks:project:post_receive event is fired for a project that does not use a CI server" do
24
24
  @project = create(:project, ci_server_name: "None")
25
25
 
26
26
  assert_no_difference "TestRun.count" do
@@ -127,7 +127,7 @@ class CIIntegrationTest < ActionDispatch::IntegrationTest
127
127
  stub(Object.new).success? { true }
128
128
  end
129
129
 
130
- Houston.observer.fire "test_run:start", test_run
130
+ Houston.observer.fire "test_run:start", test_run: test_run
131
131
  end
132
132
 
133
133
  should "publish test results to GitHub" do
@@ -148,7 +148,7 @@ class CIIntegrationTest < ActionDispatch::IntegrationTest
148
148
  stub(Object.new).success? { true }
149
149
  end
150
150
 
151
- Houston.observer.fire "test_run:complete", test_run
151
+ Houston.observer.fire "test_run:complete", test_run: test_run
152
152
  end
153
153
  end
154
154
 
@@ -162,7 +162,7 @@ class CIIntegrationTest < ActionDispatch::IntegrationTest
162
162
 
163
163
  mock(CodeClimate::CoverageReport).publish!(test_run)
164
164
 
165
- Houston.observer.fire "test_run:complete", test_run
165
+ Houston.observer.fire "test_run:complete", test_run: test_run
166
166
  end
167
167
  end
168
168
 
@@ -20,7 +20,7 @@ class WebHookTest < ActionDispatch::IntegrationTest
20
20
  end
21
21
 
22
22
  test "should trigger a project hook when it is defined" do
23
- assert_triggered "hooks:whatever" do
23
+ assert_triggered "hooks:project:whatever" do
24
24
  post "/projects/#{project.slug}/hooks/whatever"
25
25
  assert_response :success
26
26
  end
data/test/test_helper.rb CHANGED
@@ -85,6 +85,20 @@ class ActiveSupport::TestCase
85
85
 
86
86
 
87
87
 
88
+ def refute_raises(*exp)
89
+ msg = "#{exp.pop}.\n" if String === exp.last
90
+ exp << StandardError if exp.empty?
91
+
92
+ yield
93
+
94
+ pass
95
+ rescue *exp => e
96
+ exp = exp.first if exp.size == 1
97
+ flunk "#{msg}#{mu_pp(exp)} was not supposed to be raised"
98
+ end
99
+
100
+
101
+
88
102
  def assert_deep_equal(expected_value, actual_value)
89
103
  differences = differences_between_values(expected_value, actual_value)
90
104
  assert differences.none?, differences.join("\n")
@@ -62,10 +62,10 @@ class HooksControllerTest < ActionController::TestCase
62
62
  assert_response :success
63
63
  end
64
64
 
65
- should "trigger a `hooks:post_receive` event for the project" do
65
+ should "trigger a `hooks:project:post_receive` event for the project" do
66
66
  project = create(:project, slug: "public-repo")
67
67
  expected_payload = hash_including(github_push_event_payload.slice("before", "after"))
68
- mock(Houston.observer).fire("hooks:post_receive", project, expected_payload)
68
+ mock(Houston.observer).fire("hooks:project:post_receive", project: project, params: expected_payload)
69
69
  post :github, hook: github_push_event_payload
70
70
  end
71
71
  end
@@ -7,7 +7,7 @@ class SyncCommitsOnPostReceiveTest < ActiveSupport::TestCase
7
7
  should "sync commits for that project" do
8
8
  project = create(:project, version_control_name: "Mock")
9
9
  mock(project.commits).sync!
10
- Houston.observer.fire "hooks:post_receive", project, {}
10
+ Houston.observer.fire "hooks:project:post_receive", project: project, params: {}
11
11
  end
12
12
  end
13
13
 
@@ -0,0 +1,107 @@
1
+ require "test_helper"
2
+
3
+ class ActionsTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ actions.define("test-action") { }
7
+ Action.delete_all
8
+ end
9
+
10
+
11
+ context "Actions#define" do
12
+ should "allow you to define the params an action requires" do
13
+ action = actions.redefine("test-action", %w{param1 param2}) { }
14
+ assert_equal "test-action", action.name
15
+ assert_equal %w{param1 param2}, action.required_params
16
+ end
17
+ end
18
+
19
+
20
+ context "Actions#run" do
21
+ should "raise if called with an argument that isn't a Hash" do
22
+ assert_raises ArgumentError do
23
+ run! 5
24
+ end
25
+ end
26
+
27
+ should "raise if any required params are absent" do
28
+ actions.redefine("test-action", %w{param1 param2}) { }
29
+ assert_raises Houston::Observer::MissingParamError do
30
+ run! param1: 1
31
+ end
32
+ end
33
+
34
+ should "allow unrequired params to be passed" do
35
+ actions.redefine("test-action", %w{param1}) { }
36
+ refute_raises Houston::Observer::MissingParamError do
37
+ run! param1: 1, param2: 2
38
+ end
39
+
40
+ assert_equal({param1: 1, param2: 2}, Action.last.params)
41
+ end
42
+
43
+ # We require that all params be serializable so that (1) actions can
44
+ # be recorded with all their state (and retried) and (2) the actions
45
+ # system can be "outsourced" to a background job system like Sidekiq
46
+ # if need be.
47
+ should "raise if any required param is unserializable" do
48
+ actions.redefine("test-action", %w{param1}) { }
49
+ unserializable_object = Class.new.new
50
+ assert_raises Houston::Serializer::UnserializableError do
51
+ run! param1: unserializable_object
52
+ end
53
+ end
54
+
55
+ should "record every invocation of the action" do
56
+ assert_difference "Action.count", +1 do
57
+ run!
58
+ end
59
+ end
60
+
61
+ should "record any exception raised by the action" do
62
+ actions.redefine("test-action") { raise "hell" }
63
+ run!
64
+ refute Action.last.succeeded?
65
+ error = Action.last.error
66
+ assert_kind_of Error, error
67
+ assert_equal "hell", error.message
68
+ end
69
+
70
+ should "record how an action was triggered" do
71
+ run!
72
+ assert_equal "manual", Action.first.trigger
73
+
74
+ run!({}, trigger: "at(5:00pm)")
75
+ assert_equal "at(5:00pm)", Action.first.trigger
76
+ end
77
+
78
+ should "record the params with which an action was triggered" do
79
+ run!
80
+ assert_equal({}, Action.first.params)
81
+
82
+ actions.redefine("test-action", [:example]) { }
83
+ run! example: 5
84
+ assert_equal({example: 5}, Action.first.params)
85
+ end
86
+
87
+ should "invoke actions in the context of their params" do
88
+ actions.redefine("test-action") do
89
+ assert respond_to?(:example)
90
+ assert_equal 5, example
91
+ end
92
+ run! example: 5
93
+ end
94
+ end
95
+
96
+
97
+ private
98
+
99
+ def actions
100
+ @actions ||= Houston::Actions.new
101
+ end
102
+
103
+ def run!(params={}, options={})
104
+ actions.run "test-action", params, options.merge(async: false)
105
+ end
106
+
107
+ end
@@ -0,0 +1,108 @@
1
+ require "test_helper"
2
+
3
+ class ConfigurationTest < ActiveSupport::TestCase
4
+
5
+
6
+ context "#action" do
7
+ should "define a new action" do
8
+ config.action("test-action") { }
9
+ assert config.actions.exists?("test-action")
10
+ end
11
+
12
+ should "raise if a block isn't given" do
13
+ assert_raises ArgumentError do
14
+ config.action("test-action")
15
+ end
16
+ end
17
+
18
+ should "raise if the action has already been defined" do
19
+ config.action("test-action") { }
20
+ assert_raises ArgumentError do
21
+ config.action("test-action") { }
22
+ end
23
+ end
24
+ end
25
+
26
+
27
+ context "#every" do
28
+ should "define an action and a timer that triggers it when you pass two arguments" do
29
+ assert_difference "config.triggers.count", +1 do
30
+ config.every("10m", "test-action") { }
31
+ assert config.actions.exists?("test-action")
32
+ end
33
+ end
34
+
35
+ should "define an action and a timer that triggers it when you pass a hash" do
36
+ assert_difference "config.triggers.count", +1 do
37
+ config.every("10m" => "test-action") { }
38
+ assert config.actions.exists?("test-action")
39
+ end
40
+ end
41
+
42
+ should "define an action with a made-up name (for now) when you don't specify an action" do
43
+ action = nil
44
+ assert_difference ["config.triggers.count", "config.actions.count"], +1 do
45
+ action = config.every("10m") { }
46
+ end
47
+ assert_match /10m:[a-f0-9]{8,}/, action.name
48
+ end
49
+
50
+ should "define a timer for an existing action" do
51
+ assert_difference "config.triggers.count", +1 do
52
+ config.action("test-action") { }
53
+ config.every "10m" => "test-action"
54
+ end
55
+ end
56
+
57
+ should "raise if the action requires any params" do
58
+ config.action("test-action", ["required_param"]) { }
59
+ assert_raises Houston::Observer::MissingParamError do
60
+ config.every "10m" => "test-action"
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+ context "#on" do
67
+ should "define an action and an observer that triggers it when you pass two arguments" do
68
+ assert_difference "config.triggers.count", +1 do
69
+ config.on("hooks:example", "test-action") { }
70
+ assert config.actions.exists?("test-action")
71
+ end
72
+ end
73
+
74
+ should "define the action to require the params passed by the event" do
75
+ action = config.on("hooks:example", "test-action") { }
76
+ assert_equal %w{params}, action.required_params
77
+ end
78
+
79
+ should "define an action and an observer that triggers it when you pass a hash" do
80
+ assert_difference "config.triggers.count", +1 do
81
+ config.on("hooks:example" => "test-action") { }
82
+ assert config.actions.exists?("test-action")
83
+ end
84
+ end
85
+
86
+ should "define an observer for an existing action" do
87
+ assert_difference "config.triggers.count", +1 do
88
+ config.action("test-action") { }
89
+ config.on "hooks:example" => "test-action"
90
+ end
91
+ end
92
+
93
+ should "raise if the event doesn't supply the action's required params" do
94
+ config.action("test-action", ["required_param"]) { }
95
+ assert_raises Houston::Observer::MissingParamError do
96
+ config.on "hooks:example" => "test-action"
97
+ end
98
+ end
99
+ end
100
+
101
+
102
+ private
103
+
104
+ def config
105
+ @config ||= Houston::Configuration.new
106
+ end
107
+
108
+ end
@@ -1,17 +1,101 @@
1
1
  require "test_helper"
2
2
 
3
+ Houston.register_events {{
4
+ "test" => params("example").desc("For testing the observer"),
5
+ "test0" => desc("For testing the observer, takes 0 params")
6
+ }}
7
+
3
8
  class ObserverTest < ActiveSupport::TestCase
4
9
 
5
10
 
6
11
  context "Houston.observer.once" do
7
12
  should "trigger a callback and then unregister it" do
8
13
  calls = 0
9
- Houston.observer.once("test") { calls += 1 }
10
- Houston.observer.fire "test"
11
- Houston.observer.fire "test"
14
+ Houston.observer.once("test0") { calls += 1 }
15
+ Houston.observer.fire "test0"
16
+ Houston.observer.fire "test0"
12
17
  assert_equal 1, calls, "Expected the callback to be called only once"
13
18
  end
14
19
  end
15
20
 
16
21
 
22
+ context "Houston.observer.fire" do
23
+ should "raise if called with an argument that isn't a Hash" do
24
+ assert_raises ArgumentError do
25
+ Houston.observer.fire "test", 5
26
+ end
27
+ end
28
+
29
+ should "raise if called with more than one argument" do
30
+ assert_raises ArgumentError do
31
+ Houston.observer.fire "test", {}, 5
32
+ end
33
+ end
34
+
35
+ should "raise if the event isn't registered" do
36
+ assert_raises Houston::Observer::UnregisteredEventError do
37
+ Houston.observer.fire "test2"
38
+ end
39
+ end
40
+
41
+ should "raise if the event is triggered without a registered param" do
42
+ assert_raises Houston::Observer::MissingParamError do
43
+ Houston.observer.fire "test", {}
44
+ end
45
+ end
46
+
47
+ should "raise if the event is triggered with an unregistered param" do
48
+ assert_raises Houston::Observer::UnregisteredParamError do
49
+ Houston.observer.fire "test", {example: 5, counterexample: 1}
50
+ end
51
+ end
52
+
53
+ # We require that all params be serializable so that (1) actions can
54
+ # be recorded with all their state (and retried) and (2) the actions
55
+ # system can be "outsourced" to a background job system like Sidekiq
56
+ # if need be.
57
+ should "raise if the event is triggered with an unserializable param" do
58
+ unserializable_object = Class.new.new
59
+ assert_raises Houston::Serializer::UnserializableError do
60
+ Houston.observer.fire "test", {example: unserializable_object}
61
+ end
62
+ end
63
+
64
+ should "invoke callbacks with {} if called with no arguments" do
65
+ callback_args = :not_called
66
+ Houston.observer.on("test0") { |*args| callback_args = args }
67
+ Houston.observer.fire "test0"
68
+ assert_equal 1, callback_args.length
69
+ assert_equal [], callback_args[0].methods(false)
70
+ end
71
+
72
+ should "invoke callbacks with a copy of the params it was called with" do
73
+ params = {example: 5}
74
+ callback_params = :not_called
75
+ Houston.observer.on("test") { |params| callback_params = params }
76
+ Houston.observer.fire "test", params
77
+ assert_equal params, callback_params.to_h
78
+ refute_equal params.object_id, callback_params.to_h.object_id,
79
+ "Expected the observer to be invoked with a copy of the params; not the actual params itself"
80
+ end
81
+ end
82
+
83
+
84
+ context "callback params" do
85
+ should "be retrievable by array-style access" do
86
+ callback_params = :not_called
87
+ Houston.observer.on("test") { |params| callback_params = params }
88
+ Houston.observer.fire "test", example: 5
89
+ assert_equal 5, callback_params[:example]
90
+ end
91
+
92
+ should "be retrievable by method-style access" do
93
+ callback_params = :not_called
94
+ Houston.observer.on("test") { |params| callback_params = params }
95
+ Houston.observer.fire "test", example: 5
96
+ assert_equal 5, callback_params.example
97
+ end
98
+ end
99
+
100
+
17
101
  end
@@ -0,0 +1,94 @@
1
+ require "test_helper"
2
+
3
+ class PersistentTriggerTest < ActiveSupport::TestCase
4
+
5
+
6
+ context ".at" do
7
+ should "define a new trigger" do
8
+ trigger = PersistentTrigger.at("2:30pm", "test-action", example: 5)
9
+ assert_equal :at, trigger.type
10
+ assert_equal "2:30pm", trigger.value
11
+ assert_equal "test-action", trigger.action
12
+ assert_equal({example: 5}, trigger.params)
13
+ end
14
+ end
15
+
16
+
17
+ context "Validations:" do
18
+ should "require the action to exist" do
19
+ trigger = PersistentTrigger.new(action: "undefined-action").tap(&:validate)
20
+ assert_match /is not defined/, trigger.errors[:action].join
21
+ end
22
+
23
+ should "require the type to be one of :at, :every, or :on" do
24
+ trigger = PersistentTrigger.new
25
+
26
+ trigger.type = :at
27
+ refute trigger.tap(&:validate).errors[:type].any?
28
+
29
+ trigger.type = :every
30
+ refute trigger.tap(&:validate).errors[:type].any?
31
+
32
+ trigger.type = :on
33
+ refute trigger.tap(&:validate).errors[:type].any?
34
+
35
+ trigger.type = :nope
36
+ assert trigger.tap(&:validate).errors[:type].any?
37
+ end
38
+ end
39
+
40
+
41
+ context "#save!" do
42
+ setup do
43
+ Houston.config.actions.define("test-action") { }
44
+ end
45
+
46
+ teardown do
47
+ Houston.config.actions.undefine("test-action")
48
+ end
49
+
50
+ should "add the trigger to the database" do
51
+ assert_difference "PersistentTrigger.count", +1 do
52
+ PersistentTrigger.at("1:30pm", "test-action", example: 5).save!
53
+ end
54
+ end
55
+
56
+ should "register the trigger" do
57
+ assert_difference "Houston.config.triggers.count", +1 do
58
+ PersistentTrigger.at("2:30pm", "test-action", example: 5).save!
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ context ".load_all" do
65
+ setup do
66
+ PersistentTrigger.all.insert({
67
+ PersistentTrigger.column_for_attribute(:type) => "at",
68
+ PersistentTrigger.column_for_attribute(:value) => "9:00am",
69
+ PersistentTrigger.column_for_attribute(:action) => "test-action",
70
+ PersistentTrigger.column_for_attribute(:params) => {example: 5}
71
+ })
72
+ end
73
+
74
+ teardown do
75
+ Houston.config.triggers.clear
76
+ end
77
+
78
+ should "load all persisted triggers and register them" do
79
+ assert_equal 1, PersistentTrigger.count
80
+ assert_difference "Houston.config.triggers.count", +1 do
81
+ PersistentTrigger.load_all
82
+ end
83
+ end
84
+
85
+ should "be idempotent" do
86
+ PersistentTrigger.load_all
87
+ assert_no_difference "Houston.config.triggers.count" do
88
+ PersistentTrigger.load_all
89
+ end
90
+ end
91
+ end
92
+
93
+
94
+ end
@@ -0,0 +1,80 @@
1
+ require "test_helper"
2
+
3
+ class SerializerTest < ActiveSupport::TestCase
4
+
5
+
6
+ should "serialize JSON literals conventionally" do
7
+ assert_serializes(5, to: '5')
8
+ assert_serializes("five", to: '"five"')
9
+ assert_serializes(false, to: 'false')
10
+ assert_serializes(nil, to: 'null')
11
+ end
12
+
13
+ should "let Oj serialize dates via iso8601" do
14
+ assert_equal '{"^O":"Date","iso8601":"2016-07-15"}',
15
+ dump(Date.new(2016, 7, 15))
16
+
17
+ assert_equal '{"^O":"DateTime","iso8601":"2016-07-15T01:30:59+00:00"}',
18
+ dump(DateTime.new(2016, 7, 15, 1, 30, 59.457))
19
+ end
20
+
21
+ should "let Oj serialize times" do
22
+ assert_equal '{"^t":1468587630.542999999e59}',
23
+ dump(Time.new(2016, 7, 15, 13, 1, 30, 59.457))
24
+ end
25
+
26
+ should "serialize Houston::ReadonlyHash like a hash" do
27
+ assert_equal '{":x":5}', dump(Houston::ReadonlyHash.new(x: 5))
28
+ end
29
+
30
+ should "serialize ActiveSupport::TimeWithZone like a DateTime" do
31
+ time = ActiveSupport::TimeZone["Mountain Time (US & Canada)"].now
32
+ assert_equal '{"^O":"DateTime","iso8601":"' + time.iso8601 + '"}', dump(time)
33
+ end
34
+
35
+ should "serialize ActiveRecord objects by their raw attributes" do
36
+ project = Project.create!(name: "Test", slug: "test")
37
+ assert_serializes(project,
38
+ like: /"class":"Project","attributes":{.*},"\^S":"Houston::ActiveRecordSerializer"/)
39
+ end
40
+
41
+ should "serialize objects that refer to ActiveRecord objects" do
42
+ project = Project.create!(name: "Test", slug: "test")
43
+ commit = Commit.new(project: project, message: "Commit Message")
44
+ antecedent = TicketAntecedent.new(commit, "Err", 45)
45
+ assert_serializes(antecedent,
46
+ like: /"ticket_or_commit":{"class":"Commit".*},"kind":"Err","id":45,"\^S":"Houston::TicketAntecedentSerializer"/)
47
+ end
48
+
49
+ should "work even when you set a value" do
50
+ project = Project.create!(name: "Test", slug: "test")
51
+ project.updated_at = 1.week.after(project.updated_at) # ActiveSupport::TimeWithZone
52
+ refute_match /"^o":"ActiveSupport::TimeWithZone"/, dump(x: project)
53
+ end
54
+
55
+
56
+ private
57
+
58
+ def assert_serializes(object, options={})
59
+ serialized = dump(object)
60
+
61
+ if expectation = options[:to]
62
+ assert_equal expectation, serialized, "The object wasn't serialized as expected"
63
+ end
64
+
65
+ if expectation = options[:like]
66
+ assert_match expectation, serialized, "The object wasn't serialized as expected"
67
+ end
68
+
69
+ assert_equal object, load(serialized), "The object wasn't deserialized as expected"
70
+ end
71
+
72
+ def load(string)
73
+ Houston::Serializer.new.load(string)
74
+ end
75
+
76
+ def dump(object)
77
+ Houston::Serializer.new.dump(object)
78
+ end
79
+
80
+ end