basquiat 1.1.1

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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.metrics +5 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +12 -0
  6. data/.ruby-gemset +1 -0
  7. data/.ruby-version +1 -0
  8. data/.travis.yml +3 -0
  9. data/Gemfile +4 -0
  10. data/Guardfile +17 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +95 -0
  13. data/Rakefile +13 -0
  14. data/basquiat.gemspec +39 -0
  15. data/docker/Dockerfile +11 -0
  16. data/docker/basquiat_start.sh +9 -0
  17. data/docker-compose.yml +11 -0
  18. data/lib/basquiat/adapters/base_adapter.rb +54 -0
  19. data/lib/basquiat/adapters/rabbitmq_adapter.rb +130 -0
  20. data/lib/basquiat/adapters/test_adapter.rb +52 -0
  21. data/lib/basquiat/adapters.rb +2 -0
  22. data/lib/basquiat/interfaces/base.rb +62 -0
  23. data/lib/basquiat/rails/railtie.rb +14 -0
  24. data/lib/basquiat/support/configuration.rb +62 -0
  25. data/lib/basquiat/support/hash_refinements.rb +24 -0
  26. data/lib/basquiat/support/json.rb +13 -0
  27. data/lib/basquiat/support.rb +3 -0
  28. data/lib/basquiat/version.rb +4 -0
  29. data/lib/basquiat.rb +27 -0
  30. data/spec/lib/adapters/base_adapter_spec.rb +11 -0
  31. data/spec/lib/adapters/rabbitmq_adapter_spec.rb +79 -0
  32. data/spec/lib/adapters/test_adapter_spec.rb +61 -0
  33. data/spec/lib/basquiat_spec.rb +27 -0
  34. data/spec/lib/interfaces/base_spec.rb +100 -0
  35. data/spec/lib/support/configuration_spec.rb +73 -0
  36. data/spec/lib/support/hash_refinements_spec.rb +24 -0
  37. data/spec/lib/support/json_spec.rb +5 -0
  38. data/spec/spec_helper.rb +22 -0
  39. data/spec/support/basquiat.yml +9 -0
  40. data/spec/support/shared_examples/basquiat_adapter_shared_examples.rb +23 -0
  41. metadata +305 -0
