stomper 1.0.0 → 2.0.0

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.
Files changed (152) hide show
  1. data/.gitignore +5 -0
  2. data/{spec/spec.opts → .rspec} +0 -2
  3. data/Gemfile +4 -0
  4. data/LICENSE +201 -201
  5. data/README.md +130 -0
  6. data/Rakefile +5 -0
  7. data/examples/basic.rb +38 -0
  8. data/examples/events.rb +54 -0
  9. data/features/acking_messages.feature +147 -0
  10. data/features/disconnecting.feature +12 -0
  11. data/features/establish_connection.feature +44 -0
  12. data/features/protocol_version_negotiation.feature +61 -0
  13. data/features/receipts.feature +72 -0
  14. data/features/scopes.feature +32 -0
  15. data/features/secure_connections.feature +38 -0
  16. data/features/send_and_message.feature +28 -0
  17. data/features/steps/acking_messages_steps.rb +39 -0
  18. data/features/steps/disconnecting_steps.rb +8 -0
  19. data/features/steps/establish_connection_steps.rb +74 -0
  20. data/features/steps/frame_transmission_steps.rb +35 -0
  21. data/features/steps/protocol_version_negotiation_steps.rb +15 -0
  22. data/features/steps/receipts_steps.rb +79 -0
  23. data/features/steps/scopes_steps.rb +52 -0
  24. data/features/steps/secure_connections_steps.rb +41 -0
  25. data/features/steps/send_and_message_steps.rb +35 -0
  26. data/features/steps/subscribing_steps.rb +36 -0
  27. data/features/steps/threaded_receiver_steps.rb +8 -0
  28. data/features/steps/transactions_steps.rb +0 -0
  29. data/features/subscribing.feature +151 -0
  30. data/features/support/env.rb +11 -0
  31. data/features/support/header_helpers.rb +12 -0
  32. data/features/support/ssl/README +6 -0
  33. data/features/support/ssl/broker_cert.csr +17 -0
  34. data/features/support/ssl/broker_cert.pem +72 -0
  35. data/features/support/ssl/broker_key.pem +27 -0
  36. data/features/support/ssl/client_cert.csr +17 -0
  37. data/features/support/ssl/client_cert.pem +72 -0
  38. data/features/support/ssl/client_key.pem +27 -0
  39. data/features/support/ssl/demoCA/cacert.pem +17 -0
  40. data/features/support/ssl/demoCA/index.txt +2 -0
  41. data/features/support/ssl/demoCA/index.txt.attr +1 -0
  42. data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
  43. data/features/support/ssl/demoCA/index.txt.old +1 -0
  44. data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
  45. data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
  46. data/features/support/ssl/demoCA/private/cakey.pem +17 -0
  47. data/features/support/ssl/demoCA/serial +1 -0
  48. data/features/support/ssl/demoCA/serial.old +1 -0
  49. data/features/support/test_stomp_server.rb +150 -0
  50. data/features/threaded_receiver.feature +11 -0
  51. data/features/transactions.feature +66 -0
  52. data/lib/stomper.rb +30 -20
  53. data/lib/stomper/connection.rb +442 -102
  54. data/lib/stomper/errors.rb +59 -0
  55. data/lib/stomper/extensions.rb +10 -0
  56. data/lib/stomper/extensions/common.rb +258 -0
  57. data/lib/stomper/extensions/events.rb +213 -0
  58. data/lib/stomper/extensions/heartbeat.rb +101 -0
  59. data/lib/stomper/extensions/scoping.rb +56 -0
  60. data/lib/stomper/frame.rb +54 -0
  61. data/lib/stomper/frame_serializer.rb +217 -0
  62. data/lib/stomper/headers.rb +15 -0
  63. data/lib/stomper/receipt_manager.rb +36 -0
  64. data/lib/stomper/receivers.rb +7 -0
  65. data/lib/stomper/receivers/threaded.rb +71 -0
  66. data/lib/stomper/scopes.rb +9 -0
  67. data/lib/stomper/scopes/header_scope.rb +49 -0
  68. data/lib/stomper/scopes/receipt_scope.rb +44 -0
  69. data/lib/stomper/scopes/transaction_scope.rb +109 -0
  70. data/lib/stomper/sockets.rb +66 -28
  71. data/lib/stomper/subscription_manager.rb +79 -0
  72. data/lib/stomper/support.rb +68 -0
  73. data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
  74. data/lib/stomper/support/1.8/headers.rb +183 -0
  75. data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
  76. data/lib/stomper/support/1.9/headers.rb +172 -0
  77. data/lib/stomper/support/ruby.rb +13 -0
  78. data/lib/stomper/uris.rb +49 -0
  79. data/lib/stomper/version.rb +7 -0
  80. data/spec/spec_helper.rb +13 -9
  81. data/spec/stomper/connection_spec.rb +712 -0
  82. data/spec/stomper/extensions/common_spec.rb +187 -0
  83. data/spec/stomper/extensions/events_spec.rb +78 -0
  84. data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
  85. data/spec/stomper/extensions/scoping_spec.rb +21 -0
  86. data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
  87. data/spec/stomper/frame_serializer_spec.rb +316 -0
  88. data/spec/stomper/frame_spec.rb +36 -0
  89. data/spec/stomper/headers_spec.rb +224 -0
  90. data/spec/stomper/receipt_manager_spec.rb +91 -0
  91. data/spec/stomper/receivers/threaded_spec.rb +116 -0
  92. data/spec/stomper/scopes/header_scope_spec.rb +42 -0
  93. data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
  94. data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
  95. data/spec/stomper/sockets_spec.rb +113 -0
  96. data/spec/stomper/subscription_manager_spec.rb +107 -0
  97. data/spec/stomper/support_spec.rb +69 -0
  98. data/spec/stomper/uris_spec.rb +54 -0
  99. data/spec/stomper_spec.rb +9 -0
  100. data/spec/support/custom_argument_matchers.rb +57 -0
  101. data/spec/support/existential_frame_matchers.rb +19 -0
  102. data/spec/support/frame_header_matchers.rb +10 -0
  103. data/stomper.gemspec +30 -0
  104. metadata +272 -97
  105. data/AUTHORS +0 -21
  106. data/CHANGELOG +0 -20
  107. data/README.rdoc +0 -120
  108. data/lib/stomper/client.rb +0 -34
  109. data/lib/stomper/frame_reader.rb +0 -73
  110. data/lib/stomper/frame_writer.rb +0 -21
  111. data/lib/stomper/frames.rb +0 -39
  112. data/lib/stomper/frames/abort.rb +0 -10
  113. data/lib/stomper/frames/ack.rb +0 -25
  114. data/lib/stomper/frames/begin.rb +0 -11
  115. data/lib/stomper/frames/client_frame.rb +0 -89
  116. data/lib/stomper/frames/commit.rb +0 -10
  117. data/lib/stomper/frames/connect.rb +0 -10
  118. data/lib/stomper/frames/connected.rb +0 -30
  119. data/lib/stomper/frames/disconnect.rb +0 -10
  120. data/lib/stomper/frames/error.rb +0 -21
  121. data/lib/stomper/frames/message.rb +0 -48
  122. data/lib/stomper/frames/receipt.rb +0 -19
  123. data/lib/stomper/frames/send.rb +0 -10
  124. data/lib/stomper/frames/server_frame.rb +0 -38
  125. data/lib/stomper/frames/subscribe.rb +0 -42
  126. data/lib/stomper/frames/unsubscribe.rb +0 -19
  127. data/lib/stomper/open_uri_interface.rb +0 -41
  128. data/lib/stomper/receipt_handlers.rb +0 -23
  129. data/lib/stomper/receiptor.rb +0 -38
  130. data/lib/stomper/subscriber.rb +0 -76
  131. data/lib/stomper/subscription.rb +0 -128
  132. data/lib/stomper/subscriptions.rb +0 -95
  133. data/lib/stomper/threaded_receiver.rb +0 -59
  134. data/lib/stomper/transaction.rb +0 -185
  135. data/lib/stomper/transactor.rb +0 -50
  136. data/lib/stomper/uri.rb +0 -55
  137. data/spec/client_spec.rb +0 -29
  138. data/spec/connection_spec.rb +0 -22
  139. data/spec/frame_reader_spec.rb +0 -37
  140. data/spec/frame_writer_spec.rb +0 -27
  141. data/spec/frames/client_frame_spec.rb +0 -66
  142. data/spec/frames/indirect_frame_spec.rb +0 -45
  143. data/spec/frames/server_frame_spec.rb +0 -85
  144. data/spec/open_uri_interface_spec.rb +0 -132
  145. data/spec/receiptor_spec.rb +0 -35
  146. data/spec/shared_connection_examples.rb +0 -79
  147. data/spec/subscriber_spec.rb +0 -77
  148. data/spec/subscription_spec.rb +0 -157
  149. data/spec/subscriptions_spec.rb +0 -145
  150. data/spec/threaded_receiver_spec.rb +0 -33
  151. data/spec/transaction_spec.rb +0 -139
  152. data/spec/transactor_spec.rb +0 -46
