fake_servicebus 0.0.2

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.
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