isimud 0.7.0 → 1.3.1

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