onstomp 1.0.0pre1
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.
- data/.autotest +2 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +4 -0
- data/DeveloperNarrative.md +15 -0
- data/Gemfile +4 -0
- data/LICENSE.md +221 -0
- data/README.md +73 -0
- data/Rakefile +6 -0
- data/UserNarrative.md +8 -0
- data/examples/basic.rb +40 -0
- data/examples/events.rb +72 -0
- data/lib/onstomp/client.rb +152 -0
- data/lib/onstomp/components/frame.rb +108 -0
- data/lib/onstomp/components/frame_headers.rb +212 -0
- data/lib/onstomp/components/nil_processor.rb +20 -0
- data/lib/onstomp/components/scopes/header_scope.rb +25 -0
- data/lib/onstomp/components/scopes/receipt_scope.rb +25 -0
- data/lib/onstomp/components/scopes/transaction_scope.rb +191 -0
- data/lib/onstomp/components/scopes.rb +45 -0
- data/lib/onstomp/components/subscription.rb +30 -0
- data/lib/onstomp/components/threaded_processor.rb +62 -0
- data/lib/onstomp/components/uri.rb +30 -0
- data/lib/onstomp/components.rb +13 -0
- data/lib/onstomp/connections/base.rb +208 -0
- data/lib/onstomp/connections/heartbeating.rb +82 -0
- data/lib/onstomp/connections/serializers/stomp_1.rb +166 -0
- data/lib/onstomp/connections/serializers/stomp_1_0.rb +41 -0
- data/lib/onstomp/connections/serializers/stomp_1_1.rb +134 -0
- data/lib/onstomp/connections/serializers.rb +9 -0
- data/lib/onstomp/connections/stomp_1.rb +69 -0
- data/lib/onstomp/connections/stomp_1_0.rb +28 -0
- data/lib/onstomp/connections/stomp_1_1.rb +65 -0
- data/lib/onstomp/connections.rb +119 -0
- data/lib/onstomp/interfaces/client_configurable.rb +55 -0
- data/lib/onstomp/interfaces/client_events.rb +168 -0
- data/lib/onstomp/interfaces/connection_events.rb +62 -0
- data/lib/onstomp/interfaces/event_manager.rb +69 -0
- data/lib/onstomp/interfaces/frame_methods.rb +190 -0
- data/lib/onstomp/interfaces/receipt_manager.rb +33 -0
- data/lib/onstomp/interfaces/subscription_manager.rb +48 -0
- data/lib/onstomp/interfaces/uri_configurable.rb +106 -0
- data/lib/onstomp/interfaces.rb +14 -0
- data/lib/onstomp/version.rb +13 -0
- data/lib/onstomp.rb +147 -0
- data/onstomp.gemspec +29 -0
- data/spec/onstomp/client_spec.rb +265 -0
- data/spec/onstomp/components/frame_headers_spec.rb +163 -0
- data/spec/onstomp/components/frame_spec.rb +144 -0
- data/spec/onstomp/components/nil_processor_spec.rb +32 -0
- data/spec/onstomp/components/scopes/header_scope_spec.rb +27 -0
- data/spec/onstomp/components/scopes/receipt_scope_spec.rb +33 -0
- data/spec/onstomp/components/scopes/transaction_scope_spec.rb +227 -0
- data/spec/onstomp/components/scopes_spec.rb +63 -0
- data/spec/onstomp/components/subscription_spec.rb +58 -0
- data/spec/onstomp/components/threaded_processor_spec.rb +92 -0
- data/spec/onstomp/components/uri_spec.rb +33 -0
- data/spec/onstomp/connections/base_spec.rb +349 -0
- data/spec/onstomp/connections/heartbeating_spec.rb +132 -0
- data/spec/onstomp/connections/serializers/stomp_1_0_spec.rb +50 -0
- data/spec/onstomp/connections/serializers/stomp_1_1_spec.rb +99 -0
- data/spec/onstomp/connections/serializers/stomp_1_spec.rb +104 -0
- data/spec/onstomp/connections/stomp_1_0_spec.rb +54 -0
- data/spec/onstomp/connections/stomp_1_1_spec.rb +137 -0
- data/spec/onstomp/connections/stomp_1_spec.rb +113 -0
- data/spec/onstomp/connections_spec.rb +135 -0
- data/spec/onstomp/interfaces/client_events_spec.rb +108 -0
- data/spec/onstomp/interfaces/connection_events_spec.rb +55 -0
- data/spec/onstomp/interfaces/event_manager_spec.rb +72 -0
- data/spec/onstomp/interfaces/frame_methods_spec.rb +109 -0
- data/spec/onstomp/interfaces/receipt_manager_spec.rb +53 -0
- data/spec/onstomp/interfaces/subscription_manager_spec.rb +64 -0
- data/spec/onstomp_spec.rb +15 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/custom_argument_matchers.rb +51 -0
- data/spec/support/frame_matchers.rb +88 -0
- data/spec/support/shared_frame_method_examples.rb +116 -0
- data/yard_extensions.rb +32 -0
- metadata +219 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
module OnStomp::Connections
|
|
5
|
+
describe Base do
|
|
6
|
+
let(:io) {
|
|
7
|
+
mock('io', :close => nil)
|
|
8
|
+
}
|
|
9
|
+
let(:client) {
|
|
10
|
+
mock('client', :dispatch_transmitted => nil,
|
|
11
|
+
:dispatch_received => nil)
|
|
12
|
+
}
|
|
13
|
+
let(:serializer) {
|
|
14
|
+
mock('serializer')
|
|
15
|
+
}
|
|
16
|
+
let(:connection) {
|
|
17
|
+
Base.new(io, client)
|
|
18
|
+
}
|
|
19
|
+
let(:frame) {
|
|
20
|
+
mock('frame')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe ".method_missing" do
|
|
24
|
+
it "should raise an unsupported command error if the method ends in _frame" do
|
|
25
|
+
lambda { connection.lame_frame }.should raise_error(OnStomp::UnsupportedCommandError)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should do the regular jazz for other missing methods" do
|
|
29
|
+
lambda { connection.lame_lame }.should raise_error(NameError)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe ".configure" do
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe ".connected?" do
|
|
38
|
+
it "should be connected if io is not closed" do
|
|
39
|
+
io.stub(:closed? => false)
|
|
40
|
+
connection.should be_connected
|
|
41
|
+
io.stub(:closed? => true)
|
|
42
|
+
connection.should_not be_connected
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe ".close" do
|
|
47
|
+
it "should close the socket if blocking is true" do
|
|
48
|
+
io.should_receive(:close)
|
|
49
|
+
connection.close true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should close the socket within single_io_write" do
|
|
53
|
+
io.should_receive :close
|
|
54
|
+
connection.close
|
|
55
|
+
connection.io_process_write
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe ".io_process" do
|
|
60
|
+
it "should invoke one io_process_read and one io_process_write" do
|
|
61
|
+
connection.should_receive :io_process_read
|
|
62
|
+
connection.should_receive :io_process_write
|
|
63
|
+
connection.io_process
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe ".write_frame_nonblock" do
|
|
68
|
+
before(:each) do
|
|
69
|
+
connection.stub(:serializer => serializer)
|
|
70
|
+
end
|
|
71
|
+
it "should serialize the frame and push it to the buffer" do
|
|
72
|
+
serializer.stub(:frame_to_bytes).with(frame).and_return('FRAME_SERIALIZED')
|
|
73
|
+
connection.should_receive(:push_write_buffer).with('FRAME_SERIALIZED', frame)
|
|
74
|
+
connection.write_frame_nonblock frame
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe ".(push|shift|unshift)_write_buffer" do
|
|
79
|
+
it "should not add to the buffer if it's closing" do
|
|
80
|
+
connection.close
|
|
81
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
82
|
+
connection.should_not_receive :io_process_write
|
|
83
|
+
connection.close true
|
|
84
|
+
end
|
|
85
|
+
it "should shift the first element off the write buffer" do
|
|
86
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
87
|
+
connection.push_write_buffer 'FRAME_SERIALIZED2', frame
|
|
88
|
+
connection.shift_write_buffer.should == ['FRAME_SERIALIZED', frame]
|
|
89
|
+
end
|
|
90
|
+
it "should put the data and frame at the beginning of the buffer" do
|
|
91
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
92
|
+
connection.unshift_write_buffer 'FRAME_SERIALIZED2', frame
|
|
93
|
+
connection.shift_write_buffer.should == ['FRAME_SERIALIZED2', frame]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe ".io_process_write" do
|
|
98
|
+
it "should not write if the buffer is empty" do
|
|
99
|
+
io.should_not_receive :write_nonblock
|
|
100
|
+
connection.io_process_write
|
|
101
|
+
end
|
|
102
|
+
it "should not write if the buffer has something but IO.select returns nil" do
|
|
103
|
+
IO.stub(:select => nil)
|
|
104
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
105
|
+
io.should_not_receive :write_nonblock
|
|
106
|
+
connection.io_process_write
|
|
107
|
+
end
|
|
108
|
+
it "should write to IO in a non-blocking fashion otherwise" do
|
|
109
|
+
connection.stub(:connected? => true)
|
|
110
|
+
IO.stub(:select => true)
|
|
111
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
112
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_return(16)
|
|
113
|
+
client.should_receive(:dispatch_transmitted).with(frame)
|
|
114
|
+
connection.io_process_write
|
|
115
|
+
end
|
|
116
|
+
it "should put back the remaining data" do
|
|
117
|
+
connection.stub(:connected? => true)
|
|
118
|
+
IO.stub(:select => true)
|
|
119
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
120
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_return(5)
|
|
121
|
+
connection.should_receive(:unshift_write_buffer).with('_SERIALIZED', frame)
|
|
122
|
+
connection.io_process_write
|
|
123
|
+
end
|
|
124
|
+
it "should put all the data back if EINTR is raised" do
|
|
125
|
+
connection.stub(:connected? => true)
|
|
126
|
+
IO.stub(:select => true)
|
|
127
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
128
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_raise(Errno::EINTR)
|
|
129
|
+
connection.should_receive(:unshift_write_buffer).with('FRAME_SERIALIZED', frame)
|
|
130
|
+
connection.io_process_write
|
|
131
|
+
end
|
|
132
|
+
it "should put all the data back if EAGAIN is raised" do
|
|
133
|
+
connection.stub(:connected? => true)
|
|
134
|
+
IO.stub(:select => true)
|
|
135
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
136
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_raise(Errno::EAGAIN)
|
|
137
|
+
connection.should_receive(:unshift_write_buffer).with('FRAME_SERIALIZED', frame)
|
|
138
|
+
connection.io_process_write
|
|
139
|
+
end
|
|
140
|
+
it "should put all the data back if EWOULDBLOCK is raised" do
|
|
141
|
+
connection.stub(:connected? => true)
|
|
142
|
+
IO.stub(:select => true)
|
|
143
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
144
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_raise(Errno::EWOULDBLOCK)
|
|
145
|
+
connection.should_receive(:unshift_write_buffer).with('FRAME_SERIALIZED', frame)
|
|
146
|
+
connection.io_process_write
|
|
147
|
+
end
|
|
148
|
+
it "should close the connection and re-raise if an EOFError is raised" do
|
|
149
|
+
connection.stub(:connected? => true)
|
|
150
|
+
IO.stub(:select => true)
|
|
151
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
152
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_raise(EOFError)
|
|
153
|
+
io.should_receive(:close)
|
|
154
|
+
lambda { connection.io_process_write }.should raise_error(EOFError)
|
|
155
|
+
end
|
|
156
|
+
it "should close the connection and re-raise if an IOError is raised" do
|
|
157
|
+
connection.stub(:connected? => true)
|
|
158
|
+
IO.stub(:select => true)
|
|
159
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
160
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_raise(IOError)
|
|
161
|
+
io.should_receive(:close)
|
|
162
|
+
lambda { connection.io_process_write }.should raise_error(IOError)
|
|
163
|
+
end
|
|
164
|
+
it "should close the connection and re-raise if an EOFError is raised" do
|
|
165
|
+
connection.stub(:connected? => true)
|
|
166
|
+
IO.stub(:select => true)
|
|
167
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
168
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_raise(SystemCallError.new('msg', 13))
|
|
169
|
+
io.should_receive(:close)
|
|
170
|
+
lambda { connection.io_process_write }.should raise_error(SystemCallError)
|
|
171
|
+
end
|
|
172
|
+
it "should re-raise on any other exception" do
|
|
173
|
+
triggered = false
|
|
174
|
+
connection.on_terminated { triggered = true }
|
|
175
|
+
connection.stub(:connected? => true)
|
|
176
|
+
IO.stub(:select => true)
|
|
177
|
+
connection.push_write_buffer 'FRAME_SERIALIZED', frame
|
|
178
|
+
io.should_receive(:close)
|
|
179
|
+
io.should_receive(:write_nonblock).with('FRAME_SERIALIZED').and_raise(Exception)
|
|
180
|
+
lambda { connection.io_process_write }.should raise_error(Exception)
|
|
181
|
+
triggered.should be_true
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
describe ".io_process_read" do
|
|
185
|
+
before(:each) do
|
|
186
|
+
connection.stub(:serializer => serializer)
|
|
187
|
+
end
|
|
188
|
+
it "should not read if the connection is not alive" do
|
|
189
|
+
connection.stub(:connected? => false)
|
|
190
|
+
io.should_not_receive :read_nonblock
|
|
191
|
+
connection.io_process_read
|
|
192
|
+
end
|
|
193
|
+
it "should not read if IO.select returns nil" do
|
|
194
|
+
connection.stub(:connected? => true)
|
|
195
|
+
IO.stub(:select => nil)
|
|
196
|
+
io.should_not_receive :read_nonblock
|
|
197
|
+
connection.io_process_read
|
|
198
|
+
end
|
|
199
|
+
it "should read IO in a non-blocking fashion otherwise" do
|
|
200
|
+
connection.stub(:connected? => true)
|
|
201
|
+
IO.stub(:select => true)
|
|
202
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_return('FRAME_SERIALIZED')
|
|
203
|
+
serializer.should_receive(:bytes_to_frame).with(['FRAME_SERIALIZED'])
|
|
204
|
+
connection.io_process_read
|
|
205
|
+
end
|
|
206
|
+
it "should dispatch and yield a frame if the serializer yields one" do
|
|
207
|
+
yielded = nil
|
|
208
|
+
connection.stub(:connected? => true)
|
|
209
|
+
IO.stub(:select => true)
|
|
210
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_return('FRAME_SERIALIZED')
|
|
211
|
+
serializer.should_receive(:bytes_to_frame).with(['FRAME_SERIALIZED']).and_yield(frame)
|
|
212
|
+
client.should_receive(:dispatch_received).with(frame)
|
|
213
|
+
connection.io_process_read do |f|
|
|
214
|
+
yielded = f
|
|
215
|
+
end
|
|
216
|
+
yielded.should == frame
|
|
217
|
+
end
|
|
218
|
+
it "should not raise an error if EINTR is raised" do
|
|
219
|
+
connection.stub(:connected? => true)
|
|
220
|
+
IO.stub(:select => true)
|
|
221
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_raise(Errno::EINTR)
|
|
222
|
+
lambda { connection.io_process_read }.should_not raise_error
|
|
223
|
+
end
|
|
224
|
+
it "should put all the data back if EAGAIN is raised" do
|
|
225
|
+
connection.stub(:connected? => true)
|
|
226
|
+
IO.stub(:select => true)
|
|
227
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_raise(Errno::EAGAIN)
|
|
228
|
+
lambda { connection.io_process_read }.should_not raise_error
|
|
229
|
+
end
|
|
230
|
+
it "should put all the data back if EWOULDBLOCK is raised" do
|
|
231
|
+
connection.stub(:connected? => true)
|
|
232
|
+
IO.stub(:select => true)
|
|
233
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_raise(Errno::EWOULDBLOCK)
|
|
234
|
+
lambda { connection.io_process_read }.should_not raise_error
|
|
235
|
+
end
|
|
236
|
+
it "should close the connection and not raise error if an EOFError is raised?" do
|
|
237
|
+
connection.stub(:connected? => true)
|
|
238
|
+
IO.stub(:select => true)
|
|
239
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_raise(EOFError)
|
|
240
|
+
io.should_receive(:close)
|
|
241
|
+
lambda { connection.io_process_read }.should_not raise_error
|
|
242
|
+
end
|
|
243
|
+
it "should close the connection and re-raise if an IOError is raised" do
|
|
244
|
+
connection.stub(:connected? => true)
|
|
245
|
+
IO.stub(:select => true)
|
|
246
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_raise(IOError)
|
|
247
|
+
io.should_receive(:close)
|
|
248
|
+
lambda { connection.io_process_read }.should raise_error(IOError)
|
|
249
|
+
end
|
|
250
|
+
it "should close the connection and re-raise if an EOFError is raised" do
|
|
251
|
+
connection.stub(:connected? => true)
|
|
252
|
+
IO.stub(:select => true)
|
|
253
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_raise(SystemCallError.new('msg', 13))
|
|
254
|
+
io.should_receive(:close)
|
|
255
|
+
lambda { connection.io_process_read }.should raise_error(SystemCallError)
|
|
256
|
+
end
|
|
257
|
+
it "should re-raise on any other exception" do
|
|
258
|
+
triggered = false
|
|
259
|
+
connection.on_terminated { triggered = true }
|
|
260
|
+
connection.stub(:connected? => true)
|
|
261
|
+
IO.stub(:select => true)
|
|
262
|
+
io.should_receive(:close)
|
|
263
|
+
io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_raise(Exception)
|
|
264
|
+
lambda { connection.io_process_read }.should raise_error(Exception)
|
|
265
|
+
triggered.should be_true
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
describe ".connect" do
|
|
270
|
+
let(:headers) { [] }
|
|
271
|
+
let(:connect_frame) {
|
|
272
|
+
OnStomp::Components::Frame.new('CONNECT')
|
|
273
|
+
}
|
|
274
|
+
let(:connected_frame) {
|
|
275
|
+
OnStomp::Components::Frame.new('CONNECTED')
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
it "should raise an error if the first frame read is not CONNECTED" do
|
|
279
|
+
connection.should_receive(:connect_frame).and_return(connect_frame)
|
|
280
|
+
connection.should_receive(:write_frame_nonblock).with(connect_frame)
|
|
281
|
+
connection.should_receive(:io_process_write).and_yield(connect_frame)
|
|
282
|
+
connection.should_receive(:io_process_read).and_yield(connected_frame)
|
|
283
|
+
connected_frame.command = 'NOT CONNECTED'
|
|
284
|
+
lambda { connection.connect(client, *headers) }.should raise_error(OnStomp::ConnectFailedError)
|
|
285
|
+
end
|
|
286
|
+
it "should raise an error if the CONNECTED frame specifies an unsolicited version" do
|
|
287
|
+
connection.should_receive(:connect_frame).and_return(connect_frame)
|
|
288
|
+
connection.should_receive(:write_frame_nonblock).with(connect_frame)
|
|
289
|
+
connection.should_receive(:io_process_write).and_yield(connect_frame)
|
|
290
|
+
connection.should_receive(:io_process_read).and_yield(connected_frame)
|
|
291
|
+
connected_frame[:version] = '1.9'
|
|
292
|
+
client.stub(:versions => [ '1.0', '1.1' ])
|
|
293
|
+
lambda { connection.connect(client, *headers) }.should raise_error(OnStomp::UnsupportedProtocolVersionError)
|
|
294
|
+
end
|
|
295
|
+
it "should assume version 1.0 if no version header is set" do
|
|
296
|
+
connection.should_receive(:connect_frame).and_return(connect_frame)
|
|
297
|
+
connection.should_receive(:write_frame_nonblock).with(connect_frame)
|
|
298
|
+
connection.should_receive(:io_process_write).and_yield(connect_frame)
|
|
299
|
+
connection.should_receive(:io_process_read).and_yield(connected_frame)
|
|
300
|
+
client.stub(:versions => [ '1.0', '1.1' ])
|
|
301
|
+
connection.connect(client, *headers).should == ['1.0', connected_frame]
|
|
302
|
+
end
|
|
303
|
+
it "should return the CONNECTED version header if it's included" do
|
|
304
|
+
connection.should_receive(:connect_frame).and_return(connect_frame)
|
|
305
|
+
connection.should_receive(:write_frame_nonblock).with(connect_frame)
|
|
306
|
+
connection.should_receive(:io_process_write).and_yield(connect_frame)
|
|
307
|
+
connection.should_receive(:io_process_read).and_yield(connected_frame)
|
|
308
|
+
connected_frame[:version] = '2.3'
|
|
309
|
+
client.stub(:versions => [ '1.0', '2.3' ])
|
|
310
|
+
connection.connect(client, *headers).should == ['2.3', connected_frame]
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it "should trigger :on_died once, if the connection was up but is no longer connected" do
|
|
314
|
+
connection.stub(:connect_frame => connect_frame,
|
|
315
|
+
:write_frame_nonblock => connect_frame)
|
|
316
|
+
client.stub(:versions => ['1.0', '1.1'])
|
|
317
|
+
connection.stub(:io_process_write).and_yield(connect_frame)
|
|
318
|
+
connection.stub(:io_process_read).and_yield(connected_frame)
|
|
319
|
+
triggered = 0
|
|
320
|
+
connection.on_died { |cl, cn| triggered += 1 }
|
|
321
|
+
connection.connect client
|
|
322
|
+
connection.stub(:io_process_write => nil, :io_process_read => nil)
|
|
323
|
+
connection.stub(:connected? => false)
|
|
324
|
+
connection.io_process
|
|
325
|
+
connection.io_process
|
|
326
|
+
triggered.should == 1
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
describe ".configure" do
|
|
331
|
+
let(:client_bindings) {
|
|
332
|
+
{ :died => nil, :established => nil }
|
|
333
|
+
}
|
|
334
|
+
it "should set its version parameter based on the supplied CONNECTED frame" do
|
|
335
|
+
frame.stub(:header?).with(:version).and_return(true)
|
|
336
|
+
frame.stub(:[]).with(:version).and_return('9.x')
|
|
337
|
+
connection.should_receive(:install_bindings_from_client).with(client_bindings)
|
|
338
|
+
connection.configure frame, client_bindings
|
|
339
|
+
connection.version.should == '9.x'
|
|
340
|
+
end
|
|
341
|
+
it "should set its version parameter to 1.0 if the header is not present" do
|
|
342
|
+
frame.stub(:header?).with(:version).and_return(false)
|
|
343
|
+
connection.should_receive(:install_bindings_from_client).with(client_bindings)
|
|
344
|
+
connection.configure frame, client_bindings
|
|
345
|
+
connection.version.should == '1.0'
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
module OnStomp::Connections
|
|
5
|
+
describe Heartbeating do
|
|
6
|
+
let(:connection) {
|
|
7
|
+
mock('connection').tap do |m|
|
|
8
|
+
m.extend Heartbeating
|
|
9
|
+
end
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe ".configure_heartbeating" do
|
|
13
|
+
it "should use 0 for client beats if either side is zero" do
|
|
14
|
+
connection.configure_heartbeating([0,500], [100, 300])
|
|
15
|
+
connection.heartbeating.first.should == 0
|
|
16
|
+
connection.configure_heartbeating([300,500], [100, 0])
|
|
17
|
+
connection.heartbeating.first.should == 0
|
|
18
|
+
end
|
|
19
|
+
it "should use 0 for broker beats if either side is zero" do
|
|
20
|
+
connection.configure_heartbeating([500,0], [100, 300])
|
|
21
|
+
connection.heartbeating.last.should == 0
|
|
22
|
+
connection.configure_heartbeating([300,100], [0, 500])
|
|
23
|
+
connection.heartbeating.last.should == 0
|
|
24
|
+
end
|
|
25
|
+
it "should use the maximums for heartbeating" do
|
|
26
|
+
connection.configure_heartbeating([500,700], [100, 300])
|
|
27
|
+
connection.heartbeating.should == [500, 700]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe ".pulse?" do
|
|
32
|
+
it "should not have a pulse if it has no client pulse" do
|
|
33
|
+
connection.stub(:client_pulse? => false, :broker_pulse? => true)
|
|
34
|
+
connection.pulse?.should be_false
|
|
35
|
+
end
|
|
36
|
+
it "should not have a pulse if it has no broker pulse" do
|
|
37
|
+
connection.stub(:client_pulse? => true, :broker_pulse? => false)
|
|
38
|
+
connection.pulse?.should be_false
|
|
39
|
+
end
|
|
40
|
+
it "should have a pulse if it has both a client and broker pulse" do
|
|
41
|
+
connection.stub(:client_pulse? => true, :broker_pulse? => true)
|
|
42
|
+
connection.pulse?.should be_true
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe ".heartbeat_client_limit" do
|
|
47
|
+
it "should be 110% of a positive client heartbeat value" do
|
|
48
|
+
connection.stub(:heartbeating => [64, 0])
|
|
49
|
+
connection.heartbeat_client_limit.should == 70.4
|
|
50
|
+
end
|
|
51
|
+
it "should be 0 if the client heartbeat value is 0" do
|
|
52
|
+
connection.stub(:heartbeating => [0, 90])
|
|
53
|
+
connection.heartbeat_client_limit.should == 0
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe ".heartbeat_broker_limit" do
|
|
58
|
+
it "should be 110% of a positive broker heartbeat value" do
|
|
59
|
+
connection.stub(:heartbeating => [0, 32])
|
|
60
|
+
connection.heartbeat_broker_limit.should == 35.2
|
|
61
|
+
end
|
|
62
|
+
it "should be 0 if the broker heartbeat value is 0" do
|
|
63
|
+
connection.stub(:heartbeating => [90, 0])
|
|
64
|
+
connection.heartbeat_broker_limit.should == 0
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe ".duration_since_transmitted" do
|
|
69
|
+
it "should be nil if last_transmitted_at is nil" do
|
|
70
|
+
connection.stub(:last_transmitted_at => nil)
|
|
71
|
+
connection.duration_since_transmitted.should be_nil
|
|
72
|
+
end
|
|
73
|
+
it "should be the difference between now and the last_transmitted_at in milliseconds" do
|
|
74
|
+
Time.stub(:now => 10)
|
|
75
|
+
connection.stub(:last_transmitted_at => 8.5)
|
|
76
|
+
connection.duration_since_transmitted.should == 1500
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe ".duration_since_received" do
|
|
81
|
+
it "should be nil if last_received_at is nil" do
|
|
82
|
+
connection.stub(:last_received_at => nil)
|
|
83
|
+
connection.duration_since_received.should be_nil
|
|
84
|
+
end
|
|
85
|
+
it "should be the difference between now and the last_received_at in milliseconds" do
|
|
86
|
+
Time.stub(:now => 10)
|
|
87
|
+
connection.stub(:last_received_at => 6)
|
|
88
|
+
connection.duration_since_received.should == 4000
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe ".client_pulse?" do
|
|
93
|
+
it "should be true if client heartbeating is disabled" do
|
|
94
|
+
connection.stub(:heartbeat_client_limit => 0)
|
|
95
|
+
connection.client_pulse?.should be_true
|
|
96
|
+
end
|
|
97
|
+
it "should be true if duration since transmitted is at most client limit" do
|
|
98
|
+
connection.stub(:heartbeat_client_limit => 10)
|
|
99
|
+
connection.stub(:duration_since_transmitted => 1)
|
|
100
|
+
connection.client_pulse?.should be_true
|
|
101
|
+
connection.stub(:heartbeat_client_limit => 10)
|
|
102
|
+
connection.stub(:duration_since_transmitted => 10)
|
|
103
|
+
connection.client_pulse?.should be_true
|
|
104
|
+
end
|
|
105
|
+
it "should be false if duration since transmitted is greater than client limit" do
|
|
106
|
+
connection.stub(:heartbeat_client_limit => 10)
|
|
107
|
+
connection.stub(:duration_since_transmitted => 11)
|
|
108
|
+
connection.client_pulse?.should be_false
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe ".broker_pulse?" do
|
|
113
|
+
it "should be true if broker heartbeating is disabled" do
|
|
114
|
+
connection.stub(:heartbeat_broker_limit => 0)
|
|
115
|
+
connection.broker_pulse?.should be_true
|
|
116
|
+
end
|
|
117
|
+
it "should be true if duration since transmitted is at most broker limit" do
|
|
118
|
+
connection.stub(:heartbeat_broker_limit => 10)
|
|
119
|
+
connection.stub(:duration_since_received => 1)
|
|
120
|
+
connection.broker_pulse?.should be_true
|
|
121
|
+
connection.stub(:heartbeat_broker_limit => 10)
|
|
122
|
+
connection.stub(:duration_since_received => 10)
|
|
123
|
+
connection.broker_pulse?.should be_true
|
|
124
|
+
end
|
|
125
|
+
it "should be false if duration since received is greater than broker limit" do
|
|
126
|
+
connection.stub(:heartbeat_broker_limit => 10)
|
|
127
|
+
connection.stub(:duration_since_received => 11)
|
|
128
|
+
connection.broker_pulse?.should be_false
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
module OnStomp::Connections::Serializers
|
|
5
|
+
describe Stomp_1_0 do
|
|
6
|
+
let(:serializer) { Stomp_1_0.new }
|
|
7
|
+
describe "ancestors" do
|
|
8
|
+
it "should be a kind of Stomp_1 serializer" do
|
|
9
|
+
serializer.should be_a_kind_of(OnStomp::Connections::Serializers::Stomp_1)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
describe ".frame_to_string" do
|
|
13
|
+
# So tests pass with Ruby 1.8.7, we need to get the headers in order
|
|
14
|
+
let(:frame_without_body) {
|
|
15
|
+
OnStomp::Components::Frame.new('COMMAND').tap do |f|
|
|
16
|
+
f[:header1] = 'value 1'
|
|
17
|
+
f[:header2] = 'value 2'
|
|
18
|
+
f["header\nwith:specials"] = "value\nwith\nspecials"
|
|
19
|
+
end
|
|
20
|
+
}
|
|
21
|
+
let(:frame_with_body) {
|
|
22
|
+
OnStomp::Components::Frame.new('COMMAND',{},"body of message").tap do |f|
|
|
23
|
+
f[:header1] = 'value 1'
|
|
24
|
+
f[:header2] = 'value 2'
|
|
25
|
+
f["header\nwith:specials"] = "value\nwith\nspecials"
|
|
26
|
+
end
|
|
27
|
+
}
|
|
28
|
+
it "should convert a frame to a string with escaped headers" do
|
|
29
|
+
serializer.frame_to_string(frame_without_body).should ==
|
|
30
|
+
"COMMAND\nheader1:value 1\nheader2:value 2\nheaderwithspecials:valuewithspecials\n\n\000"
|
|
31
|
+
end
|
|
32
|
+
it "should generate a content-length" do
|
|
33
|
+
serializer.frame_to_string(frame_with_body).should ==
|
|
34
|
+
"COMMAND\nheader1:value 1\nheader2:value 2\nheaderwithspecials:valuewithspecials\ncontent-length:15\n\nbody of message\000"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe ".split_header" do
|
|
39
|
+
it "should raise a malformed header error if the header line has no ':'" do
|
|
40
|
+
lambda {
|
|
41
|
+
serializer.split_header('header-name')
|
|
42
|
+
}.should raise_error(OnStomp::MalformedHeaderError)
|
|
43
|
+
end
|
|
44
|
+
it "should return a header name / value pair" do
|
|
45
|
+
serializer.split_header('header-name: some : value ').should ==
|
|
46
|
+
['header-name', ' some : value ']
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
module OnStomp::Connections::Serializers
|
|
5
|
+
describe Stomp_1_1 do
|
|
6
|
+
let(:serializer) { Stomp_1_1.new }
|
|
7
|
+
describe "ancestors" do
|
|
8
|
+
it "should be a kind of Stomp_1 serializer" do
|
|
9
|
+
serializer.should be_a_kind_of(OnStomp::Connections::Serializers::Stomp_1)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe ".frame_to_string" do
|
|
14
|
+
# So tests pass with Ruby 1.8.7, we need to get the headers in order
|
|
15
|
+
let(:frame_without_body) {
|
|
16
|
+
OnStomp::Components::Frame.new('COMMAND').tap do |f|
|
|
17
|
+
f[:header1] = 'value 1'
|
|
18
|
+
f[:header2] = 'value 2'
|
|
19
|
+
f["header\nwith:special \\characters"] = "value:with\\special\ncharacters"
|
|
20
|
+
end
|
|
21
|
+
}
|
|
22
|
+
let(:frame_with_body) {
|
|
23
|
+
OnStomp::Components::Frame.new('COMMAND',{},'body of message').tap do |f|
|
|
24
|
+
f[:header1] = 'value 1'
|
|
25
|
+
f[:header2] = 'value 2'
|
|
26
|
+
f["header\nwith:special \\characters"] = "value:with\\special\ncharacters"
|
|
27
|
+
end
|
|
28
|
+
}
|
|
29
|
+
it "should convert a frame to a string with escaped headers" do
|
|
30
|
+
serializer.frame_to_string(frame_without_body).should ==
|
|
31
|
+
"COMMAND\nheader1:value 1\nheader2:value 2\nheader\\nwith\\cspecial \\\\characters:value\\cwith\\\\special\\ncharacters\n\n\000"
|
|
32
|
+
end
|
|
33
|
+
it "should generate a content-length and content-type" do
|
|
34
|
+
expected_str = if RUBY_VERSION >= '1.9'
|
|
35
|
+
frame_with_body.body = frame_with_body.body.encode('ISO-8859-1')
|
|
36
|
+
"COMMAND\nheader1:value 1\nheader2:value 2\nheader\\nwith\\cspecial \\\\characters:value\\cwith\\\\special\\ncharacters\ncontent-type:text/plain;charset=ISO-8859-1\ncontent-length:15\n\nbody of message\000"
|
|
37
|
+
else
|
|
38
|
+
"COMMAND\nheader1:value 1\nheader2:value 2\nheader\\nwith\\cspecial \\\\characters:value\\cwith\\\\special\\ncharacters\ncontent-length:15\n\nbody of message\000"
|
|
39
|
+
end
|
|
40
|
+
serializer.frame_to_string(frame_with_body).should == expected_str
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe ".split_header" do
|
|
45
|
+
it "should raise a malformed header error if the header line has no ':'" do
|
|
46
|
+
lambda {
|
|
47
|
+
serializer.split_header('header-name')
|
|
48
|
+
}.should raise_error(OnStomp::MalformedHeaderError)
|
|
49
|
+
end
|
|
50
|
+
it "should raise an invalid escape sequence error given a bad escape sequence" do
|
|
51
|
+
lambda {
|
|
52
|
+
serializer.split_header('header-name:header\\rvalue')
|
|
53
|
+
}.should raise_error(OnStomp::InvalidHeaderEscapeSequenceError)
|
|
54
|
+
end
|
|
55
|
+
it "should raise an invalid escape sequence error given an incomplete escape sequence" do
|
|
56
|
+
lambda {
|
|
57
|
+
serializer.split_header('header-name\\:header\\cvalue')
|
|
58
|
+
}.should raise_error(OnStomp::InvalidHeaderEscapeSequenceError)
|
|
59
|
+
end
|
|
60
|
+
it "should return an unescaped header name/value pair" do
|
|
61
|
+
serializer.split_header('header\\cname\\\\is\\nme:value\\\\is\\nthis\\cguy').should ==
|
|
62
|
+
["header:name\\is\nme", "value\\is\nthis:guy"]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe ".prepare_parsed_frame" do
|
|
67
|
+
let(:iso8859_frame) {
|
|
68
|
+
OnStomp::Components::Frame.new('COMMAND', {
|
|
69
|
+
:header1 => 'value 1',
|
|
70
|
+
:header2 => 'value 2',
|
|
71
|
+
:'content-length' => '5',
|
|
72
|
+
:'content-type' => 'text/plain;charset=ISO-8859-1',
|
|
73
|
+
"header\nwith:special \\characters" => "value:with\\special\ncharacters"
|
|
74
|
+
}, "h\xEBllo")
|
|
75
|
+
}
|
|
76
|
+
let(:utf8_frame) {
|
|
77
|
+
OnStomp::Components::Frame.new('COMMAND', {
|
|
78
|
+
:header1 => 'value 1',
|
|
79
|
+
:header2 => 'value 2',
|
|
80
|
+
:'content-length' => '6',
|
|
81
|
+
:'content-type' => 'text/plain',
|
|
82
|
+
"header\nwith:special \\characters" => "value:with\\special\ncharacters"
|
|
83
|
+
}, "h\xC3\xABllo")
|
|
84
|
+
}
|
|
85
|
+
before(:each) do
|
|
86
|
+
if RUBY_VERSION > '1.9'
|
|
87
|
+
iso8859_frame.body.force_encoding('ASCII-8BIT')
|
|
88
|
+
utf8_frame.body.force_encoding('ASCII-8BIT')
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
it "should force a body encoding on the frame" do
|
|
92
|
+
serializer.prepare_parsed_frame(iso8859_frame)
|
|
93
|
+
serializer.prepare_parsed_frame(utf8_frame)
|
|
94
|
+
iso8859_frame.should have_body_encoding('ISO-8859-1')
|
|
95
|
+
utf8_frame.should have_body_encoding('UTF-8')
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|