event_system 0.1.0
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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/README.md +362 -0
- data/Rakefile +4 -0
- data/docs/event_visualizations/example_timeline.html +142 -0
- data/docs/example_logs/events_20251027_151744.jsonl +3 -0
- data/docs/example_logs/events_20251027_151802.jsonl +3 -0
- data/docs/examples/basic_usage.rb +64 -0
- data/exe/event_system +139 -0
- data/lib/event_system/configuration.rb +125 -0
- data/lib/event_system/event.rb +139 -0
- data/lib/event_system/event_manager.rb +191 -0
- data/lib/event_system/event_subscriber.rb +29 -0
- data/lib/event_system/storage/base.rb +64 -0
- data/lib/event_system/storage/file_store.rb +187 -0
- data/lib/event_system/storage/memory_store.rb +134 -0
- data/lib/event_system/version.rb +5 -0
- data/lib/event_system/visualization/timeline_generator.rb +237 -0
- data/lib/event_system.rb +37 -0
- data/spec/event_system/configuration_spec.rb +197 -0
- data/spec/event_system/event_manager_spec.rb +341 -0
- data/spec/event_system/event_spec.rb +193 -0
- data/spec/event_system/event_subscriber_spec.rb +295 -0
- data/spec/event_system/storage/file_store_spec.rb +341 -0
- data/spec/event_system/storage/memory_store_spec.rb +248 -0
- data/spec/event_system/visualization/timeline_generator_spec.rb +252 -0
- data/spec/event_system_spec.rb +57 -0
- data/spec/integration/readme_examples_spec.rb +447 -0
- data/spec/spec_helper.rb +80 -0
- metadata +171 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe EventSystem::Event do
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'creates an event with minimal parameters' do
|
|
8
|
+
event = EventSystem::Event.new(:test_type)
|
|
9
|
+
expect(event.type).to eq('test_type')
|
|
10
|
+
expect(event.source).to be_nil
|
|
11
|
+
expect(event.data).to eq({})
|
|
12
|
+
expect(event.id).to be_a(String)
|
|
13
|
+
expect(event.timestamp).to be_a(Time)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'creates an event with all parameters' do
|
|
17
|
+
source = 'TestSource'
|
|
18
|
+
data = { key: 'value' }
|
|
19
|
+
id = 'test-id-123'
|
|
20
|
+
timestamp = Time.now
|
|
21
|
+
|
|
22
|
+
event = EventSystem::Event.new(:test_type, source, data, id, timestamp)
|
|
23
|
+
expect(event.type).to eq('test_type')
|
|
24
|
+
expect(event.source).to eq(source)
|
|
25
|
+
expect(event.data).to eq(data)
|
|
26
|
+
expect(event.id).to eq(id)
|
|
27
|
+
expect(event.timestamp).to eq(timestamp.utc)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'generates UUID when no ID provided' do
|
|
31
|
+
event = EventSystem::Event.new(:test_type)
|
|
32
|
+
expect(event.id).to match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'uses current time when no timestamp provided' do
|
|
36
|
+
before = Time.now
|
|
37
|
+
event = EventSystem::Event.new(:test_type)
|
|
38
|
+
after = Time.now
|
|
39
|
+
expect(event.timestamp).to be_between(before.utc, after.utc)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'converts type to string' do
|
|
43
|
+
event = EventSystem::Event.new(:symbol_type)
|
|
44
|
+
expect(event.type).to eq('symbol_type')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'parses string timestamp' do
|
|
48
|
+
timestamp_str = '2023-01-01T12:00:00Z'
|
|
49
|
+
event = EventSystem::Event.new(:test_type, nil, {}, nil, timestamp_str)
|
|
50
|
+
expect(event.timestamp).to eq(Time.parse(timestamp_str).utc)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe '#to_s' do
|
|
55
|
+
it 'returns human-readable string representation' do
|
|
56
|
+
event = EventSystem::Event.new(:test_type, 'TestSource', { key: 'value' })
|
|
57
|
+
expect(event.to_s).to include('test_type')
|
|
58
|
+
expect(event.to_s).to include('key')
|
|
59
|
+
expect(event.to_s).to include('value')
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe '#to_h' do
|
|
64
|
+
it 'returns hash representation' do
|
|
65
|
+
event = EventSystem::Event.new(:test_type, 'TestSource', { key: 'value' })
|
|
66
|
+
hash = event.to_h
|
|
67
|
+
|
|
68
|
+
expect(hash[:id]).to eq(event.id)
|
|
69
|
+
expect(hash[:type]).to eq('test_type')
|
|
70
|
+
expect(hash[:source]).to eq('TestSource')
|
|
71
|
+
expect(hash[:data]).to eq({ key: 'value' })
|
|
72
|
+
expect(hash[:timestamp]).to eq(event.timestamp.iso8601(3))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe '#to_json' do
|
|
77
|
+
it 'returns JSON representation' do
|
|
78
|
+
event = EventSystem::Event.new(:test_type, 'TestSource', { key: 'value' })
|
|
79
|
+
json = event.to_json
|
|
80
|
+
|
|
81
|
+
expect { JSON.parse(json) }.not_to raise_error
|
|
82
|
+
parsed = JSON.parse(json, symbolize_names: true)
|
|
83
|
+
expect(parsed[:type]).to eq('test_type')
|
|
84
|
+
expect(parsed[:source]).to eq('TestSource')
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe '.from_json' do
|
|
89
|
+
it 'reconstructs event from JSON' do
|
|
90
|
+
original_event = EventSystem::Event.new(:test_type, 'TestSource', { key: 'value' })
|
|
91
|
+
json = original_event.to_json
|
|
92
|
+
reconstructed = EventSystem::Event.from_json(json)
|
|
93
|
+
|
|
94
|
+
expect(reconstructed.id).to eq(original_event.id)
|
|
95
|
+
expect(reconstructed.type).to eq(original_event.type)
|
|
96
|
+
expect(reconstructed.source).to eq(original_event.source)
|
|
97
|
+
expect(reconstructed.data).to eq(original_event.data)
|
|
98
|
+
# Timestamps may have slight precision differences due to JSON serialization
|
|
99
|
+
expect(reconstructed.timestamp.to_f).to be_within(0.001).of(original_event.timestamp.to_f)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'handles JSON with missing optional fields' do
|
|
103
|
+
json = '{"type":"test_type","source":"TestSource","timestamp":"2023-01-01T12:00:00.000Z","id":"test-id"}'
|
|
104
|
+
event = EventSystem::Event.from_json(json)
|
|
105
|
+
|
|
106
|
+
expect(event.type).to eq('test_type')
|
|
107
|
+
expect(event.source).to eq('TestSource')
|
|
108
|
+
expect(event.data).to eq({})
|
|
109
|
+
expect(event.id).to eq('test-id')
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe '#==' do
|
|
114
|
+
it 'compares events by ID' do
|
|
115
|
+
event1 = EventSystem::Event.new(:test_type, nil, {}, 'same-id')
|
|
116
|
+
event2 = EventSystem::Event.new(:different_type, 'different_source', { different: 'data' }, 'same-id')
|
|
117
|
+
|
|
118
|
+
expect(event1).to eq(event2)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'returns false for different IDs' do
|
|
122
|
+
event1 = EventSystem::Event.new(:test_type, nil, {}, 'id1')
|
|
123
|
+
event2 = EventSystem::Event.new(:test_type, nil, {}, 'id2')
|
|
124
|
+
|
|
125
|
+
expect(event1).not_to eq(event2)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'returns false for non-event objects' do
|
|
129
|
+
event = EventSystem::Event.new(:test_type)
|
|
130
|
+
expect(event).not_to eq('not an event')
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '#hash' do
|
|
135
|
+
it 'returns hash based on ID' do
|
|
136
|
+
event1 = EventSystem::Event.new(:test_type, nil, {}, 'same-id')
|
|
137
|
+
event2 = EventSystem::Event.new(:different_type, nil, {}, 'same-id')
|
|
138
|
+
|
|
139
|
+
expect(event1.hash).to eq(event2.hash)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
describe '#type?' do
|
|
144
|
+
it 'checks if event matches type' do
|
|
145
|
+
event = EventSystem::Event.new(:test_type)
|
|
146
|
+
expect(event.type?('test_type')).to be true
|
|
147
|
+
expect(event.type?(:test_type)).to be true
|
|
148
|
+
expect(event.type?('different_type')).to be false
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe '#source_to_string' do
|
|
153
|
+
it 'converts nil source to string' do
|
|
154
|
+
event = EventSystem::Event.new(:test_type, nil)
|
|
155
|
+
expect(event.source_to_string).to eq('unknown')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'returns string source as-is' do
|
|
159
|
+
event = EventSystem::Event.new(:test_type, 'TestSource')
|
|
160
|
+
expect(event.source_to_string).to eq('TestSource')
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'converts object source to string' do
|
|
164
|
+
source_obj = double('SourceObject', to_s: 'ObjectString')
|
|
165
|
+
event = EventSystem::Event.new(:test_type, source_obj)
|
|
166
|
+
expect(event.source_to_string).to eq('ObjectString')
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe 'serialization safety' do
|
|
171
|
+
it 'handles non-serializable objects in data' do
|
|
172
|
+
non_serializable = Object.new
|
|
173
|
+
event = EventSystem::Event.new(:test_type, nil, { obj: non_serializable })
|
|
174
|
+
|
|
175
|
+
expect { event.to_json }.not_to raise_error
|
|
176
|
+
json = event.to_json
|
|
177
|
+
parsed = JSON.parse(json)
|
|
178
|
+
expect(parsed['data']['obj']).to include('Object')
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'handles nested hashes and arrays' do
|
|
182
|
+
data = {
|
|
183
|
+
hash: { nested: 'value' },
|
|
184
|
+
array: [1, 2, { nested: 'value' }]
|
|
185
|
+
}
|
|
186
|
+
event = EventSystem::Event.new(:test_type, nil, data)
|
|
187
|
+
|
|
188
|
+
expect { event.to_json }.not_to raise_error
|
|
189
|
+
reconstructed = EventSystem::Event.from_json(event.to_json)
|
|
190
|
+
expect(reconstructed.data).to eq(data)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe EventSystem::EventSubscriber do
|
|
6
|
+
let(:subscriber_class) do
|
|
7
|
+
Class.new do
|
|
8
|
+
include EventSystem::EventSubscriber
|
|
9
|
+
|
|
10
|
+
attr_reader :events_handled
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@events_handled = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def handle_event(event)
|
|
17
|
+
@events_handled << event
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
let(:subscriber) { subscriber_class.new }
|
|
23
|
+
|
|
24
|
+
describe '#handle_event' do
|
|
25
|
+
it 'raises NotImplementedError when not implemented' do
|
|
26
|
+
abstract_subscriber = Class.new do
|
|
27
|
+
include EventSystem::EventSubscriber
|
|
28
|
+
end.new
|
|
29
|
+
|
|
30
|
+
event = create_test_event(:test_type)
|
|
31
|
+
expect { abstract_subscriber.handle_event(event) }.to raise_error(NotImplementedError)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'handles events when implemented' do
|
|
35
|
+
event = create_test_event(:test_type)
|
|
36
|
+
subscriber.handle_event(event)
|
|
37
|
+
|
|
38
|
+
expect(subscriber.events_handled).to include(event)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe '#handles_event_type?' do
|
|
43
|
+
it 'returns true by default for all event types' do
|
|
44
|
+
expect(subscriber.handles_event_type?('any_type')).to be true
|
|
45
|
+
expect(subscriber.handles_event_type?('another_type')).to be true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'can be overridden for filtering' do
|
|
49
|
+
filtered_subscriber = Class.new do
|
|
50
|
+
include EventSystem::EventSubscriber
|
|
51
|
+
|
|
52
|
+
def handle_event(event)
|
|
53
|
+
# Implementation not needed for this test
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def handles_event_type?(event_type)
|
|
57
|
+
event_type == 'allowed_type'
|
|
58
|
+
end
|
|
59
|
+
end.new
|
|
60
|
+
|
|
61
|
+
expect(filtered_subscriber.handles_event_type?('allowed_type')).to be true
|
|
62
|
+
expect(filtered_subscriber.handles_event_type?('blocked_type')).to be false
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe '#event_priority' do
|
|
67
|
+
it 'returns 0 by default' do
|
|
68
|
+
expect(subscriber.event_priority).to eq(0)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'can be overridden for custom priority' do
|
|
72
|
+
priority_subscriber = Class.new do
|
|
73
|
+
include EventSystem::EventSubscriber
|
|
74
|
+
|
|
75
|
+
def handle_event(event)
|
|
76
|
+
# Implementation not needed for this test
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def event_priority
|
|
80
|
+
5
|
|
81
|
+
end
|
|
82
|
+
end.new
|
|
83
|
+
|
|
84
|
+
expect(priority_subscriber.event_priority).to eq(5)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe 'integration with EventManager' do
|
|
89
|
+
it 'works with EventManager subscription' do
|
|
90
|
+
manager = create_test_manager
|
|
91
|
+
manager.subscribe(:test_event, subscriber)
|
|
92
|
+
|
|
93
|
+
event = create_test_event(:test_event)
|
|
94
|
+
manager.publish(event)
|
|
95
|
+
|
|
96
|
+
expect(subscriber.events_handled).to include(event)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'respects event type filtering' do
|
|
100
|
+
filtered_subscriber = Class.new do
|
|
101
|
+
include EventSystem::EventSubscriber
|
|
102
|
+
|
|
103
|
+
attr_reader :events_handled
|
|
104
|
+
|
|
105
|
+
def initialize
|
|
106
|
+
@events_handled = []
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def handle_event(event)
|
|
110
|
+
@events_handled << event
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def handles_event_type?(event_type)
|
|
114
|
+
event_type == 'allowed_event'
|
|
115
|
+
end
|
|
116
|
+
end.new
|
|
117
|
+
|
|
118
|
+
manager = create_test_manager
|
|
119
|
+
manager.subscribe(:allowed_event, filtered_subscriber)
|
|
120
|
+
manager.subscribe(:blocked_event, filtered_subscriber)
|
|
121
|
+
|
|
122
|
+
allowed_event = create_test_event(:allowed_event)
|
|
123
|
+
blocked_event = create_test_event(:blocked_event)
|
|
124
|
+
|
|
125
|
+
manager.publish(allowed_event)
|
|
126
|
+
manager.publish(blocked_event)
|
|
127
|
+
|
|
128
|
+
expect(filtered_subscriber.events_handled).to include(allowed_event)
|
|
129
|
+
expect(filtered_subscriber.events_handled).not_to include(blocked_event)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'respects priority ordering' do
|
|
133
|
+
high_priority_subscriber = Class.new do
|
|
134
|
+
include EventSystem::EventSubscriber
|
|
135
|
+
|
|
136
|
+
attr_reader :events_handled
|
|
137
|
+
|
|
138
|
+
def initialize
|
|
139
|
+
@events_handled = []
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def handle_event(event)
|
|
143
|
+
@events_handled << "high_#{event.type}"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def event_priority
|
|
147
|
+
1
|
|
148
|
+
end
|
|
149
|
+
end.new
|
|
150
|
+
|
|
151
|
+
low_priority_subscriber = Class.new do
|
|
152
|
+
include EventSystem::EventSubscriber
|
|
153
|
+
|
|
154
|
+
attr_reader :events_handled
|
|
155
|
+
|
|
156
|
+
def initialize
|
|
157
|
+
@events_handled = []
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def handle_event(event)
|
|
161
|
+
@events_handled << "low_#{event.type}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def event_priority
|
|
165
|
+
10
|
|
166
|
+
end
|
|
167
|
+
end.new
|
|
168
|
+
|
|
169
|
+
manager = create_test_manager
|
|
170
|
+
manager.subscribe(:test_event, high_priority_subscriber)
|
|
171
|
+
manager.subscribe(:test_event, low_priority_subscriber)
|
|
172
|
+
|
|
173
|
+
event = create_test_event(:test_event)
|
|
174
|
+
manager.publish(event)
|
|
175
|
+
|
|
176
|
+
# Both should receive the event (priority affects order, not filtering)
|
|
177
|
+
expect(high_priority_subscriber.events_handled).to include('high_test_event')
|
|
178
|
+
expect(low_priority_subscriber.events_handled).to include('low_test_event')
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe 'real-world examples' do
|
|
183
|
+
it 'demonstrates logging subscriber' do
|
|
184
|
+
class LoggingSubscriber
|
|
185
|
+
include EventSystem::EventSubscriber
|
|
186
|
+
|
|
187
|
+
attr_reader :logged_events
|
|
188
|
+
|
|
189
|
+
def initialize
|
|
190
|
+
@logged_events = []
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def handle_event(event)
|
|
194
|
+
@logged_events << "[#{event.timestamp}] #{event.type}: #{event.data}"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def handles_event_type?(event_type)
|
|
198
|
+
# Log all events
|
|
199
|
+
true
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
logger = LoggingSubscriber.new
|
|
204
|
+
manager = create_test_manager
|
|
205
|
+
manager.subscribe_all(logger)
|
|
206
|
+
|
|
207
|
+
manager.publish_event(:user_login, 'AuthService', { user_id: 123 })
|
|
208
|
+
manager.publish_event(:order_created, 'OrderService', { order_id: 456 })
|
|
209
|
+
|
|
210
|
+
expect(logger.logged_events.length).to eq(2)
|
|
211
|
+
expect(logger.logged_events.first).to include('user_login')
|
|
212
|
+
expect(logger.logged_events.last).to include('order_created')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'demonstrates metrics subscriber' do
|
|
216
|
+
class MetricsSubscriber
|
|
217
|
+
include EventSystem::EventSubscriber
|
|
218
|
+
|
|
219
|
+
attr_reader :metrics
|
|
220
|
+
|
|
221
|
+
def initialize
|
|
222
|
+
@metrics = Hash.new(0)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def handle_event(event)
|
|
226
|
+
@metrics[event.type] += 1
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def handles_event_type?(event_type)
|
|
230
|
+
# Track all events
|
|
231
|
+
true
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
metrics = MetricsSubscriber.new
|
|
236
|
+
manager = create_test_manager
|
|
237
|
+
manager.subscribe_all(metrics)
|
|
238
|
+
|
|
239
|
+
manager.publish_event(:user_login, 'AuthService', {})
|
|
240
|
+
manager.publish_event(:user_login, 'AuthService', {})
|
|
241
|
+
manager.publish_event(:order_created, 'OrderService', {})
|
|
242
|
+
|
|
243
|
+
expect(metrics.metrics['user_login']).to eq(2)
|
|
244
|
+
expect(metrics.metrics['order_created']).to eq(1)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it 'demonstrates notification subscriber' do
|
|
248
|
+
class NotificationSubscriber
|
|
249
|
+
include EventSystem::EventSubscriber
|
|
250
|
+
|
|
251
|
+
attr_reader :notifications_sent
|
|
252
|
+
|
|
253
|
+
def initialize
|
|
254
|
+
@notifications_sent = []
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def handle_event(event)
|
|
258
|
+
case event.type
|
|
259
|
+
when 'user_registered'
|
|
260
|
+
send_welcome_email(event.data[:user_id])
|
|
261
|
+
when 'order_shipped'
|
|
262
|
+
send_shipping_notification(event.data[:order_id])
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def handles_event_type?(event_type)
|
|
267
|
+
['user_registered', 'order_shipped'].include?(event_type)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
private
|
|
271
|
+
|
|
272
|
+
def send_welcome_email(user_id)
|
|
273
|
+
@notifications_sent << "Welcome email sent to user #{user_id}"
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def send_shipping_notification(order_id)
|
|
277
|
+
@notifications_sent << "Shipping notification sent for order #{order_id}"
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
notifier = NotificationSubscriber.new
|
|
282
|
+
manager = create_test_manager
|
|
283
|
+
manager.subscribe(:user_registered, notifier)
|
|
284
|
+
manager.subscribe(:order_shipped, notifier)
|
|
285
|
+
|
|
286
|
+
manager.publish_event(:user_registered, 'UserService', { user_id: 123 })
|
|
287
|
+
manager.publish_event(:order_shipped, 'OrderService', { order_id: 456 })
|
|
288
|
+
manager.publish_event(:user_login, 'AuthService', {}) # Should be ignored
|
|
289
|
+
|
|
290
|
+
expect(notifier.notifications_sent.length).to eq(2)
|
|
291
|
+
expect(notifier.notifications_sent).to include('Welcome email sent to user 123')
|
|
292
|
+
expect(notifier.notifications_sent).to include('Shipping notification sent for order 456')
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|