tuber 0.0.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,627 @@
1
+ # frozen_string_literal: true
2
+
3
+ # test/connection_test.rb
4
+
5
+ require File.expand_path('../test_helper', __FILE__)
6
+
7
+ describe Tuber::Connection do
8
+
9
+ describe 'for #new' do
10
+ before do
11
+ @host = 'localhost'
12
+ @bc = Tuber::Connection.new(@host)
13
+ end
14
+
15
+ it "should store address, host and port" do
16
+ assert_equal 'localhost', @bc.address
17
+ assert_equal 'localhost', @bc.host
18
+ assert_equal 11300, @bc.port
19
+ end
20
+
21
+ it "should init connection" do
22
+ assert_kind_of TCPSocket, @bc.connection
23
+ if @bc.connection.peeraddr[0] == 'AF_INET'
24
+ assert_equal '127.0.0.1', @bc.connection.peeraddr[3]
25
+ else
26
+ assert_equal 'AF_INET6', @bc.connection.peeraddr[0]
27
+ assert_equal '::1', @bc.connection.peeraddr[3]
28
+ end
29
+ assert_equal 11300, @bc.connection.peeraddr[1]
30
+ end
31
+
32
+ it "should raise on invalid connection" do
33
+ assert_raises(Tuber::NotConnected) { Tuber::Connection.new("localhost:8544") }
34
+ end
35
+
36
+ it "should support array connection to single connection" do
37
+ @bc2 = Tuber::Connection.new([@host])
38
+ assert_equal 'localhost', @bc.address
39
+ assert_equal 'localhost', @bc.host
40
+ assert_equal 11300, @bc.port
41
+ end
42
+
43
+ it "should use TUBER_URL over BEANSTALKD_URL from env" do
44
+ ENV['TUBER_URL'] = tuber_address.to_s
45
+ ENV['BEANSTALKD_URL'] = 'unreachable.invalid:99999'
46
+ bc = Tuber::Connection.new(nil)
47
+ assert_equal tuber_address.to_s, bc.address
48
+ ensure
49
+ ENV.delete('TUBER_URL')
50
+ ENV.delete('BEANSTALKD_URL')
51
+ end
52
+
53
+ it "should fall back to BEANSTALKD_URL from env" do
54
+ ENV['BEANSTALKD_URL'] = tuber_address.to_s
55
+ bc = Tuber::Connection.new(nil)
56
+ assert_equal tuber_address.to_s, bc.address
57
+ ensure
58
+ ENV.delete('BEANSTALKD_URL')
59
+ end
60
+ end # new
61
+
62
+ describe 'for timeout configuration' do
63
+ after do
64
+ Tuber.configure do |config|
65
+ config.connect_timeout = nil
66
+ config.resolv_timeout = nil
67
+ config.read_timeout = nil
68
+ config.write_timeout = nil
69
+ end
70
+ end
71
+
72
+ it "should pass connect_timeout and resolv_timeout to TCPSocket.new on Ruby >= 3.0" do
73
+ skip("connect_timeout/resolv_timeout kwargs require Ruby >= 3.0") if RUBY_VERSION < "3.0"
74
+
75
+ Tuber.configure do |config|
76
+ config.connect_timeout = 2
77
+ config.resolv_timeout = 2
78
+ end
79
+
80
+ real_socket = TCPSocket.new('localhost', 11300)
81
+ TCPSocket.expects(:new).with('localhost', 11300, connect_timeout: 2, resolv_timeout: 2).returns(real_socket)
82
+
83
+ bc = Tuber::Connection.new('localhost')
84
+ assert_kind_of TCPSocket, bc.connection
85
+ end
86
+
87
+ it "should ignore connect_timeout and resolv_timeout on Ruby < 3.0" do
88
+ skip("connect_timeout/resolv_timeout kwargs are supported on Ruby >= 3.0") if RUBY_VERSION >= "3.0"
89
+
90
+ Tuber.configure do |config|
91
+ config.connect_timeout = 2
92
+ config.resolv_timeout = 4
93
+ end
94
+
95
+ real_socket = TCPSocket.new('localhost', 11300)
96
+ TCPSocket.expects(:new).with('localhost', 11300).returns(real_socket)
97
+
98
+ bc = Tuber::Connection.new('localhost')
99
+ assert_same real_socket, bc.connection
100
+ end
101
+
102
+ it "should set SO_RCVTIMEO and SO_SNDTIMEO when read/write timeouts are configured" do
103
+ Tuber.configure do |config|
104
+ config.read_timeout = 3
105
+ config.write_timeout = 5
106
+ end
107
+
108
+ bc = Tuber::Connection.new('localhost')
109
+ rcv = bc.connection.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO).unpack('l_l_')[0]
110
+ snd = bc.connection.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO).unpack('l_l_')[0]
111
+ assert_equal 3, rcv
112
+ assert_equal 5, snd
113
+ end
114
+
115
+ end # timeout configuration
116
+
117
+ describe 'for #transmit' do
118
+ before do
119
+ @host = 'localhost'
120
+ @bc = Tuber::Connection.new(@host)
121
+ end
122
+
123
+ it "should return yaml loaded response" do
124
+ res = @bc.transmit 'stats'
125
+ refute_nil res[:body]['current-connections']
126
+ assert_equal 'OK', res[:status]
127
+ end
128
+
129
+ it "should return id" do
130
+ res = @bc.transmit "put 0 0 100 1\r\nX"
131
+ assert res[:id]
132
+ assert_equal 'INSERTED', res[:status]
133
+ end
134
+
135
+ it "should support dashes in response" do
136
+ res = @bc.transmit "use foo-bar\r\n"
137
+ assert_equal 'USING', res[:status]
138
+ assert_equal 'foo-bar', res[:id]
139
+ end
140
+
141
+ it "should pass crlf through without changing its length" do
142
+ res = @bc.transmit "put 0 0 100 2\r\n\r\n"
143
+ assert_equal 'INSERTED', res[:status]
144
+ end
145
+
146
+ it "should handle *any* byte value without changing length" do
147
+ res = @bc.transmit "put 0 0 100 256\r\n"+(0..255).to_a.pack("c*")
148
+ assert_equal 'INSERTED', res[:status]
149
+ end
150
+
151
+ it "should retry idempotent command with success after one connection failure" do
152
+ TCPSocket.any_instance.expects(:readline).times(2).
153
+ raises(EOFError.new).then.
154
+ returns("USING foo\n")
155
+
156
+ res = @bc.transmit "use foo\r\n"
157
+ assert_equal 'USING', res[:status]
158
+ end
159
+
160
+ it "should not retransmit delete after a connection failure" do
161
+ TCPSocket.any_instance.expects(:readline).times(1).raises(EOFError.new)
162
+
163
+ assert_raises(EOFError) { @bc.transmit "delete 56\r\n" }
164
+ end
165
+
166
+ it "should not retransmit put after a connection failure" do
167
+ TCPSocket.any_instance.expects(:readline).times(1).raises(Errno::ECONNRESET.new)
168
+
169
+ assert_raises(Errno::ECONNRESET) { @bc.transmit "put 0 0 100 4\r\ndata" }
170
+ end
171
+
172
+ it "heals the connection after a non-retransmitted failure" do
173
+ $eof_once = false
174
+ TCPSocket.prepend(Module.new do
175
+ def readline
176
+ if $eof_once == false
177
+ $eof_once = true
178
+ raise EOFError
179
+ end
180
+ super
181
+ end
182
+ end)
183
+
184
+ assert_raises(EOFError) { @bc.transmit "delete 56\r\n" }
185
+
186
+ # The reconnect still happened, so the next command works.
187
+ res = @bc.transmit "use foo"
188
+ assert_equal 'USING', res[:status]
189
+ ensure
190
+ $eof_once = true
191
+ end
192
+
193
+ it "should fail after exceeding retries with DrainingError" do
194
+ TCPSocket.any_instance.expects(:readline).times(3).
195
+ raises(Tuber::UnexpectedResponse.from_status("DRAINING", "delete 56"))
196
+
197
+ assert_raises(Tuber::DrainingError) { @bc.transmit "delete 56\r\n" }
198
+ end
199
+
200
+ it "should fail after exceeding reconnect max retries" do
201
+ # next connection attempts should fail
202
+ TCPSocket.stubs(:new).times(3).raises(Errno::ECONNREFUSED.new)
203
+ TCPSocket.any_instance.stubs(:readline).times(1).raises(EOFError.new)
204
+
205
+ assert_raises(Tuber::NotConnected) { @bc.transmit "delete 56\r\n" }
206
+ end
207
+
208
+ it "tubes_watched are restored after reconnect" do
209
+ client = Tuber.new('127.0.0.1:11300')
210
+ client.tubes.watch! "another"
211
+
212
+ $called = false
213
+ TCPSocket.prepend Module.new {
214
+ def readline
215
+ if !$called
216
+ $called = true
217
+ raise EOFError
218
+ end
219
+
220
+ super
221
+ end
222
+ }
223
+
224
+ assert_equal %w[another], client.tubes.watched.map(&:name)
225
+ ensure
226
+ # Leave the prepended module inert: with $called reset to nil it would
227
+ # fire one more EOFError at an arbitrary later readline, which a
228
+ # non-retransmitted command (delete/put) now surfaces as a failure.
229
+ $called = true
230
+ end
231
+
232
+ it "replays tube weights and reserve mode after reconnect" do
233
+ require_tuber!
234
+ client = Tuber.new(tuber_address)
235
+ client.tubes.watch("reconn_heavy", weight: 8)
236
+ client.tubes.watch("reconn_light", weight: 1)
237
+ client.tubes.ignore("default")
238
+ client.tubes.reserve_mode(:weighted)
239
+
240
+ $replayed = []
241
+ $eof_pending = true
242
+ TCPSocket.prepend Module.new {
243
+ def write(data)
244
+ $replayed << data.to_s if $replayed
245
+ super
246
+ end
247
+
248
+ def readline
249
+ if $eof_pending
250
+ $eof_pending = false
251
+ raise EOFError
252
+ end
253
+
254
+ super
255
+ end
256
+ }
257
+
258
+ client.connection.transmit("list-tubes-watched")
259
+ sent = $replayed
260
+
261
+ assert_includes sent, "watch reconn_heavy 8\r\n"
262
+ assert_includes sent, "watch reconn_light 1\r\n"
263
+ assert_includes sent, "reserve-mode weighted\r\n"
264
+ # Ordering matters: watch, then drop default, then set the mode.
265
+ assert sent.index("watch reconn_heavy 8\r\n") < sent.index("ignore default\r\n"),
266
+ "expected the watches to be replayed before 'ignore default'"
267
+ assert sent.index("ignore default\r\n") < sent.index("reserve-mode weighted\r\n"),
268
+ "expected 'reserve-mode' to be replayed after the tubes are watched"
269
+ ensure
270
+ # Leave the prepended module inert for the rest of the suite.
271
+ $eof_pending = false
272
+ $replayed = nil
273
+ client.close if client
274
+ end
275
+
276
+ it "keeps watching default across a reconnect when default is watched" do
277
+ client = Tuber.new(tuber_address)
278
+ client.tubes.watch("reconn_extra")
279
+
280
+ $replayed = []
281
+ $eof_pending = true
282
+ TCPSocket.prepend Module.new {
283
+ def write(data)
284
+ $replayed << data.to_s if $replayed
285
+ super
286
+ end
287
+
288
+ def readline
289
+ if $eof_pending
290
+ $eof_pending = false
291
+ raise EOFError
292
+ end
293
+
294
+ super
295
+ end
296
+ }
297
+
298
+ client.connection.transmit("list-tubes-watched")
299
+ sent = $replayed
300
+
301
+ assert_includes sent, "watch reconn_extra\r\n"
302
+ refute_includes sent, "ignore default\r\n"
303
+ assert_equal %w[default reconn_extra].sort,
304
+ client.tubes.watched.map(&:name).sort
305
+ ensure
306
+ $eof_pending = false
307
+ $replayed = nil
308
+ client.close if client
309
+ end
310
+
311
+ it "still reserves by weight after a reconnect" do
312
+ require_tuber!
313
+ consumer = Tuber.new(tuber_address)
314
+ consumer.tubes.watch("reconn_heavy", weight: 8)
315
+ consumer.tubes.watch("reconn_light", weight: 1)
316
+ consumer.tubes.ignore("default")
317
+ consumer.tubes.reserve_mode(:weighted)
318
+
319
+ producer = Tuber.new(tuber_address)
320
+ 100.times do |i|
321
+ producer.tubes["reconn_heavy"].put("h#{i}")
322
+ producer.tubes["reconn_light"].put("l#{i}")
323
+ end
324
+
325
+ $eof_pending = true
326
+ TCPSocket.prepend Module.new {
327
+ def readline
328
+ if $eof_pending
329
+ $eof_pending = false
330
+ raise EOFError
331
+ end
332
+
333
+ super
334
+ end
335
+ }
336
+
337
+ counts = Hash.new(0)
338
+ 60.times do
339
+ job = consumer.tubes.reserve(2)
340
+ counts[job.body[0]] += 1
341
+ job.delete
342
+ end
343
+
344
+ # 8:1 over interleaved puts is ~53/7; FIFO would be ~30/30.
345
+ assert counts["h"] > 3 * counts["l"],
346
+ "expected weighted reserve after reconnect, got heavy=#{counts["h"]} light=#{counts["l"]}"
347
+ ensure
348
+ $eof_pending = false
349
+ if producer
350
+ producer.connection.transmit("flush-tube reconn_heavy") rescue nil
351
+ producer.connection.transmit("flush-tube reconn_light") rescue nil
352
+ producer.close
353
+ end
354
+ consumer.close if consumer
355
+ end
356
+ end # transmit
357
+
358
+ describe 'for idempotent insert response' do
359
+ before do
360
+ require_tuber!
361
+ @host = tuber_address
362
+ @bc = Tuber::Connection.new(@host)
363
+ @idp_key = "testkey_#{Time.now.to_f}"
364
+ end
365
+
366
+ it "should return state for dedup hit" do
367
+ @bc.transmit "use idp_test"
368
+ res1 = @bc.transmit "put 0 0 100 4 idp:#{@idp_key}\r\ndata"
369
+ assert_equal 'INSERTED', res1[:status]
370
+ assert_nil res1[:state]
371
+
372
+ res2 = @bc.transmit "put 0 0 100 4 idp:#{@idp_key}\r\ndata"
373
+ assert_equal 'INSERTED', res2[:status]
374
+ assert_equal res1[:id], res2[:id]
375
+ assert_equal 'READY', res2[:state]
376
+ end
377
+ end # idempotent insert response
378
+
379
+ describe 'for #reconnect!' do
380
+ before do
381
+ @host = tuber_address
382
+ @bc = Tuber::Connection.new(@host)
383
+ end
384
+
385
+ after do
386
+ @bc.close rescue nil
387
+ end
388
+
389
+ it "re-establishes a connection that was closed" do
390
+ @bc.close
391
+ assert_nil @bc.connection
392
+
393
+ @bc.reconnect!
394
+
395
+ assert_kind_of TCPSocket, @bc.connection
396
+ assert_equal 'OK', @bc.transmit('stats')[:status]
397
+ end
398
+
399
+ it "replays watched tubes, weights and reserve mode" do
400
+ require_tuber!
401
+ client = Tuber.new(tuber_address)
402
+ client.tubes.watch("reconn_bang_heavy", weight: 8)
403
+ client.tubes.watch("reconn_bang_light", weight: 1)
404
+ client.tubes.ignore("default")
405
+ client.tubes.reserve_mode(:weighted)
406
+
407
+ $replayed = []
408
+ TCPSocket.prepend Module.new {
409
+ def write(data)
410
+ $replayed << data.to_s if $replayed
411
+ super
412
+ end
413
+ }
414
+
415
+ client.connection.reconnect!
416
+ sent = $replayed
417
+
418
+ assert_includes sent, "watch reconn_bang_heavy 8\r\n"
419
+ assert_includes sent, "watch reconn_bang_light 1\r\n"
420
+ assert_includes sent, "ignore default\r\n"
421
+ assert_includes sent, "reserve-mode weighted\r\n"
422
+ assert_equal %w[reconn_bang_heavy reconn_bang_light].sort,
423
+ client.tubes.watched.map(&:name).sort
424
+ ensure
425
+ # Leave the prepended module inert for the rest of the suite.
426
+ $replayed = nil
427
+ client.close if client
428
+ end
429
+
430
+ it "retries while the server is still refusing connections" do
431
+ # The outage shape: the socket is gone and the server will not accept
432
+ # for another second or two. A single connect attempt loses that race.
433
+ @bc.close
434
+ real_socket = TCPSocket.new(tuber_address, 11300)
435
+ TCPSocket.stubs(:new).
436
+ raises(Errno::ECONNREFUSED.new).then.
437
+ raises(Errno::ECONNREFUSED.new).then.
438
+ returns(real_socket)
439
+
440
+ @bc.reconnect!(retry_interval: 0.01)
441
+
442
+ assert_same real_socket, @bc.connection
443
+ end
444
+
445
+ it "retries a connect that fails for a reason other than ECONNREFUSED" do
446
+ # A remote host mid-restart answers EHOSTUNREACH or ETIMEDOUT as readily
447
+ # as ECONNREFUSED; those must not escape as a raw Errno either.
448
+ @bc.close
449
+ real_socket = TCPSocket.new(tuber_address, 11300)
450
+ TCPSocket.stubs(:new).
451
+ raises(Errno::EHOSTUNREACH.new).then.
452
+ returns(real_socket)
453
+
454
+ @bc.reconnect!(retry_interval: 0.01)
455
+
456
+ assert_same real_socket, @bc.connection
457
+ end
458
+
459
+ it "raises NotConnected naming the underlying error when the server stays down" do
460
+ @bc.close
461
+ TCPSocket.stubs(:new).raises(Errno::ECONNREFUSED.new)
462
+
463
+ err = assert_raises(Tuber::NotConnected) { @bc.reconnect!(retry_interval: 0.01) }
464
+ assert_match(/ECONNREFUSED/, err.message)
465
+ assert_kind_of Errno::ECONNREFUSED, err.cause
466
+ end
467
+
468
+ it "is reachable from the Tuber instance" do
469
+ client = Tuber.new(tuber_address)
470
+ client.tubes.watch("reconn_delegate")
471
+
472
+ client.reconnect!
473
+
474
+ assert_includes client.tubes.watched.map(&:name), "reconn_delegate"
475
+ ensure
476
+ client.close if client
477
+ end
478
+ end # reconnect!
479
+
480
+ describe 'for connect retries' do
481
+ after do
482
+ Tuber.configure do |config|
483
+ config.connect_retries = 0
484
+ config.connect_retry_interval = Tuber::Connection::DEFAULT_RETRY_INTERVAL
485
+ end
486
+ end
487
+
488
+ it "should make a single connect attempt by default" do
489
+ TCPSocket.expects(:new).times(1).raises(Errno::ECONNREFUSED.new)
490
+
491
+ assert_raises(Tuber::NotConnected) { Tuber::Connection.new('localhost') }
492
+ end
493
+
494
+ it "should retry the initial connect when connect_retries is configured" do
495
+ real_socket = TCPSocket.new(tuber_address, 11300)
496
+ Tuber.configure do |config|
497
+ config.connect_retries = 3
498
+ config.connect_retry_interval = 0.01
499
+ end
500
+
501
+ TCPSocket.stubs(:new).
502
+ raises(Errno::ECONNREFUSED.new).then.
503
+ raises(Errno::ECONNREFUSED.new).then.
504
+ returns(real_socket)
505
+
506
+ bc = Tuber::Connection.new(tuber_address)
507
+
508
+ assert_same real_socket, bc.connection
509
+ end
510
+
511
+ it "should give up after connect_retries and name the cause" do
512
+ Tuber.configure do |config|
513
+ config.connect_retries = 2
514
+ config.connect_retry_interval = 0.01
515
+ end
516
+ TCPSocket.expects(:new).times(3).raises(Errno::ECONNREFUSED.new)
517
+
518
+ err = assert_raises(Tuber::NotConnected) { Tuber::Connection.new('localhost') }
519
+ assert_match(/ECONNREFUSED/, err.message)
520
+ assert_kind_of Errno::ECONNREFUSED, err.cause
521
+ end
522
+
523
+ it "should lift an established connection's reconnect budget too" do
524
+ # One knob for "how long should a client ride out a server restart":
525
+ # raising connect_retries has to cover the connection you already hold,
526
+ # not only a cold start.
527
+ bc = Tuber::Connection.new(tuber_address)
528
+ real_socket = TCPSocket.new(tuber_address, 11300)
529
+
530
+ Tuber.configure do |config|
531
+ config.connect_retries = 5
532
+ config.connect_retry_interval = 0.01
533
+ end
534
+
535
+ # One dropped read, then four refused connects: past the built-in budget
536
+ # of MAX_RETRIES, inside the configured one.
537
+ $eof_pending = true
538
+ TCPSocket.prepend Module.new {
539
+ def readline
540
+ if $eof_pending
541
+ $eof_pending = false
542
+ raise EOFError
543
+ end
544
+
545
+ super
546
+ end
547
+ }
548
+ TCPSocket.stubs(:new).
549
+ raises(Errno::ECONNREFUSED.new).then.
550
+ raises(Errno::ECONNREFUSED.new).then.
551
+ raises(Errno::ECONNREFUSED.new).then.
552
+ raises(Errno::ECONNREFUSED.new).then.
553
+ returns(real_socket)
554
+
555
+ assert_equal 'OK', bc.transmit('stats')[:status]
556
+ assert_same real_socket, bc.connection
557
+ ensure
558
+ $eof_pending = false
559
+ end
560
+ end # connect retries
561
+
562
+ describe 'for NotConnected messages' do
563
+ it "should keep the historical message as a prefix" do
564
+ # Consumers match on this string. The cause is appended, never inserted.
565
+ TCPSocket.stubs(:new).raises(Errno::ECONNREFUSED.new)
566
+
567
+ err = assert_raises(Tuber::NotConnected) { Tuber::Connection.new('localhost:8544') }
568
+
569
+ assert err.message.start_with?("Connection to beanstalk 'localhost:8544' is closed!"),
570
+ "expected the historical message as a prefix, got #{err.message.inspect}"
571
+ end
572
+
573
+ it "should not blame an unrelated in-flight exception" do
574
+ # Commands are very often issued from inside someone else's rescue block,
575
+ # where $! is still set. The cause named has to be the one that stopped
576
+ # this connect, not whatever the caller happened to be handling.
577
+ bc = Tuber::Connection.new(tuber_address)
578
+ bc.close
579
+
580
+ err = begin
581
+ raise ArgumentError, "something else entirely"
582
+ rescue ArgumentError
583
+ assert_raises(Tuber::NotConnected) { bc.transmit('stats') }
584
+ end
585
+
586
+ assert_equal "Connection to beanstalk '#{bc.host}:#{bc.port}' is closed!", err.message
587
+ end
588
+ end # NotConnected messages
589
+
590
+ describe 'for #close' do
591
+ before do
592
+ @host = 'localhost'
593
+ @bc = Tuber::Connection.new(@host)
594
+ end
595
+
596
+ it "should clear connection" do
597
+ assert_kind_of TCPSocket, @bc.connection
598
+ @bc.close
599
+ assert_nil @bc.connection
600
+ assert_raises(Tuber::NotConnected) { @bc.transmit 'stats' }
601
+ end
602
+ end # close
603
+ describe 'for drain and undrain commands' do
604
+ it "should return DRAINING status for drain command" do
605
+ @bc = Tuber::Connection.new('localhost')
606
+ TCPSocket.any_instance.stubs(:write)
607
+ TCPSocket.any_instance.expects(:readline).returns("DRAINING\r\n")
608
+ res = @bc.transmit("drain")
609
+ assert_equal 'DRAINING', res[:status]
610
+ end
611
+
612
+ it "should return NOT_DRAINING status for undrain command" do
613
+ @bc = Tuber::Connection.new('localhost')
614
+ TCPSocket.any_instance.stubs(:write)
615
+ TCPSocket.any_instance.expects(:readline).returns("NOT_DRAINING\r\n")
616
+ res = @bc.transmit("undrain")
617
+ assert_equal 'NOT_DRAINING', res[:status]
618
+ end
619
+
620
+ it "should still raise DrainingError for non-drain commands" do
621
+ @bc = Tuber::Connection.new('localhost')
622
+ TCPSocket.any_instance.stubs(:write)
623
+ TCPSocket.any_instance.expects(:readline).times(3).returns("DRAINING\r\n")
624
+ assert_raises(Tuber::DrainingError) { @bc.transmit("put 0 0 100 4\r\ntest") }
625
+ end
626
+ end # drain and undrain
627
+ end # Tuber::Connection
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # test/errors_test.rb
4
+
5
+ require File.expand_path('../test_helper', __FILE__)
6
+
7
+ describe "Tuber::Errors" do
8
+ it 'should raise proper exception for invalid status NOT_FOUND' do
9
+ @klazz = Tuber::UnexpectedResponse.from_status("NOT_FOUND", "job-stats -1")
10
+ assert_kind_of(Tuber::NotFoundError, @klazz)
11
+ assert_equal 'job-stats -1', @klazz.cmd
12
+ assert_equal 'NOT_FOUND', @klazz.status
13
+ end
14
+
15
+ it 'should raise proper exception for invalid status BAD_FORMAT' do
16
+ @klazz = Tuber::UnexpectedResponse.from_status("BAD_FORMAT", "FAKE")
17
+ assert_kind_of(Tuber::BadFormatError, @klazz)
18
+ assert_equal 'FAKE', @klazz.cmd
19
+ assert_equal 'BAD_FORMAT', @klazz.status
20
+ end
21
+
22
+ it 'should raise proper exception for invalid status DEADLINE_SOON' do
23
+ @klazz = Tuber::UnexpectedResponse.from_status("DEADLINE_SOON", "reserve 0")
24
+ assert_kind_of(Tuber::DeadlineSoonError, @klazz)
25
+ assert_equal 'reserve 0', @klazz.cmd
26
+ assert_equal 'DEADLINE_SOON', @klazz.status
27
+ end
28
+
29
+ it 'should raise proper exception for invalid status EXPECTED_CRLF' do
30
+ @klazz = Tuber::UnexpectedResponse.from_status("EXPECTED_CRLF", "reserve 0")
31
+ assert_kind_of(Tuber::ExpectedCrlfError, @klazz)
32
+ assert_equal 'reserve 0', @klazz.cmd
33
+ assert_equal 'EXPECTED_CRLF', @klazz.status
34
+ end
35
+ end