queueing_rabbit 0.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/.gitignore +17 -0
  2. data/.rvmrc +48 -0
  3. data/Gemfile +9 -0
  4. data/LICENSE +22 -0
  5. data/README.md +38 -0
  6. data/Rakefile +5 -0
  7. data/lib/queueing_rabbit/callbacks.rb +31 -0
  8. data/lib/queueing_rabbit/client/amqp.rb +148 -0
  9. data/lib/queueing_rabbit/client/bunny.rb +62 -0
  10. data/lib/queueing_rabbit/client/callbacks.rb +14 -0
  11. data/lib/queueing_rabbit/configuration.rb +24 -0
  12. data/lib/queueing_rabbit/job.rb +32 -0
  13. data/lib/queueing_rabbit/logging.rb +17 -0
  14. data/lib/queueing_rabbit/serializer.rb +19 -0
  15. data/lib/queueing_rabbit/tasks.rb +37 -0
  16. data/lib/queueing_rabbit/version.rb +3 -0
  17. data/lib/queueing_rabbit/worker.rb +96 -0
  18. data/lib/queueing_rabbit.rb +67 -0
  19. data/lib/tasks/queueing_rabbit.rake +2 -0
  20. data/queueing_rabbit.gemspec +49 -0
  21. data/spec/integration/asynchronous_publishing_and_consuming_spec.rb +62 -0
  22. data/spec/integration/jobs/print_line_job.rb +17 -0
  23. data/spec/integration/synchronous_publishing_and_asynchronous_consuming_spec.rb +39 -0
  24. data/spec/integration/synchronous_publishing_spec.rb +24 -0
  25. data/spec/spec_helper.rb +26 -0
  26. data/spec/support/shared_contexts.rb +17 -0
  27. data/spec/support/shared_examples.rb +60 -0
  28. data/spec/unit/queueing_rabbit/callbacks_spec.rb +53 -0
  29. data/spec/unit/queueing_rabbit/client/amqp_spec.rb +193 -0
  30. data/spec/unit/queueing_rabbit/client/bunny_spec.rb +68 -0
  31. data/spec/unit/queueing_rabbit/client/callbacks_spec.rb +22 -0
  32. data/spec/unit/queueing_rabbit/configuration_spec.rb +19 -0
  33. data/spec/unit/queueing_rabbit/job_spec.rb +23 -0
  34. data/spec/unit/queueing_rabbit/logging_spec.rb +9 -0
  35. data/spec/unit/queueing_rabbit/serializer_spec.rb +26 -0
  36. data/spec/unit/queueing_rabbit/worker_spec.rb +133 -0
  37. data/spec/unit/queueing_rabbit_spec.rb +105 -0
  38. metadata +168 -0
