fake_servicebus 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.md +20 -0
  7. data/README.md +41 -0
  8. data/Rakefile +31 -0
  9. data/bin/fake_servicebus +65 -0
  10. data/fake_servicebus.gemspec +33 -0
  11. data/lib/fake_servicebus.rb +90 -0
  12. data/lib/fake_servicebus/actions/create_queue.rb +64 -0
  13. data/lib/fake_servicebus/actions/delete_message.rb +19 -0
  14. data/lib/fake_servicebus/actions/delete_queue.rb +18 -0
  15. data/lib/fake_servicebus/actions/get_queue.rb +20 -0
  16. data/lib/fake_servicebus/actions/list_queues.rb +28 -0
  17. data/lib/fake_servicebus/actions/receive_message.rb +44 -0
  18. data/lib/fake_servicebus/actions/renew_lock_message.rb +19 -0
  19. data/lib/fake_servicebus/actions/send_message.rb +22 -0
  20. data/lib/fake_servicebus/actions/unlock_message.rb +19 -0
  21. data/lib/fake_servicebus/api.rb +71 -0
  22. data/lib/fake_servicebus/catch_errors.rb +19 -0
  23. data/lib/fake_servicebus/collection_view.rb +20 -0
  24. data/lib/fake_servicebus/daemonize.rb +30 -0
  25. data/lib/fake_servicebus/databases/file.rb +129 -0
  26. data/lib/fake_servicebus/databases/memory.rb +30 -0
  27. data/lib/fake_servicebus/error_response.rb +55 -0
  28. data/lib/fake_servicebus/error_responses.yml +32 -0
  29. data/lib/fake_servicebus/message.rb +59 -0
  30. data/lib/fake_servicebus/queue.rb +201 -0
  31. data/lib/fake_servicebus/queue_factory.rb +16 -0
  32. data/lib/fake_servicebus/queues.rb +72 -0
  33. data/lib/fake_servicebus/responder.rb +41 -0
  34. data/lib/fake_servicebus/server.rb +19 -0
  35. data/lib/fake_servicebus/show_output.rb +21 -0
  36. data/lib/fake_servicebus/test_integration.rb +122 -0
  37. data/lib/fake_servicebus/version.rb +3 -0
  38. data/lib/fake_servicebus/web_interface.rb +74 -0
  39. data/spec/acceptance/message_actions_spec.rb +452 -0
  40. data/spec/acceptance/queue_actions_spec.rb +82 -0
  41. data/spec/integration_spec_helper.rb +23 -0
  42. data/spec/spec_helper.rb +3 -0
  43. data/spec/unit/api_spec.rb +76 -0
  44. data/spec/unit/catch_errors_spec.rb +43 -0
  45. data/spec/unit/collection_view_spec.rb +41 -0
  46. data/spec/unit/error_response_spec.rb +65 -0
  47. data/spec/unit/message_spec.rb +76 -0
  48. data/spec/unit/queue_factory_spec.rb +13 -0
  49. data/spec/unit/queue_spec.rb +204 -0
  50. data/spec/unit/queues_spec.rb +102 -0
  51. data/spec/unit/responder_spec.rb +44 -0
  52. data/spec/unit/show_output_spec.rb +22 -0
  53. data/spec/unit/web_interface_spec.rb +15 -0
  54. metadata +266 -0
