bunny-mock 1.0.0

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