isimud 0.7.0 → 1.3.1

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -2
  4. data/.ruby-version +1 -1
  5. data/.yardoc/checksums +10 -10
  6. data/.yardoc/object_types +0 -0
  7. data/.yardoc/objects/root.dat +0 -0
  8. data/Gemfile +2 -1
  9. data/Gemfile.lock +63 -73
  10. data/LICENSE.txt +19 -0
  11. data/README.md +51 -3
  12. data/Rakefile +5 -0
  13. data/doc/Isimud/BunnyClient.html +882 -179
  14. data/doc/Isimud/Client.html +236 -18
  15. data/doc/Isimud/Event.html +211 -95
  16. data/doc/Isimud/EventListener.html +325 -307
  17. data/doc/Isimud/EventObserver/ClassMethods.html +14 -14
  18. data/doc/Isimud/EventObserver.html +418 -36
  19. data/doc/Isimud/Generators/ConfigGenerator.html +2 -2
  20. data/doc/Isimud/Generators/InitializerGenerator.html +2 -2
  21. data/doc/Isimud/Generators.html +2 -2
  22. data/doc/Isimud/Logging.html +3 -3
  23. data/doc/Isimud/ModelWatcher/ClassMethods.html +3 -3
  24. data/doc/Isimud/ModelWatcher.html +2 -2
  25. data/doc/Isimud/Railtie.html +2 -2
  26. data/doc/Isimud/TestClient/Queue.html +374 -71
  27. data/doc/Isimud/TestClient.html +169 -161
  28. data/doc/Isimud.html +80 -76
  29. data/doc/_index.html +5 -2
  30. data/doc/file.LICENSE.html +73 -0
  31. data/doc/file.README.html +131 -7
  32. data/doc/file_list.html +3 -0
  33. data/doc/index.html +131 -7
  34. data/doc/method_list.html +183 -105
  35. data/doc/top-level-namespace.html +2 -2
  36. data/isimud.gemspec +18 -16
  37. data/lib/isimud/bunny_client.rb +85 -32
  38. data/lib/isimud/client.rb +23 -7
  39. data/lib/isimud/event.rb +11 -14
  40. data/lib/isimud/event_listener.rb +123 -65
  41. data/lib/isimud/event_observer.rb +70 -10
  42. data/lib/isimud/model_watcher.rb +1 -1
  43. data/lib/isimud/test_client.rb +54 -26
  44. data/lib/isimud/version.rb +1 -1
  45. data/lib/isimud.rb +1 -1
  46. data/spec/internal/app/models/company.rb +8 -10
  47. data/spec/internal/app/models/user.rb +11 -1
  48. data/spec/internal/db/schema.rb +5 -0
  49. data/spec/isimud/bunny_client_spec.rb +21 -9
  50. data/spec/isimud/client_spec.rb +40 -0
  51. data/spec/isimud/event_listener_spec.rb +50 -22
  52. data/spec/isimud/event_observer_spec.rb +107 -16
  53. data/spec/isimud/model_watcher_spec.rb +18 -23
  54. data/spec/isimud/test_client_spec.rb +43 -8
  55. data/spec/isimud_spec.rb +2 -2
  56. data/spec/spec_helper.rb +2 -0
  57. metadata +19 -35
  58. checksums.yaml.gz.sig +0 -0
  59. data/certs/gfeil.pem +0 -21
  60. data/release +0 -31
  61. data.tar.gz.sig +0 -0
  62. metadata.gz.sig +0 -2
@@ -4,6 +4,9 @@ require 'active_support/concern'
4
4
  require 'active_support/core_ext/module/attribute_accessors'
5
5
 
6
6
  # Module for attaching and listening to events
7
+ #
8
+ # Note: the following columns must be defined in your model:
9
+ # :exchange_routing_keys text
7
10
  module Isimud
8
11
  module EventObserver
9
12
  extend ::ActiveSupport::Concern
