queueing_rabbit 0.1.0.rc1

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