@@ -0,0 +1,24 @@
1
+ module Basquiat
2
+ module HashRefinements
3
+ refine Hash do
4
+ def deep_merge(other_hash)
5
+ other_hash.each_pair do |key, value|
6
+ current = self[key]
7
+ if current.is_a?(Hash) && value.is_a?(Hash)
8
+ current.deep_merge(value)
9
+ else
10
+ self[key] = value
11
+ end
12
+ end
13
+ end
14
+
15
+ def symbolize_keys
16
+ each_with_object({}) do |(key, value), new_hash|
17
+ new_key = key.to_sym rescue key
18
+ new_value = (value.is_a? Hash) ? value.symbolize_keys : value
19
+ new_hash[new_key] = new_value
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ module Basquiat
2
+ module Json
3
+ def self.encode(object)
4
+ MultiJson.dump(object)
5
+ end
6
+
7
+ def self.decode(object)
8
+ MultiJson.load(object, symbolize_keys: true)
9
+ rescue MultiJson::ParseError
10
+ {}
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ require 'basquiat/support/hash_refinements'
2
+ require 'basquiat/support/configuration'
3
+ require 'basquiat/support/json'
@@ -0,0 +1,4 @@
1
+ # Version file
2
+ module Basquiat
3
+ VERSION = '1.1.1'
4
+ end
data/lib/basquiat.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'multi_json'
2
+ require 'naught'
3
+ require 'yaml'
4
+
5
+ require 'basquiat/support'
6
+ require 'basquiat/adapters'
7
+ require 'basquiat/version'
8
+ require 'basquiat/interfaces/base'
9
+
10
+ # Overall namespace And config class
11
+ module Basquiat
12
+ class << self
13
+ def reset
14
+ @configuration = Configuration.new
15
+ end
16
+
17
+ def configuration
18
+ @configuration ||= Configuration.new
19
+ end
20
+
21
+ def configure
22
+ yield configuration
23
+ end
24
+ end
25
+ end
26
+
27
+ require_relative 'basquiat/rails/railtie.rb' if defined?(Rails)
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ # Sample class used for testing
4
+ class SampleAdapter
5
+ include Basquiat::Adapters::Base
6
+ end
7
+
8
+ describe Basquiat::Adapters::Base do
9
+ subject { SampleAdapter.new }
10
+ it_behaves_like 'a Basquiat::Adapter'
11
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+ require 'basquiat/adapters/rabbitmq_adapter'
3
+
4
+ describe Basquiat::Adapters::RabbitMq do
5
+ subject { Basquiat::Adapters::RabbitMq.new }
6
+
7
+ it_behaves_like 'a Basquiat::Adapter'
8
+
9
+ let(:base_options) do
10
+ { servers: [{ host: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' },
11
+ port: ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } }] }
12
+ end
13
+
14
+ before(:each) do
15
+ subject.adapter_options(base_options)
16
+ end
17
+
18
+ after(:each) do
19
+ remove_queues_and_exchanges
20
+ end
21
+
22
+ context 'failover' do
23
+ let(:failover_settings) do
24
+ base_options[:servers].unshift({ host: 'localhost', port: 1234 })
25
+ base_options.merge(failover: { default_timeout: 0.2, max_retries: 2 })
26
+ end
27
+
28
+ it 'tries a reconnection after a few seconds' do
29
+ subject.adapter_options(servers: [host: 'localhost', port: 1234],
30
+ failover: { default_timeout: 0.2, max_retries: 1 })
31
+ expect { subject.connect }.to raise_exception(Bunny::TCPConnectionFailed)
32
+ end
33
+
34
+ it 'uses another server after all retries on a single one' do
35
+ subject.adapter_options(failover_settings)
36
+ expect { subject.connect }.to_not raise_error
37
+ expect(subject.connection_uri).to match(/5672/)
38
+ end
39
+ end
40
+
41
+ it '#connected?' do
42
+ expect(subject.connected?).to be_nil
43
+ subject.connect
44
+ expect(subject.connected?).to_not be_nil
45
+ end
46
+
47
+ context 'publisher' do
48
+ it '#publish [enqueue a message]' do
49
+ expect do
50
+ subject.publish('messages.welcome', data: 'A Nice Welcome Message')
51
+ end.to_not raise_error
52
+ end
53
+ end
54
+
55
+ context 'listener' do
56
+ it '#subscribe_to some event' do
57
+ message_received = ''
58
+ subject.subscribe_to('some.event', lambda do |msg|
59
+ msg[:data].upcase!
60
+ message_received = msg
61
+ end)
62
+ subject.listen(block: false)
63
+
64
+ subject.publish('some.event', data: 'coisa')
65
+ sleep 0.1 # Wait for the listening thread.
66
+
67
+ expect(message_received).to eq(data: 'COISA')
68
+ end
69
+ end
70
+
71
+ def remove_queues_and_exchanges
72
+ subject.send(:queue).delete
73
+ subject.send(:exchange).delete
74
+ rescue Bunny::TCPConnectionFailed
75
+ true
76
+ ensure
77
+ subject.send(:disconnect)
78
+ end
79
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Basquiat::Adapters::Test do
4
+ subject { Basquiat::Adapters::Test.new }
5
+ it_behaves_like 'a Basquiat::Adapter'
6
+
7
+ context 'publisher' do
8
+ it '#publish [enqueue a message]' do
9
+ expect do
10
+ subject.publish('messages.welcome', value: 'A Nice Welcome Message')
11
+ end.to change { subject.events('messages.welcome').size }.by(1)
12
+ expect(subject.events('messages.welcome')[0]).to match(/A Nice Welcome Message/)
13
+ end
14
+ end
15
+
16
+ context 'listener' do
17
+ before(:each) do
18
+ subject.publish('some.event', data: 'some message')
19
+ end
20
+
21
+ it '#subscribe_to some event' do
22
+ subject.subscribe_to('some.event', ->(msg) { msg.values.map(&:upcase) })
23
+ expect(subject.listen).to eq(['SOME MESSAGE'])
24
+ end
25
+
26
+ it '#subscribe_to multiple events' do
27
+ subject.instance_eval <<-METHCALL
28
+ subscribe_to('some.event', ->(msg) { publish 'some.other', data: msg.values.first.upcase; msg })
29
+ METHCALL
30
+ subject.subscribe_to('some.other', ->(msg) { msg.values.first.downcase })
31
+ expect(subject.listen).to eq(data: 'some message')
32
+ expect(subject.events('some.other')[0]).to match(/SOME MESSAGE/)
33
+ expect(subject.listen).to eq('some message')
34
+ end
35
+ end
36
+
37
+ describe '#clean' do
38
+ context 'when no event has been published' do
39
+
40
+ it 'should have no event registered' do
41
+ Basquiat::Adapters::Test.clean
42
+
43
+ expect(Basquiat::Adapters::Test.events).to be_empty
44
+ end
45
+ end
46
+
47
+ context 'when some events have been published' do
48
+ before do
49
+ subject.publish('some.message', value: 'A Nice Welcome Message')
50
+ subject.publish('some.message', value: 'A Nasty Welcome Message')
51
+ subject.publish('other.message', value: 'A Random Welcome Message')
52
+ end
53
+
54
+ it 'should have no event registered' do
55
+ Basquiat::Adapters::Test.clean
56
+
57
+ expect(Basquiat::Adapters::Test.events).to be_empty
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Basquiat do
4
+ after(:all) do
5
+ Basquiat.configure do |config|
6
+ config.config_file = File.expand_path('../../support/basquiat.yml', __FILE__)
7
+ end
8
+ end
9
+
10
+ it 'should have a version number' do
11
+ expect(Basquiat::VERSION).not_to be_nil
12
+ end
13
+
14
+ it '#configure yields to a block' do
15
+ expect { |block| Basquiat.configure(&block) }.to yield_control
16
+ end
17
+
18
+ it '#configuration' do
19
+ expect(Basquiat.configuration).to be_a Basquiat::Configuration
20
+ end
21
+
22
+ it '#reset' do
23
+ config = Basquiat.configuration
24
+ Basquiat.reset
25
+ expect(Basquiat.configuration).not_to equal(config)
26
+ end
27
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ # Sample class used for testing
4
+ class SampleClass
5
+ extend Basquiat::Base
6
+ self.event_adapter = Basquiat::Adapters::Test
7
+ end
8
+
9
+ describe Basquiat::Base do
10
+ subject { SampleClass }
11
+
12
+ it '.event_adapter' do
13
+ expect(subject).to respond_to(:event_adapter=)
14
+ end
15
+
16
+ it '.event_source(option_hash)' do
17
+ expect(subject).to respond_to(:adapter_options)
18
+ end
19
+
20
+ it 'set the adapter options' do
21
+ subject.adapter_options host: 'localhost', port: 5672, durable: true
22
+ expect(subject.adapter.options[:port]).to eq(5672)
23
+ expect(subject.adapter.options[:host]).to eq('localhost')
24
+ expect(subject.adapter.options[:durable]).to be_truthy
25
+ end
26
+
27
+ it 'delegates disconnect and connected? to the adapter' do
28
+ expect(subject.adapter).to receive(:connected?)
29
+ subject.connected?
30
+
31
+ expect(subject.adapter).to receive(:disconnect)
32
+ subject.disconnect
33
+ end
34
+
35
+ context 'using the defaults' do
36
+ class DefaultClass
37
+ extend Basquiat::Base
38
+ end
39
+
40
+ subject(:defaults) { DefaultClass }
41
+
42
+ it 'has an event_adapter' do
43
+ expect(defaults.adapter).to be_a(Basquiat::Adapters::Test)
44
+ end
45
+
46
+ it 'publishes to the configured queue and exchanges' do
47
+ expect do
48
+ subject.publish('test.message', message: 'useful test message')
49
+ end.to change { subject.adapter.events('test.message').size }.by(1)
50
+ end
51
+ end
52
+
53
+ context 'as a Producer' do
54
+ it '#publish' do
55
+ expect do
56
+ subject.publish('test.message', message: 'useful test message')
57
+ end.to change { subject.adapter.events('test.message').size }.by(1)
58
+ end
59
+ end
60
+
61
+ context 'as a Consumer' do
62
+ before(:each) do
63
+ subject.publish('some.event', 'test message')
64
+ end
65
+
66
+ after(:each) do
67
+ subject.adapter.events('some.event').clear
68
+ end
69
+
70
+ it 'reads a message from the queue' do
71
+ subject.subscribe_to 'some.event', ->(msg) { msg }
72
+ expect do
73
+ subject.listen(block: false)
74
+ end.to change { subject.adapter.events('some.event').size }.by(-1)
75
+ end
76
+
77
+ it 'runs the proc for each message' do
78
+ subject.subscribe_to('some.event', ->(msg) { "#{msg} LAMBDA LAMBDA LAMBDA" })
79
+ expect(subject.listen(block: false)).to match(/LAMBDA LAMBDA LAMBDA$/)
80
+ end
81
+
82
+ it 'can receive a symbol that will point to a method' do
83
+ def subject.test_method(msg)
84
+ msg.scan(/e/)
85
+ end
86
+
87
+ subject.subscribe_to('some.event', :test_method)
88
+ expect(subject.listen(block: false)).to eq(%w(e e e))
89
+ end
90
+ end
91
+
92
+ it 'trigger an event after processing a message' do
93
+ subject.publish('some.event', 'some message')
94
+ subject.instance_eval <<-METHCALL
95
+ subscribe_to 'some.event', ->(msg) { publish('other.event', "Redirected \#{msg}") }
96
+ METHCALL
97
+ expect { subject.listen(block: false) }.to_not raise_error
98
+ expect(subject.adapter.events('other.event').size).to eq(1)
99
+ end
100
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Basquiat::Configuration do
4
+ subject(:config) { Basquiat::Configuration.new }
5
+
6
+ context 'accessors' do
7
+ it '#environment' do
8
+ expect(config.environment).to eq(:test)
9
+ end
10
+
11
+ it '#environment=' do
12
+ config.environment = 'test'
13
+ expect(config.environment).to eq(:test)
14
+ end
15
+
16
+ it '#queue_name' do
17
+ expect(config.queue_name).to eq('basquiat.queue')
18
+ end
19
+
20
+ it '#queue_name=' do
21
+ config.queue_name = 'basquiat.test'
22
+ expect(config.queue_name).to eq('basquiat.test')
23
+
24
+ config.queue_name = nil
25
+ expect(config.queue_name).to eq('basquiat.queue')
26
+ end
27
+
28
+ it '#exchange_name' do
29
+ expect(config.exchange_name).to eq('basquiat.exchange')
30
+ end
31
+
32
+ it '#exchange_name=' do
33
+ config.exchange_name = 'test'
34
+ expect(config.exchange_name).to eq('test')
35
+
36
+ config.exchange_name = nil
37
+ expect(config.exchange_name).to eq('basquiat.exchange')
38
+ end
39
+
40
+ it '#logger' do
41
+ expect(config.logger).not_to be_nil
42
+ end
43
+
44
+ it '#logger=' do
45
+ config.logger = Logger.new('/dev/null')
46
+ expect(config.logger).to be_a Logger
47
+ end
48
+ end
49
+
50
+ it '#config_file=' do
51
+ config.config_file = File.join(File.dirname(__FILE__), '../../support/basquiat.yml')
52
+
53
+ expect(config.queue_name).to eq('my.nice_queue')
54
+ expect(config.exchange_name).to eq('my.test_exchange')
55
+ expect(config.default_adapter).to eq('Basquiat::Adapters::Test')
56
+ expect(config.adapter_options).to have_key(:servers)
57
+ end
58
+
59
+ it 'settings provided on the config file have lower precedence' do
60
+ config.exchange_name = 'super.nice_exchange'
61
+ config.config_file = File.join(File.dirname(__FILE__), '../../support/basquiat.yml')
62
+
63
+ expect(config.exchange_name).to eq('super.nice_exchange')
64
+ end
65
+
66
+ it '#reload_classes' do
67
+ class MyTest
68
+ extend Basquiat::Base
69
+ end
70
+
71
+ config.reload_classes
72
+ end
73
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Basquiat::HashRefinements do
4
+ using Basquiat::HashRefinements
5
+
6
+ subject(:hash) do
7
+ { 'key' => 1, 'another_key' => 2, 3 => 3, 'array' => [1, 3, 5], 'hash' => { 'inner_key' => 6 } }
8
+ end
9
+
10
+ it '#deep_merge' do
11
+ hash.deep_merge({ 'hash' => { 'inner_key' => 7, 'other_inner_key' => 10 } })
12
+ expect(hash['hash']).to have_key('other_inner_key')
13
+ expect(hash['hash']['inner_key']).to eq(7)
14
+ end
15
+
16
+ it '#symbolize_keys' do
17
+ symbol_hash = hash.symbolize_keys
18
+ expect(symbol_hash).to have_key(:array)
19
+ expect(symbol_hash).to have_key(3)
20
+ expect(symbol_hash).to have_key(:hash)
21
+ expect(symbol_hash[:hash]).to have_key(:inner_key)
22
+ end
23
+
24
+ end
@@ -0,0 +1,5 @@
1
+ describe Basquiat::Json do
2
+ it "returns an empty hash if it can't parse the payload" do
3
+ expect(Basquiat::Json.decode('Idaho Potato')).to eq({})
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'simplecov'
4
+
5
+ SimpleCov.start do
6
+ add_filter { |source| source.lines_of_code <= 3 }
7
+ add_filter { |source| source.filename =~ /spec/ }
8
+
9
+ add_group('Adapters') { |source| source.filename =~ /basquiat\/adapters/ }
10
+ add_group('Interfaces') { |source| source.filename =~ /basquiat\/interfaces/ }
11
+ add_group('Support') { |source| source.filename =~ /basquiat\/support/ }
12
+ add_group('Main Gem File') { |source| source.filename =~ %r{\/lib\/basquiat\.rb$} }
13
+ end
14
+
15
+ ENV['BASQUIAT_ENV'] = 'test'
16
+ require 'basquiat'
17
+
18
+ Basquiat.configure do |config|
19
+ config.config_file = File.expand_path('../support/basquiat.yml', __FILE__)
20
+ end
21
+
22
+ require 'support/shared_examples/basquiat_adapter_shared_examples'
@@ -0,0 +1,9 @@
1
+ test:
2
+ exchange_name: 'my.test_exchange'
3
+ queue_name: 'my.nice_queue'
4
+ default_adapter: Basquiat::Adapters::Test
5
+ adapter_options:
6
+ servers:
7
+ -
8
+ host: <%= ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_ADDR') { 'localhost' } %>
9
+ port: <%= ENV.fetch('BASQUIAT_RABBITMQ_1_PORT_5672_TCP_PORT') { 5672 } %>
@@ -0,0 +1,23 @@
1
+ shared_examples_for 'a Basquiat::Adapter' do
2
+ it '#adapter_options(opts)' do
3
+ expect(subject).to respond_to(:adapter_options)
4
+ end
5
+
6
+ it '#publish' do
7
+ expect(subject).to respond_to(:publish)
8
+ end
9
+
10
+ it '#default_options [template for option initialization]' do
11
+ expect(subject).to respond_to(:default_options)
12
+ end
13
+
14
+ it '#subscribe_to' do
15
+ expect(subject).to respond_to(:subscribe_to)
16
+ end
17
+
18
+ it 'merges the options with the default ones' do
19
+ opts = subject.instance_variable_get(:@options)
20
+ subject.adapter_options(nice_option: '127.0.0.2')
21
+ expect(opts[:nice_option]).to eq('127.0.0.2')
22
+ end
23
+ end