@@ -17,9 +20,19 @@ module Isimud
17
20
  Mutex.new
18
21
  end
19
22
 
23
+ included do
24
+ register_class
25
+ before_save :set_routing_keys
26
+ serialize :exchange_routing_keys, Array
27
+ after_commit :create_queue, on: :create, if: :enable_listener?, prepend: true
28
+ after_commit :update_queue, on: :update, prepend: true
29
+ after_commit :delete_queue, on: :destroy, prepend: true
30
+ include Isimud::ModelWatcher unless self.include?(Isimud::ModelWatcher)
31
+ end
32
+
20
33
  # Event handling hook. Override in your class.
21
34
  def handle_event(event)
22
- Rails.logger.warn("Isimud::EventObserver#handle_event not implemented for #{event_queue_name}")
35
+ logger.warn("Isimud::EventObserver#handle_event not implemented for #{event_queue_name}")
23
36
  end
24
37
 
25
38
  # Routing keys that are bound to the event queue. Override in your subclass
@@ -32,16 +45,18 @@ module Isimud
32
45
  true
33
46
  end
34
47
 
35
- # Exchange used for listening to events. Override in your subclass if you want to specify an alternative exchange for
36
- # events. Otherwise
48
+ # Exchange used for listening to events. Override in your subclass if you want to specify an alternative exchange.
37
49
  def observed_exchange
38
50
  nil
39
51
  end
40
52
 
41
53
  # Create or attach to a queue on the specified exchange. When an event message that matches the observer's routing keys
42
54
  # is received, parse the event and call handle_event on same.
43
- def observe_events(client, default_exchange)
44
- client.bind(event_queue_name, observed_exchange || default_exchange, *routing_keys) do |message|
55
+ # Returns the consumer for the observer
56
+ def observe_events(client)
57
+ return unless enable_listener?
58
+ queue = client.kind_of?(Isimud::TestClient) ? create_queue(client) : client.find_queue(event_queue_name)
59
+ client.subscribe(queue) do |message|
45
60
  event = Event.parse(message)
46
61
  handle_event(event)
47
62
  end
@@ -51,6 +66,56 @@ module Isimud
51
66
  self.class.event_queue_name(id)
52
67
  end
53
68
 
69
+ def isimud_client
70
+ Isimud.client
71
+ end
72
+
73
+ # Activate the queues for an observer. This will create the observer queue and send an update message on the
74
+ # instance, which will trigger EventListener instances to set up consumers. This is useful for situations when
75
+ # an observer is to be made active without an update.
76
+ def activate_observer(client = isimud_client)
77
+ create_queue(client)
78
+ isimud_send_action_message(:update)
79
+ end
80
+
81
+ # Deactivate the queues for an observer. This will destroy the observer queue and send an update message on the
82
+ # instance, which will trigger EventListener instances to cancel consumers. Note that enable_listener? should
83
+ # resolve to false in order for the EventListener to cancel corresponding event consumers.
84
+ def deactivate_observer(client = isimud_client)
85
+ delete_queue(client)
86
+ isimud_send_action_message(:update)
87
+ end
88
+
89
+ private
90
+
91
+ def create_queue(client = isimud_client)
92
+ exchange = observed_exchange || Isimud.events_exchange
93
+ log "Isimud::EventObserver: creating queue #{event_queue_name} on exchange #{exchange} with bindings [#{exchange_routing_keys.join(',')}]"
94
+ client.create_queue(event_queue_name, exchange, routing_keys: exchange_routing_keys)
95
+ end
96
+
97
+ def update_queue
98
+ if enable_listener?
99
+ routing_key_changes = previous_changes[:exchange_routing_keys]
100
+ prev_keys = routing_key_changes.try(:[], 0) || []
101
+ current_keys = routing_key_changes.try(:[], 1) || exchange_routing_keys
102
+ queue = isimud_client.find_queue(event_queue_name)
103
+ exchange = observed_exchange || Isimud.events_exchange
104
+ (prev_keys - current_keys).each { |key| queue.unbind(exchange, routing_key: key) }
105
+ (current_keys).each { |key| queue.bind(exchange, routing_key: key) }
106
+ else
107
+ isimud_client.delete_queue(event_queue_name)
108
+ end
109
+ end
110
+
111
+ def delete_queue
112
+ isimud_client.delete_queue(event_queue_name)
113
+ end
114
+
115
+ def set_routing_keys
116
+ self.exchange_routing_keys = routing_keys
117
+ end
118
+
54
119
  module ClassMethods
