bunny-mock 1.0.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 845c697ca137bf613dc4b11eccaeafdaad4d6cf8
4
+ data.tar.gz: 4b4322cf0a31e2557bf3088fc621e0d0d153087d
5
+ SHA512:
6
+ metadata.gz: 7a7e65dea49c9844023700baf5fb46bd2b69255d2736c0b1dece7189698482bf7d538a411bfca438a82cfcb694e84697f8dae209f0f1295393f602da0a72fb33
7
+ data.tar.gz: 82c071096f87d173b139870b42e555f8ffc0219c1f662a77ef14eb94d6b3b6b1f245a44e237c4934d1316b308b2fe31b27b96dbc563c33ba8caa2bd2b07897f5
@@ -0,0 +1,23 @@
1
+ .DS_Store
2
+ .*.swp
3
+ *.class
4
+ *.rbc
5
+ *.gem
6
+ /doc/
7
+ .yardoc
8
+ .rvmrc
9
+ Gemfile.lock
10
+ .rbx/*
11
+ .tags
12
+ .tags_sorted_by_file
13
+ .Apple*
14
+ bin/*
15
+ .bundle/*
16
+ vendor/*
17
+ playground/*
18
+ *.org
19
+ repl-*
20
+ debug/*
21
+ *.dump
22
+ deploy.docs.sh
23
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format=documentation
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ bundler_args: --without development
3
+ script: bundle exec rspec
4
+ rvm:
5
+ - "2.0.0"
6
+ - "2.1.8"
7
+ - "2.3.0"
8
+ branches:
9
+ only:
10
+ - master
@@ -0,0 +1,7 @@
1
+ --no-private
2
+ --protected
3
+ --markup="markdown" lib/**/*.rb
4
+ --main README.md
5
+ --hide-tag todo
6
+ -
7
+ LICENSE
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'amq-protocol'
4
+
5
+ group :development do
6
+
7
+ gem 'yard'
8
+ end
9
+
10
+ group :test do
11
+
12
+ gem 'rspec', '~> 3.4.0'
13
+ end
14
+
15
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Andrew Rempe and contributors.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,151 @@
1
+ Bunny Mock
2
+ ==========
3
+
4
+ [![Build Status](https://travis-ci.org/arempe93/bunny-mock.svg?branch=master)](https://travis-ci.org/arempe93/bunny-mock)
5
+
6
+ A mock client for RabbitMQ, modeled after the popular [Bunny client](https://github.com/ruby-amqp/bunny). It currently supports basic usage of Bunny for managing exchanges and queues, with the goal of being able to handle and test all Bunny use cases.
7
+
8
+ ## Usage
9
+
10
+ BunnyMock can be injected into your RabbitMQ application in place of Bunny for testing.
11
+
12
+ ### Example
13
+
14
+ Consider the following example of an RabibtMQ helper module, in Rails
15
+
16
+ ```ruby
17
+ require 'bunny'
18
+
19
+ module AMQP
20
+ module Factory
21
+
22
+ ## Connection, can be mocked for tests
23
+ mattr_accessor :connection
24
+
25
+ ####################################################
26
+ # Connection Management
27
+ ####################################################
28
+
29
+ def self.connect
30
+
31
+ # create bunny rmq client
32
+ @connection = Bunny.new Global.amqp.to_hash
33
+
34
+ # make connection
35
+ @connection.start
36
+
37
+ # return connection
38
+ @connection
39
+ end
40
+
41
+ def self.get_channel
42
+
43
+ # make connection if not connected
44
+ connect unless defined?(@connection) and @connection.open?
45
+
46
+ # get channel
47
+ @connection.channel
48
+ end
49
+
50
+ ...
51
+ end
52
+ end
53
+ ```
54
+
55
+ In this case, to set up your tests, you can simply set `AMQP::Factory.connection = BunnyMock.new.start` to inject the mock library. Then you can use the mock helpers in your tests.
56
+
57
+ ### Publishing
58
+
59
+ ```ruby
60
+ it 'should publish messages to queues' do
61
+
62
+ channel = BunnyMock.new.start.channel
63
+ queue = channel.queue 'queue.test'
64
+
65
+ queue.publish 'Testing message', priority: 5
66
+
67
+ expect(queue.message_count).to eq(1)
68
+
69
+ payload = queue.pop
70
+ expect(queue.message_count).to eq(0)
71
+
72
+ expect(payload[:message]).to eq('Testing message')
73
+ expect(payload[:options][:priority]).to eq(5)
74
+ end
75
+ ```
76
+
77
+ ### Binding
78
+
79
+ ```ruby
80
+ it 'should bind queues to exchanges' do
81
+
82
+ channel = BunnyMock.new.start.channel
83
+
84
+ queue = channel.queue 'queue.test'
85
+ xchg = channel.exchange 'xchg.test'
86
+
87
+ queue.bind xchg
88
+ expect(queue.bound_to?(xchg)).to be_truthy
89
+ expect(xchg.has_binding?(queue)).to be_truthy
90
+
91
+ queue.unbind xchg
92
+ expect(queue.bound_to?(xchg)).to be_falsey
93
+ expect(xchg.has_binding?(queue)).to be_falsey
94
+
95
+ queue.bind 'xchg.test'
96
+ expect(queue.bound_to?(xchg)).to be_truthy
97
+ expect(xchg.has_binding?(queue)).to be_truthy
98
+ end
99
+
100
+ it 'should bind exchanges to exchanges' do
101
+
102
+ channel = BunnyMock.new.start.channel
103
+
104
+ source = channel.exchange 'xchg.source'
105
+ receiver = channel.exchange 'xchg.receiver'
106
+
107
+ receiver.bind source
108
+ expect(receiver.bound_to?(source)).to be_truthy
109
+ expect(source.has_binding?(receiver)).to be_truthy
110
+
111
+ receiver.unbind source
112
+ expect(receiver.bound_to?(source)).to be_falsey
113
+ expect(xchg.has_binding?(receiver)).to be_falsey
114
+
115
+ receiver.bind 'xchg.source'
116
+ expect(receiver.bound_to?(source)).to be_truthy
117
+ expect(source.has_binding?(receiver)).to be_truthy
118
+ end
119
+ ```
120
+
121
+ ## Installation
122
+
123
+ ### With RubyGems
124
+
125
+ To install BunnyMock with RubyGems:
126
+
127
+ ```
128
+ gem install bunny-mock
129
+ ```
130
+
131
+ ### With Bundler
132
+
133
+ To use BunnyMock with a Bundler managed project:
134
+
135
+ ```
136
+ gem 'bunny-mock'
137
+ ```
138
+
139
+ ## Documentation
140
+
141
+ View the documentation on [RubyDoc](http://www.rubydoc.info/github/arempe93/bunny-mock)
142
+
143
+ ## Dependencies
144
+
145
+ * Ruby version >= 2.0
146
+
147
+ * [AMQ Protocol](https://github.com/ruby-amqp/amq-protocol) - Also a dependency of Bunny
148
+
149
+ ## License
150
+
151
+ Released under the MIT license
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env gem build
2
+ # encoding: utf-8
3
+
4
+ require 'base64'
5
+ require File.expand_path("../lib/bunny_mock/version", __FILE__)
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'bunny-mock'
9
+ s.version = BunnyMock::VERSION.dup
10
+ s.summary = 'Mocking for the popular Bunny client for RabbitMQ'
11
+ s.description = 'Easy to use mocking for testing the Bunny client for RabbitMQ'
12
+ s.license = 'MIT'
13
+ s.required_ruby_version = Gem::Requirement.new '>= 2.0'
14
+
15
+ s.authors = [ 'Andrew Rempe' ]
16
+ s.email = [ Base64.encode64('YW5kcmV3cmVtcGVAZ21haWwuY29t\n') ]
17
+
18
+ # Dependencies
19
+ s.add_dependency 'amq-protocol', '>= 2.0.1'
20
+
21
+ # Files
22
+ s.has_rdoc = true
23
+ s.extra_rdoc_files = [ 'README.md' ]
24
+ s.files = `git ls-files`.split "\n"
25
+ s.test_files = `git ls-files -- spec/*`.split "\n"
26
+ s.require_paths = [ 'lib' ]
27
+ end
@@ -0,0 +1,48 @@
1
+ require 'bunny_mock/version'
2
+ require 'amq/protocol/client'
3
+
4
+ require 'bunny_mock/session'
5
+ require 'bunny_mock/channel'
6
+ require 'bunny_mock/exchange'
7
+ require 'bunny_mock/queue'
8
+
9
+ require 'bunny_mock/exchanges/direct'
10
+ require 'bunny_mock/exchanges/topic'
11
+ require 'bunny_mock/exchanges/fanout'
12
+ require 'bunny_mock/exchanges/headers'
13
+
14
+ ##
15
+ # A mock RabbitMQ client based on Bunny
16
+ #
17
+ # @see https://github.com/ruby-amq/bunny
18
+ #
19
+ module BunnyMock
20
+
21
+ # AMQP protocol version
22
+ PROTOCOL_VERSION = AMQ::Protocol::PROTOCOL_VERSION
23
+
24
+ #
25
+ # API
26
+ #
27
+
28
+ ##
29
+ # Instantiate a new mock Bunny session
30
+ #
31
+ # @return [BunnyMock::Session] Session instance
32
+ # @api public
33
+ def self.new(*args)
34
+
35
+ # return new mock session
36
+ BunnyMock::Session.new
37
+ end
38
+
39
+ # @return [String] Bunny mock version
40
+ def self.version
41
+ VERSION
42
+ end
43
+
44
+ # @return [String] AMQP protocol version
45
+ def self.protocol_version
46
+ AMQ::Protocol::PROTOCOL_VERSION
47
+ end
48
+ end
@@ -0,0 +1,310 @@
1
+ module BunnyMock
2
+ class Channel
3
+
4
+ #
5
+ # API
6
+ #
7
+
8
+ # @return [Integer] Channel identifier
9
+ attr_reader :id
10
+
11
+ # @return [BunnyMock::Session] Session this channel belongs to
12
+ attr_reader :connection
13
+
14
+ # @return [Symbol] Current channel state
15
+ attr_reader :status
16
+
17
+ # @return [Hash<String, BunnyMock::Exchange>] Exchanges created by this channel
18
+ attr_reader :exchanges
19
+
20
+ # @return [Hash<String, BunnyMock::Queue>] Queues created by this channel
21
+ attr_reader :queues
22
+
23
+ ##
24
+ # Create a new {BunnyMock::Channel} instance
25
+ #
26
+ # @param [BunnyMock::Session] connection Mocked session instance
27
+ # @param [Integer] id Channel identifier
28
+ #
29
+ # @api public
30
+ #
31
+ def initialize(connection = nil, id = nil)
32
+
33
+ # store channel id
34
+ @id = id
35
+
36
+ # store connection information
37
+ @connection = connection
38
+
39
+ # initialize exchange and queue storage
40
+ @exchanges = Hash.new
41
+ @queues = Hash.new
42
+
43
+ # set status to opening
44
+ @status = :opening
45
+ end
46
+
47
+ # @return [Boolean] true if status is open, false otherwise
48
+ # @api public
49
+ def open?
50
+ @status == :open
51
+ end
52
+
53
+ # @return [Boolean] true if status is closed, false otherwise
54
+ # @api public
55
+ def closed?
56
+ @status == :closed
57
+ end
58
+
59
+ ##
60
+ # Sets status to open
61
+ #
62
+ # @return [BunnyMock::Channel] self
63
+ # @api public
64
+ #
65
+ def open
66
+
67
+ @status = :open
68
+
69
+ self
70
+ end
71
+
72
+ ##
73
+ # Sets status to closed
74
+ #
75
+ # @return [BunnyMock::Channel] self
76
+ # @api public
77
+ #
78
+ def close
79
+
80
+ @status = :closed
81
+
82
+ self
83
+ end
84
+
85
+ # @return [String] Object representation
86
+ def to_s
87
+ "#<#{self.class.name}:#{self.object_id} @id=#{@id} @open=#{open?}>"
88
+ end
89
+ alias inspect to_s
90
+
91
+ # @group Exchange API
92
+
93
+ ##
94
+ # Mocks an exchange
95
+ #
96
+ # @param [String] name Exchange name
97
+ # @param [Hash] opts Exchange parameters
98
+ #
99
+ # @option opts [Symbol,String] :type Type of exchange
100
+ # @option opts [Boolean] :durable
101
+ # @option opts [Boolean] :auto_delete
102
+ # @option opts [Hash] :arguments
103
+ #
104
+ # @return [BunnyMock::Exchange] Mocked exchange instance
105
+ # @api public
106
+ #
107
+ def exchange(name, opts = {})
108
+
109
+ xchg = find_exchange(name) || Exchange.declare(self, name, opts)
110
+
111
+ register_exchange xchg
112
+ end
113
+
114
+ ##
115
+ # Mocks a fanout exchange
116
+ #
117
+ # @param [String] name Exchange name
118
+ # @param [Hash] opts Exchange parameters
119
+ #
120
+ # @option opts [Boolean] :durable
121
+ # @option opts [Boolean] :auto_delete
122
+ # @option opts [Hash] :arguments
123
+ #
124
+ # @return [BunnyMock::Exchange] Mocked exchange instance
125
+ # @api public
126
+ #
127
+ def fanout(name, opts = {})
128
+ self.exchange name, opts.merge(type: :fanout)
129
+ end
130
+
131
+ ##
132
+ # Mocks a direct exchange
133
+ #
134
+ # @param [String] name Exchange name
135
+ # @param [Hash] opts Exchange parameters
136
+ #
137
+ # @option opts [Boolean] :durable
138
+ # @option opts [Boolean] :auto_delete
139
+ # @option opts [Hash] :arguments
140
+ #
141
+ # @return [BunnyMock::Exchange] Mocked exchange instance
142
+ # @api public
143
+ #
144
+ def direct(name, opts = {})
145
+ self.exchange name, opts.merge(type: :direct)
146
+ end
147
+
148
+ ##
149
+ # Mocks a topic exchange
150
+ #
151
+ # @param [String] name Exchange name
152
+ # @param [Hash] opts Exchange parameters
153
+ #
154
+ # @option opts [Boolean] :durable
155
+ # @option opts [Boolean] :auto_delete
156
+ # @option opts [Hash] :arguments
157
+ #
158
+ # @return [BunnyMock::Exchange] Mocked exchange instance
159
+ # @api public
160
+ #
161
+ def topic(name, opts = {})
162
+ self.exchange name, opts.merge(type: :topic)
163
+ end
164
+
165
+ ##
166
+ # Mocks a headers exchange
167
+ #
168
+ # @param [String] name Exchange name
169
+ # @param [Hash] opts Exchange parameters
170
+ #
171
+ # @option opts [Boolean] :durable
172
+ # @option opts [Boolean] :auto_delete
173
+ # @option opts [Hash] :arguments
174
+ #
175
+ # @return [BunnyMock::Exchange] Mocked exchange instance
176
+ # @api public
177
+ #
178
+ def header(name, opts = {})
179
+ self.exchange name, opts.merge(type: :header)
180
+ end
181
+
182
+ ##
183
+ # Mocks RabbitMQ default exchange
184
+ #
185
+ # @return [BunnyMock::Exchange] Mocked default exchange instance
186
+ # @api public
187
+ #
188
+ def default_exchange
189
+ self.direct '', no_declare: true
190
+ end
191
+
192
+ # @endgroup
193
+
194
+ # @group Queue API
195
+
196
+ ##
197
+ # Create a new {BunnyMock::Queue} instance, or find in channel cache
198
+ #
199
+ # @param [String] name Name of queue
200
+ # @param [Hash] opts Queue creation options
201
+ #
202
+ # @return [BunnyMock::Queue] Queue that was mocked or looked up
203
+ # @api public
204
+ #
205
+ def queue(name = '', opts = {})
206
+
207
+ queue = find_queue(name) || Queue.new(self, name, opts)
208
+
209
+ register_queue queue
210
+ end
211
+
212
+ ##
213
+ # Create a new {BunnyMock::Queue} instance with no name
214
+ #
215
+ # @param [Hash] opts Queue creation options
216
+ #
217
+ # @return [BunnyMock::Queue] Queue that was mocked or looked up
218
+ # @see #queue
219
+ # @api public
220
+ #
221
+ def temporary_queue(opts = {})
222
+
223
+ queue '', opts.merge(exclusive: true)
224
+ end
225
+
226
+ # @endgroup
227
+
228
+ #
229
+ # Implementation
230
+ #
231
+
232
+ # @private
233
+ def find_queue(name)
234
+ @queues[name]
235
+ end
236
+
237
+ # @private
238
+ def register_queue(queue)
239
+ @queues[queue.name] = queue
240
+ end
241
+
242
+ # @private
243
+ def deregister_queue(queue)
244
+ @queues.delete queue.name
245
+ end
246
+
247
+ # @private
248
+ def queue_bind(queue, key, xchg)
249
+
250
+ exchange = @exchanges[xchg] || exchange(xchg)
251
+
252
+ exchange.add_route key, queue
253
+ end
254
+
255
+ # @private
256
+ def queue_unbind(key, xchg)
257
+
258
+ exchange = @exchanges[xchg] || exchange(xchg)
259
+
260
+ exchange.remove_route key
261
+ end
262
+
263
+ # @private
264
+ def xchg_bound_to?(receiver, key, name)
265
+
266
+ source = @exchanges[name] || exchange(name)
267
+
268
+ source.has_binding? receiver, routing_key: key
269
+ end
270
+
271
+ # @private
272
+ def xchg_has_binding?(key, xchg)
273
+
274
+ exchange = @exchanges[xchg] || exchange(xchg)
275
+
276
+ exchange.has_binding? key
277
+ end
278
+
279
+ # @private
280
+ def find_exchange(name)
281
+ @exchanges[name]
282
+ end
283
+
284
+ # @private
285
+ def register_exchange(xchg)
286
+ @exchanges[xchg.name] = xchg
287
+ end
288
+
289
+ # @private
290
+ def deregister_exchange(xchg)
291
+ @exchanges.delete xchg.name
292
+ end
293
+
294
+ # @private
295
+ def xchg_bind(receiver, routing_key, name)
296
+
297
+ source = @exchanges[name] || exchange(name)
298
+
299
+ source.add_route routing_key, receiver
300
+ end
301
+
302
+ # @private
303
+ def xchg_unbind(routing_key, name)
304
+
305
+ source = @exchanges[name] || exchange(name)
306
+
307
+ source.remove_route routing_key
308
+ end
309
+ end
310
+ end