tochtli 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +14 -0
- data/Gemfile +32 -0
- data/History.md +138 -0
- data/README.md +46 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/assets/communication.png +0 -0
- data/assets/layers.png +0 -0
- data/examples/01-screencap-service/Gemfile +3 -0
- data/examples/01-screencap-service/README.md +5 -0
- data/examples/01-screencap-service/client.rb +15 -0
- data/examples/01-screencap-service/common.rb +15 -0
- data/examples/01-screencap-service/server.rb +26 -0
- data/examples/02-log-analyzer/Gemfile +3 -0
- data/examples/02-log-analyzer/README.md +5 -0
- data/examples/02-log-analyzer/client.rb +95 -0
- data/examples/02-log-analyzer/common.rb +33 -0
- data/examples/02-log-analyzer/sample.log +10001 -0
- data/examples/02-log-analyzer/server.rb +133 -0
- data/lib/tochtli.rb +177 -0
- data/lib/tochtli/active_record_connection_cleaner.rb +9 -0
- data/lib/tochtli/application.rb +135 -0
- data/lib/tochtli/base_client.rb +135 -0
- data/lib/tochtli/base_controller.rb +360 -0
- data/lib/tochtli/controller_manager.rb +99 -0
- data/lib/tochtli/engine.rb +15 -0
- data/lib/tochtli/message.rb +114 -0
- data/lib/tochtli/rabbit_client.rb +36 -0
- data/lib/tochtli/rabbit_connection.rb +249 -0
- data/lib/tochtli/reply_queue.rb +129 -0
- data/lib/tochtli/simple_validation.rb +23 -0
- data/lib/tochtli/test.rb +9 -0
- data/lib/tochtli/test/client.rb +28 -0
- data/lib/tochtli/test/controller.rb +66 -0
- data/lib/tochtli/test/integration.rb +78 -0
- data/lib/tochtli/test/memory_cache.rb +22 -0
- data/lib/tochtli/test/test_case.rb +191 -0
- data/lib/tochtli/test/test_unit.rb +22 -0
- data/lib/tochtli/version.rb +3 -0
- data/log_generator.rb +11 -0
- data/test/base_client_test.rb +68 -0
- data/test/controller_functional_test.rb +87 -0
- data/test/controller_integration_test.rb +274 -0
- data/test/controller_manager_test.rb +75 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/config/application.rb +36 -0
- data/test/dummy/config/boot.rb +4 -0
- data/test/dummy/config/database.yml +3 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/rabbit.yml +4 -0
- data/test/dummy/db/.gitkeep +0 -0
- data/test/dummy/log/.gitkeep +0 -0
- data/test/key_matcher_test.rb +100 -0
- data/test/log/.gitkeep +0 -0
- data/test/message_test.rb +80 -0
- data/test/rabbit_client_test.rb +71 -0
- data/test/rabbit_connection_test.rb +151 -0
- data/test/test_helper.rb +32 -0
- data/test/version_test.rb +8 -0
- data/tochtli.gemspec +129 -0
- metadata +259 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
module Tochtli
|
2
|
+
class ReplyQueue
|
3
|
+
attr_reader :connection, :logger, :queue
|
4
|
+
|
5
|
+
def initialize(rabbit_connection, logger=nil)
|
6
|
+
@connection = rabbit_connection
|
7
|
+
@logger = logger || rabbit_connection.logger
|
8
|
+
@message_handlers = {}
|
9
|
+
@message_timeout_threads = {}
|
10
|
+
|
11
|
+
subscribe
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@queue.name
|
16
|
+
end
|
17
|
+
|
18
|
+
def subscribe
|
19
|
+
channel = @connection.channel
|
20
|
+
exchange = @connection.exchange
|
21
|
+
|
22
|
+
@queue = channel.queue('', exclusive: true, auto_delete: true)
|
23
|
+
@original_queue_name = @queue.name
|
24
|
+
@queue.bind exchange, routing_key: @queue.name
|
25
|
+
|
26
|
+
@consumer = Consumer.new(self, channel, @queue)
|
27
|
+
@consumer.on_delivery(&method(:on_delivery))
|
28
|
+
|
29
|
+
@queue.subscribe_with(@consumer)
|
30
|
+
end
|
31
|
+
|
32
|
+
def reconnect(channel)
|
33
|
+
if @queue
|
34
|
+
channel.connection.logger.debug "Recovering reply queue binding (original: #{@original_queue_name}, current: #{@queue.name})"
|
35
|
+
|
36
|
+
# Re-bind queue after name change (auto-generated new on server has been re-generated)
|
37
|
+
exchange = @connection.create_exchange(channel)
|
38
|
+
@queue.unbind exchange, routing_key: @original_queue_name
|
39
|
+
@queue.bind exchange, routing_key: @queue.name
|
40
|
+
end
|
41
|
+
|
42
|
+
@original_queue_name = @queue.name
|
43
|
+
end
|
44
|
+
|
45
|
+
def register_message_handler(message, handler=nil, timeout=nil, &block)
|
46
|
+
@message_handlers[message.id] = handler || block
|
47
|
+
if timeout
|
48
|
+
timeout_thread = Thread.start do
|
49
|
+
sleep timeout
|
50
|
+
logger.warn "[#{Time.now} AMQP] TIMEOUT on message '#{message.id}' timeout: #{timeout}"
|
51
|
+
handle_timeout message
|
52
|
+
end
|
53
|
+
@message_timeout_threads[message.id] = timeout_thread
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_delivery(delivery_info, metadata, payload)
|
58
|
+
class_name = metadata.type.camelize.gsub(/[^a-zA-Z0-9\:]/, '_') # basic sanity
|
59
|
+
reply_class = eval(class_name)
|
60
|
+
reply = reply_class.new({}, metadata)
|
61
|
+
attributes = JSON.parse(payload)
|
62
|
+
reply.attributes = attributes
|
63
|
+
|
64
|
+
logger.debug "[#{Time.now} AMQP] Replay for #{reply.properties.correlation_id}: #{reply.inspect}"
|
65
|
+
|
66
|
+
handle_reply reply
|
67
|
+
|
68
|
+
rescue Exception
|
69
|
+
logger.error $!
|
70
|
+
logger.error $!.backtrace.join("\n")
|
71
|
+
end
|
72
|
+
|
73
|
+
def handle_reply(reply, correlation_id=nil)
|
74
|
+
correlation_id ||= reply.properties.correlation_id if reply.is_a?(Tochtli::Message)
|
75
|
+
raise ArgumentError, "Correlated message ID expected" unless correlation_id
|
76
|
+
if (handler = @message_handlers.delete(correlation_id))
|
77
|
+
if (timeout_thread = @message_timeout_threads.delete(correlation_id))
|
78
|
+
timeout_thread.kill
|
79
|
+
timeout_thread.join # make sure timeout thread is dead
|
80
|
+
end
|
81
|
+
|
82
|
+
if !reply.is_a?(Tochtli::ErrorMessage) && !reply.is_a?(Exception)
|
83
|
+
|
84
|
+
begin
|
85
|
+
|
86
|
+
handler.call(reply)
|
87
|
+
|
88
|
+
rescue Exception
|
89
|
+
logger.error $!
|
90
|
+
logger.error $!.backtrace.join("\n")
|
91
|
+
handler.on_error($!)
|
92
|
+
end
|
93
|
+
|
94
|
+
else
|
95
|
+
handler.on_error(reply)
|
96
|
+
end
|
97
|
+
|
98
|
+
else
|
99
|
+
logger.error "[Tochtli::ReplyQueue] Unexpected message delivery '#{correlation_id}':\n\t#{reply.inspect})"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_timeout(original_message)
|
104
|
+
if (handler = @message_handlers.delete(original_message.id))
|
105
|
+
@message_timeout_threads.delete(original_message.id)
|
106
|
+
handler.on_timeout original_message
|
107
|
+
else
|
108
|
+
raise "Internal error, timeout handler not found for message: #{original_message.id}, #{original_message.inspect}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Consumer < ::Bunny::Consumer
|
113
|
+
def initialize(reply_queue, *args)
|
114
|
+
super(*args)
|
115
|
+
@reply_queue = reply_queue
|
116
|
+
end
|
117
|
+
|
118
|
+
def recover_from_network_failure
|
119
|
+
super
|
120
|
+
@reply_queue.reconnect(@channel)
|
121
|
+
rescue Exception
|
122
|
+
logger = channel.connection.logger
|
123
|
+
logger.error $!
|
124
|
+
logger.error $!.backtrace.join("\n")
|
125
|
+
raise
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Tochtli
|
2
|
+
module SimpleValidation
|
3
|
+
attr_reader :errors
|
4
|
+
|
5
|
+
def add_error(message)
|
6
|
+
@errors << message
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
@errors = []
|
11
|
+
validate
|
12
|
+
!@errors || @errors.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def invalid?
|
16
|
+
!valid?
|
17
|
+
end
|
18
|
+
|
19
|
+
# abstract method
|
20
|
+
def validate
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/tochtli/test.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Tochtli
|
2
|
+
module Test
|
3
|
+
autoload :Helpers, 'tochtli/test/test_case'
|
4
|
+
autoload :Client, 'tochtli/test/client'
|
5
|
+
autoload :Controller, 'tochtli/test/controller'
|
6
|
+
autoload :Integration, 'tochtli/test/integration'
|
7
|
+
autoload :MemoryCache, 'tochtli/test/memory_cache'
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'test_case'
|
2
|
+
|
3
|
+
module Tochtli
|
4
|
+
module Test
|
5
|
+
module Client
|
6
|
+
extend UnitTestSupport if defined?(::Test::Unit)
|
7
|
+
include Tochtli::Test::Helpers
|
8
|
+
|
9
|
+
def before_setup
|
10
|
+
super
|
11
|
+
@logger = Tochtli.logger
|
12
|
+
@client = Tochtli::RabbitClient.new(@connection, @logger)
|
13
|
+
@reply_queue = @client.reply_queue
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_reply(reply_class, original_message, attributes)
|
17
|
+
properties = TestMessageProperties.new(nil, reply_class.generate_id, original_message.id)
|
18
|
+
reply_class.new(attributes, properties)
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle_reply(reply_class, original_message, attributes)
|
22
|
+
reply = create_reply(reply_class, original_message, attributes)
|
23
|
+
@reply_queue.handle_reply reply
|
24
|
+
reply
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative 'test_case'
|
2
|
+
|
3
|
+
module Tochtli
|
4
|
+
module Test
|
5
|
+
module Controller
|
6
|
+
module ControllerClassSupport
|
7
|
+
def included(base)
|
8
|
+
super
|
9
|
+
base.class_eval do
|
10
|
+
extend Uber::InheritableAttr
|
11
|
+
inheritable_attr :controller_class
|
12
|
+
|
13
|
+
def self.tests(controller_class)
|
14
|
+
self.controller_class = controller_class
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
extend UnitTestSupport if defined?(::Test::Unit)
|
21
|
+
extend ControllerClassSupport
|
22
|
+
include Tochtli::Test::Helpers
|
23
|
+
|
24
|
+
class RoutingNotFound < StandardError
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def before_setup
|
29
|
+
super
|
30
|
+
@cache = Object.const_defined?(:ActiveSupport) ? ActiveSupport::Cache::MemoryStore.new : Tochtli::Test::MemoryCache.new
|
31
|
+
@logger = Tochtli.logger
|
32
|
+
self.class.controller_class.setup(@connection, @cache, @logger)
|
33
|
+
@dispatcher = self.class.controller_class.dispatcher
|
34
|
+
@message_index = 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def after_teardown
|
38
|
+
self.class.controller_class.stop
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def publish(message)
|
43
|
+
@message_index += 1
|
44
|
+
delivery_info = TestDeliveryInfo.new(message.routing_key)
|
45
|
+
properties = TestMessageProperties.new("test.reply", @message_index)
|
46
|
+
payload = message.to_json
|
47
|
+
|
48
|
+
@message, @reply = nil
|
49
|
+
|
50
|
+
unless @dispatcher.process_message(delivery_info, properties, payload, {})
|
51
|
+
if (reply = @connection.publications.first) && reply[:message].is_a?(Tochtli::ErrorMessage)
|
52
|
+
raise "Process error: #{reply[:message].message}"
|
53
|
+
else
|
54
|
+
raise RoutingNotFound, "Message #{message.class.name} not processed by #{self.class.controller_class} - #{message.inspect}."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
reply = @connection.publications.first
|
59
|
+
if reply && reply[:routing_key] == "test.reply" && reply[:correlation_id] == @message_index
|
60
|
+
@connection.publications.shift
|
61
|
+
@reply = reply[:message]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'test_case'
|
2
|
+
|
3
|
+
module Tochtli
|
4
|
+
# Ensure all queues are temporary
|
5
|
+
BaseController.queue_durable = false
|
6
|
+
BaseController.queue_auto_delete = true
|
7
|
+
|
8
|
+
module Test
|
9
|
+
module Integration
|
10
|
+
extend UnitTestSupport if defined?(::Test::Unit)
|
11
|
+
|
12
|
+
def before_setup
|
13
|
+
super
|
14
|
+
@logger = Tochtli.logger
|
15
|
+
@logger.level = Logger::DEBUG
|
16
|
+
@client = Tochtli::RabbitClient.new(nil, @logger)
|
17
|
+
@connection = @client.rabbit_connection
|
18
|
+
@controller_manager = Tochtli::ControllerManager.instance
|
19
|
+
@controller_manager.setup(connection: @connection, logger: @logger)
|
20
|
+
@controller_manager.start(:all)
|
21
|
+
|
22
|
+
# Reply support
|
23
|
+
@mutex = Mutex.new
|
24
|
+
@cv = ConditionVariable.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def after_teardown
|
28
|
+
begin
|
29
|
+
@controller_manager.stop if @controller_manager
|
30
|
+
rescue Timeout::Error
|
31
|
+
warn "Unable to stop controller manager: #{$!} [#{$!.class}]"
|
32
|
+
end
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def publish(message, options={})
|
39
|
+
@reply = nil
|
40
|
+
timeout = options.fetch(:timeout, 1.0)
|
41
|
+
@reply_message_class = options[:expect]
|
42
|
+
@reply_handler = options[:reply_handler]
|
43
|
+
|
44
|
+
if @reply_message_class || @reply_handler
|
45
|
+
handler = @reply_handler || method(:synchronous_reply_handler)
|
46
|
+
if handler.is_a?(Proc)
|
47
|
+
@client.reply_queue.register_message_handler message, &handler
|
48
|
+
else
|
49
|
+
@client.reply_queue.register_message_handler message, handler, timeout
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@client.publish message
|
54
|
+
|
55
|
+
if @reply_message_class && !@reply_handler
|
56
|
+
synchronous_timeout_handler(message, timeout)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def synchronous_reply_handler(reply)
|
61
|
+
assert_kind_of @reply_message_class, reply, "Unexpected reply"
|
62
|
+
@mutex.synchronize do
|
63
|
+
@reply = reply
|
64
|
+
@cv.signal
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def synchronous_timeout_handler(message, timeout)
|
69
|
+
@mutex.synchronize { @cv.wait(@mutex, timeout) unless @reply }
|
70
|
+
|
71
|
+
raise "Reply on #{message.class.name} timeout" unless @reply
|
72
|
+
raise @reply.message if @reply.is_a?(Tochtli::ErrorMessage)
|
73
|
+
|
74
|
+
@reply
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'mini_cache'
|
2
|
+
|
3
|
+
module Tochtli
|
4
|
+
module Test
|
5
|
+
# a simple proxy to replicate ActiveSupport cache interface using mini store
|
6
|
+
class MemoryCache
|
7
|
+
attr_reader :store
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@store = MiniCache::Store.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(name, value)
|
14
|
+
store.set(name, value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(name)
|
18
|
+
store.get(name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require_relative 'test_unit' if defined?(::Test::Unit)
|
2
|
+
|
3
|
+
module Tochtli
|
4
|
+
module Test
|
5
|
+
module Helpers
|
6
|
+
extend UnitTestSupport if defined?(::Test::Unit)
|
7
|
+
|
8
|
+
def before_setup
|
9
|
+
super
|
10
|
+
@connection = TestRabbitConnection.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def assert_published(message_class, attributes={})
|
14
|
+
publication = @connection.get_publication
|
15
|
+
assert !publication.nil?, "No message published"
|
16
|
+
@message = publication[:message]
|
17
|
+
assert_kind_of message_class, @message
|
18
|
+
attributes.each do |attr_name, value|
|
19
|
+
assert_equal value, @message.send(attr_name), "Message attribute :#{attr_name} value does not match"
|
20
|
+
end
|
21
|
+
yield @message if block_given?
|
22
|
+
@message
|
23
|
+
end
|
24
|
+
|
25
|
+
def expect_published(message_class, attributes={})
|
26
|
+
@connection.callback do
|
27
|
+
assert_published message_class, attributes
|
28
|
+
yield @message
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class TestRabbitConnection
|
34
|
+
attr_reader :channel, :exchange, :publications
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@channel = TestRabbitChannel.new(self)
|
38
|
+
@exchange = TestRabbitExchange.new
|
39
|
+
@publications = []
|
40
|
+
@queues = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def exchange_name
|
44
|
+
@exchange.name
|
45
|
+
end
|
46
|
+
|
47
|
+
def reply_queue
|
48
|
+
@reply_queue ||= Tochtli::ReplyQueue.new(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def publish(routing_key, message, options={})
|
52
|
+
@publications << options.merge(routing_key: routing_key, message: message)
|
53
|
+
run_callback
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_publication
|
57
|
+
@publications.shift
|
58
|
+
end
|
59
|
+
|
60
|
+
def callback(&block)
|
61
|
+
@callback = block
|
62
|
+
end
|
63
|
+
|
64
|
+
def run_callback
|
65
|
+
if @callback
|
66
|
+
@callback.call
|
67
|
+
@callback = nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def logger
|
72
|
+
Logger.new(STDOUT)
|
73
|
+
end
|
74
|
+
|
75
|
+
def queue(name=nil, routing_keys=[], options={})
|
76
|
+
queue = @queues[name]
|
77
|
+
unless queue
|
78
|
+
@queues[name] = queue = TestQueue.new(@channel, name, options)
|
79
|
+
end
|
80
|
+
queue
|
81
|
+
end
|
82
|
+
|
83
|
+
def queue_exists?(name)
|
84
|
+
@queues.has_key?(name)
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_channel(consumer_pool_size = 1)
|
88
|
+
TestRabbitChannel.new(self)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class TestRabbitChannel
|
93
|
+
def initialize(connection)
|
94
|
+
@connection = connection
|
95
|
+
end
|
96
|
+
|
97
|
+
def queue(name, options={})
|
98
|
+
@connection.queue(name, [], options)
|
99
|
+
end
|
100
|
+
|
101
|
+
[:topic, :fanout, :direct].each do |type|
|
102
|
+
define_method type do |name, options|
|
103
|
+
TestRabbitExchange.new(name, options)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def generate_consumer_tag
|
108
|
+
"test-consumer-tag-#{rand(1000)}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class TestRabbitExchange
|
113
|
+
attr_reader :name
|
114
|
+
|
115
|
+
def initialize(name='test.exchange', options={})
|
116
|
+
@name = name
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class TestQueue
|
121
|
+
attr_reader :channel, :name, :options, :routing_key
|
122
|
+
|
123
|
+
def initialize(channel, name, options)
|
124
|
+
@name = name
|
125
|
+
@channel = channel
|
126
|
+
@options = options
|
127
|
+
end
|
128
|
+
|
129
|
+
def bind(exchange, options)
|
130
|
+
@routing_key = options[:routing_key]
|
131
|
+
end
|
132
|
+
|
133
|
+
def subscribe(*args)
|
134
|
+
TestConsumer.new
|
135
|
+
end
|
136
|
+
|
137
|
+
def subscribe_with(*args)
|
138
|
+
TestConsumer.new
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class TestConsumer
|
143
|
+
def cancel
|
144
|
+
TestConsumer.new
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class TestDeliveryInfo
|
149
|
+
attr_reader :routing_key, :exchange
|
150
|
+
|
151
|
+
def initialize(routing_key, exchange='TestExchange')
|
152
|
+
@routing_key = routing_key
|
153
|
+
@exchange = exchange
|
154
|
+
end
|
155
|
+
|
156
|
+
def [](key)
|
157
|
+
send(key)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class TestMessageProperties
|
162
|
+
attr_reader :reply_to, :message_id, :correlation_id
|
163
|
+
|
164
|
+
def initialize(reply_to, message_id=nil, correlation_id=nil)
|
165
|
+
@reply_to = reply_to
|
166
|
+
@message_id = message_id
|
167
|
+
@correlation_id = correlation_id
|
168
|
+
end
|
169
|
+
|
170
|
+
def [](key)
|
171
|
+
send(key)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class TestMessageHandler
|
176
|
+
attr_reader :reply, :timeout_message, :error
|
177
|
+
|
178
|
+
def call(reply)
|
179
|
+
@reply = reply
|
180
|
+
end
|
181
|
+
|
182
|
+
def on_timeout(original_message=nil)
|
183
|
+
@timeout_message = original_message
|
184
|
+
end
|
185
|
+
|
186
|
+
def on_error(error)
|
187
|
+
@error = error
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|