55
120
  # Method used to retrieve active observers. Override in your EventObserver class
56
121
  def find_active_observers
@@ -76,10 +141,5 @@ module Isimud
76
141
  end
77
142
  end
78
143
  end
79
-
80
- included do
81
- include Isimud::ModelWatcher unless self.include?(Isimud::ModelWatcher)
82
- register_class
83
- end
84
144
  end
85
145
  end
@@ -135,7 +135,7 @@ module Isimud
135
135
  id: id,
136
136
  timestamp: (updated_at || Time.now).utc
137
137
  }
138
- payload[:attributes] = isimud_attribute_data unless action == :destroy
138
+ payload[:attributes] = isimud_attribute_data
139
139
  routing_key = isimud_model_watcher_routing_key(action)
140
140
  log "Isimud::ModelWatcher#publish: exchange #{isimud_model_watcher_exchange} routing_key #{routing_key} payload #{payload.inspect}"
141
141
  Isimud.client.publish(isimud_model_watcher_exchange, routing_key, payload.to_json)
@@ -1,42 +1,66 @@
1
1
  module Isimud
2
+
3
+ # Interface for a messaging client that is suitable for testing. No network connections are involved.
4
+ # Note that all message deliveries are handled in a synchronous manner. When a message is published to the
5
+ # client, each declared queue is examined and, if the message's routing key matches any of the patterns bound to the
6
+ # queue, the queue's block is called with the message. Any uncaught exceptions raised within a message processing
7
+ # block will cause any declared exception handlers to be run. However, the message will not be re-placed onto the
8
+ # queue should this occur.
2
9
  class TestClient < Isimud::Client
3
10
  attr_accessor :queues
4
11
 
5
12
  class Queue
6
13
  include Isimud::Logging
7
- attr_reader :name, :routing_keys
14
+ attr_reader :name, :bindings
15
+ attr_accessor :proc
8
16
 
9
- def initialize(name, listener)
17
+ def initialize(name, proc = Proc.new{ |_| } )
10
18
  @name = name
11
- @listener = listener
12
- @routing_keys = Set.new
19
+ @bindings = Hash.new{ |hash, key| hash[key] = Set.new }
20
+ @proc = proc
21
+ end
22
+
23
+ def bind(exchange, opts = {})
24
+ routing_key = opts[:routing_key]
25
+ log "TestClient: adding routing key #{routing_key} for exchange #{exchange} to queue #{name}"
26
+ @bindings[exchange] << routing_key
27
+ end
28
+
29
+ def cancel
30
+ @proc = nil
13
31
  end
14
32
 
15
- def bind(exchange, options = {})
16
- key = "\\A#{options[:routing_key]}\\Z"
17
- log "TestClient: adding routing key #{key} to queue #{name}"
18
- @routing_keys << Regexp.new(key.gsub(/\./, "\\.").gsub(/\*/, ".*"))
33
+ def delete(opts = {})
34
+ log "TestClient: delete queue #{name}"
35
+ @bindings.clear
36
+ @proc = nil
19
37
  end
20
38
 
21
- def unbind(exchange)
22
- @routing_keys.clear
39
+ def unbind(exchange, opts = {})
40
+ routing_key = opts[:routing_key]
41
+ log "TestClient: removing routing key #{routing_key} for exchange #{exchange} from queue #{name}"
42
+ @bindings[exchange].delete(routing_key)
23
43
  end