@@ -0,0 +1,21 @@
1
+ require 'rack'
2
+ require 'yaml'
3
+
4
+ module FakeServiceBus
5
+ class ShowOutput
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ request = Rack::Request.new(env)
13
+ result = @app.call(env)
14
+ puts request.params.to_yaml
15
+ puts
16
+ puts(*result.last)
17
+ result
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,122 @@
1
+ require "net/http"
2
+
3
+ module FakeServiceBus
4
+ class TestIntegration
5
+
6
+ attr_reader :options
7
+
8
+ def initialize(options = {})
9
+ @options = options
10
+ end
11
+
12
+ def host
13
+ option :servicebus_endpoint
14
+ end
15
+
16
+ def port
17
+ option :servicebus_port
18
+ end
19
+
20
+ def start
21
+ start! unless up?
22
+ reset
23
+ end
24
+
25
+ def start!
26
+ args = [ binfile, "-p", port.to_s, verbose, logging, "--database", database, { :out => out, :err => out } ].flatten.compact
27
+ @pid = Process.spawn(*args)
28
+ wait_until_up(Time.now + start_timeout)
29
+ end
30
+
31
+ def stop
32
+ if @pid
33
+ Process.kill("INT", @pid)
34
+ Process.waitpid(@pid)
35
+ @pid = nil
36
+ else
37
+ $stderr.puts "FakeServiceBus is not running"
38
+ end
39
+ end
40
+
41
+ def reset
42
+ connection.delete("/")
43
+ end
44
+
45
+ def expire
46
+ connection.put("/", "")
47
+ end
48
+
49
+ def url
50
+ "http://#{host}:#{port}"
51
+ end
52
+
53
+ def uri
54
+ URI(url)
55
+ end
56
+
57
+ def up?
58
+ @pid && connection.get("/ping").code.to_s == "200"
59
+ rescue Errno::ECONNREFUSED
60
+ false
61
+ end
62
+
63
+ private
64
+
65
+ def option(key)
66
+ options.fetch(key)
67
+ end
68
+
69
+ def database
70
+ options.fetch(:database)
71
+ end
72
+
73
+ def start_timeout
74
+ options[:start_timeout] || 2
75
+ end
76
+
77
+ def verbose
78
+ if options[:verbose]
79
+ "--verbose"
80
+ else
81
+ "--no-verbose"
82
+ end
83
+ end
84
+
85
+ def logging
86
+ if (file = ENV["ServiceBus_LOG"] || options[:log])
87
+ [ "--log", file ]
88
+ else
89
+ []
90
+ end
91
+ end
92
+
93
+ def wait_until_up(deadline)
94
+ fail "FakeServiceBus didn't start in time" if Time.now > deadline
95
+ unless up?
96
+ sleep 0.1
97
+ wait_until_up(deadline)
98
+ end
99
+ end
100
+
101
+ def binfile
102
+ File.expand_path("../../../bin/fake_servicebus", __FILE__)
103
+ end
104
+
105
+ def out
106
+ if debug?
107
+ :out
108
+ else
109
+ "/dev/null"
110
+ end
111
+ end
112
+
113
+ def connection
114
+ @connection ||= Net::HTTP.new(host, port)
115
+ end
116
+
117
+ def debug?
118
+ ENV["DEBUG"].to_s == "true" || options[:debug]
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,3 @@
1
+ module FakeServiceBus
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,74 @@
1
+ require 'sinatra/base'
2
+ require 'fake_servicebus/catch_errors'
3
+ require 'fake_servicebus/error_response'
4
+
5
+ module FakeServiceBus
6
+ class WebInterface < Sinatra::Base
7
+
8
+ def self.handle(path, verbs, &block)
9
+ verbs.each do |verb|
10
+ send(verb, path, &block)
11
+ end
12
+ end
13
+
14
+ configure do
15
+ use FakeServiceBus::CatchErrors, response: ErrorResponse
16
+ end
17
+
18
+ helpers do
19
+ def action
20
+ params.fetch("Action")
21
+ end
22
+ end
23
+
24
+ get "/ping" do
25
+ 200
26
+ end
27
+
28
+ delete "/" do
29
+ settings.api.reset
30
+ 200
31
+ end
32
+
33
+ put "/" do
34
+ settings.api.expire
35
+ 200
36
+ end
37
+
38
+ handle "/$Resources/Queues", [:get] do
39
+ settings.api.call(:ListQueues, request, params)
40
+ end
41
+
42
+ handle "/:queue_name", [:put] do |queue_name|
43
+ settings.api.call(:CreateQueue, request, queue_name, params)
44
+ end
45
+
46
+ handle "/:queue_name", [:delete] do |queue_name|
47
+ settings.api.call(:DeleteQueue, request, queue_name, params)
48
+ end
49
+
50
+ handle "/:queue_name", [:get] do |queue_name|
51
+ settings.api.call(:GetQueue, request, queue_name, params)
52
+ end
53
+
54
+ handle "/:queue_name/messages", [:post] do |queue_name|
55
+ settings.api.call(:SendMessage, request, queue_name, params)
56
+ end
57
+
58
+ handle "/:queue_name/messages/head", [:post, :delete] do |queue_name|
59
+ settings.api.call(:ReceiveMessage, request, queue_name, params)
60
+ end
61
+
62
+ handle "/:queue_name/messages/:sequence_number/:lock_token", [:put] do |queue_name, sequence_number, lock_token|
63
+ settings.api.call(:UnlockMessage, request, queue_name, lock_token, params)
64
+ end
65
+
66
+ handle "/:queue_name/messages/:sequence_number/:lock_token", [:post] do |queue_name, sequence_number, lock_token|
67
+ settings.api.call(:RenewLockMessage, request, queue_name, lock_token, params)
68
+ end
69
+
70
+ handle "/:queue_name/messages/:sequence_number/:lock_token", [:delete] do |queue_name, sequence_number, lock_token|
71
+ settings.api.call(:DeleteMessage, request, queue_name, lock_token, params)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,452 @@
1
+ require "integration_spec_helper"
2
+ require "securerandom"
3
+
4
+ RSpec.describe "Actions for Messages", :sqs do
5
+
6
+ QUEUE_NAME = "test"
7
+
8
+ before do
9
+ sqs.config.endpoint = $fake_sqs.uri
10
+ sqs.create_queue(queue_name: QUEUE_NAME)
11
+ end
12
+
13
+ let(:sqs) { Aws::SQS::Client.new }
14
+ let(:queue_url) { sqs.get_queue_url(queue_name: QUEUE_NAME).queue_url }
15
+
16
+ specify "SendMessage" do
17
+ msg = "this is my message"
18
+
19
+ result = sqs.send_message(
20
+ queue_url: queue_url,
21
+ message_body: msg,
22
+ )
23
+
24
+ expect(result.md5_of_message_body).to eq Digest::MD5.hexdigest(msg)
25
+ expect(result.message_id.size).to eq 36
26
+ end
27
+
28
+ specify "ReceiveMessage" do
29
+ body = "test 123"
30
+
31
+ sqs.send_message(
32
+ queue_url: queue_url,
33
+ message_body: body
34
+ )
35
+
36
+ response = sqs.receive_message(
37
+ queue_url: queue_url
38
+ )
39
+
40
+ expect(response.messages.size).to eq 1
41
+ expect(response.messages.first.body).to eq body
42
+ end
43
+
44
+ specify "ReceiveMessage with attribute_names parameters" do
45
+ body = "test 123"
46
+
47
+ sqs.send_message(
48
+ queue_url: queue_url,
49
+ message_body: body
50
+ )
51
+
52
+ sent_time = Time.now.to_i * 1000
53
+
54
+ response = sqs.receive_message(
55
+ queue_url: queue_url,
56
+ attribute_names: ["All"]
57
+ )
58
+
59
+ received_time = Time.now.to_i * 1000
60
+
61
+ expect(response.messages.first.attributes.reject{|k,v| k == "SenderId"}).to eq({
62
+ "SentTimestamp" => sent_time.to_s,
63
+ "ApproximateReceiveCount" => "1",
64
+ "ApproximateFirstReceiveTimestamp" => received_time.to_s
65
+ })
66
+ expect(response.messages.first.attributes["SenderId"]).to be_kind_of(String)
67
+ expire_message(response.messages.first)
68
+
69
+ response = sqs.receive_message(
70
+ queue_url: queue_url
71
+ )
72
+ expect(response.messages.first.attributes).to eq({})
73
+ expire_message(response.messages.first)
74
+
75
+ response = sqs.receive_message(
76
+ queue_url: queue_url,
77
+ attribute_names: ["SentTimestamp", "ApproximateReceiveCount", "ApproximateFirstReceiveTimestamp"]
78
+ )
79
+ expect(response.messages.first.attributes).to eq({
80
+ "SentTimestamp" => sent_time.to_s,
81
+ "ApproximateReceiveCount" => "3",
82
+ "ApproximateFirstReceiveTimestamp" => received_time.to_s
83
+ })
84
+ end
85
+
86
+ describe "ReceiveMessage long polling" do
87
+ LONG_POLLING_QUEUE_NAME = 'test-long-polling'
88
+
89
+ before do
90
+ sqs.create_queue(
91
+ queue_name: LONG_POLLING_QUEUE_NAME,
92
+ )
93
+ sqs.set_queue_attributes(
94
+ queue_url: long_polling_queue_url,
95
+ attributes: {
96
+ "ReceiveMessageWaitTimeSeconds" => "1"
97
+ }
98
+ )
99
+ end
100
+
101
+ let(:long_polling_queue_url) { sqs.get_queue_url(queue_name: LONG_POLLING_QUEUE_NAME).queue_url }
102
+
103
+ specify "default behavior is no long polling" do
104
+ start = Time.now
105
+ response = sqs.receive_message(
106
+ queue_url: queue_url
107
+ )
108
+
109
+ expect(response.messages.size).to eq 0
110
+ expect(Time.now - start).to be < 0.5
111
+ end
112
+
113
+ specify "can configure long polling on queue" do
114
+ start = Time.now
115
+ response = sqs.receive_message(
116
+ queue_url: long_polling_queue_url
117
+ )
118
+
119
+ expect(response.messages.size).to eq 0
120
+ expect(Time.now - start).to be > 1
121
+ end
122
+
123
+ specify "specifying WaitTimeSeconds overrides queue configuration" do
124
+ start = Time.now
125
+ response = sqs.receive_message(
126
+ queue_url: queue_url,
127
+ wait_time_seconds: 1,
128
+ )
129
+
130
+ expect(response.messages.size).to eq 0
131
+ expect(Time.now - start).to be > 1
132
+
133
+ start = Time.now
134
+ response = sqs.receive_message(
135
+ queue_url: long_polling_queue_url,
136
+ wait_time_seconds: 2,
137
+ )
138
+
139
+ expect(response.messages.size).to eq 0
140
+ expect(Time.now - start).to be > 2
141
+
142
+ start = Time.now
143
+ response = sqs.receive_message(
144
+ queue_url: long_polling_queue_url,
145
+ wait_time_seconds: 0,
146
+ )
147
+
148
+ expect(response.messages.size).to eq 0
149
+ expect(Time.now - start).to be < 0.5
150
+ end
151
+
152
+ specify "a non-empty result immediately returns without waiting" do
153
+ body = "test 123"
154
+
155
+ sqs.send_message(
156
+ queue_url: queue_url,
157
+ message_body: body
158
+ )
159
+
160
+ start = Time.now
161
+ response = sqs.receive_message(
162
+ queue_url: queue_url,
163
+ wait_time_seconds: 1,
164
+ )
165
+
166
+ expect(response.messages.size).to eq 1
167
+ expect(Time.now - start).to be < 0.5
168
+ end
169
+ end
170
+
171
+ specify "DeleteMessage" do
172
+ sqs.send_message(
173
+ queue_url: queue_url,
174
+ message_body: "test",
175
+ )
176
+
177
+ message1 = sqs.receive_message(
178
+ queue_url: queue_url,
179
+ ).messages.first
180
+
181
+ let_messages_in_flight_expire
182
+
183
+ sqs.delete_message(
184
+ queue_url: queue_url,
185
+ receipt_handle: message1.receipt_handle,
186
+ )
187
+
188
+ response = sqs.receive_message(
189
+ queue_url: queue_url,
190
+ )
191
+ expect(response.messages.size).to eq 0
192
+ end
193
+
194
+ specify "DeleteMessageBatch" do
195
+ sqs.send_message(
196
+ queue_url: queue_url,
197
+ message_body: "test1"
198
+ )
199
+ sqs.send_message(
200
+ queue_url: queue_url,
201
+ message_body: "test2"
202
+ )
203
+
204
+ messages_response = sqs.receive_message(
205
+ queue_url: queue_url,
206
+ max_number_of_messages: 2,
207
+ )
208
+ expect(messages_response.messages.size).to eq 2
209
+
210
+ let_messages_in_flight_expire
211
+
212
+ response = sqs.delete_message_batch(
213
+ queue_url: queue_url,
214
+ entries: messages_response.messages.map { |msg|
215
+ {
216
+ id: SecureRandom.uuid,
217
+ receipt_handle: msg.receipt_handle,
218
+ }
219
+ },
220
+ )
221
+ expect(response.successful.size).to eq(2)
222
+
223
+ messages_response = sqs.receive_message(
224
+ queue_url: queue_url,
225
+ max_number_of_messages: 2,
226
+ )
227
+ expect(messages_response.messages.size).to eq 0
228
+ end
229
+
230
+ specify "PurgeQueue" do
231
+ sqs.send_message(
232
+ queue_url: queue_url,
233
+ message_body: "test1"
234
+ )
235
+ sqs.send_message(
236
+ queue_url: queue_url,
237
+ message_body: "test2"
238
+ )
239
+
240
+ sqs.purge_queue(
241
+ queue_url: queue_url,
242
+ )
243
+
244
+ response = sqs.receive_message(
245
+ queue_url: queue_url,
246
+ )
247
+ expect(response.messages.size).to eq 0
248
+ end
249
+
250
+ specify "DeleteQueue" do
251
+ sent_message = sqs.send_message(
252
+ queue_url: queue_url,
253
+ message_body: "test1"
254
+ )
255
+
256
+ response = sqs.receive_message(
257
+ queue_url: queue_url,
258
+ )
259
+ expect(response.messages[0].message_id).to eq sent_message.message_id
260
+ expect(response.messages.size).to eq 1
261
+
262
+ let_messages_in_flight_expire
263
+
264
+ sqs.delete_queue(queue_url: queue_url)
265
+ sqs.create_queue(queue_name: QUEUE_NAME)
266
+
267
+ response = sqs.receive_message(
268
+ queue_url: queue_url,
269
+ )
270
+ expect(response.messages.size).to eq 0
271
+ end
272
+
273
+ specify "SendMessageBatch" do
274
+ bodies = %w(a b c)
275
+
276
+ response = sqs.send_message_batch(
277
+ queue_url: queue_url,
278
+ entries: bodies.map { |bd|
279
+ {
280
+ id: SecureRandom.uuid,
281
+ message_body: bd,
282
+ }
283
+ }
284
+ )
285
+ expect(response.successful.size).to eq(3)
286
+
287
+ messages_response = sqs.receive_message(
288
+ queue_url: queue_url,
289
+ max_number_of_messages: 3,
290
+ )
291
+ expect(messages_response.messages.map(&:body)).to match_array bodies
292
+ end
293
+
294
+ specify "set message timeout to 0" do
295
+ body = 'some-sample-message'
296
+
297
+ sqs.send_message(
298
+ queue_url: queue_url,
299
+ message_body: body,
300
+ )
301
+
302
+ message = sqs.receive_message(
303
+ queue_url: queue_url,
304
+ visibility_timeout: 10,
305
+ ).messages.first
306
+ expect(message.body).to eq body
307
+
308
+ sqs.change_message_visibility(
309
+ queue_url: queue_url,
310
+ receipt_handle: message.receipt_handle,
311
+ visibility_timeout: 0,
312
+ )
313
+
314
+ same_message = sqs.receive_message(
315
+ queue_url: queue_url,
316
+ ).messages.first
317
+ expect(same_message.body).to eq body
318
+ end
319
+
320
+ specify 'set message timeout and wait for message to come' do
321
+ body = 'some-sample-message'
322
+
323
+ sqs.send_message(
324
+ queue_url: queue_url,
325
+ message_body: body,
326
+ )
327
+
328
+ message = sqs.receive_message(
329
+ queue_url: queue_url,
330
+ visibility_timeout: 10,
331
+ ).messages.first
332
+ expect(message.body).to eq body
333
+
334
+ sqs.change_message_visibility(
335
+ queue_url: queue_url,
336
+ receipt_handle: message.receipt_handle,
337
+ visibility_timeout: 1,
338
+ )
339
+
340
+ nothing = sqs.receive_message(
341
+ queue_url: queue_url,
342
+ )
343
+ expect(nothing.messages.size).to eq 0
344
+
345
+ sleep(2)
346
+
347
+ same_message = sqs.receive_message(
348
+ queue_url: queue_url,
349
+ ).messages.first
350
+ expect(same_message.body).to eq body
351
+ end
352
+
353
+ specify 'should fail if trying to update the visibility_timeout for a message that is not in flight' do
354
+ response = sqs.send_message(
355
+ queue_url: queue_url,
356
+ message_body: 'some-sample-message',
357
+ )
358
+
359
+ expect {
360
+ sqs.change_message_visibility(
361
+ queue_url: queue_url,
362
+ receipt_handle: response.message_id,
363
+ visibility_timeout: 30
364
+ )
365
+ }.to raise_error(Aws::SQS::Errors::MessageNotInflight)
366
+ end
367
+
368
+ specify 'ChangeMessageVisibilityBatch' do
369
+ bodies = (1..10).map { |n| n.to_s }
370
+ response = sqs.send_message_batch(
371
+ queue_url: queue_url,
372
+ entries: bodies.map { |bd|
373
+ {
374
+ id: SecureRandom.uuid,
375
+ message_body: bd,
376
+ }
377
+ }
378
+ )
379
+ expect(response.successful.size).to eq(10)
380
+
381
+ message = sqs.receive_message(
382
+ queue_url: queue_url,
383
+ max_number_of_messages: 10,
384
+ visibility_timeout: 1,
385
+ )
386
+ expect(message.messages.size).to eq(10)
387
+
388
+ response = sqs.change_message_visibility_batch(
389
+ queue_url: queue_url,
390
+ entries: message.messages.map { |m|
391
+ {
392
+ id: m.message_id,
393
+ receipt_handle: m.receipt_handle,
394
+ visibility_timeout: 10,
395
+ }
396
+ }
397
+ )
398
+ expect(response.successful.size).to eq(10)
399
+
400
+ sleep(2)
401
+
402
+ message = sqs.receive_message(
403
+ queue_url: queue_url,
404
+ max_number_of_messages: 10,
405
+ )
406
+ expect(message.messages.size).to eq(0)
407
+ end
408
+
409
+ specify 'should be moved to configured DLQ after maxReceiveCount if RedrivePolicy is set' do
410
+ dlq_queue_url = sqs.create_queue(queue_name: "TestSourceQueueDLQ").queue_url
411
+
412
+ dlq_arn = sqs.get_queue_attributes(queue_url: dlq_queue_url).attributes.fetch("QueueArn")
413
+ sqs.set_queue_attributes(
414
+ queue_url: queue_url,
415
+ attributes: {
416
+ "RedrivePolicy" => "{\"deadLetterTargetArn\":\"#{dlq_arn}\",\"maxReceiveCount\":2}"
417
+ }
418
+ )
419
+
420
+ message_id = sqs.send_message(
421
+ queue_url: queue_url,
422
+ message_body: "test",
423
+ ).message_id
424
+
425
+
426
+ 2.times do
427
+ message = sqs.receive_message(queue_url: queue_url)
428
+ expect(message.messages.size).to eq(1)
429
+ expect(message.messages.first.message_id).to eq(message_id)
430
+ expire_message(message.messages.first)
431
+ end
432
+
433
+ expect(sqs.receive_message(queue_url: queue_url).messages.size).to eq(0)
434
+
435
+ message = sqs.receive_message(queue_url: dlq_queue_url)
436
+ expect(message.messages.size).to eq(1)
437
+ expect(message.messages.first.message_id).to eq(message_id)
438
+ end
439
+
440
+ def let_messages_in_flight_expire
441
+ $fake_sqs.expire
442
+ end
443
+
444
+ def expire_message(message)
445
+ sqs.change_message_visibility(
446
+ queue_url: queue_url,
447
+ receipt_handle: message.receipt_handle,
448
+ visibility_timeout: 0,
449
+ )
450
+ end
451
+
452
+ end