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