klomp 0.0.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,329 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klomp::Connection do
4
+
5
+ Given(:data) { frame(:connected) }
6
+ Given(:server) { "127.0.0.1:61613" }
7
+ Given(:options) { { "login" => "admin", "passcode" => "password", "logger" => logger } }
8
+ Given(:socket) { double(TCPSocket, gets:data, write:nil, set_encoding:nil, close:nil) }
9
+ Given(:logger) { double("Logger", error:nil, warn:nil, info:nil, debug:nil).as_null_object }
10
+ Given(:subscriber) { double "subscriber", call:nil }
11
+ Given(:thread) { double Thread }
12
+
13
+ Given do
14
+ IO.stub!(:select).and_return([[socket], [socket]])
15
+ TCPSocket.stub!(:new).and_return socket
16
+ Thread.stub!(:new).and_return {|*args,&blk| thread.stub!(:block => blk); thread }
17
+ Klomp::Sentinel.stub!(new: double("sentinel"))
18
+ end
19
+
20
+ context "new" do
21
+
22
+ When { Klomp::Connection.new server, options }
23
+
24
+ Then do
25
+ socket.should have_received(:set_encoding).with('UTF-8').ordered
26
+ socket.should have_received(:write).with(frame(:connect)).ordered
27
+ socket.should have_received(:gets).with("\x00").ordered
28
+ end
29
+
30
+ end
31
+
32
+ context "new with vhost" do
33
+
34
+ Given(:server) { "virtual-host:127.0.0.1:61613" }
35
+
36
+ When { Klomp::Connection.new server, options }
37
+
38
+ Then do
39
+ TCPSocket.should have_received(:new).with("127.0.0.1", 61613)
40
+ socket.should have_received(:write).with(frame(:connect_vhost)).ordered
41
+ end
42
+
43
+ end
44
+
45
+ context "new with stomp:// URL" do
46
+
47
+ Given(:server) { "stomp://admin:password@127.0.0.1:61613?host=virtual-host" }
48
+ Given(:options) { {} }
49
+
50
+ When { Klomp::Connection.new server, options }
51
+
52
+ Then do
53
+ TCPSocket.should have_received(:new).with("127.0.0.1", 61613)
54
+ socket.should have_received(:write).with(frame(:connect_vhost)).ordered
55
+ end
56
+
57
+ end
58
+
59
+ context "new with connection error" do
60
+
61
+ Given(:data) { frame(:auth_error) }
62
+
63
+ When(:expect_connect) { expect { Klomp::Connection.new server, options } }
64
+
65
+ Then { expect_connect.to raise_error(Klomp::Error) }
66
+ end
67
+
68
+ context "publish" do
69
+
70
+ Given(:connection) { Klomp::Connection.new server, options }
71
+
72
+ When { connection.publish "/queue/greeting", "hello" }
73
+
74
+ Then { socket.should have_received(:write).with(frame(:greeting)) }
75
+
76
+ context "logs when logger level is debug" do
77
+
78
+ Given(:logger) { double("Logger").as_null_object.tap {|l| l.stub!(debug?: true) } }
79
+
80
+ Then { logger.should have_received(:debug) }
81
+
82
+ end
83
+
84
+ end
85
+
86
+ context "subscribe" do
87
+
88
+ Given!(:connection) { Klomp::Connection.new server, options }
89
+
90
+ context "writes the subscribe message" do
91
+
92
+ When { connection.subscribe "/queue/greeting", subscriber }
93
+
94
+ Then { socket.should have_received(:write).with(frame(:subscribe)) }
95
+
96
+ context "and logs when logger level is debug" do
97
+
98
+ Given(:logger) { double("Logger").as_null_object.tap {|l| l.stub!(debug?: true) } }
99
+
100
+ Then { logger.should have_received(:debug) }
101
+
102
+ end
103
+
104
+ end
105
+
106
+ context "called twice writes to the socket only once" do
107
+
108
+ When do
109
+ connection.subscribe "/queue/greeting", subscriber
110
+ connection.subscribe "/queue/greeting", double("another subscriber that replaces the first", call:nil)
111
+ end
112
+
113
+ Then { socket.should have_received(:write).with(frame(:subscribe)).once }
114
+
115
+ end
116
+
117
+ context "and accepts a block as the subscriber" do
118
+
119
+ When { connection.subscribe("/queue/foo") { true } }
120
+
121
+ Then { connection.subscriptions["/queue/foo"].call.should == true }
122
+
123
+ end
124
+
125
+ context "and accepts an object that responds to #call as the subscriber" do
126
+
127
+ When { connection.subscribe("/queue/foo", subscriber) }
128
+
129
+ Then { connection.subscriptions["/queue/foo"].should == subscriber }
130
+
131
+ end
132
+
133
+ context "and dispatches to the message callback" do
134
+
135
+ When do
136
+ connection.subscribe "/queue/greeting", subscriber
137
+ socket.stub!(:gets).and_return frame(:message)
138
+ connection.send :close!
139
+ thread.block.call
140
+ end
141
+
142
+ Then do
143
+ subscriber.should have_received(:call).with(an_instance_of(Klomp::Frames::Message))
144
+ end
145
+
146
+ end
147
+
148
+ context "does not dispatch if an error frame was read" do
149
+
150
+ When do
151
+ connection.subscribe "/queue/greeting", subscriber
152
+ socket.stub!(:gets).and_return frame(:error)
153
+ connection.send :close!
154
+ thread.block.call
155
+ end
156
+
157
+ Then { subscriber.should_not have_received(:call) }
158
+
159
+ Then { logger.should have_received(:warn) }
160
+
161
+ end
162
+
163
+ context "fails if neither a subscriber nor a block is given" do
164
+
165
+ When(:expect_subscribe) { expect { connection.subscribe("/queue/greeting") } }
166
+
167
+ Then { expect_subscribe.to raise_error(Klomp::Error) }
168
+
169
+ end
170
+
171
+ context "fails if the subscriber does not respond to #call" do
172
+
173
+ When(:expect_subscribe) { expect { connection.subscribe("/queue/greeting", double("subscriber")) } }
174
+
175
+ Then { expect_subscribe.to raise_error(Klomp::Error) }
176
+
177
+ end
178
+
179
+ context "subscriptions" do
180
+
181
+ context "is not empty after subscribing" do
182
+
183
+ When { connection.subscribe("/queue/greeting") { true } }
184
+
185
+ Then { connection.subscriptions.length.should == 1 }
186
+
187
+ context "and empty after unsubscribing" do
188
+
189
+ When { connection.unsubscribe("/queue/greeting") }
190
+
191
+ Then { connection.subscriptions.length.should == 0 }
192
+
193
+ end
194
+
195
+ end
196
+
197
+ end
198
+
199
+ end
200
+
201
+ context "unsubscribe" do
202
+
203
+ Given(:connection) { Klomp::Connection.new server, options }
204
+
205
+ Given { connection.subscriptions["/queue/greeting"] = double "subscribers" }
206
+
207
+ When { connection.unsubscribe "/queue/greeting" }
208
+
209
+ Then { socket.should have_received(:write).with(frame(:unsubscribe)) }
210
+
211
+ end
212
+
213
+ context "disconnect" do
214
+
215
+ Given!(:connection) { Klomp::Connection.new server, options }
216
+
217
+ When { connection.disconnect }
218
+
219
+ Then do
220
+ socket.should have_received(:write).with(frame(:disconnect))
221
+ socket.should have_received(:close)
222
+ end
223
+
224
+ context "makes connection useless (raises error)" do
225
+
226
+ Then { expect { connection.publish "/queue/greeting", "hello" }.to raise_error(Klomp::Error) }
227
+
228
+ end
229
+
230
+ end
231
+
232
+ context "socket error on write causes connection to be disconnected" do
233
+
234
+ Given!(:connection) { Klomp::Connection.new server, options }
235
+ Given do
236
+ socket.stub!(:write).and_raise SystemCallError.new("some socket error")
237
+ end
238
+
239
+ When(:expect_publish) { expect { connection.publish "/queue/greeting", "hello" } }
240
+
241
+ Then do
242
+ expect_publish.to raise_error(SystemCallError)
243
+ connection.should_not be_connected
244
+ end
245
+
246
+ context "and subsequent calls raise Klomp::Error" do
247
+
248
+ Then do
249
+ expect_publish.to raise_error(SystemCallError)
250
+ expect_publish.to raise_error(Klomp::Error)
251
+ end
252
+
253
+ end
254
+
255
+ context "and starts reconnect sentinel" do
256
+
257
+ Then do
258
+ expect_publish.to raise_error(SystemCallError)
259
+ Klomp::Sentinel.should have_received(:new).with(connection)
260
+ end
261
+
262
+ end
263
+
264
+ end
265
+
266
+ context "socket error on read causes connection to be disconnected" do
267
+
268
+ Given!(:connection) { Klomp::Connection.new server, options }
269
+
270
+ Given do
271
+ thread.stub!(:raise).and_return {|e| raise e }
272
+ socket.stub!(:gets).and_raise SystemCallError.new("some socket error")
273
+ end
274
+
275
+ When do
276
+ connection.subscribe "/queue/greeting", subscriber
277
+ thread.block.call
278
+ end
279
+
280
+ Then { connection.should_not be_connected }
281
+
282
+ end
283
+
284
+ context "reconnect" do
285
+
286
+ Given!(:connection) { Klomp::Connection.new server, options }
287
+
288
+ context "creates new socket" do
289
+
290
+ Given { connection.disconnect }
291
+
292
+ When { connection.reconnect }
293
+
294
+ Then { connection.should be_connected }
295
+
296
+ Then { logger.should have_received(:warn) }
297
+
298
+ end
299
+
300
+ context "has no effect if connection is already connected" do
301
+
302
+ Given { socket.messages_received.clear }
303
+
304
+ When { connection.reconnect }
305
+
306
+ Then { connection.should be_connected }
307
+
308
+ Then { socket.should_not have_received(:write) }
309
+
310
+ end
311
+
312
+ context "re-subscribes all subscriptions" do
313
+
314
+ Given do
315
+ connection.subscribe "/queue/greeting", subscriber
316
+ thread.stub!(:raise)
317
+ connection.disconnect
318
+ socket.messages_received.clear
319
+ end
320
+
321
+ When { connection.reconnect }
322
+
323
+ Then { socket.should have_received(:write).with(frame(:subscribe)) }
324
+
325
+ end
326
+
327
+ end
328
+
329
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klomp::Frames do
4
+
5
+ context "CONNECT" do
6
+
7
+ Given(:options) { {'login' => 'admin', 'passcode' => 'password', 'host' => '127.0.0.1'} }
8
+
9
+ When(:connect) { Klomp::Frames::Connect.new(options).to_s }
10
+
11
+ Then { connect.should == frame(:connect) }
12
+
13
+ end
14
+
15
+ context "CONNECTED" do
16
+
17
+ When(:connected) { Klomp::Frames::Connected.new frame(:connected) }
18
+
19
+ Then { connected.headers['version'].should == "1.1" }
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klomp::Sentinel do
4
+
5
+ Given(:connection) { double "Connection", connected?:false, reconnect:nil }
6
+ Given(:sentinel) { Klomp::Sentinel.new connection }
7
+ Given(:thread) { double Thread }
8
+ Given do
9
+ Thread.stub!(:new).and_return {|*args,&blk| thread.stub!(:block => blk); thread }
10
+ end
11
+
12
+ context "does nothing if the connection is already connected" do
13
+
14
+ Given { connection.stub!(connected?: true) }
15
+
16
+ When { sentinel }
17
+
18
+ Then { Thread.should_not have_received(:new) }
19
+
20
+ end
21
+
22
+ context "reconnects the connection" do
23
+
24
+ When { sentinel ; thread.block.call }
25
+
26
+ Then { connection.should have_received(:reconnect) }
27
+
28
+ end
29
+
30
+ context "does fibonacci backoff if reconnection fails" do
31
+
32
+ Given(:number_of_reconnects) { 7 }
33
+ Given do
34
+ count = 0
35
+ connection.stub!(:reconnect).and_return { count += 1; raise Klomp::Error if count < number_of_reconnects }
36
+ end
37
+
38
+ When do
39
+ sentinel.stub!(:sleep)
40
+ thread.block.call
41
+ end
42
+
43
+ Then do
44
+ connection.should have_received(:reconnect).exactly(number_of_reconnects).times
45
+ end
46
+
47
+ Then do
48
+ sentinel.should have_received(:sleep).with(1).twice
49
+ sentinel.should have_received(:sleep).with(2)
50
+ sentinel.should have_received(:sleep).with(3)
51
+ sentinel.should have_received(:sleep).with(5)
52
+ sentinel.should have_received(:sleep).with(8)
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+
3
+ describe Klomp do
4
+
5
+ Given(:servers) { ["127.0.0.1:61613", "127.0.0.1:67673"] }
6
+ Given(:connections) { Hash[*servers.map {|s| [s, double("connection #{s}")] }.flatten] }
7
+ Given { Klomp::Connection.stub!(:new).and_return {|s| connections[s] } }
8
+ Given(:klomp) { Klomp.new servers }
9
+
10
+ context "new" do
11
+
12
+ context "raises ArgumentError" do
13
+
14
+ context "when created with no arguments" do
15
+
16
+ When(:expect_new) { expect { Klomp.new } }
17
+
18
+ Then { expect_new.to raise_error(ArgumentError) }
19
+
20
+ end
21
+
22
+ context "when created with an empty array" do
23
+
24
+ When(:expect_new) { expect { Klomp.new([]) } }
25
+
26
+ Then { expect_new.to raise_error(ArgumentError) }
27
+
28
+ end
29
+
30
+ end
31
+
32
+ context "creates a Klomp::Connection for each server" do
33
+
34
+ Given { Klomp::Connection.stub!(:new).and_return double(Klomp::Connection) }
35
+
36
+ When(:klomp) { Klomp.new servers }
37
+
38
+ Then { servers.each {|s| Klomp::Connection.should have_received(:new).with(s, {}) } }
39
+
40
+ end
41
+
42
+ end
43
+
44
+ context "publish" do
45
+
46
+ context "calls publish on one of the connections" do
47
+
48
+ Given do
49
+ connections.values.each do |conn|
50
+ conn.stub!(:connected? => false)
51
+ conn.stub!(:publish).and_return { conn.stub!(:connected? => true) }
52
+ end
53
+ end
54
+
55
+ When { klomp.publish "/queue/greeting", "hello" }
56
+
57
+ Then { connections.values.select {|conn| conn.connected? }.length.should == 1 }
58
+
59
+ Then { connections.values.detect {|conn| conn.connected? }.
60
+ should have_received(:publish).with("/queue/greeting", "hello", {}) }
61
+
62
+ end
63
+
64
+ context "calls publish on another connection if the first one fails" do
65
+
66
+ Given do
67
+ first_exception = false
68
+ connections.values.each do |conn|
69
+ conn.stub!(:connected? => false)
70
+ conn.stub!(:publish).and_return do
71
+ if first_exception
72
+ conn.stub!(:connected? => true)
73
+ else
74
+ first_exception = true
75
+ raise Klomp::Error.new
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ When { klomp.publish "/queue/greeting", "hello" }
82
+
83
+ Then { connections.values.select {|conn| conn.connected? }.length.should == 1 }
84
+
85
+ Then { connections.values.detect {|conn| conn.connected? }.
86
+ should have_received(:publish).with("/queue/greeting", "hello", {}) }
87
+
88
+ end
89
+
90
+ context "raises an exception if all connections failed" do
91
+
92
+ Given { connections.values.each {|conn| conn.stub!(:publish).and_raise(Klomp::Error.new) } }
93
+
94
+ When(:expect_publish) { expect { klomp.publish "/queue/greeting", "hello" } }
95
+
96
+ Then do
97
+ expect_publish.to raise_error(Klomp::Error)
98
+ connections.values.each {|conn| conn.should have_received(:publish) }
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+
105
+ context "subscribe" do
106
+
107
+ context "calls subscribe on all of the servers" do
108
+
109
+ Given { connections.values.each {|conn| conn.stub!(:subscribe) } }
110
+
111
+ When { klomp.subscribe("/queue/greeting") { true } }
112
+
113
+ Then { connections.values.each {|conn| conn.should have_received(:subscribe).with("/queue/greeting", nil) } }
114
+
115
+ end
116
+
117
+ context "fails if any subscribe calls fail" do
118
+
119
+ Given { connections.values.each {|conn| conn.stub!(:subscribe).and_raise(Klomp::Error.new) } }
120
+
121
+ When(:expect_publish) { expect { klomp.subscribe("/queue/greeting") { true } } }
122
+
123
+ Then { expect_publish.to raise_error(Klomp::Error) }
124
+
125
+ end
126
+
127
+ end
128
+
129
+ context "unsubscribe" do
130
+
131
+ context "calls unsubscribe on all the servers" do
132
+
133
+ Given { connections.values.each {|conn| conn.stub!(:unsubscribe) } }
134
+
135
+ When { klomp.unsubscribe("/queue/greeting") }
136
+
137
+ Then { connections.values.each {|conn| conn.should have_received(:unsubscribe).with("/queue/greeting") } }
138
+
139
+ end
140
+
141
+ context "calls unsubscribe on all the servers even if the unsubscribe errs" do
142
+
143
+ Given { connections.values.each {|conn| conn.stub!(:unsubscribe).and_raise Klomp::Error.new } }
144
+
145
+ When { klomp.unsubscribe("/queue/greeting") }
146
+
147
+ Then { connections.values.each {|conn| conn.should have_received(:unsubscribe).with("/queue/greeting") } }
148
+
149
+ end
150
+
151
+ end
152
+
153
+ context "disconnect" do
154
+
155
+ context "disconnects all the servers" do
156
+
157
+ Given { connections.values.each {|conn| conn.stub!(:disconnect) } }
158
+
159
+ When { klomp.disconnect }
160
+
161
+ Then { connections.values.each {|conn| conn.should have_received(:disconnect) } }
162
+
163
+ end
164
+
165
+ end
166
+
167
+ end