harbinger 0.0.1.pre → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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