isimud 0.7.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -2
- data/.ruby-version +1 -1
- data/.yardoc/checksums +10 -10
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +63 -73
- data/LICENSE.txt +19 -0
- data/README.md +51 -3
- data/Rakefile +5 -0
- data/doc/Isimud/BunnyClient.html +882 -179
- data/doc/Isimud/Client.html +236 -18
- data/doc/Isimud/Event.html +211 -95
- data/doc/Isimud/EventListener.html +325 -307
- data/doc/Isimud/EventObserver/ClassMethods.html +14 -14
- data/doc/Isimud/EventObserver.html +418 -36
- data/doc/Isimud/Generators/ConfigGenerator.html +2 -2
- data/doc/Isimud/Generators/InitializerGenerator.html +2 -2
- data/doc/Isimud/Generators.html +2 -2
- data/doc/Isimud/Logging.html +3 -3
- data/doc/Isimud/ModelWatcher/ClassMethods.html +3 -3
- data/doc/Isimud/ModelWatcher.html +2 -2
- data/doc/Isimud/Railtie.html +2 -2
- data/doc/Isimud/TestClient/Queue.html +374 -71
- data/doc/Isimud/TestClient.html +169 -161
- data/doc/Isimud.html +80 -76
- data/doc/_index.html +5 -2
- data/doc/file.LICENSE.html +73 -0
- data/doc/file.README.html +131 -7
- data/doc/file_list.html +3 -0
- data/doc/index.html +131 -7
- data/doc/method_list.html +183 -105
- data/doc/top-level-namespace.html +2 -2
- data/isimud.gemspec +18 -16
- data/lib/isimud/bunny_client.rb +85 -32
- data/lib/isimud/client.rb +23 -7
- data/lib/isimud/event.rb +11 -14
- data/lib/isimud/event_listener.rb +123 -65
- data/lib/isimud/event_observer.rb +70 -10
- data/lib/isimud/model_watcher.rb +1 -1
- data/lib/isimud/test_client.rb +54 -26
- data/lib/isimud/version.rb +1 -1
- data/lib/isimud.rb +1 -1
- data/spec/internal/app/models/company.rb +8 -10
- data/spec/internal/app/models/user.rb +11 -1
- data/spec/internal/db/schema.rb +5 -0
- data/spec/isimud/bunny_client_spec.rb +21 -9
- data/spec/isimud/client_spec.rb +40 -0
- data/spec/isimud/event_listener_spec.rb +50 -22
- data/spec/isimud/event_observer_spec.rb +107 -16
- data/spec/isimud/model_watcher_spec.rb +18 -23
- data/spec/isimud/test_client_spec.rb +43 -8
- data/spec/isimud_spec.rb +2 -2
- data/spec/spec_helper.rb +2 -0
- metadata +19 -35
- checksums.yaml.gz.sig +0 -0
- data/certs/gfeil.pem +0 -21
- data/release +0 -31
- data.tar.gz.sig +0 -0
- 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
|
-
|
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
|
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
|
-
|
44
|
-
|
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
|
data/lib/isimud/model_watcher.rb
CHANGED
@@ -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
|
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)
|
data/lib/isimud/test_client.rb
CHANGED
@@ -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, :
|
14
|
+
attr_reader :name, :bindings
|
15
|
+
attr_accessor :proc
|
8
16
|
|
9
|
-
def initialize(name,
|
17
|
+
def initialize(name, proc = Proc.new{ |_| } )
|
10
18
|
@name = name
|
11
|
-
@
|
12
|
-
@
|
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
|
16
|
-
|
17
|
-
|
18
|
-
@
|
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
|
-
|
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
|
26
|
-
|
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
|
-
@
|
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
|
-
|
58
|
+
run_exception_handlers(e)
|
35
59
|
end
|
36
60
|
end
|
37
61
|
end
|
38
62
|
|
39
|
-
def initialize(connection = nil, options =
|
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, &
|
63
|
-
create_queue(queue_name, exchange_name, routing_keys: keys
|
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
|
67
|
-
|
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 = {}
|
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 =
|
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
|
83
|
-
|
84
|
-
|
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)
|
data/lib/isimud/version.rb
CHANGED
data/lib/isimud.rb
CHANGED
@@ -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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
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
|
data/spec/internal/db/schema.rb
CHANGED
@@ -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
|
-
|
33
|
-
expect(
|
34
|
-
expect(
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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('
|
30
|
-
expect(listener.error_limit).to eq(
|
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 '
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
company.
|
60
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
83
|
-
|
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
|
-
|
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(:
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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 '
|
20
|
-
before
|
21
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
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 '
|
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
|