@@ -0,0 +1,67 @@
1
+ require "queueing_rabbit/version"
2
+ require "queueing_rabbit/callbacks"
3
+ require "queueing_rabbit/configuration"
4
+ require "queueing_rabbit/logging"
5
+ require "queueing_rabbit/serializer"
6
+ require "queueing_rabbit/client/callbacks"
7
+ require "queueing_rabbit/client/amqp"
8
+ require "queueing_rabbit/client/bunny"
9
+ require "queueing_rabbit/job"
10
+ require "queueing_rabbit/worker"
11
+ # require "queueing_rabbit/new_relic"
12
+
13
+ module QueueingRabbit
14
+ extend self
15
+ extend Logging
16
+ extend Callbacks
17
+ extend Configuration
18
+
19
+ class QueueingRabbitError < Exception; end
20
+ class JobNotFoundError < QueueingRabbitError; end
21
+ class JobNotPresentError < QueueingRabbitError; end
22
+
23
+ attr_accessor :logger, :client
24
+
25
+ def connect
26
+ @connection ||= client.connect
27
+ end
28
+
29
+ def connection
30
+ @connection ||= connect
31
+ end
32
+
33
+ def drop_connection
34
+ @connection = nil
35
+ end
36
+
37
+ def enqueue(job, arguments = {})
38
+ info "enqueueing job #{job} with arguments: #{arguments.inspect}."
39
+
40
+ connection.open_channel(job.channel_options) do |c, _|
41
+ connection.define_queue(c, job.queue_name, job.queue_options)
42
+ connection.enqueue(c, job.queue_name, arguments)
43
+ end
44
+
45
+ true
46
+ end
47
+ alias_method :publish, :enqueue
48
+
49
+ def queue_size(job)
50
+ size = 0
51
+ connection.open_channel(job.channel_options) do |c, _|
52
+ queue = connection.define_queue(c, job.queue_name, job.queue_options)
53
+ size = connection.queue_size(queue)
54
+ end
55
+ size
56
+ end
57
+
58
+ def purge_queue(job)
59
+ connection.open_channel(job.channel_options) do |c, _|
60
+ connection.define_queue(c, job.queue_name, job.queue_options).purge
61
+ end
62
+ true
63
+ end
64
+ end
65
+
66
+ QueueingRabbit.client = QueueingRabbit.default_client
67
+ QueueingRabbit.logger = Logger.new(STDOUT)
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib'
2
+ require 'queueing_rabbit/tasks'
@@ -0,0 +1,49 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/queueing_rabbit/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Artem Chistyakov"]
6
+ gem.email = ["chistyakov.artem@gmail.com"]
7
+ gem.summary = %q{QueueingRabbit is an AMQP-based queueing system}
8
+ gem.homepage = "https://github.com/temochka/queueing_rabbit"
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "queueing_rabbit"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = QueueingRabbit::VERSION
16
+
17
+ gem.extra_rdoc_files = [ "LICENSE", "README.md" ]
18
+ gem.rdoc_options = ["--charset=UTF-8"]
19
+
20
+ gem.add_dependency "amqp", ">= 0.9.0"
21
+ gem.add_dependency "bunny", ">= 0.9.0.pre7"
22
+ gem.add_dependency "rake", ">= 0"
23
+
24
+ gem.description = <<description
25
+ QueueingRabbit is a Ruby library providing convenient object-oriented syntax
26
+ for managing background jobs using AMQP. All jobs' argumets are serialized
27
+ to JSON and transfered to jobs using AMQP message payload. The library
28
+ implements amqp and bunny gems as adapters making it possible to use
29
+ synchronous publishing and asynchronous consuming, which might be useful for
30
+ Rails app running on non-EventMachine based application servers (i. e.
31
+ Passenger).
32
+
33
+ Any Ruby class or Module can be transformed into QueueingRabbit's background
34
+ job by including QueueingRabbit::Job module. It is also possible to inherit
35
+ your class from QueueingRabbit::AbstractJob abstract class.
36
+
37
+ The library is bundled with a Rake task which is capable of starting a
38
+ worker processing a specified list of jobs.
39
+
40
+ To achieve the required simplicity the gem doesn't try to support all
41
+ features of AMQP protocol. It uses a restricted subset instead:
42
+
43
+ * Only a single direct exchange is used
44
+ * Every job is consumed using a separate channel
45
+ * All jobs are consumed with acknowledgements
46
+ * ACK is only sent to the broker if a job was processed successfully
47
+ * Currently all messages are published with persistent option
48
+ description
49
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+ require 'integration/jobs/print_line_job'
3
+
4
+ describe "Asynchronous publishing and consuming example" do
5
+ include_context "Evented spec"
6
+ include_context "StringIO logger"
7
+
8
+ let(:job) { PrintLineJob }
9
+
10
+ before(:all) { QueueingRabbit.client = QueueingRabbit::Client::AMQP }
11
+ after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
12
+
13
+ context "basic consuming" do
14
+ let(:connection) { QueueingRabbit.connection }
15
+ let(:worker) { QueueingRabbit::Worker.new(job.to_s) }
16
+ let(:io) { StringIO.new }
17
+ let(:line) { "Hello, world!" }
18
+
19
+ before(:each) do
20
+ QueueingRabbit.drop_connection
21
+ PrintLineJob.io = io
22
+ end
23
+
24
+ it "processes enqueued jobs" do
25
+ em {
26
+ QueueingRabbit.connect
27
+ queue_size = nil
28
+
29
+ delayed(0.5) {
30
+ 3.times { QueueingRabbit.enqueue(job, :line => line) }
31
+ }
32
+
33
+ delayed(1.0) { worker.work }
34
+
35
+ delayed(2.0) {
36
+ connection.open_channel do |c, _|
37
+ connection.define_queue(c, :print_line_job, job.queue_options).status do |s, _|
38
+ queue_size = s
39
+ end
40
+ end
41
+ }
42
+
43
+ done(2.5) { queue_size.should == 0 }
44
+ }
45
+ end
46
+
47
+ it "actually outputs the line" do
48
+ em {
49
+ QueueingRabbit.connect
50
+
51
+ delayed(0.5) { QueueingRabbit.enqueue(job, :line => line) }
52
+
53
+ delayed(1.0) { worker.work }
54
+
55
+ done(2.0) {
56
+ io.string.should include(line)
57
+ }
58
+ }
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,17 @@
1
+ class PrintLineJob
2
+ extend QueueingRabbit::Job
3
+
4
+ class << self
5
+ attr_writer :io
6
+ end
7
+
8
+ queue :print_line_job, :durable => true
9
+
10
+ def self.perform(arguments = {})
11
+ self.io.puts arguments[:line]
12
+ end
13
+
14
+ def self.io
15
+ @io ||= STDOUT
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'integration/jobs/print_line_job'
3
+
4
+ describe "Synchronous publishing and asynchronous consuming example" do
5
+ include_context "StringIO logger"
6
+ include_context "Evented spec"
7
+
8
+ let(:job) { PrintLineJob }
9
+ let(:line) { "Hello, world!" }
10
+
11
+ after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
12
+
13
+ context "when a message is published synchronously" do
14
+ before do
15
+ QueueingRabbit.publish(job, line: line)
16
+ QueueingRabbit.drop_connection
17
+ end
18
+
19
+ context "and being consumed asynchornously" do
20
+ let(:worker) { QueueingRabbit::Worker.new(job.to_s) }
21
+ let(:io) { StringIO.new }
22
+
23
+ before do
24
+ job.io = io
25
+ end
26
+
27
+ it "works" do
28
+ em {
29
+ worker.work
30
+
31
+ done(1.0) {
32
+ io.string.should include(line)
33
+ }
34
+ }
35
+ end
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'integration/jobs/print_line_job'
3
+
4
+ describe "Synchronous publishing example" do
5
+ include_context "StringIO logger"
6
+
7
+ let(:job) { PrintLineJob }
8
+
9
+ context "when publishing a message" do
10
+ after do
11
+ QueueingRabbit.purge_queue(job)
12
+ end
13
+
14
+ let(:publishing) {
15
+ -> { QueueingRabbit.publish(job, line: "Hello, World!") }
16
+ }
17
+
18
+ it 'affects the queue size' do
19
+ expect { 5.times { publishing.call } }
20
+ .to change{QueueingRabbit.queue_size(job)}.by(5)
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,26 @@
1
+ $:.unshift File.expand_path('..', __FILE__)
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ require 'rubygems'
5
+ require 'bundler'
6
+ Bundler.setup(:test)
7
+
8
+ require 'rspec'
9
+ require 'rspec/autorun'
10
+ require 'evented-spec'
11
+ require 'support/shared_contexts'
12
+ require 'support/shared_examples'
13
+
14
+ require 'queueing_rabbit'
15
+
16
+ RSpec.configure do |config|
17
+ config.before(:each) {
18
+ QueueingRabbit.drop_connection
19
+ }
20
+
21
+ QueueingRabbit.configure do |qr|
22
+ qr.amqp_uri = "amqp://guest:guest@localhost:5672"
23
+ qr.amqp_exchange_name = "queueing_rabbit_test"
24
+ qr.amqp_exchange_options = {:durable => true}
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ require 'stringio'
2
+
3
+ shared_context "StringIO logger" do
4
+
5
+ before(:all) do
6
+ QueueingRabbit.logger = Logger.new(StringIO.new)
7
+ end
8
+
9
+ after(:all) do
10
+ QueueingRabbit.logger = Logger.new(STDOUT)
11
+ end
12
+
13
+ end
14
+
15
+ shared_context "Evented spec" do
16
+ include EventedSpec::SpecHelper
17
+ end
@@ -0,0 +1,60 @@
1
+ shared_examples :client do
2
+
3
+ describe '#define_queue' do
4
+ let(:exchange) { mock }
5
+ let(:channel) { mock }
6
+ let(:queue) { mock }
7
+ let(:queue_name) { "test_queue_name" }
8
+ let(:routing_keys) { [:test_job] }
9
+ let(:options) { {:durable => false, :routing_keys => routing_keys} }
10
+
11
+ before do
12
+ client.stub(:exchange => exchange)
13
+ channel.should_receive(:queue).with(queue_name, options)
14
+ .and_yield(queue)
15
+ queue.should_receive(:bind)
16
+ .with(exchange, :routing_key => routing_keys.first.to_s).ordered
17
+ queue.should_receive(:bind)
18
+ .with(exchange, :routing_key => queue_name).ordered
19
+ end
20
+
21
+ it "defines a queue and binds it to its name and the given routing keys" do
22
+ client.define_queue(channel, queue_name, options)
23
+ end
24
+ end
25
+
26
+ describe '#define_exchange' do
27
+ let(:channel) { mock }
28
+ let(:options) { {:durable => true} }
29
+
30
+ before do
31
+ channel.should_receive(:direct)
32
+ .with(QueueingRabbit.amqp_exchange_name,
33
+ QueueingRabbit.amqp_exchange_options.merge(options))
34
+ end
35
+
36
+ it 'defines a new AMQP direct exchange with given name and options' do
37
+ client.define_exchange(channel, options)
38
+ end
39
+ end
40
+
41
+ describe '#enqueue' do
42
+ let(:channel) { mock }
43
+ let(:exchange) { mock }
44
+ let(:routing_key) { :routing_key }
45
+ let(:payload) { {"test" => "data"} }
46
+
47
+ before do
48
+ client.should_receive(:exchange).with(channel).and_return(exchange)
49
+ exchange.should_receive(:publish).with(JSON.dump(payload),
50
+ :key => routing_key.to_s,
51
+ :persistent => true)
52
+ end
53
+
54
+ it "publishes a new persistent message to the used exchange with " \
55
+ "serialized payload and routed using given routing key" do
56
+ client.enqueue(channel, routing_key, payload)
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe QueueingRabbit::Callbacks do
4
+ let!(:klass) { Class.new { extend QueueingRabbit::Callbacks } }
5
+ subject { klass }
6
+
7
+ context 'when a single callback is set for an event' do
8
+ let(:callback) { Proc.new {} }
9
+
10
+ before do
11
+ subject.setup_callback(:test, &callback)
12
+ end
13
+
14
+ it 'saves the callback internally' do
15
+ subject.instance_variable_get(:@callbacks).should include(:test)
16
+ end
17
+
18
+ context 'and when an event is triggered' do
19
+ before do
20
+ callback.should_receive(:call)
21
+ end
22
+
23
+ it 'executes the registered callback' do
24
+ subject.trigger_event(:test)
25
+ end
26
+ end
27
+ end
28
+
29
+ context 'when multiple callbacks are set for an event' do
30
+ let(:callback_1) { Proc.new {} }
31
+ let(:callback_2) { Proc.new {} }
32
+
33
+ before do
34
+ subject.setup_callback(:test, &callback_1)
35
+ subject.setup_callback(:test, &callback_2)
36
+ end
37
+
38
+ it 'saves the callbacks internally' do
39
+ subject.instance_variable_get(:@callbacks).should include(:test)
40
+ end
41
+
42
+ context 'and when an event is triggered' do
43
+ before do
44
+ callback_1.should_receive(:call)
45
+ callback_2.should_receive(:call)
46
+ end
47
+
48
+ it 'executes all the registered callbacks' do
49
+ subject.trigger_event(:test)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,193 @@
1
+ require 'spec_helper'
2
+
3
+ describe QueueingRabbit::Client::AMQP do
4
+
5
+ include_context "StringIO logger"
6
+
7
+ let(:connection) { mock :on_tcp_connection_loss => nil, :on_recovery => nil }
8
+
9
+ before do
10
+ QueueingRabbit.stub(:amqp_uri => 'amqp://localhost:5672',
11
+ :amqp_exchange_name => 'queueing_rabbit_test',
12
+ :amqp_exchange_options => {:durable => true})
13
+ end
14
+
15
+ context "class" do
16
+ subject { QueueingRabbit::Client::AMQP }
17
+
18
+ its(:connection_options) { should include(:timeout) }
19
+ its(:connection_options) { should include(:heartbeat) }
20
+ its(:connection_options) { should include(:on_tcp_connection_failure) }
21
+
22
+ describe '.run_event_machine' do
23
+ context 'when the event machine reactor is running' do
24
+ before do
25
+ EM.should_receive(:reactor_running?).and_return(true)
26
+ end
27
+
28
+ it 'has no effect' do
29
+ subject.run_event_machine
30
+ end
31
+ end
32
+
33
+ context 'when the event machine reactor is not running' do
34
+ before do
35
+ EM.should_receive(:reactor_running?).and_return(false)
36
+ Thread.should_receive(:new).and_yield
37
+ EM.should_receive(:run).and_yield
38
+ end
39
+
40
+ it 'runs the event machine reactor in a separate thread' do
41
+ subject.run_event_machine
42
+ end
43
+
44
+ it 'triggers :event_machine_started event' do
45
+ QueueingRabbit.should_receive(:trigger_event)
46
+ .with(:event_machine_started)
47
+ subject.run_event_machine
48
+ end
49
+ end
50
+ end
51
+
52
+ describe ".connect" do
53
+ before do
54
+ AMQP.should_receive(:connect).with(QueueingRabbit.amqp_uri)
55
+ .and_return(connection)
56
+ subject.should_receive(:run_event_machine)
57
+ end
58
+
59
+ it "creates a class' instance" do
60
+ subject.connect.should be_instance_of(subject)
61
+ end
62
+ end
63
+
64
+ describe '.join_event_machine_thread' do
65
+ let(:thread) { mock(:join => true) }
66
+
67
+ before do
68
+ subject.instance_variable_set(:@event_machine_thread, thread)
69
+ end
70
+
71
+ it "joins the thread if exists" do
72
+ subject.join_event_machine_thread
73
+ end
74
+ end
75
+ end
76
+
77
+ context "instance" do
78
+ let(:client) { QueueingRabbit::Client::AMQP.connect }
79
+ subject { client }
80
+
81
+ before do
82
+ EM.stub(:reactor_running? => true)
83
+ AMQP.stub(:connect => connection)
84
+ QueueingRabbit::Client::AMQP.stub(:run_event_machine => true)
85
+ end
86
+
87
+ it_behaves_like :client
88
+
89
+ it { should be }
90
+
91
+ describe '#listen_queue' do
92
+ let(:channel) { mock }
93
+ let(:queue_name) { mock }
94
+ let(:options) { mock }
95
+ let(:queue) { mock }
96
+ let(:metadata) { stub(:ack => true) }
97
+ let(:data) { {:data => "data"}}
98
+ let(:payload) { JSON.dump(data) }
99
+
100
+ before do
101
+ client.should_receive(:define_queue).with(channel, queue_name, options)
102
+ .and_return(queue)
103
+ queue.should_receive(:subscribe).with(:ack => true)
104
+ .and_yield(metadata, payload)
105
+ end
106
+
107
+ it 'listens to the queue and passes deserialized arguments to the block' do
108
+ client.listen_queue(channel, queue_name, options) do |arguments|
109
+ arguments.should == data
110
+ end
111
+ end
112
+
113
+ context "when deserialization problems occur" do
114
+ let(:error) { JSON::JSONError.new }
115
+ before do
116
+ client.should_receive(:deserialize).and_raise(error)
117
+ client.should_receive(:error)
118
+ client.should_receive(:debug).with(error)
119
+ end
120
+
121
+ it "keeps the record of the errors" do
122
+ client.listen_queue(channel, queue_name, options)
123
+ end
124
+
125
+ it "silences JSON errors" do
126
+ expect { client.listen_queue(channel, queue_name, options) }
127
+ .to_not raise_error(error)
128
+ end
129
+ end
130
+ end
131
+
132
+ describe '#process_message' do
133
+ let(:arguments) { mock }
134
+
135
+ it "yields given arguments to the block" do
136
+ client.process_message(arguments) do |a|
137
+ a.should == arguments
138
+ end
139
+ end
140
+
141
+ it "silences all errors risen" do
142
+ expect {
143
+ client.process_message(arguments) { |a| raise StandardError.new }
144
+ }.to_not raise_error(StandardError)
145
+ end
146
+
147
+ context "logging" do
148
+ let(:error) { StandardError.new }
149
+ before do
150
+ client.should_receive(:error)
151
+ client.should_receive(:debug).with(error)
152
+ end
153
+
154
+ it "keeps the record of all errors risen" do
155
+ client.process_message(arguments) { |a| raise error }
156
+ end
157
+ end
158
+ end
159
+
160
+ describe '#disconnect' do
161
+ before do
162
+ subject.should_receive(:info)
163
+ connection.should_receive(:close).and_yield
164
+ EM.should_receive(:stop)
165
+ end
166
+
167
+ it 'writes the log, closes connection and stops the reactor' do
168
+ client.disconnect
169
+ end
170
+ end
171
+
172
+ describe "#open_channel" do
173
+ let(:next_channel_id) { mock }
174
+ let(:options) { mock }
175
+ let(:channel) { mock }
176
+ let(:open_ok) { mock }
177
+
178
+ before do
179
+ AMQP::Channel.should_receive(:next_channel_id)
180
+ .and_return(next_channel_id)
181
+ AMQP::Channel.should_receive(:new)
182
+ .with(connection, next_channel_id, options)
183
+ .and_yield(channel, open_ok)
184
+ channel.should_receive(:on_error)
185
+ end
186
+
187
+ it 'opens a new AMQP channel with given options and installs ' \
188
+ 'on-error callback' do
189
+ client.open_channel(options) {}
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe QueueingRabbit::Client::Bunny do
4
+ let(:connection) { stub(:start => true) }
5
+
6
+ before do
7
+ QueueingRabbit.stub(:amqp_uri => 'amqp://localhost:5672',
8
+ :amqp_exchange_name => 'queueing_rabbit_test',
9
+ :amqp_exchange_options => {:durable => true})
10
+ end
11
+
12
+ context 'class' do
13
+ subject { QueueingRabbit::Client::Bunny }
14
+
15
+ describe '.connect' do
16
+ before do
17
+ Bunny.should_receive(:new).with(QueueingRabbit.amqp_uri)
18
+ .and_return(connection)
19
+ end
20
+
21
+ it "instantiates an instance of itself" do
22
+ subject.connect.should be_kind_of(subject)
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'instance' do
28
+ let(:client) { QueueingRabbit::Client::Bunny.connect }
29
+ subject { client }
30
+
31
+ before do
32
+ Bunny.stub(:new => connection)
33
+ end
34
+
35
+ it_behaves_like :client
36
+
37
+ it { should be }
38
+
39
+ describe '#open_channel' do
40
+ let(:options) { mock }
41
+ let(:channel) { mock }
42
+
43
+ before do
44
+ connection.should_receive(:create_channel).and_return(channel)
45
+ end
46
+
47
+ it 'creates a channel and yields it' do
48
+ client.open_channel do |c, _|
49
+ c.should == channel
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '#queue_size' do
55
+ let(:queue) { mock }
56
+ let(:status) { {:message_count => 42} }
57
+
58
+ before do
59
+ queue.should_receive(:status).and_return(status)
60
+ end
61
+
62
+ it "returns a number of messages in queue" do
63
+ client.queue_size(queue).should be_a(Fixnum)
64
+ end
65
+ end
66
+
67
+ end
68
+ end