hutch 0.19.0-java

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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.travis.yml +11 -0
  4. data/CHANGELOG.md +438 -0
  5. data/Gemfile +22 -0
  6. data/Guardfile +5 -0
  7. data/LICENSE +22 -0
  8. data/README.md +317 -0
  9. data/Rakefile +14 -0
  10. data/bin/hutch +8 -0
  11. data/circle.yml +3 -0
  12. data/examples/consumer.rb +13 -0
  13. data/examples/producer.rb +10 -0
  14. data/hutch.gemspec +30 -0
  15. data/lib/hutch.rb +62 -0
  16. data/lib/hutch/adapter.rb +11 -0
  17. data/lib/hutch/adapters/bunny.rb +33 -0
  18. data/lib/hutch/adapters/march_hare.rb +37 -0
  19. data/lib/hutch/broker.rb +374 -0
  20. data/lib/hutch/cli.rb +205 -0
  21. data/lib/hutch/config.rb +125 -0
  22. data/lib/hutch/consumer.rb +75 -0
  23. data/lib/hutch/error_handlers.rb +8 -0
  24. data/lib/hutch/error_handlers/airbrake.rb +26 -0
  25. data/lib/hutch/error_handlers/honeybadger.rb +28 -0
  26. data/lib/hutch/error_handlers/logger.rb +16 -0
  27. data/lib/hutch/error_handlers/sentry.rb +23 -0
  28. data/lib/hutch/exceptions.rb +7 -0
  29. data/lib/hutch/logging.rb +32 -0
  30. data/lib/hutch/message.rb +31 -0
  31. data/lib/hutch/serializers/identity.rb +19 -0
  32. data/lib/hutch/serializers/json.rb +22 -0
  33. data/lib/hutch/tracers.rb +6 -0
  34. data/lib/hutch/tracers/newrelic.rb +19 -0
  35. data/lib/hutch/tracers/null_tracer.rb +15 -0
  36. data/lib/hutch/version.rb +4 -0
  37. data/lib/hutch/worker.rb +143 -0
  38. data/spec/hutch/broker_spec.rb +377 -0
  39. data/spec/hutch/cli_spec.rb +80 -0
  40. data/spec/hutch/config_spec.rb +126 -0
  41. data/spec/hutch/consumer_spec.rb +130 -0
  42. data/spec/hutch/error_handlers/airbrake_spec.rb +34 -0
  43. data/spec/hutch/error_handlers/honeybadger_spec.rb +36 -0
  44. data/spec/hutch/error_handlers/logger_spec.rb +15 -0
  45. data/spec/hutch/error_handlers/sentry_spec.rb +20 -0
  46. data/spec/hutch/logger_spec.rb +28 -0
  47. data/spec/hutch/message_spec.rb +38 -0
  48. data/spec/hutch/serializers/json_spec.rb +17 -0
  49. data/spec/hutch/worker_spec.rb +99 -0
  50. data/spec/hutch_spec.rb +87 -0
  51. data/spec/spec_helper.rb +40 -0
  52. metadata +194 -0
