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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.metrics +5 -0
- data/.rspec +4 -0
- data/.rubocop.yml +12 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/Guardfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +95 -0
- data/Rakefile +13 -0
- data/basquiat.gemspec +39 -0
- data/docker/Dockerfile +11 -0
- data/docker/basquiat_start.sh +9 -0
- data/docker-compose.yml +11 -0
- data/lib/basquiat/adapters/base_adapter.rb +54 -0
- data/lib/basquiat/adapters/rabbitmq_adapter.rb +130 -0
- data/lib/basquiat/adapters/test_adapter.rb +52 -0
- data/lib/basquiat/adapters.rb +2 -0
- data/lib/basquiat/interfaces/base.rb +62 -0
- data/lib/basquiat/rails/railtie.rb +14 -0
- data/lib/basquiat/support/configuration.rb +62 -0
- data/lib/basquiat/support/hash_refinements.rb +24 -0
- data/lib/basquiat/support/json.rb +13 -0
- data/lib/basquiat/support.rb +3 -0
- data/lib/basquiat/version.rb +4 -0
- data/lib/basquiat.rb +27 -0
- data/spec/lib/adapters/base_adapter_spec.rb +11 -0
- data/spec/lib/adapters/rabbitmq_adapter_spec.rb +79 -0
- data/spec/lib/adapters/test_adapter_spec.rb +61 -0
- data/spec/lib/basquiat_spec.rb +27 -0
- data/spec/lib/interfaces/base_spec.rb +100 -0
- data/spec/lib/support/configuration_spec.rb +73 -0
- data/spec/lib/support/hash_refinements_spec.rb +24 -0
- data/spec/lib/support/json_spec.rb +5 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/basquiat.yml +9 -0
- data/spec/support/shared_examples/basquiat_adapter_shared_examples.rb +23 -0
- 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
|
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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|