24
44
 
25
- def has_matching_key?(route)
26
- @routing_keys.any? { |k| route =~ k }
45
+ def make_regexp(key)
46
+ Regexp.new(key.gsub(/\./, "\\.").gsub(/\*/, '.*'))
47
+ end
48
+
49
+ def has_matching_key?(exchange, route)
50
+ @bindings[exchange].any? { |key| route =~ make_regexp(key) }
27
51
  end
28
52
 
29
53
  def deliver(data)
30
54
  begin
31
- @listener.call(data)
55
+ @proc.try(:call, data)
32
56
  rescue => e
33
57
  log "TestClient: error delivering message: #{e.message}\n #{e.backtrace.join("\n ")}", :error
34
- @listener.exception_handler.try(:call, e)
58
+ run_exception_handlers(e)
35
59
  end
36
60
  end
37
61
  end
38
62
 
39
- def initialize(connection = nil, options = nil)
63
+ def initialize(connection = nil, options = {})
40
64
  self.queues = Hash.new
41
65
  end
42
66
 
@@ -59,29 +83,33 @@ module Isimud
59
83
  queues.delete(queue_name)
60
84
  end
61
85
 
62
- def bind(queue_name, exchange_name, *keys, &method)
63
- create_queue(queue_name, exchange_name, routing_keys: keys, &method)
86
+ def bind(queue_name, exchange_name, *keys, &block)
87
+ queue = create_queue(queue_name, exchange_name, routing_keys: keys)
88
+ subscribe(queue, &block)
64
89
  end
65
90
 
66
- def rebind(queue_name, exchange_name, keys)
67
- queue = queues[queue_name] || return
68
- queue.unbind(exchange_name)
69
- keys.each { |key| queue.bind(exchange_name, routing_key: key) }
91
+ def find_queue(queue_name, options = {})
92
+ queues[queue_name] ||= Queue.new(queue_name)
70
93
  end
71
94
 
72
- def create_queue(queue_name, exchange_name, options = {}, &method)
95
+ def create_queue(queue_name, exchange_name, options = {})
73
96
  keys = options[:routing_keys] || []
74
97
  log "Isimud::TestClient: Binding queue #{queue_name} for keys #{keys.inspect}"
75
- queue = queues[queue_name] ||= Queue.new(queue_name, method)
98
+ queue = find_queue(queue_name)
76
99
  keys.each do |k|
77
100
  queue.bind(exchange_name, routing_key: k)
78
101
  end
79
102
  queue
80
103
  end
81
104
 
82
- def publish(exchange, routing_key, payload, _options = {})
83
- log "Isimud::TestClient: Delivering message key: #{routing_key} payload: #{payload}"
84
- call_queues = queues.values.select { |queue| queue.has_matching_key?(routing_key) }
105
+ def subscribe(queue, options = {}, &block)
106
+ queue.proc = block
107
+ queue
108
+ end
109
+
110
+ def publish(exchange, routing_key, payload)
111
+ log "Isimud::TestClient: Delivering message exchange: #{exchange} key: #{routing_key} payload: #{payload}"
112
+ call_queues = queues.values.select { |queue| queue.has_matching_key?(exchange, routing_key) }
85
113
  call_queues.each do |queue|
86
114
  log "Isimud::TestClient: Queue #{queue.name} matches routing key #{routing_key}"
87
115
  queue.deliver(payload)
@@ -1,3 +1,3 @@
1
1
  module Isimud
2
- VERSION = '0.7.0'
2
+ VERSION = '1.3.1'
3
3
  end
data/lib/isimud.rb CHANGED
@@ -37,7 +37,7 @@ module Isimud
37
37
  100
38
38
  end
39
39
  config_accessor :logger do
40
- Rails.logger
40
+ logger
41
41
  end
42
42
  config_accessor :log_level do
43
43
  :debug
@@ -13,6 +13,10 @@ class Company < ActiveRecord::Base
13
13
  active
14
14
  end
