stomper 1.0.0 → 2.0.0

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