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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.hound.yml +818 -0
- data/.travis.yml +20 -0
- data/Gemfile +23 -3
- data/README.md +8 -27
- data/Rakefile +47 -1
- data/app/controllers/harbinger/messages_controller.rb +24 -0
- data/app/models/harbinger/database_channel_message.rb +51 -0
- data/app/models/harbinger/database_channel_message_element.rb +19 -0
- data/app/views/harbinger/messages/index.html.erb +43 -0
- data/app/views/harbinger/messages/show.html.erb +24 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20140310185338_create_harbinger_database_channel_message.rb +11 -0
- data/db/migrate/20140310185339_create_harbinger_database_channel_message_elements.rb +14 -0
- data/gemfiles/rails4.1.gemfile +12 -0
- data/gemfiles/rails4.gemfile +13 -0
- data/harbinger.gemspec +22 -7
- data/lib/generators/harbinger/install/install_generator.rb +23 -0
- data/lib/generators/harbinger/install/templates/harbinger_initializer.rb.erb +6 -0
- data/lib/harbinger.rb +100 -1
- data/lib/harbinger/channels.rb +25 -0
- data/lib/harbinger/channels/database_channel.rb +15 -0
- data/lib/harbinger/channels/logger_channel.rb +31 -0
- data/lib/harbinger/channels/null_channel.rb +7 -0
- data/lib/harbinger/configuration.rb +58 -0
- data/lib/harbinger/engine.rb +8 -1
- data/lib/harbinger/exceptions.rb +4 -0
- data/lib/harbinger/message.rb +20 -0
- data/lib/harbinger/reporters.rb +34 -0
- data/lib/harbinger/reporters/exception_reporter.rb +26 -0
- data/lib/harbinger/reporters/null_reporter.rb +14 -0
- data/lib/harbinger/reporters/request_reporter.rb +20 -0
- data/lib/harbinger/reporters/user_reporter.rb +20 -0
- data/lib/harbinger/version.rb +1 -1
- data/run_each_spec_in_isolation +9 -0
- data/script/fast_specs +20 -0
- data/spec/controllers/harbinger/messages_controller_spec.rb +26 -0
- data/spec/features/end_to_end_exception_handling_spec.rb +39 -0
- data/spec/lib/harbinger/channels/database_channel_spec.rb +18 -0
- data/spec/lib/harbinger/channels/logger_channel_spec.rb +21 -0
- data/spec/lib/harbinger/channels/null_channel_spec.rb +8 -0
- data/spec/lib/harbinger/channels_spec.rb +40 -0
- data/spec/lib/harbinger/configuration_spec.rb +53 -0
- data/spec/lib/harbinger/message_spec.rb +15 -0
- data/spec/lib/harbinger/reporters/exception_reporter_spec.rb +24 -0
- data/spec/lib/harbinger/reporters/null_reporter_spec.rb +21 -0
- data/spec/lib/harbinger/reporters/request_reporter_spec.rb +23 -0
- data/spec/lib/harbinger/reporters/user_reporter_spec.rb +17 -0
- data/spec/lib/harbinger/reporters_spec.rb +46 -0
- data/spec/lib/harbinger_spec.rb +60 -0
- data/spec/models/harbinger/database_channel_message_element_spec.rb +16 -0
- data/spec/models/harbinger/database_channel_message_spec.rb +68 -0
- data/spec/routing/harbinger/messages_routing_spec.rb +16 -0
- data/spec/spec_active_record_helper.rb +41 -0
- data/spec/spec_fast_helper.rb +70 -0
- data/spec/spec_slow_helper.rb +57 -0
- data/spec/spec_view_helper.rb +38 -0
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +13 -0
- data/spec/views/harbinger/messages/index.html.erb_spec.rb +31 -0
- data/spec/views/harbinger/messages/show.html.erb_spec.rb +36 -0
- metadata +205 -20
- 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,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
|