15
15
 
16
+ def observed_exchange
17
+ 'isimud.test.events'
18
+ end
19
+
16
20
  def routing_keys
17
21
  ["*.User.create", "*.User.destroy"]
18
22
  end
@@ -20,15 +24,9 @@ class Company < ActiveRecord::Base
20
24
  def handle_event(event)
21
25
  user = User.find(event.parameters[:id])
22
26
  return unless user.company_id == id
23
- case event.action.to_s
24
- when 'create'
25
- reload
26
- update_attributes!(user_count: user_count + 1)
27
- when 'destroy'
28
- reload
29
- update_attributes!(user_count: user_count - 1)
30
- else
31
- raise "unexpected action: #{event.action}"
32
- end
27
+ raise "unexpected action: #{event.action}" unless ['create','destroy' ].include?(event.action.to_s)
28
+ self.user_count = User.where(company: self).count
29
+ self.total_points = user_count * points_per_user
30
+ save!
33
31
  end
34
32
  end
@@ -5,7 +5,9 @@ class User < ActiveRecord::Base
5
5
 
6
6
  belongs_to :company
7
7
 
8
- attr_accessor :events, :routing_keys
8
+ attr_accessor :events
9
+
10
+ serialize :keys, Array
9
11
 
10
12
  scope :active, -> {where('deactivated != ?', true)}
11
13
 
@@ -18,6 +20,14 @@ class User < ActiveRecord::Base
18
20
  'test'
19
21
  end
20
22
 
23
+ def routing_keys
24
+ keys
25
+ end
26
+
27
+ def enable_listener?
28
+ !deactivated
29
+ end
30
+
21
31
  watch_attributes :key, :login_count
22
32
 
23
33
  def key
@@ -5,6 +5,9 @@ ActiveRecord::Schema.define do
5
5
  t.string :url
6
6
  t.integer :user_count, default: 0, null: false
7
7
  t.boolean :active, default: true, null: false
8
+ t.string :exchange_routing_keys
9
+ t.integer :points_per_user, default: 1, null: false
10
+ t.integer :total_points, default: 0, null: false
8
11
  t.timestamps
9
12
  end
10
13
 
@@ -17,6 +20,8 @@ ActiveRecord::Schema.define do
17
20
  t.boolean :is_admin
18
21
  t.boolean :deactivated
19
22
  t.integer :login_count, default: 0, null: false
23
+ t.string :keys
24
+ t.string :exchange_routing_keys
20
25
  t.timestamps
21
26
  end
22
27
  end
@@ -29,17 +29,29 @@ describe Isimud::BunnyClient do
29
29
 
30
30
 
31
31
  it 'creates a new queue' do
32
- queue = client.bind('my_queue', @exchange_name, keys, &proc)
33
- expect(queue).to be_a Bunny::Queue
34
- expect(queue.name).to eq('my_queue')
32
+ consumer = client.bind('my_queue', @exchange_name, keys, &proc)
33
+ expect(consumer).to be_a Bunny::Consumer
34
+ expect(consumer.queue_name).to eq('my_queue')
35
+ end
36
+
37
+ context 'when a block is passed to the call' do
38
+ it 'binds specified routing keys and subscribes to the specified exchange' do
39
+ queue = double('queue', name: 'my_queue', bind: 'ok')
40
+ expect(client).to receive(:find_queue).with('my_queue', durable: true).and_return(queue)
41
+ expect(queue).to receive(:subscribe).with({manual_ack: true})
42
+ keys.each { |key| expect(queue).to receive(:bind).with(@exchange_name, routing_key: key, nowait: false).once }
43
+ client.bind('my_queue', @exchange_name, *keys, &proc)
44
+ end
35
45
  end
36
46
 
