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,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe EventSystem::Configuration do
6
+ let(:config) { EventSystem::Configuration.new }
7
+
8
+ describe '#initialize' do
9
+ it 'sets default values' do
10
+ expect(config.storage_type).to eq(:memory)
11
+ expect(config.storage_options).to eq({})
12
+ expect(config.logger).to be_a(Logger) # Logger is created on first access
13
+ expect(config.session_id).to be_a(String) # Session ID is generated on first access
14
+ expect(config.auto_flush).to be true
15
+ end
16
+ end
17
+
18
+ describe '#storage_class' do
19
+ it 'returns MemoryStore for memory type' do
20
+ config.storage_type = :memory
21
+ expect(config.storage_class).to eq(EventSystem::Storage::MemoryStore)
22
+ end
23
+
24
+ it 'returns FileStore for file type' do
25
+ config.storage_type = :file
26
+ expect(config.storage_class).to eq(EventSystem::Storage::FileStore)
27
+ end
28
+
29
+ it 'raises error for unknown storage type' do
30
+ config.storage_type = :unknown
31
+ expect { config.storage_class }.to raise_error(ArgumentError, /Unknown storage type/)
32
+ end
33
+ end
34
+
35
+ describe '#create_storage' do
36
+ it 'creates MemoryStore with session_id' do
37
+ config.storage_type = :memory
38
+ config.session_id = 'test_session'
39
+
40
+ storage = config.create_storage
41
+ expect(storage).to be_a(EventSystem::Storage::MemoryStore)
42
+ expect(storage.current_session).to eq('test_session')
43
+ end
44
+
45
+ it 'creates FileStore with directory and session_id' do
46
+ config.storage_type = :file
47
+ config.storage_options = { directory: 'test_logs' }
48
+ config.session_id = 'test_session'
49
+
50
+ storage = config.create_storage
51
+ expect(storage).to be_a(EventSystem::Storage::FileStore)
52
+ expect(storage.current_session).to eq('test_session')
53
+ end
54
+
55
+ it 'uses default directory for file storage' do
56
+ config.storage_type = :file
57
+ storage = config.create_storage
58
+ expect(storage).to be_a(EventSystem::Storage::FileStore)
59
+ end
60
+ end
61
+
62
+ describe '#logger' do
63
+ it 'creates default logger when none set' do
64
+ logger = config.logger
65
+ expect(logger).to be_a(Logger)
66
+ expect(logger.level).to eq(Logger::INFO)
67
+ end
68
+
69
+ it 'returns set logger' do
70
+ custom_logger = Logger.new(StringIO.new)
71
+ config.logger = custom_logger
72
+ expect(config.logger).to eq(custom_logger)
73
+ end
74
+ end
75
+
76
+ describe '#session_id' do
77
+ it 'generates timestamp-based session ID when none set' do
78
+ session_id = config.session_id
79
+ expect(session_id).to match(/\d{8}_\d{6}/)
80
+ end
81
+
82
+ it 'returns set session ID' do
83
+ config.session_id = 'custom_session'
84
+ expect(config.session_id).to eq('custom_session')
85
+ end
86
+ end
87
+
88
+ describe '#reset!' do
89
+ it 'resets to default values' do
90
+ config.storage_type = :file
91
+ config.storage_options = { directory: 'test' }
92
+ config.logger = Logger.new(StringIO.new)
93
+ config.session_id = 'test_session'
94
+ config.auto_flush = false
95
+
96
+ config.reset!
97
+
98
+ expect(config.storage_type).to eq(:memory)
99
+ expect(config.storage_options).to eq({})
100
+ expect(config.logger).to be_a(Logger) # Logger is recreated on first access
101
+ expect(config.session_id).to be_a(String) # Session ID is generated on first access
102
+ expect(config.auto_flush).to be true
103
+ end
104
+ end
105
+
106
+ describe '#valid?' do
107
+ it 'returns true for valid memory configuration' do
108
+ config.storage_type = :memory
109
+ expect(config.valid?).to be true
110
+ end
111
+
112
+ it 'returns true for valid file configuration with directory' do
113
+ config.storage_type = :file
114
+ config.storage_options = { directory: 'test_logs' }
115
+ expect(config.valid?).to be true
116
+ end
117
+
118
+ it 'returns false for file configuration without directory' do
119
+ config.storage_type = :file
120
+ config.storage_options = {}
121
+ expect(config.valid?).to be false
122
+ end
123
+
124
+ it 'returns false for file configuration with empty directory' do
125
+ config.storage_type = :file
126
+ config.storage_options = { directory: '' }
127
+ expect(config.valid?).to be false
128
+ end
129
+
130
+ it 'returns false for unknown storage type' do
131
+ config.storage_type = :unknown
132
+ expect(config.valid?).to be false
133
+ end
134
+
135
+ it 'returns false for nil storage options' do
136
+ config.storage_type = :memory
137
+ config.storage_options = nil
138
+ expect(config.valid?).to be false
139
+ end
140
+
141
+ it 'handles string keys in storage options' do
142
+ config.storage_type = :file
143
+ config.storage_options = { 'directory' => 'test_logs' }
144
+ expect(config.valid?).to be true
145
+ end
146
+ end
147
+
148
+ describe '#to_h' do
149
+ it 'returns configuration as hash' do
150
+ config.storage_type = :file
151
+ config.storage_options = { directory: 'test' }
152
+ config.session_id = 'test_session'
153
+ config.auto_flush = false
154
+
155
+ hash = config.to_h
156
+ expect(hash[:storage_type]).to eq(:file)
157
+ expect(hash[:storage_options]).to eq({ directory: 'test' })
158
+ expect(hash[:session_id]).to eq('test_session')
159
+ expect(hash[:auto_flush]).to be false
160
+ end
161
+ end
162
+
163
+ describe 'storage constructor args' do
164
+ it 'returns correct args for memory storage' do
165
+ config.storage_type = :memory
166
+ config.session_id = 'test_session'
167
+
168
+ args = config.send(:storage_constructor_args)
169
+ expect(args).to eq(['test_session'])
170
+ end
171
+
172
+ it 'returns correct args for file storage' do
173
+ config.storage_type = :file
174
+ config.storage_options = { directory: 'test_logs' }
175
+ config.session_id = 'test_session'
176
+
177
+ args = config.send(:storage_constructor_args)
178
+ expect(args).to eq(['test_logs', 'test_session'])
179
+ end
180
+
181
+ it 'uses default directory for file storage' do
182
+ config.storage_type = :file
183
+ config.storage_options = {}
184
+
185
+ args = config.send(:storage_constructor_args)
186
+ expect(args).to eq(['event_logs', nil])
187
+ end
188
+
189
+ it 'handles string keys in storage options' do
190
+ config.storage_type = :file
191
+ config.storage_options = { 'directory' => 'test_logs' }
192
+
193
+ args = config.send(:storage_constructor_args)
194
+ expect(args).to eq(['test_logs', nil])
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,341 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe EventSystem::EventManager do
6
+ let(:manager) { create_test_manager }
7
+
8
+ describe '#initialize' do
9
+ it 'initializes with default configuration' do
10
+ manager = EventSystem::EventManager.new
11
+ expect(manager.config).to be_a(EventSystem::Configuration)
12
+ expect(manager.config.storage_type).to eq(:memory)
13
+ end
14
+
15
+ it 'initializes with hash configuration' do
16
+ config = { storage_type: :file, storage_options: { directory: 'test' } }
17
+ manager = EventSystem::EventManager.new(config)
18
+ expect(manager.config.storage_type).to eq(:file)
19
+ end
20
+
21
+ it 'initializes with Configuration object' do
22
+ config = EventSystem::Configuration.new
23
+ config.storage_type = :file
24
+ manager = EventSystem::EventManager.new(config)
25
+ expect(manager.config).to eq(config)
26
+ end
27
+ end
28
+
29
+ describe '#subscribe' do
30
+ it 'subscribes a subscriber to a specific event type' do
31
+ subscriber = create_test_subscriber
32
+ manager.subscribe(:test_event, subscriber)
33
+
34
+ event = create_test_event(:test_event)
35
+ manager.publish(event)
36
+
37
+ expect(subscriber.events_received).to include(event)
38
+ end
39
+
40
+ it 'converts event type to string' do
41
+ subscriber = create_test_subscriber
42
+ manager.subscribe(:test_event, subscriber)
43
+
44
+ event = create_test_event('test_event')
45
+ manager.publish(event)
46
+
47
+ expect(subscriber.events_received).to include(event)
48
+ end
49
+
50
+ it 'allows multiple subscribers for same event type' do
51
+ subscriber1 = create_test_subscriber('Sub1')
52
+ subscriber2 = create_test_subscriber('Sub2')
53
+
54
+ manager.subscribe(:test_event, subscriber1)
55
+ manager.subscribe(:test_event, subscriber2)
56
+
57
+ event = create_test_event(:test_event)
58
+ manager.publish(event)
59
+
60
+ expect(subscriber1.events_received).to include(event)
61
+ expect(subscriber2.events_received).to include(event)
62
+ end
63
+ end
64
+
65
+ describe '#unsubscribe' do
66
+ it 'unsubscribes a subscriber from an event type' do
67
+ subscriber = create_test_subscriber
68
+ manager.subscribe(:test_event, subscriber)
69
+ manager.unsubscribe(:test_event, subscriber)
70
+
71
+ event = create_test_event(:test_event)
72
+ manager.publish(event)
73
+
74
+ expect(subscriber.events_received).to be_empty
75
+ end
76
+
77
+ it 'handles unsubscribing non-existent subscriber gracefully' do
78
+ subscriber = create_test_subscriber
79
+ expect { manager.unsubscribe(:test_event, subscriber) }.not_to raise_error
80
+ end
81
+ end
82
+
83
+ describe '#subscribe_all' do
84
+ it 'subscribes a subscriber to all event types' do
85
+ subscriber = create_test_subscriber
86
+ manager.subscribe_all(subscriber)
87
+
88
+ event1 = create_test_event(:event1)
89
+ event2 = create_test_event(:event2)
90
+
91
+ manager.publish(event1)
92
+ manager.publish(event2)
93
+
94
+ expect(subscriber.events_received).to include(event1, event2)
95
+ end
96
+ end
97
+
98
+ describe '#unsubscribe_all' do
99
+ it 'unsubscribes a subscriber from all events' do
100
+ subscriber = create_test_subscriber
101
+ manager.subscribe_all(subscriber)
102
+ manager.unsubscribe_all(subscriber)
103
+
104
+ event = create_test_event(:test_event)
105
+ manager.publish(event)
106
+
107
+ expect(subscriber.events_received).to be_empty
108
+ end
109
+ end
110
+
111
+ describe '#publish' do
112
+ it 'publishes an event to subscribers' do
113
+ subscriber = create_test_subscriber
114
+ manager.subscribe(:test_event, subscriber)
115
+
116
+ event = create_test_event(:test_event)
117
+ manager.publish(event)
118
+
119
+ expect(subscriber.events_received).to include(event)
120
+ end
121
+
122
+ it 'stores event in storage' do
123
+ manager = create_file_manager
124
+ event = create_test_event(:test_event)
125
+
126
+ manager.publish(event)
127
+
128
+ events = manager.query_events(type: 'test_event')
129
+ expect(events).to include(event)
130
+ end
131
+
132
+ it 'delivers to both type-specific and universal subscribers' do
133
+ type_subscriber = create_test_subscriber('TypeSub')
134
+ universal_subscriber = create_test_subscriber('UniversalSub')
135
+
136
+ manager.subscribe(:test_event, type_subscriber)
137
+ manager.subscribe_all(universal_subscriber)
138
+
139
+ event = create_test_event(:test_event)
140
+ manager.publish(event)
141
+
142
+ expect(type_subscriber.events_received).to include(event)
143
+ expect(universal_subscriber.events_received).to include(event)
144
+ end
145
+
146
+ it 'handles subscriber errors gracefully' do
147
+ error_subscriber = Class.new do
148
+ include EventSystem::EventSubscriber
149
+ def handle_event(event)
150
+ raise StandardError, 'Test error'
151
+ end
152
+ end.new
153
+
154
+ manager.subscribe(:test_event, error_subscriber)
155
+
156
+ event = create_test_event(:test_event)
157
+ expect { manager.publish(event) }.not_to raise_error
158
+ end
159
+ end
160
+
161
+ describe '#publish_event' do
162
+ it 'creates and publishes an event' do
163
+ subscriber = create_test_subscriber
164
+ manager.subscribe(:test_event, subscriber)
165
+
166
+ event = manager.publish_event(:test_event, 'TestSource', { key: 'value' })
167
+
168
+ expect(event).to be_a(EventSystem::Event)
169
+ expect(event.type).to eq('test_event')
170
+ expect(event.source).to eq('TestSource')
171
+ expect(event.data).to eq({ key: 'value' })
172
+ expect(subscriber.events_received).to include(event)
173
+ end
174
+
175
+ it 'creates event with minimal parameters' do
176
+ event = manager.publish_event(:test_event)
177
+ expect(event.type).to eq('test_event')
178
+ expect(event.source).to be_nil
179
+ expect(event.data).to eq({})
180
+ end
181
+ end
182
+
183
+ describe '#query_events' do
184
+ it 'queries events from storage' do
185
+ manager = create_file_manager
186
+ event1 = create_test_event(:type1)
187
+ event2 = create_test_event(:type2)
188
+
189
+ manager.publish(event1)
190
+ manager.publish(event2)
191
+
192
+ events = manager.query_events(type: 'type1')
193
+ expect(events).to include(event1)
194
+ expect(events).not_to include(event2)
195
+ end
196
+
197
+ it 'returns empty array when no storage' do
198
+ manager = create_test_manager(:memory)
199
+ events = manager.query_events
200
+ expect(events).to eq([])
201
+ end
202
+ end
203
+
204
+ describe '#current_session' do
205
+ it 'returns current session from storage' do
206
+ manager = create_file_manager
207
+ expect(manager.current_session).to be_a(String)
208
+ end
209
+
210
+ it 'returns current session from memory storage' do
211
+ manager = create_test_manager(:memory)
212
+ expect(manager.current_session).to be_a(String)
213
+ end
214
+ end
215
+
216
+ describe '#switch_session' do
217
+ it 'switches to different session' do
218
+ manager = create_file_manager
219
+ original_session = manager.current_session
220
+
221
+ manager.switch_session('new_session')
222
+
223
+ expect(manager.current_session).to eq('new_session')
224
+ expect(manager.current_session).not_to eq(original_session)
225
+ end
226
+ end
227
+
228
+ describe '#create_session' do
229
+ it 'creates new session with given ID' do
230
+ manager = create_file_manager
231
+ session_id = manager.create_session('custom_session')
232
+
233
+ expect(session_id).to eq('custom_session')
234
+ expect(manager.current_session).to eq('custom_session')
235
+ end
236
+
237
+ it 'creates new session with auto-generated ID' do
238
+ manager = create_file_manager
239
+ session_id = manager.create_session
240
+
241
+ expect(session_id).to match(/\d{8}_\d{6}/)
242
+ expect(manager.current_session).to eq(session_id)
243
+ end
244
+ end
245
+
246
+ describe '#stats' do
247
+ it 'returns system statistics' do
248
+ stats = manager.stats
249
+
250
+ expect(stats).to have_key(:subscribers)
251
+ expect(stats).to have_key(:storage)
252
+ expect(stats).to have_key(:config)
253
+ end
254
+
255
+ it 'includes subscriber counts' do
256
+ subscriber1 = create_test_subscriber
257
+ subscriber2 = create_test_subscriber
258
+
259
+ manager.subscribe(:event1, subscriber1)
260
+ manager.subscribe(:event1, subscriber2)
261
+ manager.subscribe(:event2, subscriber1)
262
+
263
+ stats = manager.stats
264
+ expect(stats[:subscribers]['event1']).to eq(2)
265
+ expect(stats[:subscribers]['event2']).to eq(1)
266
+ end
267
+ end
268
+
269
+ describe '#close' do
270
+ it 'closes the event manager' do
271
+ manager = create_file_manager
272
+ expect { manager.close }.not_to raise_error
273
+ end
274
+ end
275
+
276
+ describe '#ready?' do
277
+ it 'returns true when storage is available' do
278
+ manager = create_file_manager
279
+ expect(manager.ready?).to be true
280
+ end
281
+
282
+ it 'returns true when memory storage is available' do
283
+ manager = create_test_manager(:memory)
284
+ expect(manager.ready?).to be true
285
+ end
286
+ end
287
+
288
+ describe 'subscriber priority' do
289
+ it 'processes subscribers in priority order' do
290
+ high_priority = Class.new do
291
+ include EventSystem::EventSubscriber
292
+ attr_reader :events_received
293
+ def initialize; @events_received = []; end
294
+ def handle_event(event); @events_received << "high_#{event.type}"; end
295
+ def event_priority; 1; end
296
+ end.new
297
+
298
+ low_priority = Class.new do
299
+ include EventSystem::EventSubscriber
300
+ attr_reader :events_received
301
+ def initialize; @events_received = []; end
302
+ def handle_event(event); @events_received << "low_#{event.type}"; end
303
+ def event_priority; 10; end
304
+ end.new
305
+
306
+ manager.subscribe(:test_event, high_priority)
307
+ manager.subscribe(:test_event, low_priority)
308
+
309
+ event = create_test_event(:test_event)
310
+ manager.publish(event)
311
+
312
+ # High priority should be processed first (lower number = higher priority)
313
+ expect(high_priority.events_received).to include('high_test_event')
314
+ expect(low_priority.events_received).to include('low_test_event')
315
+ end
316
+ end
317
+
318
+ describe 'event type filtering' do
319
+ it 'respects subscriber event type filtering' do
320
+ filtered_subscriber = Class.new do
321
+ include EventSystem::EventSubscriber
322
+ attr_reader :events_received
323
+ def initialize; @events_received = []; end
324
+ def handle_event(event); @events_received << event; end
325
+ def handles_event_type?(event_type); event_type == 'allowed_event'; end
326
+ end.new
327
+
328
+ manager.subscribe(:allowed_event, filtered_subscriber)
329
+ manager.subscribe(:blocked_event, filtered_subscriber)
330
+
331
+ allowed_event = create_test_event(:allowed_event)
332
+ blocked_event = create_test_event(:blocked_event)
333
+
334
+ manager.publish(allowed_event)
335
+ manager.publish(blocked_event)
336
+
337
+ expect(filtered_subscriber.events_received).to include(allowed_event)
338
+ expect(filtered_subscriber.events_received).not_to include(blocked_event)
339
+ end
340
+ end
341
+ end