combi 0.0.3

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