harbinger 0.0.1.pre → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.hound.yml +818 -0
  4. data/.travis.yml +20 -0
  5. data/Gemfile +23 -3
  6. data/README.md +8 -27
  7. data/Rakefile +47 -1
  8. data/app/controllers/harbinger/messages_controller.rb +24 -0
  9. data/app/models/harbinger/database_channel_message.rb +51 -0
  10. data/app/models/harbinger/database_channel_message_element.rb +19 -0
  11. data/app/views/harbinger/messages/index.html.erb +43 -0
  12. data/app/views/harbinger/messages/show.html.erb +24 -0
  13. data/config/routes.rb +3 -0
  14. data/db/migrate/20140310185338_create_harbinger_database_channel_message.rb +11 -0
  15. data/db/migrate/20140310185339_create_harbinger_database_channel_message_elements.rb +14 -0
  16. data/gemfiles/rails4.1.gemfile +12 -0
  17. data/gemfiles/rails4.gemfile +13 -0
  18. data/harbinger.gemspec +22 -7
  19. data/lib/generators/harbinger/install/install_generator.rb +23 -0
  20. data/lib/generators/harbinger/install/templates/harbinger_initializer.rb.erb +6 -0
  21. data/lib/harbinger.rb +100 -1
  22. data/lib/harbinger/channels.rb +25 -0
  23. data/lib/harbinger/channels/database_channel.rb +15 -0
  24. data/lib/harbinger/channels/logger_channel.rb +31 -0
  25. data/lib/harbinger/channels/null_channel.rb +7 -0
  26. data/lib/harbinger/configuration.rb +58 -0
  27. data/lib/harbinger/engine.rb +8 -1
  28. data/lib/harbinger/exceptions.rb +4 -0
  29. data/lib/harbinger/message.rb +20 -0
  30. data/lib/harbinger/reporters.rb +34 -0
  31. data/lib/harbinger/reporters/exception_reporter.rb +26 -0
  32. data/lib/harbinger/reporters/null_reporter.rb +14 -0
  33. data/lib/harbinger/reporters/request_reporter.rb +20 -0
  34. data/lib/harbinger/reporters/user_reporter.rb +20 -0
  35. data/lib/harbinger/version.rb +1 -1
  36. data/run_each_spec_in_isolation +9 -0
  37. data/script/fast_specs +20 -0
  38. data/spec/controllers/harbinger/messages_controller_spec.rb +26 -0
  39. data/spec/features/end_to_end_exception_handling_spec.rb +39 -0
  40. data/spec/lib/harbinger/channels/database_channel_spec.rb +18 -0
  41. data/spec/lib/harbinger/channels/logger_channel_spec.rb +21 -0
  42. data/spec/lib/harbinger/channels/null_channel_spec.rb +8 -0
  43. data/spec/lib/harbinger/channels_spec.rb +40 -0
  44. data/spec/lib/harbinger/configuration_spec.rb +53 -0
  45. data/spec/lib/harbinger/message_spec.rb +15 -0
  46. data/spec/lib/harbinger/reporters/exception_reporter_spec.rb +24 -0
  47. data/spec/lib/harbinger/reporters/null_reporter_spec.rb +21 -0
  48. data/spec/lib/harbinger/reporters/request_reporter_spec.rb +23 -0
  49. data/spec/lib/harbinger/reporters/user_reporter_spec.rb +17 -0
  50. data/spec/lib/harbinger/reporters_spec.rb +46 -0
  51. data/spec/lib/harbinger_spec.rb +60 -0
  52. data/spec/models/harbinger/database_channel_message_element_spec.rb +16 -0
  53. data/spec/models/harbinger/database_channel_message_spec.rb +68 -0
  54. data/spec/routing/harbinger/messages_routing_spec.rb +16 -0
  55. data/spec/spec_active_record_helper.rb +41 -0
  56. data/spec/spec_fast_helper.rb +70 -0
  57. data/spec/spec_slow_helper.rb +57 -0
  58. data/spec/spec_view_helper.rb +38 -0
  59. data/spec/test_app_templates/lib/generators/test_app_generator.rb +13 -0
  60. data/spec/views/harbinger/messages/index.html.erb_spec.rb +31 -0
  61. data/spec/views/harbinger/messages/show.html.erb_spec.rb +36 -0
  62. metadata +205 -20
  63. data/MIT-LICENSE +0 -20