37
- it 'binds specified routing keys and subscribes to the specified exchange' do
38
- queue = double('queue', bind: 'ok')
39
- expect(channel).to receive(:queue).and_return(queue)
40
- expect(queue).to receive(:subscribe).with(manual_ack: true)
41
- keys.each { |key| expect(queue).to receive(:bind).with(@exchange_name, routing_key: key, nowait: false).once }
42
- client.bind('my_queue', @exchange_name, *keys, proc)
47
+ context 'when a block is NOT passed' do
48
+ it 'binds specified routing keys BUT does not subscribes to the specified exchange' do
49
+ queue = double('queue', name: 'my_queue', bind: 'ok')
50
+ expect(client).to receive(:find_queue).with('my_queue', durable: true).and_return(queue)
51
+ expect(queue).not_to receive(:subscribe).with(manual_ack: true)
52
+ keys.each { |key| expect(queue).to receive(:bind).with(@exchange_name, routing_key: key, nowait: false).once }
53
+ client.bind('my_queue', @exchange_name, *keys)
54
+ end
43
55
  end
44
56
 
45
57
  it 'calls block when a message is received' do
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Isimud::Client do
4
+ let(:client) { Isimud::Client.new }
5
+
6
+ before do
7
+ @exceptions_1 = Array.new
8
+ @exceptions_2 = Array.new
9
+ end
10
+
11
+ describe 'exception handling' do
12
+ let!(:exception_handler_1) { Proc.new { |e| @exceptions_1 << e } }
13
+ let!(:exception_handler_2) { Proc.new { |e| @exceptions_2 << e } }
14
+ let!(:exception) { double(:exception) }
15
+
16
+ context 'with one handler added' do
17
+ before do
18
+ client.on_exception(&exception_handler_1)
19
+ client.run_exception_handlers(exception)
20
+ end
21
+
22
+ it 'calls the exception handler' do
23
+ expect(@exceptions_1).to include(exception)
24
+ end
25
+ end
26
+
27
+ context 'with two handlers added' do
28
+ before do
29
+ client.on_exception(&exception_handler_1)
30
+ client.on_exception(&exception_handler_2)
31
+ client.run_exception_handlers(exception)
32
+ end
33
+
34
+ it 'calls both exception handlers' do
35
+ expect(@exceptions_1).to include(exception)
36
+ expect(@exceptions_2).to include(exception)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -26,8 +26,8 @@ describe Isimud::EventListener do
26
26
  it 'applies defaults' do
27
27
  listener = Isimud::EventListener.new
28
28
  expect(listener.events_exchange).to eq('events')
29
- expect(listener.models_exchange).to eq('models')
30
- expect(listener.error_limit).to eq(10)
29
+ expect(listener.models_exchange).to eq('isimud.test.events')
30
+ expect(listener.error_limit).to eq(50)
31
31
  expect(listener.error_interval).to eq(1.hour)
32
32
  expect(listener.name).to eq('combustion-listener')
33
33
  end
@@ -52,35 +52,63 @@ describe Isimud::EventListener do
52
52
  end
53
53
  end
54
54
 
55
- describe 'handling messages' do
56
- it 'dispatches events to observer' do
57
- expect {
58
- User.create!(company: company, first_name: 'Larry', last_name: 'Page')
59
- company.reload
60
- }.to change(company, :user_count)
55
+ describe '#handle_observer_event' do
56
+ context 'handling observer destroy messages' do
57
+ it 'purges the queue for a destroy observer' do
58
+ expect(listener).to have_observer(company)
59
+ company.destroy
60
+ expect(listener).not_to have_observer(company)
61
+ end
61
62
  end
62
- end
63
63
 
64
- describe 'handling observer updates' do
65
- it 'registers a new observer' do
66
- another_company = Company.create!(name: 'Apple', active: true)
67
- expect(listener.has_observer?(another_company)).to eql(true)
68
- end
64
+ context 'handling new observer messages' do
65
+ it 'registers a new observer' do
66
+ another_company = Company.create!(name: 'Apple', active: true)
67
+ expect(listener).to have_observer(another_company)
68
+ end
69
69
 