@@ -0,0 +1,13 @@
1
+ # -*- encoding: utf-8 -*-
2
+ ::Stomper::Support::RUBY_SUPPORT = RUBY_VERSION >= '1.9' ? '1.9' : '1.8'
3
+
4
+ # Module for supporting Ruby 1.8.7
5
+ module Stomper::Support::Ruby1_8
6
+ end
7
+
8
+ # Module for supporting Ruby 1.9
9
+ module Stomper::Support::Ruby1_9
10
+ end
11
+
12
+ require "stomper/support/#{::Stomper::Support::RUBY_SUPPORT}/frame_serializer"
13
+ require "stomper/support/#{::Stomper::Support::RUBY_SUPPORT}/headers"
@@ -0,0 +1,49 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Subclasses of URI::Generic to ease working with Stomp URIs.
4
+ module URI
5
+ # This class encapsulates a URI with a schema of "stomp". For example,
6
+ # "stomp://host.domain.tld"
7
+ class STOMP < ::URI::Generic
8
+ # Specifies the default port of a standard Stomp connection. Any URI
9
+ # without explicit port specified will use this value instead.
10
+ #
11
+ # @note The Stomp specification does not define any default ports, this port
12
+ # has been chosen because it is fairly common amongst brokers that
13
+ # provide a Stomp interface, such as Apache's ActiveMQ.
14
+ DEFAULT_PORT = 61613
15
+
16
+ # Creates a TCP socket connection to the host and port specified by
17
+ # this URI.
18
+ # @return Stomper::Sockets::TCP
19
+ def create_socket(*args)
20
+ ::Stomper::Sockets::TCP.new(self.host||'localhost', self.port)
21
+ end
22
+ end
23
+
24
+ # This class encapsulates a URI with a schema of "stomp+ssl". For example,
25
+ # "stomp+ssl://host.domain.tld"
26
+ class STOMP_SSL < ::URI::STOMP
27
+ # Specifies the default port of a standard Stomp connection. Any URI
28
+ # without explicit port specified will use this value instead.
29
+ #
30
+ # @note The Stomp specification does not define any default ports, this port
31
+ # has been chosen because it is fairly common amongst brokers that
32
+ # provide a Secure Stomp interface, such as Apache's ActiveMQ.
33
+ DEFAULT_PORT = 61612
34
+
35
+ # Creates a SSL socket connection to the host and port specified by
36
+ # this URI. If a hash is included as the last argument to the call,
37
+ # it will be passed along as SSL options to {Stomper::Sockets::SSL#initialize}
38
+ # @return Stomper::Sockets::SSL
39
+ def create_socket(*args)
40
+ ::Stomper::Sockets::SSL.new(self.host||'localhost', self.port,
41
+ (args.last.is_a?(Hash) ? args.pop : {}))
42
+ end
43
+ end
44
+
45
+ # Add these classes to the URI @@schemas hash, thus making URI aware
46
+ # of these two handlers.
47
+ @@schemes['STOMP'] = STOMP
48
+ @@schemes['STOMP+SSL'] = STOMP_SSL
49
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Primary namespace of the stomper gem.
4
+ module Stomper
5
+ # The current version of the stomper gem.
6
+ VERSION = '2.0.0'
7
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,16 +1,20 @@
1
- #begin
2
- # require 'spec'
3
- #rescue LoadError
4
- # require 'rubygems'
5
- # #gem 'rspec'
6
- # require 'spec'
7
- #end
8
- if RUBY_VERSION >= '1.9.0'
1
+ # -*- encoding: utf-8 -*-
2
+ Dir[File.expand_path('support', File.dirname(__FILE__)) + "/**/*.rb"].each { |f| require f }
3
+
4
+ if RUBY_VERSION < '1.9'
5
+ class Symbol
6
+ def <=>(other)
7
+ self.to_s <=> other.to_s
8
+ end
9
+ end
10
+ end
11
+
12
+ begin
9
13
  require 'simplecov'
