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,28 @@
1
+ module FakeServiceBus
2
+ module Actions
3
+ class ListQueues
4
+
5
+ def initialize(options = {})
6
+ @server = options.fetch(:server)
7
+ @queues = options.fetch(:queues)
8
+ @responder = options.fetch(:responder)
9
+ @request = options.fetch(:request)
10
+ end
11
+
12
+ def call(params)
13
+ found = @queues.list(params)
14
+ xml = Builder::XmlMarkup.new()
15
+ xml.tag! "feed", :xmlns=>"http://www.w3.org/2005/Atom" do
16
+ xml.title "Queues", :type=>"text"
17
+ xml.id "https://fake_servicebus/$Resources/Queues"
18
+ xml.updated Time.now.utc.iso8601
19
+ xml.link :rel=>"self", :href=>"fake_servicebus/$Resources/Queues"
20
+ found.each do |queue|
21
+ @responder.queue xml, queue
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ module FakeServiceBus
2
+ module Actions
3
+ class ReceiveMessage
4
+
5
+ MAX_WAIT_TIME_SECONDS = 20
6
+
7
+ def initialize(options = {})
8
+ @server = options.fetch(:server)
9
+ @queues = options.fetch(:queues)
10
+ @start_ts = Time.now.to_f
11
+ @satisfied = false
12
+ end
13
+
14
+ def call(queue_name, params)
15
+ queue = @queues.get(queue_name)
16
+ message = queue.receive_message(params.merge(queues: @queues))
17
+ @satisfied = !message.nil? || expired?(queue, params)
18
+ if !message.nil?
19
+ [201,
20
+ {'location'=>message.location,
21
+ 'BrokerProperties'=>{'SequenceNumber'=>message.sequence_number,
22
+ 'LockToken'=>message.lock_token}.to_json},
23
+ message.body]
24
+ end
25
+ end
26
+
27
+ def satisfied?
28
+ @satisfied
29
+ end
30
+
31
+ protected
32
+ def elapsed
33
+ Time.now.to_f - @start_ts
34
+ end
35
+
36
+ def expired?(queue, params)
37
+ wait_time_seconds = Integer params.fetch("timeout") { 0 }
38
+ wait_time_seconds <= 0 ||
39
+ elapsed >= wait_time_seconds ||
40
+ elapsed >= MAX_WAIT_TIME_SECONDS
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ module FakeServiceBus
2
+ module Actions
3
+ class RenewLockMessage
4
+
5
+ def initialize(options = {})
6
+ @server = options.fetch(:server)
7
+ @queues = options.fetch(:queues)
8
+ end
9
+
10
+ def call(queue_name, lock_token, params)
11
+ queue = @queues.get(queue_name)
12
+
13
+ queue.renew_lock_message(lock_token)
14
+ 200
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module FakeServiceBus
2
+ module Actions
3
+ class SendMessage
4
+
5
+ def initialize(options = {})
6
+ @server = options.fetch(:server)
7
+ @queues = options.fetch(:queues)
8
+ @request = options.fetch(:request)
9
+ end
10
+
11
+ def call(queue_name, params)
12
+ queue = @queues.get(queue_name)
13
+ message = queue.send_message(params.merge(
14
+ {'queue_name'=>queue_name,
15
+ 'body'=>@request.body.read})
16
+ )
17
+ 201
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module FakeServiceBus
2
+ module Actions
3
+ class UnlockMessage
4
+
5
+ def initialize(options = {})
6
+ @server = options.fetch(:server)
7
+ @queues = options.fetch(:queues)
8
+ end
9
+
10
+ def call(queue_name, lock_token, params)
11
+ queue = @queues.get(queue_name)
12
+
13
+ queue.unlock_message(lock_token)
14
+ 200
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ require 'fake_servicebus/actions/create_queue'
2
+ require 'fake_servicebus/actions/delete_queue'
3
+ require 'fake_servicebus/actions/list_queues'
4
+ require 'fake_servicebus/actions/get_queue'
5
+ require 'fake_servicebus/actions/send_message'
6
+ require 'fake_servicebus/actions/receive_message'
7
+ require 'fake_servicebus/actions/unlock_message'
8
+ require 'fake_servicebus/actions/renew_lock_message'
9
+ require 'fake_servicebus/actions/delete_message'
10
+
11
+ module FakeServiceBus
12
+
13
+ InvalidAction = Class.new(ArgumentError)
14
+
15
+ class API
16
+
17
+ attr_reader :queues, :options
18
+
19
+ def initialize(options = {})
20
+ @queues = options.fetch(:queues)
21
+ @options = options
22
+ @halt = false
23
+ @timer = Thread.new do
24
+ until @halt
25
+ queues.timeout_messages!
26
+ sleep(0.1)
27
+ end
28
+ end
29
+ end
30
+
31
+ def call(action, request, *args)
32
+ if FakeServiceBus::Actions.const_defined?(action)
33
+ action = FakeServiceBus::Actions.const_get(action).new(options.merge({:request => request}))
34
+ if action.respond_to?(:satisfied?)
35
+ result = nil
36
+ until @halt
37
+ result = attempt_once(action, *args)
38
+ break if action.satisfied?
39
+ sleep(0.1)
40
+ end
41
+ result
42
+ else
43
+ attempt_once(action, *args)
44
+ end
45
+ else
46
+ fail InvalidAction, "Unknown (or not yet implemented) action: #{action}"
47
+ end
48
+ end
49
+
50
+ def attempt_once(action, *args)
51
+ queues.transaction do
52
+ action.call(*args)
53
+ end
54
+ end
55
+
56
+ # Fake actions
57
+
58
+ def reset
59
+ queues.reset
60
+ end
61
+
62
+ def expire
63
+ queues.expire
64
+ end
65
+
66
+ def stop
67
+ @halt = true
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,19 @@
1
+ require 'securerandom'
2
+
3
+ module FakeServiceBus
4
+ class CatchErrors
5
+
6
+ def initialize(app, options = {})
7
+ @app = app
8
+ @response = options.fetch(:response)
9
+ end
10
+
11
+ def call(env)
12
+ @app.call(env)
13
+ rescue => error
14
+ response = @response.new(error)
15
+ [ response.status, {}, [ response.body ] ]
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require 'forwardable'
2
+
3
+ module FakeServiceBus
4
+ class CollectionView
5
+ include Enumerable
6
+ extend Forwardable
7
+ def_delegators :@original, :[], :each, :empty?, :size, :length
8
+
9
+ UnmodifiableObjectError = Class.new(StandardError)
10
+
11
+ def initialize( original )
12
+ @original = original
13
+ end
14
+
15
+ def []=(key_or_index,value)
16
+ raise UnmodifiableObjectError.new("This is a collection view and can not be modified - #{key_or_index} => #{value}")
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ module FakeServiceBus
2
+ class Daemonize
3
+
4
+ attr_reader :pid
5
+
6
+ def initialize(options)
7
+ @pid = options.fetch(:pid) {
8
+ warn "No PID file specified while daemonizing!"
9
+ exit 1
10
+ }
11
+ end
12
+
13
+ def call
14
+ Process.daemon(true, true)
15
+
16
+ if File.exist?(pid)
17
+ existing_pid = File.open(pid, 'r').read.chomp.to_i
18
+ running = Process.getpgid(existing_pid) rescue false
19
+ if running
20
+ warn "Error, Process #{existing_pid} already running"
21
+ exit 1
22
+ else
23
+ warn "Cleaning up stale pid at #{pid}"
24
+ end
25
+ end
26
+ File.open(pid, 'w') { |f| f.write(Process.pid) }
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,129 @@
1
+ require "yaml/store"
2
+
3
+ module FakeServiceBus
4
+ class FileDatabase
5
+
6
+ attr_reader :filename
7
+
8
+ def initialize(filename)
9
+ @filename = filename
10
+ @queue_objects = {}
11
+ unless thread_safe_store?
12
+ # before ruby 2.4, YAML::Store cannot be declared thread safe
13
+ #
14
+ # without that declaration, attempting to have some thread B enter a
15
+ # store.transaction on the store while another thread A is in one
16
+ # already will raise an error unnecessarily.
17
+ #
18
+ # to prevent this, we'll use our own mutex around store.transaction,
19
+ # so only one thread can even _try_ to enter the transaction at a
20
+ # time.
21
+ @store_mutex = Mutex.new
22
+ @store_mutex_owner = nil
23
+ end
24
+ end
25
+
26
+ def load
27
+ transaction do
28
+ store["queues"] ||= {}
29
+ end
30
+ end
31
+
32
+ def transaction
33
+ if thread_safe_store? || store_mutex_owned?
34
+ # if we already own the store mutex, we can expect the next line to
35
+ # raise (appropriately) when we try to nest transactions in the store.
36
+ # but if we took the other branch, the # @store_mutex.synchronize call
37
+ # would self-deadlock before we could raise the error.
38
+ store.transaction do
39
+ yield
40
+ end
41
+ else
42
+ # we still need to use an inner store.transaction block because it does
43
+ # more than just lock synchronization. it's unfortunately inefficient,
44
+ # but this isn't a production-oriented library.
45
+ @store_mutex.synchronize do
46
+ begin
47
+ # allows us to answer `store_mutex_owned?` above
48
+ @store_mutex_owner = Thread.current
49
+ store.transaction do
50
+ yield
51
+ end
52
+ ensure
53
+ @store_mutex_owner = nil
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def reset
60
+ transaction do
61
+ store["queues"] = {}
62
+ end
63
+ @queue_objects = {}
64
+ end
65
+
66
+ def []=(key, value)
67
+ storage[key] = value.to_yaml
68
+ end
69
+
70
+ def [](key)
71
+ value = storage[key]
72
+ if value
73
+ deserialize(key)
74
+ else
75
+ value
76
+ end
77
+ end
78
+
79
+ def each(&block)
80
+ storage.each do |key, value|
81
+ yield key, deserialize(key)
82
+ end
83
+ end
84
+
85
+ def select(&block)
86
+ new_list = storage.select do |key, value|
87
+ yield key, deserialize(key)
88
+ end
89
+ Hash[new_list.map { |key, value| [key, deserialize(key)] }]
90
+ end
91
+
92
+ def delete(key)
93
+ @queue_objects.delete(key)
94
+ storage.delete(key)
95
+ end
96
+
97
+ def values
98
+ storage.map { |key, value|
99
+ deserialize(key)
100
+ }
101
+ end
102
+
103
+ private
104
+
105
+ def deserialize(key)
106
+ @queue_objects[key] ||= Queue.new(storage[key].merge(name: key, message_factory: Message))
107
+ end
108
+
109
+ def storage
110
+ store["queues"]
111
+ end
112
+
113
+ def thread_safe_store?
114
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.4")
115
+ end
116
+
117
+ def store_mutex_owned?
118
+ # this could be just "@store_mutex && @store_mutex.owned?" in ruby 2.x,
119
+ # but we still support 1.9.3 which doesn't have the "owned?" method
120
+ @store_mutex_owner == Thread.current
121
+ end
122
+
123
+ def store
124
+ @store ||= thread_safe_store? ?
125
+ YAML::Store.new(filename, true) :
126
+ YAML::Store.new(filename)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,30 @@
1
+ require "forwardable"
2
+ require "thread"
3
+
4
+ module FakeServiceBus
5
+ class MemoryDatabase
6
+ extend Forwardable
7
+
8
+ def_delegators :@queues,
9
+ :[], :[]=, :delete, :each, :select, :values
10
+
11
+ def initialize
12
+ @semaphore = Mutex.new
13
+ end
14
+
15
+ def load
16
+ @queues = {}
17
+ end
18
+
19
+ def transaction
20
+ @semaphore.synchronize do
21
+ yield
22
+ end
23
+ end
24
+
25
+ def reset
26
+ @queues = {}
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ require 'yaml'
2
+ require 'builder'
3
+ require 'securerandom'
4
+
5
+ module FakeServiceBus
6
+ class ErrorResponse
7
+
8
+ attr_reader :error
9
+
10
+ def initialize(error)
11
+ @error = error
12
+ end
13
+
14
+ def status
15
+ @status ||= statuses.fetch(code)
16
+ end
17
+
18
+ def body
19
+ xml = Builder::XmlMarkup.new()
20
+ xml.ErrorResponse do
21
+ xml.Error do
22
+ xml.Type type
23
+ xml.Code code
24
+ xml.Message error.to_s
25
+ xml.Detail
26
+ end
27
+ xml.RequestId SecureRandom.uuid
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def code
34
+ code = error.class.name.sub(/^FakeServiceBus::/, '')
35
+ if statuses.has_key?(code)
36
+ code
37
+ else
38
+ "InternalError"
39
+ end
40
+ end
41
+
42
+ def type
43
+ if status < 500
44
+ "Sender"
45
+ else
46
+ "Receiver"
47
+ end
48
+ end
49
+
50
+ def statuses
51
+ @statuses ||= YAML.load_file(File.expand_path('../error_responses.yml', __FILE__))
52
+ end
53
+
54
+ end
55
+ end