70
- it 're-registers an updated observer' do
71
- expect(listener.has_observer?(company)).to eql(true)
72
- company.update_attributes!(active: false)
73
- expect(listener.has_observer?(company)).to eql(false)
70
+ it 'does not register an observer when listening is disabled' do
71
+ another_company = Company.create!(name: 'Apple', active: false)
72
+ expect(listener).not_to have_observer(another_company)
73
+ end
74
74
  end
75
75
 
76
- it 'does not register an observer when listening is disabled'
76
+ context 'handling observer update messages' do
77
+ it 'reloads the observer and processes events accordingly' do
78
+ company.update_attributes!(points_per_user: 2)
79
+ expect(listener).to have_observer(company)
80
+ expect {
81
+ User.create!(company: company, first_name: 'Larry', last_name: 'Page')
82
+ company.reload
83
+ }.to change(company, :user_count).by(1)
84
+ expect(company.total_points).to eql(company.user_count * 2)
85
+ end
86
+ end
77
87
 
78
- it 'purges the queue for a deleted observer'
88
+ context 'handling messages' do
89
+ it 'dispatches events to observer' do
90
+ expect {
91
+ User.create!(company: company, first_name: 'Larry', last_name: 'Page')
92
+ company.reload
93
+ }.to change(company, :user_count)
94
+ end
95
+ end
79
96
  end
80
97
 
81
98
  describe 'handling errors' do
82
- it 'counts errors'
83
- it 'triggers a shutdown if errors exceed limit'
99
+ before do
100
+ @some_company = Company.create!(name: 'Apple', active: true)
101
+ end
102
+ xit 'counts errors' do
103
+ # Verify that the listener's error count starts from zero
104
+ expect(listener.error_count).to eql 0
105
+
106
+ # Verify that the listener's error count increased to one with the first new exception
107
+ # Verify that the listener's error count increased to two with the second new exception
108
+ end
109
+ xit 'triggers a shutdown if errors exceed limit' do
110
+ # Verify that the listener shuts down / stops listening when the threshold is exceeded
111
+ end
84
112
  end
85
113
  end
86
114
  end
@@ -1,32 +1,123 @@
1
+ require 'spec_helper'
2
+
1
3
  describe Isimud::EventObserver do
2
- let(:client) { Isimud.client }
4
+ before do
5
+ Isimud.model_watcher_exchange = 'isimud.test.events'
6
+ @client = Isimud.client
7
+ end
8
+
9
+ #let(:client) { Isimud.client }
3
10
  let(:exchange_name) { 'events' }
4
- let(:user) { User.create!(first_name: 'Geo',
5
- last_name: 'Feil',
6
- encrypted_password: "itsasecret",
7
- email: 'george.feil@keas.com') }
8
- let!(:eventful) { Company.create!(name: 'Keas', description: 'Health Engagement Platform', url: 'http://keas.com') }
11
+ let(:company) { Company.create!(name: 'Keas', description: 'Health Engagement Platform', url: 'http://keas.com') }
12
+ let(:keys) { %w(a.b.c d.*.f) }
13
+ let(:user_params) { {first_name: 'Geo',
14
+ last_name: 'Feil',
15
+ encrypted_password: "itsasecret",
16
+ keys: keys,
17
+ email: 'george.feil@keas.com'} }
9
18
  let(:time) { 1.hour.ago }
10
19
  let(:params) { {'a' => 'foo', 'b' => 123} }
11
- let!(:evented) { User.create(routing_keys: ['model.Company.*.create', 'model.Event.*.report']) }
12
- let(:event) { Isimud::Event.new(user, eventful, action: :create, occurred_at: time, parameters: params) }
13
-
14
20
 
15
21
  it 'registers class in observed_models' do
16
22
  expect(Isimud::EventObserver.observed_models).to include(User)
17
23
  end
18
24
 
