bunny-mock 1.5.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c50a06b6ad300048718d1da4576cf4aa26bb3f7a
4
- data.tar.gz: d3a46df9f089b40a09bf763a55dec107f1adde22
3
+ metadata.gz: 884e4998df1d5701f0384a2cb1c009d80ed81b67
4
+ data.tar.gz: 198414bf1eabff599a44310c514cbfa9ce770b0d
5
5
  SHA512:
6
- metadata.gz: a66ab64795f09af460aecb30c702a177caeed40f20603a2680bdccbc8c2307f6c312fb73ec1de039c4a787c21bf671569ae7ecacae9e59143e238d27b174e852
7
- data.tar.gz: fa47402ad74277744d11cbf0c560476ea507379b385d0ab2453f0d5ba1589d4938777244cd9b86a7241d608f47ade3b8535f5aa200217d3e9ef513ca480a0a44
6
+ metadata.gz: 868c41d0849c0ea46f2049b15e6ee9122d662f4953089baefc7e35efc37114baff7ea09c6f66bb322a03252820513115526f27e40e2c71c30f312a9526c34e7a
7
+ data.tar.gz: ee9f02bc843c883b4df2780a3373219bf283d9780219eccea3edc6e7bcfebca177c53806f6a379dba35469c22d2dfc1dc9dec7a2d8462eb8fbad34cc99d69f26
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.3
2
+ TargetRubyVersion: 2.0
3
3
  DisplayCopNames: true
4
4
  Include:
5
5
  - 'lib/**/*'
@@ -2,9 +2,14 @@
2
2
 
3
3
  _Add contribution here_
4
4
 