@@ -0,0 +1,80 @@
1
+ require 'hutch/cli'
2
+ require 'tempfile'
3
+
4
+ describe Hutch::CLI do
5
+ let(:cli) { Hutch::CLI.new }
6
+
7
+ describe "#parse_options" do
8
+ context "--config" do
9
+ context "when the config file does not exist" do
10
+ let(:file) { "/path/to/nonexistant/file" }
11
+ before { allow(STDERR).to receive(:write) }
12
+
13
+ it "bails" do
14
+ expect {
15
+ cli.parse_options(["--config=#{file}"])
16
+ }.to raise_error SystemExit
17
+ end
18
+ end
19
+
20
+ context "when the config file exists" do
21
+ let(:file) do
22
+ Tempfile.new("hutch-test-config.yaml").to_path
23
+ end
24
+
25
+ it "parses the config" do
26
+ expect(Hutch::Config).to receive(:load_from_file)
27
+ cli.parse_options(["--config=#{file}"])
28
+ end
29
+ end
30
+ end
31
+
32
+ context "--mq-tls-key" do
33
+ context "when the keyfile file does not exist" do
34
+ let(:file) { "/path/to/nonexistant/file" }
35
+ before { allow(STDERR).to receive(:write) }
36
+
37
+ it "bails" do
38
+ expect {
39
+ cli.parse_options(["--mq-tls-key=#{file}"])
40
+ }.to raise_error SystemExit
41
+ end
42
+ end
43
+
44
+ context "when the keyfile file exists" do
45
+ let(:file) do
46
+ Tempfile.new("hutch-test-key.pem").to_path
47
+ end
48
+
49
+ it "sets mq_tls_key to the file" do
50
+ expect(Hutch::Config).to receive(:mq_tls_key=)
51
+ cli.parse_options(["--mq-tls-key=#{file}"])
52
+ end
53
+ end
54
+ end
55
+
56
+ context "--mq-tls-cert" do
57
+ context "when the certfile file does not exist" do
58
+ let(:file) { "/path/to/nonexistant/file" }
59
+ before { allow(STDERR).to receive(:write) }
60
+
61
+ it "bails" do
62
+ expect {
63
+ cli.parse_options(["--mq-tls-cert=#{file}"])
64
+ }.to raise_error SystemExit
65
+ end
66
+ end
67
+
68
+ context "when the certfile file exists" do
69
+ let(:file) do
70
+ Tempfile.new("hutch-test-cert.pem").to_path
71
+ end
72
+
73
+ it "sets mq_tls_cert to the file" do
74
+ expect(Hutch::Config).to receive(:mq_tls_cert=)
75
+ cli.parse_options(["--mq-tls-cert=#{file}"])
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,126 @@
1
+ require 'hutch/config'
2
+ require 'tempfile'
3
+
4
+ describe Hutch::Config do
5
+ let(:new_value) { 'not-localhost' }
6
+
7
+ describe '.get' do
8
+ context 'for valid attributes' do
9
+ subject { Hutch::Config.get(:mq_host) }
10
+
11
+ context 'with no overridden value' do
12
+ it { is_expected.to eq('localhost') }
13
+ end
14
+
15
+ context 'with an overridden value' do
16
+ before { allow(Hutch::Config).to receive_messages(user_config: { mq_host: new_value }) }
17
+ it { is_expected.to eq(new_value) }
18
+ end
19
+ end
20
+
21
+ context 'for invalid attributes' do
22
+ let(:invalid_get) { ->{ Hutch::Config.get(:invalid_attr) } }
23
+ specify { expect(invalid_get).to raise_error Hutch::UnknownAttributeError }
24
+ end
25
+ end
26
+
27
+ describe '.set' do
28
+ context 'for valid attributes' do
29
+ before { Hutch::Config.set(:mq_host, new_value) }
30
+ subject { Hutch::Config.user_config[:mq_host] }
31
+
32
+ context 'sets value in user config hash' do
33
+ it { is_expected.to eq(new_value) }
34
+ end
35
+ end
36
+
37
+ context 'for invalid attributes' do
38
+ let(:invalid_set) { ->{ Hutch::Config.set(:invalid_attr, new_value) } }
39
+ specify { expect(invalid_set).to raise_error Hutch::UnknownAttributeError }
40
+ end
41
+ end
42
+
43
+ describe 'a magic getter' do
44
+ context 'for a valid attribute' do
45
+ it 'calls get' do
46
+ expect(Hutch::Config).to receive(:get).with(:mq_host)
47
+ Hutch::Config.mq_host
48
+ end
49
+ end
50
+
51
+ context 'for an invalid attribute' do
52
+ let(:invalid_getter) { ->{ Hutch::Config.invalid_attr } }
53
+ specify { expect(invalid_getter).to raise_error NoMethodError }
54
+ end
55
+ end
56
+
57
+ describe 'a magic setter' do
58
+ context 'for a valid attribute' do
59
+ it 'calls set' do
60
+ expect(Hutch::Config).to receive(:set).with(:mq_host, new_value)
61
+ Hutch::Config.mq_host = new_value
62
+ end
63
+ end
64
+
65
+ context 'for an invalid attribute' do
66
+ let(:invalid_setter) { ->{ Hutch::Config.invalid_attr = new_value } }
67
+ specify { expect(invalid_setter).to raise_error NoMethodError }
68
+ end
69
+ end
70
+
71
+ describe '.load_from_file' do
72
+ let(:host) { 'broker.yourhost.com' }
73
+ let(:username) { 'calvin' }
74
+ let(:file) do
75
+ Tempfile.new('configs.yaml').tap do |t|
76
+ t.write(YAML.dump(config_data))
77
+ t.rewind
78
+ end
79
+ end
80
+
81
+ context 'when an attribute is invalid' do
82
+ let(:config_data) { { random_attribute: 'socks' } }
83
+ it 'raises an error' do
84
+ expect {
85
+ Hutch::Config.load_from_file(file)
86
+ }.to raise_error(NoMethodError)
87
+ end
88
+ end
89
+
90
+ context 'when attributes are valid' do
91
+ let(:config_data) { { mq_host: host, mq_username: username } }
92
+
93
+ it 'loads in the config data' do
94
+ Hutch::Config.load_from_file(file)
95
+ expect(Hutch::Config.mq_host).to eq host
96
+ expect(Hutch::Config.mq_username).to eq username
97
+ end
98
+ end
99
+ end
100
+
101
+ describe '.load_from_file' do
102
+ let(:host) { 'localhost' }
103
+ let(:username) { 'calvin' }
104
+ let(:file) do
105
+ Tempfile.new('configs.yaml').tap do |t|
106
+ t.write(config_contents)
107
+ t.rewind
108
+ end
109
+ end
110
+
111
+ context 'when using ERb' do
112
+ let(:config_contents) do
113
+ <<-YAML
114
+ mq_host: 'localhost'
115
+ mq_username: '<%= "calvin" %>'
116
+ YAML
117
+ end
118
+
119
+ it 'loads in the config data' do
120
+ Hutch::Config.load_from_file(file)
121
+ expect(Hutch::Config.mq_host).to eq host
122
+ expect(Hutch::Config.mq_username).to eq username
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,130 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::Consumer do
4
+ around(:each) do |example|
5
+ isolate_constants do
6
+ example.run
7
+ end
8
+ end
9
+
10
+ let(:simple_consumer) do
11
+ unless defined? SimpleConsumer
12
+ class SimpleConsumer
13
+ include Hutch::Consumer
14
+ consume 'hutch.test1'
15
+ end
16
+ end
17
+ SimpleConsumer
18
+ end
19
+
20
+ let(:complex_consumer) do
21
+ unless defined? ComplexConsumer
22
+ class ComplexConsumer
23
+ include Hutch::Consumer
24
+ consume 'hutch.test1', 'hutch.test2'
25
+ arguments foo: :bar
26
+ end
27
+ end
28
+ ComplexConsumer
29
+ end
30
+
31
+ describe 'module inclusion' do
32
+ it 'registers the class as a consumer' do
33
+ expect(Hutch).to receive(:register_consumer) do |klass|
34
+ expect(klass).to eq(simple_consumer)
35
+ end
36
+
37
+ simple_consumer
38
+ end
39
+ end
40
+
41
+
42
+ describe '.consume' do
43
+ it 'saves the routing key to the consumer' do
44
+ expect(simple_consumer.routing_keys).to include 'hutch.test1'
45
+ end
46
+
47
+ context 'with multiple routing keys' do
48
+ it 'registers the class once for each routing key' do
49
+ expect(complex_consumer.routing_keys).to include 'hutch.test1'
50
+ expect(complex_consumer.routing_keys).to include 'hutch.test2'
51
+ end
52
+ end
53
+
54
+ context 'when given the same routing key multiple times' do
55
+ subject { simple_consumer.routing_keys }
56
+ before { simple_consumer.consume 'hutch.test1' }
57
+
58
+ describe '#length' do
59
+ subject { super().length }
60
+ it { is_expected.to eq(1)}
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '.queue_name' do
66
+ let(:queue_name) { 'foo' }
67
+
68
+ it 'overrides the queue name' do
69
+ simple_consumer.queue_name(queue_name)
70
+ expect(simple_consumer.get_queue_name).to eq(queue_name)
71
+ end
72
+ end
73
+
74
+ describe '.arguments' do
75
+ let(:args) { { foo: :bar} }
76
+
77
+ it 'overrides the arguments' do
78
+ simple_consumer.arguments(args)
79
+ expect(simple_consumer.get_arguments).to eq(args)
80
+ end
81
+
82
+ end
83
+
84
+ describe '.get_arguments' do
85
+
86
+ context 'when defined' do
87
+ it { expect(complex_consumer.get_arguments).to eq(foo: :bar) }
88
+ end
89
+
90
+ context 'when not defined' do
91
+ it { expect(simple_consumer.get_arguments).to eq({}) }
92
+ end
93
+
94
+ end
95
+
96
+ describe '.get_queue_name' do
97
+
98
+ context 'when queue name has been set explicitly' do
99
+ it 'returns the give queue name' do
100
+ class Foo
101
+ include Hutch::Consumer
102
+ queue_name "bar"
103
+ end
104
+
105
+ expect(Foo.get_queue_name).to eq("bar")
106
+ end
107
+ end
108
+
109
+ context 'when no queue name has been set' do
110
+ it 'replaces module separators with colons' do
111
+ module Foo
112
+ class Bar
113
+ include Hutch::Consumer
114
+ end
115
+ end
116
+
117
+ expect(Foo::Bar.get_queue_name).to eq('foo:bar')
118
+ end
119
+
120
+ it 'converts camelcase class names to snake case' do
121
+ class FooBarBAZ
122
+ include Hutch::Consumer
123
+ end
124
+
125
+ expect(FooBarBAZ.get_queue_name).to eq('foo_bar_baz')
126
+ end
127
+ end
128
+ end
129
+ end
130
+
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Airbrake do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Airbrake.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) do
8
+ begin
9
+ raise "Stuff went wrong"
10
+ rescue RuntimeError => err
11
+ err
12
+ end
13
+ end
14
+
15
+ it "logs the error to Airbrake" do
16
+ message_id = "1"
17
+ payload = "{}"
18
+ consumer = double
19
+ ex = error
20
+ message = {
21
+ :error_class => ex.class.name,
22
+ :error_message => "#{ ex.class.name }: #{ ex.message }",
23
+ :backtrace => ex.backtrace,
24
+ :parameters => {
25
+ :payload => payload,
26
+ :consumer => consumer,
27
+ },
28
+ :cgi_data => ENV.to_hash,
29
+ }
30
+ expect(::Airbrake).to receive(:notify_or_ignore).with(ex, message)
31
+ error_handler.handle(message_id, payload, consumer, ex)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Honeybadger do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Honeybadger.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) do
8
+ begin
9
+ raise "Stuff went wrong"
10
+ rescue RuntimeError => err
11
+ err
12
+ end
13
+ end
14
+
15
+ it "logs the error to Honeybadger" do
16
+ message_id = "1"
17
+ payload = "{}"
18
+ consumer = double
19
+ ex = error
20
+ message = {
21
+ :error_class => ex.class.name,
22
+ :error_message => "#{ ex.class.name }: #{ ex.message }",
23
+ :backtrace => ex.backtrace,
24
+ :context => {
25
+ :message_id => message_id,
26
+ :consumer => consumer
27
+ },
28
+ :parameters => {
29
+ :payload => payload
30
+ }
31
+ }
32
+ expect(::Honeybadger).to receive(:notify_or_ignore).with(message)
33
+ error_handler.handle(message_id, payload, consumer, ex)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Logger do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Logger.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) { double(message: "Stuff went wrong", class: "RuntimeError",
8
+ backtrace: ["line 1", "line 2"]) }
9
+
10
+ it "logs three separate lines" do
11
+ expect(Hutch::Logging.logger).to receive(:error).exactly(3).times
12
+ error_handler.handle("1", "{}", double, error)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Sentry do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Sentry.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) do
8
+ begin
9
+ raise "Stuff went wrong"
10
+ rescue RuntimeError => err
11
+ err
12
+ end
13
+ end
14
+
15
+ it "logs the error to Sentry" do
16
+ expect(Raven).to receive(:capture_exception).with(error)
17
+ error_handler.handle("1", "{}", double, error)
18
+ end
19
+ end
20
+ end