19
- describe '#observe_events' do
20
- before(:each) do
21
- evented.observe_events(client, exchange_name)
25
+ describe 'when created' do
26
+ before do
27
+ @queue = double(:queue)
28
+ @exchange = Isimud.events_exchange
29
+ end
30
+
31
+ it 'creates observer queue' do
32
+ expect(@client).to receive(:find_queue).and_return(@queue)
33
+ keys.each { |key| expect(@queue).to receive(:bind).with(@exchange, routing_key: key) }
34
+ User.create(user_params)
35
+ end
36
+
37
+ it 'does not create observer queue when listener is not enabled' do
38
+ expect(@client).not_to receive(:create_queue)
39
+ User.create(user_params.merge(deactivated: true))
40
+ end
41
+
42
+ it 'sets exchange_routing_keys' do
43
+ user = User.create(user_params)
44
+ expect(user.exchange_routing_keys).to eql(user.routing_keys)
45
+ end
46
+ end
47
+
48
+ describe 'when modified' do
49
+ before do
50
+ @exchange = Isimud.events_exchange
51
+ end
52
+
53
+ context 'for an instance that is made active on update' do
54
+ before do
55
+ @user = User.create(user_params.merge(deactivated: true))
56
+ @queue = @client.find_queue(@user.event_queue_name)
57
+ end
58
+
59
+ it 'creates the queue' do
60
+ expect(@client).to receive(:find_queue).with(@user.event_queue_name).and_call_original
61
+ @user.update_attributes(deactivated: false)
62
+ end
63
+
64
+ it 'binds the routing keys' do
65
+ @user.exchange_routing_keys.each do |k|
66
+ expect(@queue).to receive(:bind).with(@exchange, routing_key: k).and_call_original
67
+ end
68
+ @user.update_attributes(deactivated: false)
69
+ end
70
+ end
71
+
72
+ context 'for an already active instance' do
73
+ before do
74
+ @user = User.create(user_params)
75
+ @queue = @client.find_queue(@user.event_queue_name)
76
+ end
77
+
78
+ it 'binds new keys' do
79
+ @user.exchange_routing_keys.each do |k|
80
+ expect(@queue).to receive(:bind).with(@exchange, routing_key: k).and_call_original
81
+ end
82
+ expect(@queue).to receive(:bind).with(@exchange, routing_key: 'some_other_value').and_call_original
83
+ @user.transaction do
84
+ @user.keys << 'some_other_value'
85
+ @user.save!
86
+ end
87
+ end
88
+
89
+ it 'removes old keys' do
90
+ expect(@queue).to receive(:unbind).with(@exchange, routing_key: 'a.b.c')
91
+ expect(@queue).not_to receive(:unbind).with(@exchange, routing_key: 'd.*.f')
92
+ @user.keys.delete('a.b.c')
93
+ @user.save!
94
+ end
22
95
  end
96
+ end
23
97
 
24
- it 'binds routing keys to the named queue in the exchange' do
25
- queue = client.queues["combustion.user.#{evented.id}"]
26
- expect(queue).to have_matching_key("model.Company.123.create")
98
+ describe 'when destroyed' do
99
+ before do
100
+ @exchange = Isimud.events_exchange
101
+ @user = User.create(user_params)
27
102
  end
28
103
 
29
- it 'parses messages and dispatches to the handle_event method'
104
+ it 'removes the queue' do
105
+ expect(@client).to receive(:delete_queue).with("combustion.user.#{@user.id}")
106
+ @user.destroy
107
+ end
108
+ end
109
+
110
+ describe '#observe_events' do
111
+ before do
112
+ @user = User.create(user_params.merge(keys: ['model.Company.*.create']))
113
+ @user.observe_events(@client)
114
+ end
115
+
116
+ it 'parses messages and dispatches to the handle_event method' do
117
+ event = Isimud::Event.new(@user, company, exchange: @exchange, action: :create, occurred_at: time, parameters: params)
118
+ event.publish
119
+ expect(@user.events).not_to be_empty
120
+ end
30
121
  end
31
122
 
32
123
  end