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.
@@ -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