5
+ ## v1.6.0
6
+
7
+ * [#27](https://github.com/arempe93/bunny-mock/pull/27): Allow `Session#create_channel` to accept additional args - [@eebs](https://github.com/eebs)
8
+ * [#20](https://github.com/arempe93/bunny-mock/pull/30): Adds implementation of `ack` and `nack` - [@dwhenry](https://github.com/dwhenry)
9
+
5
10
  ## v1.5.0
6
11
 
7
- *[#20](https://github.com/arempe93/bunny-mock/pull/20): Adds implementation of `Queue#subscribe` - [@baelter](https://github.com/baelter)
12
+ * [#20](https://github.com/arempe93/bunny-mock/pull/20): Adds implementation of `Queue#subscribe` - [@baelter](https://github.com/baelter)
8
13
 
9
14
  ## v1.4.0
10
15
 
@@ -26,9 +26,11 @@ Gem::Specification.new do |s|
26
26
  end
27
27
 
28
28
  s.add_development_dependency 'rake', '~> 10.5.0'
29
- s.add_development_dependency 'rubocop'
29
+ s.add_development_dependency 'rubocop', '= 0.40.0'
30
30
  s.add_development_dependency 'yard'
31
31
  s.add_development_dependency 'rspec', '~> 3.4.0'
32
+ s.add_development_dependency 'term-ansicolor', '~> 1.3.0'
33
+ s.add_development_dependency 'tins', '= 1.6.0'
32
34
  s.add_development_dependency 'coveralls'
33
35
 
34
36
  s.files = `git ls-files`.split "\n"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'bunny_mock/version'
3
3
 
4
+ require 'timeout'
4
5
  require 'bunny/exceptions'
5
6
  require 'amq/protocol/client'
6
7
 
@@ -14,6 +14,9 @@ module BunnyMock
14
14
  # @return [Symbol] Current channel state
15
15
  attr_reader :status
16
16
 
17
+ # @return [Hash] with details of pending, acked and nacked messaged
18
+ attr_reader :acknowledged_state
19
+
17
20
  ##
18
21
  # Create a new {BunnyMock::Channel} instance
19
22
  #
@@ -32,6 +35,7 @@ module BunnyMock
32
35
  # initialize exchange and queue storage
33
36
  @exchanges = {}
34
37
  @queues = {}
38
+ @acknowledged_state = { pending: {}, acked: {}, nacked: {} }
35
39
 
36
40
  # set status to opening
37
41
  @status = :opening
@@ -256,13 +260,46 @@ module BunnyMock
256
260
  end
257
261
 
258
262
  ##
259
- # Does nothing atm.
263
+ # Acknowledge message.
264
+ #
265
+ # @param [Integer] delivery_tag Delivery tag to acknowledge
266
+ # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be acknowleded as well?
260
267
  #
261
268
  # @return nil
262
269
  # @api public
263
270
  #
264
- def acknowledge(*)
265
- # noop
271
+ def ack(delivery_tag, multiple = false)
272
+ if multiple
273
+ @acknowledged_state[:pending].keys.each do |key|
274
+ ack(key, false) if key <= delivery_tag
275
+ end
276
+ elsif @acknowledged_state[:pending].key?(delivery_tag)
277
+ update_acknowledgement_state(delivery_tag, :acked)
278
+ end
279
+ nil
280
+ end
281
+ alias acknowledge ack
282
+
283
+ ##
284
+ # Unacknowledge message.
285
+ #
286
+ # @param [Integer] delivery_tag Delivery tag to acknowledge
287
+ # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be rejected as well?
288
+ # @param [Boolean] requeue (false) Should this message be requeued instead of dropping it?
289
+ #
290
+ # @return nil
291
+ # @api public
292
+ #
293
+ def nack(delivery_tag, multiple = false, requeue = false)
294
+ if multiple
295
+ @acknowledged_state[:pending].keys.each do |key|
296
+ nack(key, false, requeue) if key <= delivery_tag
297
+ end
298
+ elsif @acknowledged_state[:pending].key?(delivery_tag)
299
+ delivery, header, body = update_acknowledgement_state(delivery_tag, :nacked)
300
+ delivery.queue.publish(body, header.to_hash) if requeue
301
+ end
302
+ nil
266
303
  end
267
304
 
268
305
  ##
@@ -345,5 +382,10 @@ module BunnyMock
345
382
  def xchg_find_or_create(name, opts = {})
346
383
  @connection.find_exchange(name) || Exchange.declare(self, name, opts)
347
384
  end
385
+
386
+ # @private
387
+ def update_acknowledgement_state(delivery_tag, new_state)
388
+ @acknowledged_state[new_state][delivery_tag] = @acknowledged_state[:pending].delete(delivery_tag)
389
+ end
348
390
  end
349
391
  end
@@ -4,11 +4,11 @@ module BunnyMock
4
4
  class Topic < BunnyMock::Exchange
5
5
  # @private
6
6
  # @return [String] Multiple subdomain wildcard
7
- MULTI_WILDCARD = '#'
7
+ MULTI_WILDCARD = '#'.freeze
8
8
 
9
9
  # @private
10
10
  # @return [String] Single subdomain wildcard
11
- SINGLE_WILDCARD = '*'
11
+ SINGLE_WILDCARD = '*'.freeze
12
12
 
13
13
  #
14
14
  # API
@@ -14,11 +14,15 @@ module BunnyMock
14
14
  # @return [BunnyMock::Channel] Channel the response is from
15
15
  attr_reader :channel
16
16
 
17
+ # @return [BunnyMock::Queue] Queue the response is from
18
+ attr_reader :queue
19
+
17
20
  # @private
18
21
  def initialize(channel, queue, opts = {})
19
22
  @channel = channel
23
+ @queue = queue
20
24
  @hash = {
21
- delivery_tag: '',
25
+ delivery_tag: self.class.next_delivery_tag,
22
26
  redelivered: false,
23
27
  exchange: opts.fetch(:exchange, ''),
24
28
  routing_key: opts.fetch(:routing_key, queue.name)
@@ -72,5 +76,11 @@ module BunnyMock
72
76
  def routing_key
73
77
  @hash[:routing_key]
74
78
  end
79
+
80
+ # @return [Integer] incrementing numerically value to support `#ack` with multiple=true
81
+ def self.next_delivery_tag
82
+ @delivery_tag ||= 0
83
+ @delivery_tag += 1
84
+ end
75
85
  end
76
86
  end
@@ -72,14 +72,30 @@ module BunnyMock
72
72
  ##
73
73
  # Adds a consumer to the queue (subscribes for message deliveries).
74
74
  #
75
- # All params are ignored atm. Takes a block which is called when a message is delivered
76
- # to the queue
75
+ # Params are so they can be used when the message is processed. Takes a block which is called when a message
76
+ # is delivered to the queue
77
77
  #
78
78
  # @api public
79
79
  #
80
- def subscribe(*_args, &block)
80
+ def subscribe(*args, &block)
81
81
  @consumers ||= []
82
- @consumers << block
82
+ @consumers << [block, args]
83
+ yield_consumers
84
+
85
+ self
86
+ end
87
+
88
+ ##
89
+ # Adds a specific consumer object to the queue (subscribes for message deliveries).
90
+ #
91
+ # @param [#call] consumer A subclass of Bunny::Consumer or any callable object
92
+ # Secondary params are so they can be used when the message is processed.
93
+ #
94
+ # @api public
95
+ #
96
+ def subscribe_with(consumer, *args)
97
+ @consumers ||= []
98
+ @consumers << [consumer, args]
83
99
  yield_consumers
84
100
 
85
101
  self
@@ -241,13 +257,21 @@ module BunnyMock
241
257
  # @private
242
258
  def yield_consumers
243
259
  return if @consumers.nil?
244
- @consumers.each do |c|
260
+ @consumers.each do |c, args|
245
261
  # rubocop:disable AssignmentInCondition
246
262
  while message = all.pop
247
263
  response = pop_response(message)
264
+ store_acknowledgement(response, args)
248
265
  c.call(response)
249
266
  end
250
267
  end
251
268
  end
269
+
270
+ def store_acknowledgement(response, args)
271
+ if args[0].is_a?(Hash) && args[0][:manual_ack]
272
+ delivery_tag = response[0][:delivery_tag]
273
+ @channel.acknowledged_state[:pending][delivery_tag] = response
274
+ end
275
+ end
252
276
  end
253
277
  end
@@ -88,9 +88,9 @@ module BunnyMock
88
88
  #
89
89
  # @return [BunnyMock::Channel] Channel instance
90
90
  # @api public
91
- def create_channel(n = nil, _pool_size = 1)
91
+ def create_channel(n = nil, _pool_size = 1, *_args)
92
92
  # raise same error as {Bunny::Session#create_channel}
93
- raise ArgumentError, 'channel number 0 is reserved in the protocol and cannot be used' if n == 0
93
+ raise ArgumentError, 'channel number 0 is reserved in the protocol and cannot be used' if n && n.zero?
94
94
 
95
95
  # return cached channel if exists
96
96
  return @channels[n] if n && @channels.key?(n)
@@ -3,5 +3,5 @@
3
3
 
4
4
  module BunnyMock
5
5
  # @return [String] Version of the library
6
- VERSION = '1.5.0'
6
+ VERSION = '1.6.0'.freeze
7
7
  end
@@ -0,0 +1,87 @@
1
+ describe BunnyMock::Channel, 'acknowledgement' do
2
+ let(:queue) { @channel.queue('test.q') }
3
+ let(:delivery_tags) { {} }
4
+
5
+ context 'when `manual_ack` = true' do
6
+ before do
7
+ queue.subscribe(manual_ack: true) do |delivery, _headers, body|
8
+ delivery_tags[body] = delivery[:delivery_tag]
9
+ end
10
+ queue.publish 'Another message on the queue'
11
+ end
12
+
13
+ it 'should allow messages which have been acked to be identified' do
14
+ queue.publish 'Message to acknowledge'
15
+ delivery_tag = delivery_tags['Message to acknowledge']
16
+ @channel.ack delivery_tag
17
+
18
+ expect(@channel.acknowledged_state[:pending]).not_to include(delivery_tag)
19
+ expect(@channel.acknowledged_state[:acked]).to include(delivery_tag)
20
+ end
21
+
22
+ it 'should allow messages which have not been acked to be identified' do
23
+ queue.publish 'Message without acknowledgement'
24
+ delivery_tag = delivery_tags['Message without acknowledgement']
25
+
26
+ expect(@channel.acknowledged_state[:pending]).to include(delivery_tag)
27
+ expect(@channel.acknowledged_state[:acked]).not_to include(delivery_tag)
28
+ end
29
+
30
+ it 'should allow messages which have been nacked to be identified' do
31
+ queue.publish 'Message to nack'
32
+ delivery_tag = delivery_tags['Message to nack']
33
+ @channel.nack delivery_tag
34
+
35
+ expect(@channel.acknowledged_state[:pending]).not_to include(delivery_tag)
36
+ expect(@channel.acknowledged_state[:acked]).not_to include(delivery_tag)
37
+ expect(@channel.acknowledged_state[:nacked]).to include(delivery_tag)
38
+ end
39
+
40
+ it 'should requeue messages with have been nacked with `requeue` = true' do
41
+ queue.publish 'Message to nack'
42
+ delivery_tag = delivery_tags['Message to nack']
43
+ @channel.nack delivery_tag, false, true
44
+ new_delivery_tag = delivery_tags['Message to nack']
45
+
46
+ expect(@channel.acknowledged_state[:pending]).not_to include(delivery_tag)
47
+ expect(@channel.acknowledged_state[:acked]).not_to include(delivery_tag)
48
+ expect(@channel.acknowledged_state[:nacked]).to include(delivery_tag)
49
+
50
+ expect(@channel.acknowledged_state[:pending]).to include(new_delivery_tag)
51
+ end
52
+
53
+ it 'should allow multiple messages to be acked when `multiple` = true' do
54
+ queue.publish 'Message to be automatically acked'
55
+ delivery_tag_1 = delivery_tags['Message to be automatically acked']
56
+ queue.publish 'Message to acked'
57
+ delivery_tag_2 = delivery_tags['Message to acked']
58
+ queue.publish 'Message to be left as pending'
59
+ delivery_tag_3 = delivery_tags['Message to be left as pending']
60
+
61
+ @channel.ack delivery_tag_2, true
62
+
63
+ expect(@channel.acknowledged_state[:pending]).not_to include(delivery_tag_1)
64
+ expect(@channel.acknowledged_state[:pending]).not_to include(delivery_tag_2)
65
+ expect(@channel.acknowledged_state[:pending]).to include(delivery_tag_3)
66
+ expect(@channel.acknowledged_state[:acked]).to include(delivery_tag_1)
67
+ expect(@channel.acknowledged_state[:acked]).to include(delivery_tag_2)
68
+ end
69
+
70
+ it 'should allow multiple messages to be nacked when `multiple` = true' do
71
+ queue.publish 'Message to be automatically nacked'
72
+ delivery_tag_1 = delivery_tags['Message to be automatically nacked']
73
+ queue.publish 'Message to nacked'
74
+ delivery_tag_2 = delivery_tags['Message to nacked']
75
+ queue.publish 'Message to be left as pending'
76
+ delivery_tag_3 = delivery_tags['Message to be left as pending']
77
+
78
+ @channel.nack delivery_tag_2, true
79
+
80
+ expect(@channel.acknowledged_state[:pending]).not_to include(delivery_tag_1)
81
+ expect(@channel.acknowledged_state[:pending]).not_to include(delivery_tag_2)
82
+ expect(@channel.acknowledged_state[:pending]).to include(delivery_tag_3)
83
+ expect(@channel.acknowledged_state[:nacked]).to include(delivery_tag_1)
84
+ expect(@channel.acknowledged_state[:nacked]).to include(delivery_tag_2)
85
+ end
86
+ end
87
+ end
@@ -172,6 +172,39 @@ describe BunnyMock::Queue do
172
172
  end
173
173
  @queue.publish 'test'
174
174
  end
175
+
176
+ it 'should create responses with uniq delivery_tags' do
177
+ delivery_tags = []
178
+ @queue.subscribe do |delivery, _headers, _body|
179
+ delivery_tags << delivery[:delivery_tag]
180
+ end
181
+ @queue.publish 'test one'
182
+ @queue.publish 'test two'
183
+
184
+ expect(delivery_tags.uniq.count).to eq(2)
185
+ end
186
+
187
+ context 'when `manual_ack` is set to true' do
188
+
189
+ it 'should mark the message as unacknowledged on the channel before processing' do
190
+ @queue.subscribe(manual_ack: true) do |delivery, _headers, _body|
191
+ expect(@channel.acknowledged_state[:pending]).to include(delivery[:delivery_tag])
192
+ end
193
+ @queue.publish 'test'
194
+ end
195
+ end
196
+ end
197
+
198
+ context '#subscribe_with' do
199
+
200
+ it 'should consume messages delivered' do
201
+ consumer = proc do |_delivery, _headers, body|
202
+ expect(body).to eq('test')
203
+ end
204
+
205
+ @queue.subscribe_with consumer
206
+ @queue.publish 'test'
207
+ end
175
208
  end
176
209
 
177
210
  context '#purge' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny-mock
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Rempe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-17 00:00:00.000000000 Z
11
+ date: 2017-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: rubocop
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 0.40.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 0.40.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: yard
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 3.4.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: term-ansicolor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.3.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.3.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: tins
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.6.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.6.0
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: coveralls
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,6 +156,7 @@ files:
128
156
  - lib/bunny_mock/queue.rb
129
157
  - lib/bunny_mock/session.rb
130
158
  - lib/bunny_mock/version.rb
159
+ - spec/integration/message_acknowledgement_spec.rb
131
160
  - spec/integration/queue_pop_spec.rb
132
161
  - spec/integration/queue_subscribe_spec.rb
133
162
  - spec/spec_helper.rb
@@ -159,11 +188,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
188
  version: '0'
160
189
  requirements: []
161
190
  rubyforge_project:
162
- rubygems_version: 2.5.1
191
+ rubygems_version: 2.6.4
163
192
  signing_key:
164
193
  specification_version: 4
165
194
  summary: Mocking for the popular Bunny client for RabbitMQ
166
195
  test_files:
196
+ - spec/integration/message_acknowledgement_spec.rb
167
197
  - spec/integration/queue_pop_spec.rb
168
198
  - spec/integration/queue_subscribe_spec.rb
169
199
  - spec/spec_helper.rb