@@ -0,0 +1,26 @@
1
+ require 'spec_slow_helper'
2
+ require 'harbinger/messages_controller'
3
+
4
+ module Harbinger
5
+ describe MessagesController, type: :controller do
6
+ routes { Harbinger::Engine.routes }
7
+ render_views false
8
+ context 'GET :index' do
9
+ it 'searches the DatabaseChannelMessage' do
10
+ message_1, message_2 = double, double
11
+ allow(DatabaseChannelMessage).to receive(:search) { [message_1, message_2] }
12
+ get :index
13
+ expect(assigns(:messages)).to eq([message_1, message_2])
14
+ end
15
+ end
16
+
17
+ context 'GET :show' do
18
+ it 'retrieves the DatabaseChannelMessage' do
19
+ message = double
20
+ allow(DatabaseChannelMessage).to receive(:find).with('1') { message }
21
+ get :show, id: '1'
22
+ expect(assigns(:message)).to eq(message)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_slow_helper'
2
+ require 'harbinger'
3
+
4
+ module Harbinger
5
+ describe 'handling a message', type: :feature do
6
+ let(:message) do
7
+ begin
8
+ {}.fetch(:missing_key)
9
+ rescue KeyError => exception
10
+ Harbinger.build_message(contexts: [exception])
11
+ end
12
+ end
13
+
14
+ it 'sends the exception message to the database channel' do
15
+ expect(Harbinger.logger).to receive(:add).at_least(:once).and_call_original
16
+
17
+ expect { Harbinger.deliver_message(message, channels: [:database, :logger]) }.
18
+ to change { DatabaseChannelMessage.count }.
19
+ by(1)
20
+
21
+ message = DatabaseChannelMessage.last
22
+
23
+ visit 'harbinger/messages'
24
+
25
+ # Search page
26
+ page.within('.search-form') do
27
+ page.fill_in('Search Text', with: 'KeyError')
28
+ page.click_button('Search')
29
+ end
30
+
31
+ page.find(:xpath, "//a[@href='#{harbinger.message_path(message.to_param)}']").click
32
+
33
+ expect(page.html).to have_tag('.message') do
34
+ with_tag('.message-contexts-detail', text: 'exception')
35
+ with_tag('.message-state-detail', text: 'new')
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/channels/database_channel'
3
+
4
+ module Harbinger::Channels
5
+ describe DatabaseChannel do
6
+
7
+ it_should_behave_like "a harbinger channel"
8
+
9
+ context '.deliver' do
10
+ Given(:database_channel) { described_class }
11
+ Given(:storage) { double('Database', store_message: true ) }
12
+ Given(:message) { double("Message") }
13
+ When { database_channel.deliver(message, storage: storage) }
14
+ Then { expect(storage).to have_received(:store_message).with(message) }
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/channels/logger_channel'
3
+
4
+ module Harbinger::Channels
5
+ describe LoggerChannel do
6
+
7
+ it_should_behave_like "a harbinger channel"
8
+
9
+ context '.deliver' do
10
+ Given(:logger_channel) { described_class }
11
+ Given(:logger) { double('Logger', add: true ) }
12
+ Given(:severity) { double('Severity') }
13
+ Given(:message) { double("Message", attributes: {to_s: 'Hello'}) }
14
+ When { logger_channel.deliver(message, logger: logger, severity: severity) }
15
+ Then { expect(logger).to have_received(:add).with(severity, %(BEGIN MESSAGE OBJECT ID=#{message.object_id})) }
16
+ And { expect(logger).to have_received(:add).with(severity, %(:to_s => \"Hello\")) }
17
+ And { expect(logger).to have_received(:add).with(severity, %(END MESSAGE OBJECT ID=#{message.object_id})) }
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/channels/null_channel'
3
+
4
+ module Harbinger::Channels
5
+ describe NullChannel do
6
+ it_should_behave_like "a harbinger channel"
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/channels'
3
+
4
+ module Harbinger
5
+ module Channels
6
+ class TestChannel
7
+ def self.deliver(message, config = {})
8
+ end
9
+ end
10
+ end
11
+
12
+ describe Channels do
13
+ context '.find_for' do
14
+
15
+ context 'existing named channel' do
16
+ Given(:channel_name) { :test }
17
+ When(:result) { described_class.find_for(channel_name) }
18
+ Then { expect(result).to eq(Channels::TestChannel) }
19
+ And { expect(result).to respond_to(:deliver) }
20
+ end
21
+
22
+ context 'missing named channel' do
23
+ Given(:channel_name) { :four_oh_four }
24
+ When(:result) { described_class.find_for(channel_name) }
25
+ Then { expect(result).to eq(Channels::NullChannel) }
26
+ And { expect(result).to respond_to(:deliver) }
27
+ end
28
+
29
+ context 'an exception happened' do
30
+ Given(:channel_name) { :test }
31
+ When(:result) do
32
+ expect(described_class).to receive(:channel_name_for_instance).and_raise(RuntimeError)
33
+ described_class.find_for(channel_name)
34
+ end
35
+ Then { expect(result).to eql(Channels::NullChannel) }
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/configuration'
3
+ require 'logger'
4
+
5
+ module Harbinger
6
+ describe Configuration do
7
+ Given(:configuration) { described_class.new }
8
+
9
+ context '#default_channels' do
10
+ When { configuration.default_channels = [:logger, 'Database'] }
11
+ Then { expect(configuration.default_channels).to eq([:logger, :database]) }
12
+ end
13
+
14
+ context '#default_channels without assignment' do
15
+ When(:default_channels) { configuration.default_channels }
16
+ Then { expect(default_channels).to eq([:logger]) }
17
+ end
18
+
19
+ context '#logger' do
20
+ context 'interface' do
21
+ Then { expect(configuration.logger).to respond_to :add }
22
+ end
23
+
24
+ context 'override with valid logger' do
25
+ Given(:logger) { double('Logger', add: true) }
26
+ When { configuration.logger = logger }
27
+ Then { expect(configuration.logger).to eq(logger) }
28
+ end
29
+
30
+ context 'override without valid logger' do
31
+ Given(:logger) { double('Logger') }
32
+ Then { expect { configuration.logger = logger }.to raise_error(ConfigurationError) }
33
+ end
34
+ end
35
+
36
+ context '#database_storage' do
37
+ context 'interface' do
38
+ Then { expect(configuration.database_storage).to respond_to :store_message }
39
+ end
40
+
41
+ context 'override with valid database_storage' do
42
+ Given(:database_storage) { double('Database Storage', store_message: true) }
43
+ When { configuration.database_storage = database_storage }
44
+ Then { expect(configuration.database_storage).to eq(database_storage) }
45
+ end
46
+
47
+ context 'override without valid database_storage' do
48
+ Given(:database_storage) { double('Database Storage') }
49
+ Then { expect { configuration.database_storage = database_storage }.to raise_error(ConfigurationError) }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/message'
3
+
4
+ module Harbinger
5
+ describe Message do
6
+ Given(:message) { described_class.new }
7
+
8
+ When { message.append('container', 'key', 'value')}
9
+ When { message.append('other_container', 'other_key', 'other_value')}
10
+
11
+ Then { expect(message.attributes).to eq({ 'container.key' => ['value'], 'other_container.other_key' => ['other_value']}) }
12
+ And { expect(message.contexts).to eq(['container', 'other_container']) }
13
+
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/reporters/exception_reporter'
3
+
4
+ module Harbinger::Reporters
5
+ describe ExceptionReporter do
6
+
7
+ it_should_behave_like "a harbinger reporter"
8
+
9
+ context 'specific behavior' do
10
+
11
+ Given(:message) { double('Message', append: true) }
12
+ Given(:exception_message) { 'exception message' }
13
+ Given(:exception) { RuntimeError.new(exception_message) }
14
+ Given(:reporter) { described_class.new(exception) }
15
+
16
+ When { reporter.accept(message) }
17
+
18
+ Then { expect(message).to have_received(:append).with('exception', 'class_name', exception.class.to_s) }
19
+ And { expect(message).to have_received(:append).with('exception', 'backtrace', [].join("\n")) }
20
+ And { expect(message).to have_received(:append).with('exception', 'message', exception.message) }
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/reporters/null_reporter'
3
+
4
+ module Harbinger::Reporters
5
+ describe NullReporter do
6
+
7
+ it_should_behave_like "a harbinger reporter"
8
+
9
+ context 'specific behavior' do
10
+
11
+ Given(:message) { double('Message', append: true) }
12
+ Given(:context) { double('Request') }
13
+ Given(:reporter) { described_class.new(context) }
14
+
15
+ When { reporter.accept(message) }
16
+
17
+ Then { expect(message).to have_received(:append).with('nil', context.class.to_s, context.inspect) }
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/reporters/request_reporter'
3
+
4
+ module Harbinger::Reporters
5
+ describe RequestReporter do
6
+
7
+ it_should_behave_like "a harbinger reporter"
8
+
9
+ context 'specific behavior' do
10
+
11
+ Given(:request) { double('Request', path: '/path/to/url', params: { id: 'id', contorller: 'controller'}, user_agent: 'user_agent' ) }
12
+ Given(:reporter) { described_class.new(request) }
13
+ Given(:message) { double('Message', append: true) }
14
+
15
+
16
+ When { reporter.accept(message) }
17
+ Then { expect(message).to have_received(:append).with('request', 'path', request.path) }
18
+ And { expect(message).to have_received(:append).with('request', 'params', request.params) }
19
+ And { expect(message).to have_received(:append).with('request', 'user_agent', request.user_agent) }
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/reporters/user_reporter'
3
+
4
+ module Harbinger::Reporters
5
+ describe UserReporter do
6
+
7
+ it_should_behave_like "a harbinger reporter"
8
+
9
+ context 'specific behavior' do
10
+ Given(:user) { double('User', username: 'a username' ) }
11
+ Given(:message) { double('Message', append: true) }
12
+ Given(:reporter) { described_class.new(user) }
13
+ When { reporter.accept(message) }
14
+ Then { expect(message).to have_received(:append).with('user', 'username', user.username) }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger/reporters'
3
+
4
+ module Harbinger
5
+ describe Reporters do
6
+ context '.find_for' do
7
+ Given(:reporter) { double('Reporter') }
8
+
9
+ context 'explicit conversion' do
10
+ Given(:context) { double('User', to_harbinger_reporter: reporter) }
11
+ When(:result) { described_class.find_for(context) }
12
+ Then { expect(result).to eq(reporter) }
13
+ end
14
+
15
+ context 'implicit conversion' do
16
+ Given(:context) { User.new }
17
+ When(:result) { described_class.find_for(context) }
18
+ Then { expect(result).to be_an_instance_of(Reporters::UserReporter) }
19
+ end
20
+
21
+ context 'implicit conversion of an exception' do
22
+ Given(:context) do
23
+ begin
24
+ {}.fetch(:missing_key)
25
+ rescue KeyError => exception
26
+ exception
27
+ end
28
+ end
29
+ When(:result) { described_class.find_for(context) }
30
+ Then { expect(result).to be_an_instance_of(Reporters::ExceptionReporter) }
31
+ end
32
+
33
+ context 'constant that raises a name error' do
34
+ Given(:context) { Class.new.new }
35
+ When(:result) { described_class.find_for(context) }
36
+ Then { expect(result).to be_an_instance_of(Reporters::NullReporter) }
37
+ end
38
+
39
+ context 'implicit conversion' do
40
+ Given(:context) { Object.new }
41
+ When(:result) { described_class.find_for(context) }
42
+ Then { expect(result).to be_an_instance_of(Reporters::NullReporter) }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_fast_helper'
2
+ require 'harbinger'
3
+ require 'harbinger/message'
4
+
5
+ describe Harbinger do
6
+ Given(:user) { User.new(username: 'a username') }
7
+ Given(:request) { Request.new(path: '/path/to/awesome', params: { hello: :world }, user_agent: "Ruby") }
8
+
9
+ context '.call' do
10
+ before(:each) do
11
+ expect(Harbinger::Channels).to receive(:find_for).with(channel_name).and_return(channel)
12
+ end
13
+ Given(:channel_name) { 'channel_double' }
14
+ Given(:channel_name) { 'channel_double' }
15
+ Given(:channel) { double('Channel', deliver: true) }
16
+ Given(:message) { Harbinger::Message.new }
17
+ When { Harbinger.call(contexts: [user, request], message: message, channels: channel_name) }
18
+ Then do expect(message.attributes).to eq(
19
+ 'user.username' => [user.username],
20
+ 'request.path' => [request.path],
21
+ 'request.params' => [request.params],
22
+ 'request.user_agent' => [request.user_agent]
23
+ )
24
+ end
25
+ And { expect(channel).to have_received(:deliver).with(message) }
26
+ end
27
+
28
+ context '.build_message' do
29
+ Given(:message) { Harbinger::Message.new }
30
+ When { Harbinger.build_message(contexts: [user, request], message: message) }
31
+ Then do expect(message.attributes).to eq(
32
+ 'user.username' => [user.username],
33
+ 'request.path' => [request.path],
34
+ 'request.params' => [request.params],
35
+ 'request.user_agent' => [request.user_agent]
36
+ )
37
+ end
38
+ end
39
+
40
+ context '.deliver_message' do
41
+ before(:each) do
42
+ expect(Harbinger::Channels).to receive(:find_for).with(channel_name).and_return(channel)
43
+ end
44
+ Given(:message) { Harbinger::Message.new }
45
+ Given(:channel_name) { 'channel_double' }
46
+ Given(:channel) { double('Channel', deliver: true) }
47
+ When { Harbinger.deliver_message(message, channels: channel_name) }
48
+ Then { expect(channel).to have_received(:deliver).with(message) }
49
+ end
50
+
51
+ context '.logger' do
52
+ Given(:logger) { Harbinger.logger }
53
+ Then { expect(logger).to respond_to :add }
54
+ end
55
+
56
+ context '.database_storage' do
57
+ Given(:database_storage) { Harbinger.database_storage }
58
+ Then { expect(database_storage).to respond_to :store_message }
59
+ end
60
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_active_record_helper'
2
+ require 'harbinger/database_channel_message_element'
3
+
4
+ module Harbinger
5
+ describe DatabaseChannelMessageElement do
6
+ context '.search_text' do
7
+ it 'handles a query parameter' do
8
+ expect(described_class.search_text('Hello').to_sql).to be_a(String)
9
+ end
10
+
11
+ it 'wild card searches the value' do
12
+ expect(described_class.search_text('Hello').to_sql).to match(/%Hello%/)
13
+ end
14
+ end
15
+ end
16
+ end