10
14
  SimpleCov.start do
11
15
  add_filter "/spec/"
12
16
  end
17
+ rescue LoadError
13
18
  end
14
19
 
15
20
  require 'stomper'
16
-
@@ -0,0 +1,712 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Stomper
5
+ describe Connection do
6
+ before(:each) do
7
+ @uri = mock("uri", :path => '',
8
+ :query => '',
9
+ :is_a? => true,
10
+ :host => nil,
11
+ :user => nil,
12
+ :password => nil)
13
+ end
14
+
15
+ it "should alias Stomper::Client to Stomper::Connection" do
16
+ ::Stomper::Client.should == ::Stomper::Connection
17
+ end
18
+
19
+ describe "default configuration" do
20
+ before(:each) do
21
+ @uri.stub!(:host => 'uri.host.name')
22
+ @connection = Connection.new(@uri)
23
+ end
24
+
25
+ it "should default to all supported protocol versions" do
26
+ @connection.versions.should == Stomper::Connection::PROTOCOL_VERSIONS
27
+ end
28
+
29
+ it "should default to no heartbeating" do
30
+ @connection.heartbeats.should == [ 0, 0 ]
31
+ end
32
+
33
+ it "should default the virtual host to the URI's host" do
34
+ @connection.host.should == 'uri.host.name'
35
+ end
36
+
37
+ it "should have an empty login and passcode" do
38
+ @connection.login.should == ''
39
+ @connection.passcode.should == ''
40
+ end
41
+
42
+ it "should default to a threaded receiver" do
43
+ @connection.receiver_class.should == ::Stomper::Receivers::Threaded
44
+ end
45
+
46
+ it "should include Extensions::Common" do
47
+ @connection.should be_a_kind_of(::Stomper::Extensions::Common)
48
+ end
49
+
50
+ it "should include Extensions::Events" do
51
+ @connection.should be_a_kind_of(::Stomper::Extensions::Events)
52
+ end
53
+
54
+ it "should include Extensions::Heartbeat" do
55
+ @connection.should be_a_kind_of(::Stomper::Extensions::Heartbeat)
56
+ end
57
+ end
58
+
59
+ describe "configuration through uri" do
60
+ before(:each) do
61
+ @uri.stub!(:path => '/path/dest',
62
+ :query => 'versions=1.1&versions=1.0')
63
+ end
64
+
65
+ it "should use the version query parameter" do
66
+ connection = Connection.new(@uri)
67
+ connection.versions.should == ['1.0', '1.1']
68
+
69
+ @uri.stub!(:query => 'versions=1.1&versions=1.1')
70
+ connection = Connection.new(@uri)
71
+ connection.versions.should == ['1.1']
72
+
73
+ @uri.stub!(:query => 'versions=1.0')
74
+ connection = Connection.new(@uri)
75
+ connection.versions.should == ['1.0']
76
+ end
77
+
78
+ it "should use the user and password of the URI" do
79
+ @uri.stub!(:user => 'some guy')
80
+ @uri.stub!(:password => 's3cr3tk3yz')
81
+ connection = Connection.new(@uri)
82
+ connection.login.should == 'some guy'
83
+ connection.passcode.should == 's3cr3tk3yz'
84
+ end
85
+
86
+ it "should use the login and passcode query parameters" do
87
+ @uri.stub!(:query => 'login=other%20dude&passcode=yermom')
88
+ connection = Connection.new(@uri)
89
+ connection.login.should == 'other dude'
90
+ connection.passcode.should == 'yermom'
91
+ end
92
+
93
+ it "should use the receiver_class query parameter" do
94
+ @uri.stub!(:query => 'receiver_class=Stomper::Scopes::HeaderScope')
95
+ connection = Connection.new(@uri)
96
+ connection.receiver_class.should == ::Stomper::Scopes::HeaderScope
97
+ end
98
+ end
99
+
100
+ describe "configuration through options" do
101
+ it "should use the version option" do
102
+ connection = Connection.new(@uri, { :versions => '1.0' })
103
+ connection.versions.should == [ '1.0' ]
104
+
105
+ connection = Connection.new(@uri, { 'versions' => ['1.1', '1.1'] })
106
+ connection.versions.should == ['1.1']
107
+ end
108
+
109
+ it "should use the login and passcode options" do
110
+ connection = Connection.new(@uri, { :login => 'me also', 'passcode' => 'm3t00'})
111
+ connection.login.should == 'me also'
112
+ connection.passcode.should == 'm3t00'
113
+ end
114
+
115
+ it "should use the receiver_class option" do
116
+ connection = Connection.new(@uri, :receiver_class => ::Stomper::Scopes::HeaderScope)
117
+ connection.receiver_class.should == ::Stomper::Scopes::HeaderScope
118
+ connection = Connection.new(@uri, :receiver_class => 'Stomper::Scopes::ReceiptScope')
119
+ connection.receiver_class.should == ::Stomper::Scopes::ReceiptScope
120
+ end
121
+ end
122
+
123
+ describe "configuration collision" do
124
+ it "should favor the login/passcode option over the query over the user/password of the URI" do
125
+ @uri.stub!(:user => 'ian', :password => 's3cr3tz')
126
+ @uri.stub!(:query => 'login=not%20ian&passcode=my_super_secret_key')
127
+ connection = Connection.new(@uri)
128
+ connection.login.should == 'not ian'
129
+ connection.passcode.should == 'my_super_secret_key'
130
+ connection = Connection.new(@uri, { :login => '', :passcode => nil })
131
+ connection.login.should == ''
132
+ connection.passcode.should == ''
133
+ end
134
+
135
+ it "should favor the version option over the query parameter" do
136
+ @uri.stub!(:query => 'versions=1.1&versions=1.1')
137
+ connection = Connection.new(@uri, { :versions => '1.0' })
138
+ connection.versions.should == [ '1.0' ]
139
+ end
140
+ end
141
+
142
+ describe "version configuration" do
143
+ before(:each) do
144
+ @connection = Connection.new(@uri)
145
+ end
146
+
147
+ it "should only use versions numbers that are supported" do
148
+ @connection.versions = [ '1.1', '9.3', '1.0', '7.garbage' ]
149
+ @connection.versions.should == ['1.0', '1.1']
150
+ end
151
+
152
+ it "should raise an error when no supported versions have been specified" do
153
+ lambda { @connection.versions = [ '2.0', '3.8', '1.2' ] }.should raise_error(::Stomper::Errors::UnsupportedProtocolVersionError)
154
+ end
155
+ end
156
+
157
+ describe "broker URI" do
158
+ it "should use the URI provided" do
159
+ uri = URI.parse('stomp:///')
160
+ connection = Connection.new(uri)
161
+ connection.uri.should == uri
162
+ end
163
+
164
+ it "should convert a string into a URI" do
165
+ connection = Connection.new('stomp:///')
166
+ connection.uri.should be_a_kind_of(::URI)
167
+ end
168
+ end
169
+
170
+ describe "duration since transmitted and received" do
171
+ before(:each) do
172
+ @connection = Connection.new(@uri)
173
+ end
174
+ it "should return nil if no frames have been transmitted" do
175
+ @connection.duration_since_transmitted.should be_nil
176
+ end
177
+
178
+ it "should return nil if no frames have been received" do
179
+ @connection.duration_since_received.should be_nil
180
+ end
181
+ end
182
+
183
+ describe "Connection IO" do
184
+ before(:each) do
185
+ @socket = mock('socket', :closed? => false, :close => true, :shutdown => true)
186
+ @serializer = mock('serializer', :extend_for_protocol => true)
187
+ @connected_frame = mock('CONNECTED', :command => 'CONNECTED', :[] => '')
188
+ @connected_frame.stub!(:[]).with(:version).and_return('1.0')
189
+ @uri.should_receive(:create_socket).at_least(:once).and_return(@socket)
190
+ ::Stomper::FrameSerializer.stub!(:new => @serializer)
191
+ @connection = Connection.new(@uri)
192
+ end
193
+
194
+ it "should create and connect a Connection through Connection.open/connect" do
195
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
196
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).at_least(:once).and_return { |f| f }
197
+
198
+ connection = @connection.class.open(@uri)
199
+ connection.connected?.should be_true
200
+
201
+ connection = @connection.class.connect(@uri)
202
+ connection.connected?.should be_true
203
+ end
204
+
205
+ it "should raise an error if the first frame received after CONNECT is sent is not CONNECTED" do
206
+ not_connected = mock('NOT CONNECTED', :command => 'NOT CONNECTED', :[] => '')
207
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
208
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(not_connected)
209
+ lambda { @connection.connect }.should raise_error(::Stomper::Errors::ConnectFailedError)
210
+ end
211
+
212
+ it "should send a DISCONNECT frame when disconnecting politely" do
213
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
214
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
215
+ @connection.connect
216
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'DISCONNECT')).once
217
+ @connection.disconnect
218
+ @connection.connected?.should be_false
219
+ end
220
+
221
+ describe "frame reading" do
222
+ before(:each) do
223
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
224
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
225
+ end
226
+
227
+ it "should have durations since received and transmitted" do
228
+ ::Time.stub(:now => 1)
229
+ @connection.connect
230
+ ::Time.stub(:now => 3)
231
+ @connection.duration_since_received.should == 2000
232
+ ::Time.stub(:now => 2)
233
+ @connection.duration_since_transmitted.should == 1000
234
+
235
+ @serializer.stub(:write_frame => true)
236
+ @serializer.stub(:read_frame => mock('frame', :command => nil))
237
+
238
+ ::Time.stub(:now => 2)
239
+ @connection.transmit ::Stomper::Frame.new('SEND', {}, 'test message')
240
+ ::Time.stub(:now => 5)
241
+ @connection.duration_since_transmitted.should == 3000
242
+
243
+ ::Time.stub(:now => 6)
244
+ @connection.receive
245
+ ::Time.stub(:now => 8.5)
246
+ @connection.duration_since_received.should == 2500
247
+ end
248
+
249
+ it "should receive a frame" do
250
+ @connection.connect
251
+ frame = mock("frame", :command => 'MOCK')
252
+ @serializer.should_receive(:read_frame).and_return(frame)
253
+ @connection.receive.should == frame
254
+ end
255
+
256
+ it "should not receive_nonblock a frame if io is not ready" do
257
+ @connection.connect
258
+ @socket.should_receive(:ready?).and_return(false)
259
+ @serializer.should_not_receive(:read_frame)
260
+ @connection.receive_nonblock.should be_nil
261
+ end
262
+
263
+ it "should receive_nonblock a frame if io is ready" do
264
+ @connection.connect
265
+ frame = mock("frame", :command => 'MOCK')
266
+ @socket.should_receive(:ready?).and_return(true)
267
+ @serializer.should_receive(:read_frame).and_return(frame)
268
+ @connection.receive_nonblock.should == frame
269
+ end
270
+
271
+ it "should close the socket if reading a frame returns nil" do
272
+ @connection.connect
273
+ @serializer.should_receive(:read_frame).and_return(nil)
274
+ @connection.should_receive(:close).with(true)
275
+ @connection.receive.should be_nil
276
+ end
277
+ end
278
+
279
+ describe "connection state events" do
280
+ before(:each) do
281
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
282
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
283
+ end
284
+
285
+ it "should trigger on_connection_established after connecting" do
286
+ triggered = false
287
+ @connection.on_connection_established { triggered = true }
288
+ @connection.connect
289
+ triggered.should be_true
290
+ end
291
+
292
+ it "should trigger on_connection_closed & on_connection_disconnected after disconnecting" do
293
+ triggered = [false, false]
294
+ @connection.on_connection_closed { triggered[0] = true }
295
+ @connection.on_connection_disconnected { triggered[1] = true }
296
+ @connection.connect
297
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'DISCONNECT')).once
298
+ @connection.disconnect
299
+ triggered.should == [true, true]
300
+ end
301
+
302
+ it "should trigger on_connection_died before transmitting if the connection is dead" do
303
+ triggered = false
304
+ @connection.on_connection_died { triggered = true }
305
+ @connection.connect
306
+ @serializer.stub!(:write_frame).and_return { |f| f }
307
+ @connection.stub!(:alive?).and_return(false)
308
+ @connection.transmit(::Stomper::Frame.new('ACK'))
309
+ triggered.should be_true
310
+ end
311
+
312
+ it "should trigger on_connection_died before receiving if the connection is dead" do
313
+ triggered = false
314
+ @connection.on_connection_died { triggered = true }
315
+ @connection.connect
316
+ @connection.stub!(:alive?).and_return(false)
317
+ @serializer.stub!(:read_frame).and_return(::Stomper::Frame.new('MESSAGE'))
318
+ @connection.receive
319
+ triggered.should be_true
320
+ end
321
+
322
+ it "should trigger on_connection_died before receiving non-blocking (even if not ready) if the connection is dead" do
323
+ triggered = false
324
+ @connection.on_connection_died { triggered = true }
325
+ @connection.connect
326
+ @connection.stub!(:alive?).and_return(false)
327
+ @serializer.stub!(:read_frame).and_return(::Stomper::Frame.new('MESSAGE'))
328
+ @socket.stub!(:ready?).and_return(false)
329
+ @connection.receive_nonblock
330
+ triggered.should be_true
331
+ end
332
+
333
+ it "should trigger on_connection_terminated if the socket raises an IOError while transmitting" do
334
+ triggered = false
335
+ @connection.on_connection_terminated { triggered = true }
336
+ @connection.connect
337
+ @serializer.stub!(:write_frame).and_raise(IOError.new('io error'))
338
+ lambda { @connection.transmit(::Stomper::Frame.new('ACK')) }.should raise_error(IOError)
339
+ triggered.should be_true
340
+ end
341
+
342
+ it "should trigger on_connection_terminated if the socket raises a SystemCallError while transmitting" do
343
+ triggered = false
344
+ @connection.on_connection_terminated { triggered = true }
345
+ @connection.connect
346
+ @serializer.stub!(:write_frame).and_raise(SystemCallError.new('syscall error'))
347
+ lambda { @connection.transmit(::Stomper::Frame.new('ACK')) }.should raise_error(SystemCallError)
348
+ triggered.should be_true
349
+ end
350
+
351
+ it "should trigger on_connection_terminated if the socket raises an IOError while receiving" do
352
+ triggered = false
353
+ @connection.on_connection_terminated { triggered = true }
354
+ @connection.connect
355
+ @serializer.stub!(:read_frame).and_raise(IOError.new('io error'))
356
+ lambda { @connection.receive }.should raise_error(IOError)
357
+ triggered.should be_true
358
+ end
359
+
360
+ it "should trigger on_connection_terminated if the socket raises a SystemCallError while receiving" do
361
+ triggered = false
362
+ @connection.on_connection_terminated { triggered = true }
363
+ @connection.connect
364
+ @serializer.stub!(:read_frame).and_raise(SystemCallError.new('syscall error'))
365
+ lambda { @connection.receive }.should raise_error(SystemCallError)
366
+ triggered.should be_true
367
+ end
368
+
369
+ end
370
+
371
+ describe "frame events" do
372
+ before(:each) do
373
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
374
+ @connected_frame.stub!(:[]).with(:version).and_return('1.1')
375
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
376
+ @connection.connect
377
+ @serializer.stub!(:write_frame).and_return { |f| f }
378
+ end
379
+
380
+ it "should trigger all on_abort handlers when an ABORT frame is transmitted" do
381
+ triggered = [ false, false, false, false ]
382
+ @connection.on_abort { triggered[0] = true }
383
+ @connection.on_abort { triggered[1] = true }
384
+ @connection.before_transmitting { triggered[2] = true }
385
+ @connection.after_transmitting { triggered[3] = true }
386
+ @connection.transmit(::Stomper::Frame.new('ABORT'))
387
+ triggered.should == [true, true, true, true]
388
+ end
389
+
390
+ it "should trigger all on_ack handlers when an ACK frame is transmitted" do
391
+ triggered = [ false, false, false, false ]
392
+ @connection.on_ack { triggered[0] = true }
393
+ @connection.on_ack { triggered[1] = true }
394
+ @connection.before_transmitting { triggered[2] = true }
395
+ @connection.after_transmitting { triggered[3] = true }
396
+ @connection.transmit(::Stomper::Frame.new('ACK'))
397
+ triggered.should == [true, true, true, true]
398
+ end
399
+
400
+ it "should trigger all on_begin handlers when a BEGIN frame is transmitted" do
401
+ triggered = [ false, false, false, false ]
402
+ @connection.on_begin { triggered[0] = true }
403
+ @connection.on_begin { triggered[1] = true }
404
+ @connection.before_transmitting { triggered[2] = true }
405
+ @connection.after_transmitting { triggered[3] = true }
406
+ @connection.transmit(::Stomper::Frame.new('BEGIN'))
407
+ triggered.should == [true, true, true, true]
408
+ end
409
+
410
+ it "should trigger all on_commit handlers when a COMMIT frame is transmitted" do
411
+ triggered = [ false, false, false, false ]
412
+ @connection.on_commit { triggered[0] = true }
413
+ @connection.on_commit { triggered[1] = true }
414
+ @connection.before_transmitting { triggered[2] = true }
415
+ @connection.after_transmitting { triggered[3] = true }
416
+ @connection.transmit(::Stomper::Frame.new('COMMIT'))
417
+ triggered.should == [true, true, true, true]
418
+ end
419
+
420
+ it "should trigger all on_connect & on_stomp handlers when a CONNECT frame is transmitted" do
421
+ triggered = [ false, false, false, false, false ]
422
+ @connection.on_connect { triggered[0] = true }
423
+ @connection.on_stomp { triggered[1] = true }
424
+ @connection.on_connect { triggered[2] = true }
425
+ @connection.before_transmitting { triggered[3] = true }
426
+ @connection.after_transmitting { triggered[4] = true }
427
+ @connection.transmit(::Stomper::Frame.new('CONNECT'))
428
+ triggered.should == [true, true, true, true, true]
429
+ end
430
+
431
+ it "should trigger all on_disconnect handlers when a DISCONNECT frame is transmitted" do
432
+ triggered = [ false, false, false, false ]
433
+ @connection.on_disconnect { triggered[0] = true }
434
+ @connection.on_disconnect { triggered[1] = true }
435
+ @connection.before_transmitting { triggered[2] = true }
436
+ @connection.after_transmitting { triggered[3] = true }
437
+ @connection.transmit(::Stomper::Frame.new('DISCONNECT'))
438
+ triggered.should == [true, true, true, true]
439
+ end
440
+
441
+ it "should trigger all on_nack handlers when a NACK frame is transmitted" do
442
+ triggered = [ false, false, false, false ]
443
+ @connection.on_nack { triggered[0] = true }
444
+ @connection.on_nack { triggered[1] = true }
445
+ @connection.before_transmitting { triggered[2] = true }
446
+ @connection.after_transmitting { triggered[3] = true }
447
+ @connection.transmit(::Stomper::Frame.new('NACK'))
448
+ triggered.should == [true, true, true, true]
449
+ end
450
+
451
+ it "should trigger all on_send handlers when a SEND frame is transmitted" do
452
+ triggered = [ false, false, false, false ]
453
+ @connection.on_send { triggered[0] = true }
454
+ @connection.on_send { triggered[1] = true }
455
+ @connection.before_transmitting { triggered[2] = true }
456
+ @connection.after_transmitting { triggered[3] = true }
457
+ @connection.transmit(::Stomper::Frame.new('SEND'))
458
+ triggered.should == [true, true, true, true]
459
+ end
460
+
461
+ it "should trigger all on_subscribe handlers when a SUBSCRIBE frame is transmitted" do
462
+ triggered = [ false, false, false, false ]
463
+ @connection.on_subscribe { triggered[0] = true }
464
+ @connection.on_subscribe { triggered[1] = true }
465
+ @connection.before_transmitting { triggered[2] = true }
466
+ @connection.after_transmitting { triggered[3] = true }
467
+ @connection.transmit(::Stomper::Frame.new('SUBSCRIBE'))
468
+ triggered.should == [true, true, true, true]
469
+ end
470
+
471
+ it "should trigger all on_unsubscribe handlers when an UNSUBSCRIBE frame is transmitted" do
472
+ triggered = [ false, false, false, false ]
473
+ @connection.on_unsubscribe { triggered[0] = true }
474
+ @connection.on_unsubscribe { triggered[1] = true }
475
+ @connection.before_transmitting { triggered[2] = true }
476
+ @connection.after_transmitting { triggered[3] = true }
477
+ @connection.transmit(::Stomper::Frame.new('UNSUBSCRIBE'))
478
+ triggered.should == [true, true, true, true]
479
+ end
480
+
481
+ it "should trigger all on_client_beat handlers when a frame is transmitted without a command" do
482
+ triggered = [ false, false, false, false ]
483
+ @connection.on_client_beat { triggered[0] = true }
484
+ @connection.on_client_beat { triggered[1] = true }
485
+ @connection.before_transmitting { triggered[2] = true }
486
+ @connection.after_transmitting { triggered[3] = true }
487
+ @connection.transmit(::Stomper::Frame.new)
488
+ triggered.should == [true, true, true, true]
489
+ end
490
+
491
+ it "should trigger all on_connected handlers when a CONNECTED frame is received" do
492
+ triggered = [ false, false, false, false ]
493
+ @connection.on_connected { triggered[0] = true }
494
+ @connection.on_connected { triggered[1] = true }
495
+ @connection.before_receiving { triggered[2] = true }
496
+ @connection.after_receiving { triggered[3] = true }
497
+ @serializer.should_receive(:read_frame).and_return(::Stomper::Frame.new('CONNECTED'))
498
+ @connection.receive
499
+ triggered.should == [true, true, true, true]
500
+ end
501
+
502
+ it "should trigger all on_message handlers when a MESSAGE frame is received" do
503
+ triggered = [ false, false, false, false ]
504
+ @connection.on_message { triggered[0] = true }
505
+ @connection.on_message { triggered[1] = true }
506
+ @connection.before_receiving { triggered[2] = true }
507
+ @connection.after_receiving { triggered[3] = true }
508
+ @serializer.should_receive(:read_frame).and_return(::Stomper::Frame.new('MESSAGE'))
509
+ @connection.receive
510
+ triggered.should == [true, true, true, true]
511
+ end
512
+
513
+ it "should trigger all on_error handlers when an ERROR frame is received" do
514
+ triggered = [ false, false, false, false ]
515
+ @connection.on_error { triggered[0] = true }
516
+ @connection.on_error { triggered[1] = true }
517
+ @connection.before_receiving { triggered[2] = true }
518
+ @connection.after_receiving { triggered[3] = true }
519
+ @serializer.should_receive(:read_frame).and_return(::Stomper::Frame.new('ERROR'))
520
+ @connection.receive
521
+ triggered.should == [true, true, true, true]
522
+ end
523
+
524
+ it "should trigger all on_receipt handlers when a RECEIPT frame is received" do
525
+ triggered = [ false, false, false, false ]
526
+ @connection.on_receipt { triggered[0] = true }
527
+ @connection.on_receipt { triggered[1] = true }
528
+ @connection.before_receiving { triggered[2] = true }
529
+ @connection.after_receiving { triggered[3] = true }
530
+ @serializer.should_receive(:read_frame).and_return(::Stomper::Frame.new('RECEIPT'))
531
+ @connection.receive
532
+ triggered.should == [true, true, true, true]
533
+ end
534
+
535
+ it "should trigger all on_broker_beat handlers when a frame is received without a command" do
536
+ triggered = [ false, false, false, false ]
537
+ @connection.on_broker_beat { triggered[0] = true }
538
+ @connection.on_broker_beat { triggered[1] = true }
539
+ @connection.before_receiving { triggered[2] = true }
540
+ @connection.after_receiving { triggered[3] = true }
541
+ @serializer.should_receive(:read_frame).and_return(::Stomper::Frame.new)
542
+ @connection.receive
543
+ triggered.should == [true, true, true, true]
544
+ end
545
+ end
546
+
547
+ describe "receiver handling" do
548
+ before(:each) do
549
+ @receiver = mock('receiver')
550
+ @receiver_class = mock('receiver class', :new => @receiver)
551
+ @connection.stub!(:receiver_class).and_return(@receiver_class)
552
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
553
+ @connected_frame.stub!(:[]).with(:version).and_return('1.0')
554
+ end
555
+
556
+ it "should connect and start a receiver if it is not connected" do
557
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
558
+ @receiver.should_receive(:start)
559
+ @receiver.should_receive(:running?).and_return(true)
560
+
561
+ @connection.start
562
+ @connection.running?.should be_true
563
+ end
564
+
565
+ it "should stop a running receiver and disconnect if it is connected" do
566
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
567
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'DISCONNECT')).once.and_return { |f| f }
568
+ @receiver.should_receive(:start)
569
+ @receiver.should_receive(:stop)
570
+
571
+ @connection.start
572
+ @connection.stop
573
+ end
574
+
575
+ it "should pass along headers to the connect frame when starting" do
576
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({:test => 'value', :other => 'val2'}, 'CONNECT')).once.and_return { |f| f }
577
+ @receiver.should_receive(:start)
578
+
579
+ @connection.start(:test => 'value', :other => 'val2')
580
+ end
581
+
582
+ it "should pass along headers to the disconnect frame when stopping" do
583
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
584
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({:canon => 'crab', :violini => 'in unisono'}, 'DISCONNECT')).once.and_return { |f| f }
585
+ @receiver.should_receive(:start)
586
+ @receiver.should_receive(:stop)
587
+
588
+ @connection.start
589
+ @connection.stop(:canon => 'crab', :violini => 'in unisono')
590
+ end
591
+
592
+ it "should not attempt to stop a receiver that has not been started" do
593
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
594
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'DISCONNECT')).once.and_return { |f| f }
595
+ @receiver.should_not_receive(:stop)
596
+
597
+ @connection.connect
598
+ @connection.stop
599
+ end
600
+ it "should not attempt to disconnect a receiver that is not connected" do
601
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
602
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'DISCONNECT')).once.and_return { |f| f }
603
+ @receiver.should_receive(:start)
604
+ @receiver.should_receive(:stop)
605
+
606
+ @connection.start
607
+ @connection.disconnect
608
+ @connection.stop
609
+ end
610
+
611
+ it "should not attempt to connect a receiver that is already connected" do
612
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
613
+ @receiver.should_receive(:start).once
614
+
615
+ @connection.connect
616
+ @connection.start
617
+ end
618
+ end
619
+
620
+ describe "heartbeat negotiation" do
621
+ before(:each) do
622
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
623
+ @connected_frame.stub!(:[]).with(:version).and_return('1.1')
624
+ @serializer.stub!(:write_frame).and_return { |f| f }
625
+ end
626
+ it "should use 0 as a client parameter if either the client or server say so" do
627
+ @connected_frame.should_receive(:[]).with(:'heart-beat').and_return('1_000,0')
628
+ @connection.heartbeats = [0, 2_000]
629
+ @connection.connect
630
+ @connection.heartbeating.should == [0, 2_000]
631
+ @connection.disconnect
632
+
633
+ @connected_frame.should_receive(:[]).with(:'heart-beat').and_return('1_000,3_000')
634
+ @connection.heartbeats = [0, 2_000]
635
+ @connection.connect
636
+ @connection.heartbeating.should == [0, 2_000]
637
+ @connection.disconnect
638
+
639
+ @connected_frame.should_receive(:[]).with(:'heart-beat').and_return('1_000,0')
640
+ @connection.heartbeats = [3_000, 2_000]
641
+ @connection.connect
642
+ @connection.heartbeating.should == [0, 2_000]
643
+ @connection.disconnect
644
+ end
645
+
646
+ it "should use 0 as a broker parameter if either the client or broker say so" do
647
+ @connected_frame.should_receive(:[]).with(:'heart-beat').and_return('0,1_000')
648
+ @connection.heartbeats = [2_000, 0]
649
+ @connection.connect
650
+ @connection.heartbeating.should == [2_000, 0]
651
+ @connection.disconnect
652
+
653
+ @connected_frame.should_receive(:[]).with(:'heart-beat').and_return('3_000,1_000')
654
+ @connection.heartbeats = [2_000, 0]
655
+ @connection.connect
656
+ @connection.heartbeating.should == [2_000, 0]
657
+ @connection.disconnect
658
+
659
+ @connected_frame.should_receive(:[]).with(:'heart-beat').and_return('0,1_000')
660
+ @connection.heartbeats = [2_000, 3_000]
661
+ @connection.connect
662
+ @connection.heartbeating.should == [2_000, 0]
663
+ @connection.disconnect
664
+ end
665
+
666
+ it "should use the max of client/server beat durations if both are greater than zero" do
667
+ @connected_frame.should_receive(:[]).with(:'heart-beat').and_return('2_000,1_000')
668
+ @connection.heartbeats = [4_000, 1_000]
669
+ @connection.connect
670
+ @connection.heartbeating.should == [4_000, 2_000]
671
+ @connection.disconnect
672
+
673
+ @connected_frame.should_receive(:[]).with(:'heart-beat').and_return('3_000,2_000')
674
+ @connection.heartbeats = [1_000, 4_000]
675
+ @connection.connect
676
+ @connection.heartbeating.should == [2_000, 4_000]
677
+ @connection.disconnect
678
+ end
679
+ end
680
+
681
+ describe "version negotiation" do
682
+ before(:each) do
683
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
684
+ end
685
+ it "should not include any 1.1 extensions if the negotiated protocol is 1.0" do
686
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({:'accept-version' => '1.0,1.1', :'heart-beat' => '0,0'}, 'CONNECT'))
687
+ @connected_frame.stub!(:[]).with(:version).and_return('1.0')
688
+ @connection.connect
689
+ @connection.version.should == '1.0'
690
+ @connection.should_not be_a_kind_of(::Stomper::Extensions::Common::V1_1)
691
+ @connection.should_not be_a_kind_of(::Stomper::Extensions::Heartbeat::V1_1)
692
+ end
693
+
694
+ it "should include Extensions::Protocols::V1_0 if the negotiated protocol is 1.1" do
695
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({:'accept-version' => '1.0,1.1', :'heart-beat' => '0,0'}, 'CONNECT'))
696
+ @connected_frame.stub!(:[]).with(:version).and_return('1.1')
697
+ @connection.connect
698
+ @connection.version.should == '1.1'
699
+ @connection.should be_a_kind_of(::Stomper::Extensions::Common::V1_1)
700
+ @connection.should be_a_kind_of(::Stomper::Extensions::Heartbeat::V1_1)
701
+ end
702
+
703
+ it "should raise an error if the version returned by the broker is not in the list of acceptable versions and close the connection" do
704
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({:'accept-version' => '1.0,1.1', :'heart-beat' => '0,0'}, 'CONNECT'))
705
+ @connected_frame.stub!(:[]).with(:version).and_return('2.0')
706
+ lambda { @connection.connect }.should raise_error(::Stomper::Errors::UnsupportedProtocolVersionError)
707
+ @connection.connected?.should be_false
708
+ end
709
+ end
710
+ end
711
+ end
712
+ end