combi 0.0.3

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.
@@ -0,0 +1,31 @@
1
+ module Combi
2
+ class ServiceBus
3
+ class << self
4
+ @@buses = {}
5
+
6
+ def for(kind, options = {})
7
+ @@buses[kind] ||= init_for(kind, options)
8
+ end
9
+
10
+ def init_for(kind, options)
11
+ require 'combi/buses/bus'
12
+
13
+ case kind
14
+ when :in_process
15
+ require 'combi/buses/in_process'
16
+ Combi::InProcess.new(options)
17
+ when :queue
18
+ require 'combi/buses/queue'
19
+ Combi::Queue.new(options)
20
+ when :web_socket
21
+ require 'combi/buses/web_socket'
22
+ Combi::WebSocket.new(options)
23
+ when :http
24
+ require 'combi/buses/http'
25
+ Combi::Http.new(options)
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Combi
2
+ VERSION = '0.0.3'
3
+ end
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+
3
+ require 'combi/service_bus'
4
+ require 'combi/buses/in_process'
5
+
6
+ describe "in a multi bus environment" do
7
+ include EventedSpec::SpecHelper
8
+
9
+ Given(:boring_salutation_service) do
10
+ Module.new do
11
+ def actions; [:say_hello]; end
12
+ def do_it(params); "Hello #{params['who']}"; end
13
+ end
14
+ end
15
+
16
+ Given(:composed_service_class) do
17
+ Class.new do
18
+ include Combi::Service
19
+
20
+ def initialize(other_client)
21
+ @other_client = other_client
22
+ end
23
+
24
+ def actions; [:repeat_with_me]; end
25
+
26
+ def do_it(params)
27
+ defer = EventMachine::DefaultDeferrable.new
28
+ EM.synchrony do
29
+ req = @other_client.request(:say_hello, :do_it, params, timeout: 3)
30
+ service_result = EM::Synchrony.sync req
31
+ defer.succeed(service_result*2)
32
+ end
33
+ defer
34
+ end
35
+ end
36
+ end
37
+
38
+ Given(:amqp_config) do
39
+ {
40
+ :host => "127.0.0.1",
41
+ :port => RabbitmqServer.instance.port,
42
+ :user => "admin",
43
+ :pass => RabbitmqServer::PASSWORD,
44
+ :vhost => "/",
45
+ :ssl => false,
46
+ :heartbeat => 0,
47
+ :frame_max => 131072
48
+ }
49
+ end
50
+
51
+ Given(:handler) { double('handler', on_open: nil) }
52
+ Given(:server_port_socket) { 9292 + rand(30000) }
53
+ Given(:socket_client_options) do
54
+ { remote_api: "ws://localhost:#{server_port_socket}/",
55
+ handler: handler }
56
+ end
57
+ Given(:server_port_http) { 9292 + rand(30000) }
58
+ Given(:http_client_options) do
59
+ { remote_api: "http://localhost:#{server_port_http}/" }
60
+ end
61
+
62
+ before(:all) {
63
+ RabbitmqServer.instance.stop! if ENV['CLEAN']
64
+ RabbitmqServer.instance.start!
65
+ }
66
+
67
+ after(:all) {
68
+ RabbitmqServer.instance.stop! if ENV['CLEAN']
69
+ }
70
+
71
+ Given(:options) { {timeout: 3} }
72
+
73
+ Given(:service_for_main_bus) { main_bus_provider.add_service composed_service_class.new(internal_bus_consumer) }
74
+ Given(:service_for_internal_bus) { internal_bus_provider.add_service boring_salutation_service }
75
+ When(:params) { { who: 'world' } }
76
+ When(:expected_result) { "Hello worldHello world" }
77
+
78
+ Given(:in_process_provider) { Combi::ServiceBus.init_for(:in_process, {}) }
79
+ Given(:in_process_consumer) { in_process_provider }
80
+ Given(:http_provider) { Combi::ServiceBus.init_for(:http, {} ) }
81
+ Given(:http_consumer) { Combi::ServiceBus.init_for(:http, http_client_options) }
82
+ Given(:queue_provider) { Combi::ServiceBus.init_for(:queue, { amqp_config: amqp_config } ) }
83
+ Given(:queue_consumer) { Combi::ServiceBus.init_for(:queue, { amqp_config: amqp_config } ) }
84
+ Given(:socket_provider) { Combi::ServiceBus.init_for(:web_socket, {} ) }
85
+ Given(:socket_consumer) { Combi::ServiceBus.init_for(:web_socket, socket_client_options) }
86
+
87
+ BUSES = %w{in_process socket http queue}
88
+
89
+ BUSES.each do |main|
90
+ BUSES.each do |internal|
91
+ context "#{internal} inside #{main}" do
92
+ Given(:main_bus_provider) { send "#{main}_provider" }
93
+ Given(:main_bus_consumer) { send "#{main}_consumer" }
94
+ Given(:internal_bus_provider) { send "#{internal}_provider" }
95
+ Given(:internal_bus_consumer) { send "#{internal}_consumer" }
96
+
97
+ Given("#{main}_server".to_sym) do
98
+ if main == 'http'
99
+ start_web_server main_bus_provider, server_port_http
100
+ end
101
+ if main == 'socket'
102
+ start_em_websocket_server main_bus_provider, server_port_socket
103
+ end
104
+ end
105
+ Given("#{internal}_server".to_sym) do
106
+ if internal == 'http'
107
+ start_web_server internal_bus_provider, server_port_http
108
+ end
109
+ if internal == 'socket'
110
+ start_em_websocket_server internal_bus_provider, server_port_socket
111
+ end
112
+ end
113
+
114
+ Then do
115
+ em do
116
+ service_for_main_bus
117
+ service_for_internal_bus
118
+ main_bus_provider.start!
119
+ internal_bus_provider.start!
120
+ send("#{main}_server")
121
+ send("#{internal}_server")
122
+ main_bus_consumer.start!
123
+ internal_bus_consumer.start!
124
+
125
+ EM.synchrony do
126
+ service_result = EM::Synchrony.sync main_bus_consumer.request(:repeat_with_me, :do_it, params, options)
127
+ service_result.should eq expected_result
128
+ done
129
+ main_bus_provider.stop!
130
+ internal_bus_provider.stop!
131
+ main_bus_consumer.stop!
132
+ internal_bus_consumer.stop!
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Combi::Bus' do
4
+ context 'registers services' do
5
+ Given(:subject) { Combi::Bus.new({}) }
6
+
7
+ context 'via instances' do
8
+ Given(:service_class) { Class.new{include Combi::Service} }
9
+ Given(:service_definition) { service_class.new }
10
+ When { subject.add_service service_definition }
11
+ Then { subject.services == [service_definition] }
12
+ end
13
+
14
+ context 'via modules' do
15
+ Given(:service_definition) { Module.new }
16
+ When { subject.add_service service_definition }
17
+ Then { subject.services.length == 1 }
18
+ And { Combi::Service === subject.services[0] }
19
+ And { service_definition === subject.services[0] }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ require 'combi/service_bus'
4
+ require 'combi/buses/http'
5
+
6
+ describe 'Combi::Http' do
7
+ include EventedSpec::SpecHelper
8
+
9
+ context 'can be instantiated via ServiceBus' do
10
+ When(:bus) { Combi::ServiceBus.init_for(:http, {}) }
11
+ Then { Combi::Http === bus }
12
+ end
13
+ Given(:server_port) { 9292 + rand(30000) }
14
+ Given(:client_options) { { remote_api: "http://localhost:#{server_port}/" } }
15
+ Given(:provider) { Combi::ServiceBus.init_for(:http, {} ) }
16
+ Given(:consumer) { Combi::ServiceBus.init_for(:http, client_options) }
17
+ Given(:prepare) do
18
+ provider.start!
19
+ start_web_server provider, server_port
20
+ consumer.start!
21
+ end
22
+
23
+ it_behaves_like 'standard_bus'
24
+
25
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ require 'combi/service_bus'
4
+ require 'combi/buses/in_process'
5
+
6
+ describe 'Combi::InProcess' do
7
+ include EventedSpec::SpecHelper
8
+
9
+ context 'can be instanitated via ServiceBus' do
10
+ When(:bus) { Combi::ServiceBus.init_for(:in_process, {}) }
11
+ Then { Combi::InProcess === bus }
12
+ end
13
+
14
+ Given(:subject) { Combi::ServiceBus.init_for(:in_process, {}) }
15
+ Given(:provider) { subject }
16
+ Given(:consumer) { subject }
17
+ Given(:prepare) do
18
+ provider.start!
19
+ consumer.start!
20
+ end
21
+
22
+ it_behaves_like 'standard_bus'
23
+
24
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ require 'combi/service_bus'
4
+ require 'combi/buses/queue'
5
+
6
+ describe 'Combi::Queue' do
7
+ include EventedSpec::SpecHelper
8
+
9
+ context 'can be instanitated via ServiceBus' do
10
+ When(:bus) { Combi::ServiceBus.init_for(:queue, {init_queue: false}) }
11
+ Then { Combi::Queue === bus }
12
+ end
13
+
14
+ Given(:amqp_config) do
15
+ {
16
+ :host => "127.0.0.1",
17
+ :port => RabbitmqServer.instance.port,
18
+ :user => "admin",
19
+ :pass => RabbitmqServer::PASSWORD,
20
+ :vhost => "/",
21
+ :ssl => false,
22
+ :heartbeat => 0,
23
+ :frame_max => 131072
24
+ }
25
+ end
26
+ Given(:provider) { Combi::ServiceBus.init_for(:queue, { amqp_config: amqp_config } ) }
27
+ Given(:consumer) { Combi::ServiceBus.init_for(:queue, { amqp_config: amqp_config } ) }
28
+ Given(:prepare) do
29
+ provider.start!
30
+ consumer.start!
31
+ true
32
+ end
33
+
34
+ it_behaves_like 'standard_bus' do
35
+ before(:all) do
36
+ RabbitmqServer.instance.stop! if ENV['CLEAN']
37
+ RabbitmqServer.instance.start!
38
+ end
39
+ after(:all) { RabbitmqServer.instance.stop! if ENV['CLEAN'] }
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ require 'combi/service_bus'
4
+ require 'combi/buses/web_socket'
5
+
6
+ describe 'Combi::WebSocket' do
7
+ include EventedSpec::SpecHelper
8
+
9
+ context 'can be instanitated via ServiceBus' do
10
+ When(:bus) { Combi::ServiceBus.init_for(:web_socket, {}) }
11
+ Then { Combi::WebSocket === bus }
12
+ end
13
+ Given(:handler) do
14
+ Class.new do
15
+ def on_open; end
16
+ end.new
17
+ end
18
+
19
+ Given(:server_port) { 9292 + rand(30000) }
20
+ Given(:client_options) do
21
+ { remote_api: "ws://localhost:#{server_port}/",
22
+ handler: handler }
23
+ end
24
+ Given(:provider) { Combi::ServiceBus.init_for(:web_socket, {} )}
25
+ Given(:consumer) { Combi::ServiceBus.init_for(:web_socket, client_options) }
26
+ Given(:prepare) do
27
+ provider.start!
28
+ start_em_websocket_server provider, server_port
29
+ consumer.start!
30
+ end
31
+
32
+ it_behaves_like 'standard_bus'
33
+
34
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Combi::Service' do
4
+ Given(:subject_class) { Class.new{include Combi::Service} }
5
+ Given(:subject) { subject_class.new }
6
+
7
+ describe '#setup' do
8
+ Given(:context) { {foo: 'bar', one: 1} }
9
+ Given!("setup methods are stubbed") do
10
+ subject.stub(:setup_context).and_call_original
11
+ subject.stub(:setup_services).and_call_original
12
+ subject.stub(:register_actions).and_call_original
13
+ end
14
+ Given(:the_bus) { double "the bus" }
15
+ When { subject.setup the_bus, context }
16
+ context 'sets up the context' do
17
+ Then { subject.should have_received :setup_context }
18
+ end
19
+ context 'allows the service to setup itself' do
20
+ Then { subject.should have_received :setup_services }
21
+ end
22
+ context 'registers the advertised actions' do
23
+ Then { subject.should have_received :register_actions }
24
+ end
25
+ context 'always includes the service_bus in the context' do
26
+ Then { subject.service_bus == the_bus }
27
+ end
28
+ end
29
+
30
+ describe '#setup_context' do
31
+ context 'creates accessors for keys in the context' do
32
+ Given(:context) { {foo: 'bar', one: 1} }
33
+ When { subject.setup_context context }
34
+ Then { subject.respond_to? :foo }
35
+ And { subject.respond_to? :one}
36
+ And { subject.foo == 'bar' }
37
+ And { subject.one == 1 }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,62 @@
1
+ require "em-synchrony"
2
+
3
+ shared_examples_for "standard_bus" do
4
+
5
+ Given(:slow_service) do
6
+ Module.new do
7
+ def actions; [:sleep]; end
8
+ def do_it(params)
9
+ sleep params['time']
10
+ end
11
+ end
12
+ end
13
+
14
+ Given(:boring_salutation_service) do
15
+ Module.new do
16
+ def actions; [:say_hello]; end
17
+ def do_it(params)
18
+ "Hello #{params['who']}"
19
+ end
20
+ end
21
+ end
22
+
23
+ Given(:finalize) do
24
+ provider.stop!
25
+ consumer.stop!
26
+ end
27
+
28
+ context 'can invoke services' do
29
+ When(:service) { provider.add_service boring_salutation_service }
30
+ When(:params) { { who: 'world' } }
31
+ Then do
32
+ em do
33
+ prepare
34
+ EM.synchrony do
35
+ service_result = EM::Synchrony.sync consumer.request(:say_hello, :do_it, params, { timeout: 2 })
36
+ service_result.should eq "Hello world"
37
+ done
38
+ finalize
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ context 'raises Timeout::Error when the response is slow' do
45
+ Given(:time_base) { 0.01 }
46
+ When(:params) { { time: time_base*4 } }
47
+ When(:service) { provider.add_service slow_service }
48
+ Then do
49
+ em do
50
+ prepare
51
+ EM.synchrony do
52
+ expect do
53
+ service_result = EM::Synchrony.sync consumer.request(:sleep, :do_it, params, { timeout: time_base/2.0 })
54
+ raise Kernel.const_get(service_result.message) if service_result.is_a? Exception
55
+ end.to raise_error Timeout::Error
56
+ done(time_base) #timeout response must came before this timeout
57
+ end
58
+ finalize
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
18
+
19
+ require 'rspec-given'
20
+ require 'evented-spec'
21
+ Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
22
+ Dir["./spec/shared_examples/**/*.rb"].sort.each {|f| require f}