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,266 @@
1
+ module BunnyMock
2
+ class Exchange
3
+
4
+ ##
5
+ # Create a new {BunnyMock::Exchange} instance
6
+ #
7
+ # @param [BunnyMock::Channel] channel Channel this exchange will use
8
+ # @param [String] name Name of exchange
9
+ # @param [Hash] opts Creation options
10
+ #
11
+ # @option opts [Boolean] :durable (false) Should this exchange be durable?
12
+ # @option opts [Boolean] :auto_delete (false) Should this exchange be automatically deleted when it is no longer used?
13
+ # @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
14
+ #
15
+ # @return [BunnyMock::Exchange] A new exchange
16
+ # @see BunnyMock::Channel#exchange
17
+ # @api public
18
+ #
19
+ def self.declare(channel, name = '', opts = {})
20
+
21
+ # get requested type
22
+ type = opts.fetch :type, :direct
23
+
24
+ # get needed class type
25
+ klazz = BunnyMock::Exchanges.const_get type.to_s.capitalize
26
+
27
+ # create exchange of desired type
28
+ klazz.new channel, name, type, opts
29
+ end
30
+
31
+ #
32
+ # API
33
+ #
34
+
35
+ # @return [BunnyMock::Channel] Channel used by exchange
36
+ attr_reader :channel
37
+
38
+ # @return [String] Exchange name
39
+ attr_reader :name
40
+
41
+ # @return [String] Exchange type
42
+ attr_reader :type
43
+
44
+ # @return [Hash] Creation options
45
+ attr_reader :opts
46
+
47
+ # @private
48
+ # @return [Boolean] If exchange has been deleted
49
+ attr_reader :deleted
50
+
51
+ # @private
52
+ def initialize(channel, name, type, opts)
53
+
54
+ # store creation information
55
+ @channel = channel
56
+ @name = name
57
+ @opts = opts
58
+ @type = type
59
+
60
+ # get options
61
+ @durable = @opts[:durable]
62
+ @auto_delete = @opts[:auto_delete]
63
+ @internal = @opts[:internal]
64
+ @arguments = @opts[:arguments]
65
+
66
+ # create binding storage
67
+ @routes = Hash.new
68
+ end
69
+
70
+ # @group Bunny API
71
+
72
+ # @return [Boolean] true if this exchange was declared as durable, false otherwise
73
+ # @api public
74
+ def durable?
75
+ @durable
76
+ end
77
+
78
+ # @return [Boolean] true if this exchange was set to auto delete, false otherwise
79
+ # @api public
80
+ def auto_delete?
81
+ @auto_delete
82
+ end
83
+
84
+ # @return [Boolean] true if this exchange was declared as internal, false otherwise
85
+ # @api public
86
+ def internal?
87
+ @internal
88
+ end
89
+
90
+ # @return [Hash] Additional option arguments
91
+ # @api public
92
+ def arguments
93
+ @arguments
94
+ end
95
+
96
+ ##
97
+ # Publish a message
98
+ #
99
+ # @param [Object] payload Message payload
100
+ # @param [Hash] opts Message properties
101
+ #
102
+ # @option opts [String] :routing_key Routing key
103
+ # @option opts [Boolean] :persistent Should the message be persisted to disk?
104
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
105
+ # @option opts [Integer] :timestamp A timestamp associated with this message
106
+ # @option opts [Integer] :expiration Expiration time after which the message will be deleted
107
+ # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
108
+ # @option opts [String] :reply_to Queue name other apps should send the response to
109
+ # @option opts [String] :content_type Message content type (e.g. application/json)
110
+ # @option opts [String] :content_encoding Message content encoding (e.g. gzip)
111
+ # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
112
+ # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
113
+ # @option opts [String] :message_id Any message identifier
114
+ # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
115
+ # @option opts [String] :app_id Optional application ID
116
+ #
117
+ # @return [BunnyMock::Exchange] self
118
+ # @see {BunnyMock::Exchanges::Direct#deliver}
119
+ # @see {BunnyMock::Exchanges::Topic#deliver}
120
+ # @see {BunnyMock::Exchanges::Fanout#deliver}
121
+ # @see {BunnyMock::Exchanges::Headers#deliver}
122
+ # @api public
123
+ #
124
+ def publish(payload, opts = {})
125
+
126
+ # handle message sending, varies by type
127
+ deliver payload, opts, opts.fetch(:routing_key, '')
128
+
129
+ self
130
+ end
131
+
132
+ ##
133
+ # Delete this exchange
134
+ #
135
+ # @param [Hash] opts Options (insignificant)
136
+ #
137
+ # @api public
138
+ #
139
+ def delete(opts = {})
140
+ @deleted = true
141
+ end
142
+
143
+ ##
144
+ # Bind this exchange to another exchange
145
+ #
146
+ # @param [BunnyMock::Exchange,String] exchange Exchange to bind to
147
+ # @param [Hash] opts Binding properties
148
+ #
149
+ # @option opts [String] :routing_key Custom routing key
150
+ #
151
+ # @return [BunnyMock::Exchange] self
152
+ # @api public
153
+ #
154
+ def bind(exchange, opts = {})
155
+
156
+ if exchange.respond_to?(:add_route)
157
+
158
+ # we can do the binding ourselves
159
+ exchange.add_route opts.fetch(:routing_key, @name), self
160
+
161
+ else
162
+
163
+ # we need the channel to look up the exchange
164
+ @channel.xchg_bind self, opts.fetch(:routing_key, @name), exchange
165
+ end
166
+
167
+ self
168
+ end
169
+
170
+ ##
171
+ # Unbind this exchange from another exchange
172
+ #
173
+ # @param [BunnyMock::Exchange,String] exchange Exchange to unbind from
174
+ # @param [Hash] opts Binding properties
175
+ #
176
+ # @option opts [String] :routing_key Custom routing key
177
+ #
178
+ # @api public
179
+ #
180
+ def unbind(exchange, opts = {})
181
+
182
+ if exchange.respond_to?(:remove_route)
183
+
184
+ # we can do the unbinding ourselves
185
+ exchange.remove_route opts.fetch(:routing_key, @name)
186
+
187
+ else
188
+
189
+ # we need the channel to look up the exchange
190
+ @channel.xchg_unbind opts.fetch(:routing_key, @name), exchange
191
+ end
192
+ end
193
+
194
+ # @endgroup
195
+
196
+ ##
197
+ # Check if this exchange is bound to another exchange
198
+ #
199
+ # @param [BunnyMock::Exchange,String] exchange Exchange to check
200
+ # @param [Hash] opts Binding properties
201
+ #
202
+ # @option opts [String] :routing_key Routing key from binding
203
+ #
204
+ # @return [Boolean] true if this exchange is bound to the given exchange, false otherwise
205
+ # @api public
206
+ #
207
+ def bound_to?(exchange, opts = {})
208
+
209
+ if exchange.respond_to?(:has_binding?)
210
+
211
+ # we can find out on the exchange object
212
+ exchange.has_binding? self, opts
213
+
214
+ else
215
+
216
+ # we need the channel to look up the exchange
217
+ @channel.xchg_bound_to? self, opts.fetch(:routing_key, @name), exchange
218
+ end
219
+ end
220
+
221
+ ##
222
+ # Check if a queue is bound to this exchange
223
+ #
224
+ # @param [BunnyMock::Queue,String] exchange_or_queue Exchange or queue to check
225
+ # @param [Hash] opts Binding properties
226
+ #
227
+ # @option opts [String] :routing_key Custom routing key
228
+ #
229
+ # @return [Boolean] true if the given queue or exchange matching options is bound to this exchange, false otherwise
230
+ # @api public
231
+ #
232
+ def has_binding?(exchange_or_queue, opts = {})
233
+
234
+ route = exchange_or_queue.respond_to?(:name) ? exchange_or_queue.name : exchange_or_queue
235
+
236
+ @routes.key? opts.fetch(:routing_key, route)
237
+ end
238
+
239
+ ##
240
+ # Deliver a message to routes
241
+ #
242
+ # @see {BunnyMock::Exchanges::Direct#deliver}
243
+ # @see {BunnyMock::Exchanges::Topic#deliver}
244
+ # @see {BunnyMock::Exchanges::Fanout#deliver}
245
+ # @see {BunnyMock::Exchanges::Headers#deliver}
246
+ # @api public
247
+ #
248
+ def deliver(payload, opts, key)
249
+ # noOp
250
+ end
251
+
252
+ #
253
+ # Implementation
254
+ #
255
+
256
+ # @private
257
+ def add_route(key, xchg_or_queue)
258
+ @routes[key] = xchg_or_queue
259
+ end
260
+
261
+ # @private
262
+ def remove_route(key)
263
+ @routes.delete key
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,24 @@
1
+ module BunnyMock
2
+ module Exchanges
3
+ class Direct < BunnyMock::Exchange
4
+
5
+ #
6
+ # API
7
+ #
8
+
9
+ ##
10
+ # Deliver a message to route with direct key match
11
+ #
12
+ # @param [Object] payload Message content
13
+ # @param [Hash] opts Message properties
14
+ # @param [String] key Routing key
15
+ #
16
+ # @api public
17
+ #
18
+ def deliver(payload, opts, key)
19
+
20
+ @routes[key].publish payload, opts if @routes[key]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ module BunnyMock
2
+ module Exchanges
3
+ class Fanout < BunnyMock::Exchange
4
+
5
+ #
6
+ # API
7
+ #
8
+
9
+ ##
10
+ # Deliver a message to all routes
11
+ #
12
+ # @param [Object] payload Message content
13
+ # @param [Hash] opts Message properties
14
+ # @param [String] key Routing key
15
+ #
16
+ # @api public
17
+ #
18
+ def deliver(payload, opts, key)
19
+
20
+ @routes.each do |route, destination|
21
+
22
+ # send to all routes
23
+ destination.publish payload, opts
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module BunnyMock
2
+ module Exchanges
3
+ class Header < BunnyMock::Exchange
4
+
5
+ # @private
6
+ # @return [Regexp] Any match
7
+ ANY = /^any$/i
8
+
9
+ # @private
10
+ # @return [Regexp] All match
11
+ ALL = /^all$/i
12
+
13
+ #
14
+ # API
15
+ #
16
+
17
+ ##
18
+ # Deliver a message to routes with header matches
19
+ #
20
+ # @param [Object] payload Message content
21
+ # @param [Hash] opts Message properties
22
+ # @param [String] key Routing key
23
+ #
24
+ # @api public
25
+ #
26
+ def deliver(payload, opts, key)
27
+
28
+ # ~: proper headers exchange implementation
29
+ @routes[key].publish payload, opts if @routes[key]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ module BunnyMock
2
+ module Exchanges
3
+ class Topic < BunnyMock::Exchange
4
+
5
+ # @private
6
+ # @return [String] Multiple subdomain wildcard
7
+ MULTI_WILDCARD = '#'
8
+
9
+ # @private
10
+ # @return [String] Single subdomain wildcard
11
+ SINGLE_WILDCARD = '*'
12
+
13
+ #
14
+ # API
15
+ #
16
+
17
+ ##
18
+ # Deliver a message to route with keys matching wildcards
19
+ #
20
+ # @param [Object] payload Message content
21
+ # @param [Hash] opts Message properties
22
+ # @param [String] key Routing key
23
+ #
24
+ # @api public
25
+ #
26
+ def deliver(payload, opts, key)
27
+
28
+ # escape periods with backslash for regex
29
+ key.gsub! '.', '\.'
30
+
31
+ # replace single wildcards with regex for a single domain
32
+ key.gsub! SINGLE_WILDCARD, '(\w+)'
33
+
34
+ # replace multi wildcards with regex for many domains separated by '.'
35
+ key.gsub! MULTI_WILDCARD, '\w+\.?'
36
+
37
+ # turn key into regex
38
+ key = Regexp.new key
39
+
40
+ # get all route keys for this exchange
41
+ delivery_keys = @routes.keys.dup
42
+
43
+ delivery_keys.each do |route|
44
+
45
+ # deliver to all matches
46
+ @routes[route].publish payload, opts if route =~ key
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,208 @@
1
+ module BunnyMock
2
+ class Queue
3
+
4
+ #
5
+ # API
6
+ #
7
+
8
+ # @return {BunnyMock::Channel} Channel used by queue
9
+ attr_reader :channel
10
+
11
+ # @return [String] Queue name
12
+ attr_reader :name
13
+
14
+ # @return [Hash] Creation options
15
+ attr_reader :opts
16
+
17
+ ##
18
+ # Create a new [BunnyMock::Queue] instance
19
+ #
20
+ # @param [BunnyMock::Channel] channel Channel this queue will use
21
+ # @param [String] name Name of queue
22
+ # @param [Hash] opts Creation options
23
+ #
24
+ # @see BunnyMock::Channel#queue
25
+ #
26
+ def initialize(channel, name = '', opts = {})
27
+
28
+ # Store creation information
29
+ @channel = channel
30
+ @name = name
31
+ @opts = opts
32
+
33
+ # Store messages
34
+ @messages = Array.new
35
+ end
36
+
37
+ # @group Bunny API
38
+
39
+ ##
40
+ # Publish a message
41
+ #
42
+ # @param [Object] payload Message payload
43
+ # @param [Hash] opts Message properties
44
+ #
45
+ # @option opts [String] :routing_key Routing key
46
+ # @option opts [Boolean] :persistent Should the message be persisted to disk?
47
+ # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue?
48
+ # @option opts [Integer] :timestamp A timestamp associated with this message
49
+ # @option opts [Integer] :expiration Expiration time after which the message will be deleted
50
+ # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string
51
+ # @option opts [String] :reply_to Queue name other apps should send the response to
52
+ # @option opts [String] :content_type Message content type (e.g. application/json)
53
+ # @option opts [String] :content_encoding Message content encoding (e.g. gzip)
54
+ # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for
55
+ # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications
56
+ # @option opts [String] :message_id Any message identifier
57
+ # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username
58
+ # @option opts [String] :app_id Optional application ID
59
+ #
60
+ # @return [BunnyMock::Queue] self
61
+ # @see {BunnyMock::Exchange#publish}
62
+ # @api public
63
+ #
64
+ def publish(payload, opts = {})
65
+
66
+ check_queue_deleted!
67
+
68
+ # add to messages
69
+ @messages << { message: payload, options: opts }
70
+
71
+ self
72
+ end
73
+
74
+ ##
75
+ # Bind this queue to an exchange
76
+ #
77
+ # @param [BunnyMock::Exchange,String] exchange Exchange to bind to
78
+ # @param [Hash] opts Binding properties
79
+ #
80
+ # @option opts [String] :routing_key Custom routing key
81
+ #
82
+ # @api public
83
+ #
84
+ def bind(exchange, opts = {})
85
+
86
+ check_queue_deleted!
87
+
88
+ if exchange.respond_to?(:add_route)
89
+
90
+ # we can do the binding ourselves
91
+ exchange.add_route opts.fetch(:routing_key, @name), self
92
+
93
+ else
94
+
95
+ # we need the channel to lookup the exchange
96
+ @channel.queue_bind self, opts.fetch(:routing_key, @name), exchange
97
+ end
98
+ end
99
+
100
+ ##
101
+ # Unbind this queue from an exchange
102
+ #
103
+ # @param [BunnyMock::Exchange,String] exchange Exchange to unbind from
104
+ # @param [Hash] opts Binding properties
105
+ #
106
+ # @option opts [String] :routing_key Custom routing key
107
+ #
108
+ # @api public
109
+ #
110
+ def unbind(exchange, opts = {})
111
+
112
+ check_queue_deleted!
113
+
114
+ if exchange.respond_to?(:remove_route)
115
+
116
+ # we can do the unbinding ourselves
117
+ exchange.remove_route opts.fetch(:routing_key, @name)
118
+
119
+ else
120
+
121
+ # we need the channel to lookup the exchange
122
+ @channel.queue_unbind opts.fetch(:routing_key, @name), exchange
123
+ end
124
+ end
125
+
126
+ # @endgroup
127
+
128
+ ##
129
+ # Check if this queue is bound to the exchange
130
+ #
131
+ # @param [BunnyMock::Exchange,String] exchange Exchange to test
132
+ # @param [Hash] opts Binding properties
133
+ #
134
+ # @option opts [String] :routing_key Routing key from binding
135
+ #
136
+ # @return [Boolean] true if this queue is bound to the given exchange, false otherwise
137
+ # @api public
138
+ #
139
+ def bound_to?(exchange, opts = {})
140
+
141
+ check_queue_deleted!
142
+
143
+ if exchange.respond_to?(:has_binding?)
144
+
145
+ # we can do the check ourselves
146
+ exchange.has_binding? opts.fetch(:routing_key, @name)
147
+
148
+ else
149
+
150
+ # we need the channel to lookup the exchange
151
+ @channel.xchg_has_binding? opts.fetch(:routing_key, @name), exchange
152
+ end
153
+ end
154
+
155
+ ##
156
+ # Count of messages in queue
157
+ #
158
+ # @return [Integer] Number of messages in queue
159
+ # @api public
160
+ #
161
+ def message_count
162
+ @messages.count
163
+ end
164
+
165
+ ##
166
+ # Get oldest message in queue
167
+ #
168
+ # @return [Hash] Message data
169
+ # @api public
170
+ #
171
+ def pop
172
+ @messages.shift
173
+ end
174
+
175
+ ##
176
+ # Clear all messages in queue
177
+ #
178
+ # @api public
179
+ #
180
+ def purge
181
+ @messages = []
182
+ end
183
+
184
+ ##
185
+ # Get all messages in queue
186
+ #
187
+ # @return [Array] All messages
188
+ # @api public
189
+ #
190
+ def all
191
+ @messages
192
+ end
193
+
194
+ ##
195
+ # Deletes this queue
196
+ #
197
+ # @api public
198
+ #
199
+ def delete
200
+ @deleted = true
201
+ end
202
+
203
+ # @private
204
+ def check_queue_deleted!
205
+ raise 'Queue has been deleted' if @deleted
206
+ end
207
+ end
208
+ end