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,341 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe EventSystem::Storage::FileStore do
|
|
6
|
+
let(:temp_dir) { @temp_dir }
|
|
7
|
+
let(:store) { EventSystem::Storage::FileStore.new(temp_dir, 'test_session') }
|
|
8
|
+
|
|
9
|
+
describe '#initialize' do
|
|
10
|
+
it 'initializes with given directory and session ID' do
|
|
11
|
+
store = EventSystem::Storage::FileStore.new(temp_dir, 'custom_session')
|
|
12
|
+
expect(store.current_session).to eq('custom_session')
|
|
13
|
+
expect(store.storage_path).to eq(temp_dir)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'generates timestamp session ID when none provided' do
|
|
17
|
+
store = EventSystem::Storage::FileStore.new(temp_dir)
|
|
18
|
+
expect(store.current_session).to match(/\d{8}_\d{6}/)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'creates directory if it does not exist' do
|
|
22
|
+
new_dir = File.join(temp_dir, 'new_directory')
|
|
23
|
+
store = EventSystem::Storage::FileStore.new(new_dir)
|
|
24
|
+
expect(Dir.exist?(new_dir)).to be true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '#store' do
|
|
29
|
+
it 'stores an event to file' do
|
|
30
|
+
event = create_test_event(:test_type, 'TestSource', { key: 'value' })
|
|
31
|
+
store.store(event)
|
|
32
|
+
|
|
33
|
+
# Read the file to verify event was stored
|
|
34
|
+
file_path = File.join(temp_dir, "events_#{store.current_session}.jsonl")
|
|
35
|
+
expect(File.exist?(file_path)).to be true
|
|
36
|
+
|
|
37
|
+
content = File.read(file_path)
|
|
38
|
+
expect(content).to include(event.id)
|
|
39
|
+
expect(content).to include('test_type')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'flushes data immediately' do
|
|
43
|
+
event = create_test_event(:test_type)
|
|
44
|
+
store.store(event)
|
|
45
|
+
|
|
46
|
+
# File should be immediately available
|
|
47
|
+
file_path = File.join(temp_dir, "events_#{store.current_session}.jsonl")
|
|
48
|
+
expect(File.exist?(file_path)).to be true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#query' do
|
|
53
|
+
let(:event1) { create_test_event(:type1, 'Source1', { key: 'value1' }) }
|
|
54
|
+
let(:event2) { create_test_event(:type2, 'Source2', { key: 'value2' }) }
|
|
55
|
+
let(:event3) { create_test_event(:type1, 'Source1', { key: 'value3' }) }
|
|
56
|
+
|
|
57
|
+
before do
|
|
58
|
+
store.store(event1)
|
|
59
|
+
store.store(event2)
|
|
60
|
+
store.store(event3)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'returns all events by default' do
|
|
64
|
+
events = store.query
|
|
65
|
+
expect(events).to include(event1, event2, event3)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'filters by type' do
|
|
69
|
+
events = store.query(type: 'type1')
|
|
70
|
+
expect(events).to include(event1, event3)
|
|
71
|
+
expect(events).not_to include(event2)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'filters by session_id' do
|
|
75
|
+
store2 = EventSystem::Storage::FileStore.new(temp_dir, 'other_session')
|
|
76
|
+
other_event = create_test_event(:type1)
|
|
77
|
+
store2.store(other_event)
|
|
78
|
+
|
|
79
|
+
events = store.query(session_id: 'other_session')
|
|
80
|
+
expect(events).to include(other_event)
|
|
81
|
+
expect(events).not_to include(event1, event2, event3)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'filters by start_time' do
|
|
85
|
+
# Create a new store to avoid session conflicts
|
|
86
|
+
new_store = EventSystem::Storage::FileStore.new(@temp_dir, 'time_test_session')
|
|
87
|
+
|
|
88
|
+
# Store events with different timestamps
|
|
89
|
+
past_event = EventSystem::Event.new(:past_type, 'Source', { key: 'past' }, nil, Time.now - 1)
|
|
90
|
+
future_event = EventSystem::Event.new(:future_type, 'Source', { key: 'future' }, nil, Time.now + 1)
|
|
91
|
+
|
|
92
|
+
new_store.store(past_event)
|
|
93
|
+
new_store.store(future_event)
|
|
94
|
+
|
|
95
|
+
# Query with start_time
|
|
96
|
+
start_time = Time.now
|
|
97
|
+
events = new_store.query(start_time: start_time)
|
|
98
|
+
expect(events).to include(future_event)
|
|
99
|
+
expect(events).not_to include(past_event)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'filters by end_time' do
|
|
103
|
+
# Create a new store to avoid session conflicts
|
|
104
|
+
new_store = EventSystem::Storage::FileStore.new(@temp_dir, 'time_test_session2')
|
|
105
|
+
|
|
106
|
+
# Store events with different timestamps
|
|
107
|
+
past_event = EventSystem::Event.new(:past_type, 'Source', { key: 'past' }, nil, Time.now - 1)
|
|
108
|
+
future_event = EventSystem::Event.new(:future_type, 'Source', { key: 'future' }, nil, Time.now + 1)
|
|
109
|
+
|
|
110
|
+
new_store.store(past_event)
|
|
111
|
+
new_store.store(future_event)
|
|
112
|
+
|
|
113
|
+
# Query with end_time
|
|
114
|
+
end_time = Time.now
|
|
115
|
+
events = new_store.query(end_time: end_time)
|
|
116
|
+
expect(events).to include(past_event)
|
|
117
|
+
expect(events).not_to include(future_event)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'limits results' do
|
|
121
|
+
events = store.query(limit: 2)
|
|
122
|
+
expect(events.length).to eq(2)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'sorts by timestamp' do
|
|
126
|
+
events = store.query
|
|
127
|
+
timestamps = events.map(&:timestamp)
|
|
128
|
+
expect(timestamps).to eq(timestamps.sort)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'combines multiple filters' do
|
|
132
|
+
events = store.query(type: 'type1', limit: 1)
|
|
133
|
+
expect(events.length).to eq(1)
|
|
134
|
+
expect(events.first.type).to eq('type1')
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe '#load_session' do
|
|
139
|
+
it 'loads events from current session by default' do
|
|
140
|
+
event = create_test_event(:test_type)
|
|
141
|
+
store.store(event)
|
|
142
|
+
|
|
143
|
+
events = store.load_session
|
|
144
|
+
expect(events).to include(event)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'loads events from specific session' do
|
|
148
|
+
event = create_test_event(:test_type)
|
|
149
|
+
store.store(event)
|
|
150
|
+
|
|
151
|
+
events = store.load_session('test_session')
|
|
152
|
+
expect(events).to include(event)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'returns empty array for non-existent session' do
|
|
156
|
+
events = store.load_session('non_existent')
|
|
157
|
+
expect(events).to eq([])
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'handles malformed JSON lines gracefully' do
|
|
161
|
+
file_path = File.join(temp_dir, "events_#{store.current_session}.jsonl")
|
|
162
|
+
File.write(file_path, "valid_json_line\nmalformed_json_line\n")
|
|
163
|
+
|
|
164
|
+
events = store.load_session
|
|
165
|
+
expect(events).to be_empty
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'skips empty lines' do
|
|
169
|
+
file_path = File.join(temp_dir, "events_#{store.current_session}.jsonl")
|
|
170
|
+
File.write(file_path, "\n\n")
|
|
171
|
+
|
|
172
|
+
events = store.load_session
|
|
173
|
+
expect(events).to be_empty
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe '#list_sessions' do
|
|
178
|
+
it 'returns list of session files' do
|
|
179
|
+
# Store an event to create the session file
|
|
180
|
+
store.store(create_test_event(:test_type))
|
|
181
|
+
expect(store.list_sessions).to eq(['test_session'])
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'includes sessions created after initialization' do
|
|
185
|
+
# Store an event to create the initial session file
|
|
186
|
+
store.store(create_test_event(:test_type))
|
|
187
|
+
store.create_session('new_session')
|
|
188
|
+
# Store an event in the new session to create its file
|
|
189
|
+
store.store(create_test_event(:test_type))
|
|
190
|
+
expect(store.list_sessions).to include('test_session', 'new_session')
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'sorts sessions' do
|
|
194
|
+
store.create_session('a_session')
|
|
195
|
+
store.store(create_test_event(:test_type)) # Create file for a_session
|
|
196
|
+
store.create_session('z_session')
|
|
197
|
+
store.store(create_test_event(:test_type)) # Create file for z_session
|
|
198
|
+
|
|
199
|
+
sessions = store.list_sessions
|
|
200
|
+
expect(sessions).to eq(sessions.sort)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'handles different filename patterns' do
|
|
204
|
+
# Create files with different patterns
|
|
205
|
+
File.write(File.join(temp_dir, 'events_session1.jsonl'), '')
|
|
206
|
+
File.write(File.join(temp_dir, 'session2.jsonl'), '')
|
|
207
|
+
|
|
208
|
+
store = EventSystem::Storage::FileStore.new(temp_dir)
|
|
209
|
+
sessions = store.list_sessions
|
|
210
|
+
expect(sessions).to include('session1')
|
|
211
|
+
# session2.jsonl doesn't match the events_*.jsonl pattern
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
describe '#current_session' do
|
|
216
|
+
it 'returns current session ID' do
|
|
217
|
+
expect(store.current_session).to eq('test_session')
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
describe '#switch_session' do
|
|
222
|
+
it 'switches to different session' do
|
|
223
|
+
store.switch_session('new_session')
|
|
224
|
+
expect(store.current_session).to eq('new_session')
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 'closes current file when switching' do
|
|
228
|
+
event = create_test_event(:test_type)
|
|
229
|
+
store.store(event)
|
|
230
|
+
|
|
231
|
+
store.switch_session('new_session')
|
|
232
|
+
|
|
233
|
+
# Should be able to store to new session
|
|
234
|
+
new_event = create_test_event(:new_type)
|
|
235
|
+
expect { store.store(new_event) }.not_to raise_error
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
describe '#create_session' do
|
|
240
|
+
it 'creates session with given ID' do
|
|
241
|
+
session_id = store.create_session('custom_session')
|
|
242
|
+
expect(session_id).to eq('custom_session')
|
|
243
|
+
expect(store.current_session).to eq('custom_session')
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
it 'generates timestamp ID when none provided' do
|
|
247
|
+
session_id = store.create_session
|
|
248
|
+
expect(session_id).to match(/\d{8}_\d{6}/)
|
|
249
|
+
expect(store.current_session).to eq(session_id)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
describe '#close' do
|
|
254
|
+
it 'closes file handle' do
|
|
255
|
+
event = create_test_event(:test_type)
|
|
256
|
+
store.store(event)
|
|
257
|
+
|
|
258
|
+
store.close
|
|
259
|
+
|
|
260
|
+
# Should be able to close multiple times
|
|
261
|
+
expect { store.close }.not_to raise_error
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
describe '#stats' do
|
|
266
|
+
it 'returns storage statistics' do
|
|
267
|
+
store.store(create_test_event(:type1))
|
|
268
|
+
store.create_session('other_session')
|
|
269
|
+
store.store(create_test_event(:type2)) # Create file for other_session
|
|
270
|
+
|
|
271
|
+
stats = store.stats
|
|
272
|
+
expect(stats[:type]).to eq('EventSystem::Storage::FileStore')
|
|
273
|
+
expect(stats[:available]).to be true
|
|
274
|
+
expect(stats[:directory]).to eq(temp_dir)
|
|
275
|
+
expect(stats[:total_sessions]).to eq(2)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe '#available?' do
|
|
280
|
+
it 'returns true' do
|
|
281
|
+
expect(store.available?).to be true
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
describe 'session isolation' do
|
|
286
|
+
it 'maintains separate files per session' do
|
|
287
|
+
event1 = create_test_event(:type1)
|
|
288
|
+
store.store(event1)
|
|
289
|
+
|
|
290
|
+
store.create_session('session2')
|
|
291
|
+
event2 = create_test_event(:type2)
|
|
292
|
+
store.store(event2)
|
|
293
|
+
|
|
294
|
+
session1_file = File.join(temp_dir, 'events_test_session.jsonl')
|
|
295
|
+
session2_file = File.join(temp_dir, 'events_session2.jsonl')
|
|
296
|
+
|
|
297
|
+
expect(File.exist?(session1_file)).to be true
|
|
298
|
+
expect(File.exist?(session2_file)).to be true
|
|
299
|
+
|
|
300
|
+
session1_content = File.read(session1_file)
|
|
301
|
+
session2_content = File.read(session2_file)
|
|
302
|
+
|
|
303
|
+
expect(session1_content).to include('type1')
|
|
304
|
+
expect(session1_content).not_to include('type2')
|
|
305
|
+
expect(session2_content).to include('type2')
|
|
306
|
+
expect(session2_content).not_to include('type1')
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
describe 'file format' do
|
|
311
|
+
it 'stores events in JSONL format' do
|
|
312
|
+
event = create_test_event(:test_type, 'TestSource', { key: 'value' })
|
|
313
|
+
store.store(event)
|
|
314
|
+
|
|
315
|
+
file_path = File.join(temp_dir, "events_#{store.current_session}.jsonl")
|
|
316
|
+
content = File.read(file_path)
|
|
317
|
+
|
|
318
|
+
# Should be valid JSON
|
|
319
|
+
expect { JSON.parse(content) }.not_to raise_error
|
|
320
|
+
|
|
321
|
+
# Should contain event data
|
|
322
|
+
parsed = JSON.parse(content, symbolize_names: true)
|
|
323
|
+
expect(parsed[:type]).to eq('test_type')
|
|
324
|
+
expect(parsed[:source]).to eq('TestSource')
|
|
325
|
+
expect(parsed[:data][:key]).to eq('value')
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
describe 'backward compatibility' do
|
|
330
|
+
it 'finds session files with different naming patterns' do
|
|
331
|
+
# Create file without events_ prefix
|
|
332
|
+
file_path = File.join(temp_dir, 'legacy_session.jsonl')
|
|
333
|
+
File.write(file_path, '{"type":"test","source":"test","timestamp":"2023-01-01T00:00:00.000Z","id":"test-id","data":{}}')
|
|
334
|
+
|
|
335
|
+
store = EventSystem::Storage::FileStore.new(temp_dir)
|
|
336
|
+
events = store.load_session('legacy_session')
|
|
337
|
+
expect(events.length).to eq(1)
|
|
338
|
+
expect(events.first.type).to eq('test')
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe EventSystem::Storage::MemoryStore do
|
|
6
|
+
let(:store) { EventSystem::Storage::MemoryStore.new('test_session') }
|
|
7
|
+
|
|
8
|
+
describe '#initialize' do
|
|
9
|
+
it 'initializes with given session ID' do
|
|
10
|
+
store = EventSystem::Storage::MemoryStore.new('custom_session')
|
|
11
|
+
expect(store.current_session).to eq('custom_session')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'generates timestamp session ID when none provided' do
|
|
15
|
+
store = EventSystem::Storage::MemoryStore.new
|
|
16
|
+
expect(store.current_session).to match(/\d{8}_\d{6}/)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'initializes empty events and sessions' do
|
|
20
|
+
expect(store.size).to eq(0)
|
|
21
|
+
expect(store.list_sessions).to eq(['test_session'])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#store' do
|
|
26
|
+
it 'stores an event' do
|
|
27
|
+
event = create_test_event(:test_type)
|
|
28
|
+
store.store(event)
|
|
29
|
+
|
|
30
|
+
expect(store.size).to eq(1)
|
|
31
|
+
expect(store.load_session).to include(event)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'stores event in current session' do
|
|
35
|
+
event = create_test_event(:test_type)
|
|
36
|
+
store.store(event)
|
|
37
|
+
|
|
38
|
+
events = store.load_session('test_session')
|
|
39
|
+
expect(events).to include(event)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '#query' do
|
|
44
|
+
let(:event1) { create_test_event(:type1, 'Source1', { key: 'value1' }) }
|
|
45
|
+
let(:event2) { create_test_event(:type2, 'Source2', { key: 'value2' }) }
|
|
46
|
+
let(:event3) { create_test_event(:type1, 'Source1', { key: 'value3' }) }
|
|
47
|
+
|
|
48
|
+
before do
|
|
49
|
+
store.store(event1)
|
|
50
|
+
store.store(event2)
|
|
51
|
+
store.store(event3)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'returns all events by default' do
|
|
55
|
+
events = store.query
|
|
56
|
+
expect(events).to include(event1, event2, event3)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'filters by type' do
|
|
60
|
+
events = store.query(type: 'type1')
|
|
61
|
+
expect(events).to include(event1, event3)
|
|
62
|
+
expect(events).not_to include(event2)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'filters by session_id' do
|
|
66
|
+
store2 = EventSystem::Storage::MemoryStore.new('other_session')
|
|
67
|
+
other_event = create_test_event(:type1)
|
|
68
|
+
store2.store(other_event)
|
|
69
|
+
|
|
70
|
+
events = store2.query(session_id: 'other_session')
|
|
71
|
+
expect(events).to include(other_event)
|
|
72
|
+
expect(events).not_to include(event1, event2, event3)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'filters by start_time' do
|
|
76
|
+
start_time = event2.timestamp
|
|
77
|
+
events = store.query(start_time: start_time)
|
|
78
|
+
expect(events).to include(event2, event3)
|
|
79
|
+
expect(events).not_to include(event1)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'filters by end_time' do
|
|
83
|
+
end_time = event1.timestamp
|
|
84
|
+
events = store.query(end_time: end_time)
|
|
85
|
+
expect(events).to include(event1)
|
|
86
|
+
expect(events).not_to include(event2, event3)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'limits results' do
|
|
90
|
+
events = store.query(limit: 2)
|
|
91
|
+
expect(events.length).to eq(2)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'sorts by timestamp' do
|
|
95
|
+
events = store.query
|
|
96
|
+
timestamps = events.map(&:timestamp)
|
|
97
|
+
expect(timestamps).to eq(timestamps.sort)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'combines multiple filters' do
|
|
101
|
+
events = store.query(type: 'type1', limit: 1)
|
|
102
|
+
expect(events.length).to eq(1)
|
|
103
|
+
expect(events.first.type).to eq('type1')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe '#load_session' do
|
|
108
|
+
it 'loads events from current session by default' do
|
|
109
|
+
event = create_test_event(:test_type)
|
|
110
|
+
store.store(event)
|
|
111
|
+
|
|
112
|
+
events = store.load_session
|
|
113
|
+
expect(events).to include(event)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'loads events from specific session' do
|
|
117
|
+
event = create_test_event(:test_type)
|
|
118
|
+
store.store(event)
|
|
119
|
+
|
|
120
|
+
events = store.load_session('test_session')
|
|
121
|
+
expect(events).to include(event)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'returns empty array for non-existent session' do
|
|
125
|
+
events = store.load_session('non_existent')
|
|
126
|
+
expect(events).to eq([])
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe '#list_sessions' do
|
|
131
|
+
it 'returns list of session IDs' do
|
|
132
|
+
expect(store.list_sessions).to eq(['test_session'])
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'includes sessions created after initialization' do
|
|
136
|
+
store.create_session('new_session')
|
|
137
|
+
expect(store.list_sessions).to include('test_session', 'new_session')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'sorts sessions' do
|
|
141
|
+
store.create_session('a_session')
|
|
142
|
+
store.create_session('z_session')
|
|
143
|
+
|
|
144
|
+
sessions = store.list_sessions
|
|
145
|
+
expect(sessions).to eq(sessions.sort)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe '#current_session' do
|
|
150
|
+
it 'returns current session ID' do
|
|
151
|
+
expect(store.current_session).to eq('test_session')
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe '#switch_session' do
|
|
156
|
+
it 'switches to different session' do
|
|
157
|
+
store.switch_session('new_session')
|
|
158
|
+
expect(store.current_session).to eq('new_session')
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'creates session if it does not exist' do
|
|
162
|
+
store.switch_session('new_session')
|
|
163
|
+
expect(store.list_sessions).to include('new_session')
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe '#create_session' do
|
|
168
|
+
it 'creates session with given ID' do
|
|
169
|
+
session_id = store.create_session('custom_session')
|
|
170
|
+
expect(session_id).to eq('custom_session')
|
|
171
|
+
expect(store.current_session).to eq('custom_session')
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'generates timestamp ID when none provided' do
|
|
175
|
+
session_id = store.create_session
|
|
176
|
+
expect(session_id).to match(/\d{8}_\d{6}/)
|
|
177
|
+
expect(store.current_session).to eq(session_id)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe '#clear!' do
|
|
182
|
+
it 'clears all events and sessions' do
|
|
183
|
+
event = create_test_event(:test_type)
|
|
184
|
+
store.store(event)
|
|
185
|
+
store.create_session('other_session')
|
|
186
|
+
|
|
187
|
+
store.clear!
|
|
188
|
+
|
|
189
|
+
expect(store.size).to eq(0)
|
|
190
|
+
expect(store.list_sessions.length).to eq(1)
|
|
191
|
+
expect(store.current_session).to match(/\d{8}_\d{6}/)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
describe '#size' do
|
|
196
|
+
it 'returns total number of events' do
|
|
197
|
+
expect(store.size).to eq(0)
|
|
198
|
+
|
|
199
|
+
store.store(create_test_event(:type1))
|
|
200
|
+
store.store(create_test_event(:type2))
|
|
201
|
+
|
|
202
|
+
expect(store.size).to eq(2)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
describe '#stats' do
|
|
207
|
+
it 'returns storage statistics' do
|
|
208
|
+
store.store(create_test_event(:type1))
|
|
209
|
+
store.create_session('other_session')
|
|
210
|
+
store.store(create_test_event(:type2)) # Store event in new session
|
|
211
|
+
|
|
212
|
+
stats = store.stats
|
|
213
|
+
expect(stats[:type]).to eq('EventSystem::Storage::MemoryStore')
|
|
214
|
+
expect(stats[:available]).to be true
|
|
215
|
+
expect(stats[:total_events]).to eq(2)
|
|
216
|
+
expect(stats[:sessions]).to eq(2)
|
|
217
|
+
expect(stats[:current_session_events]).to eq(1)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
describe '#available?' do
|
|
222
|
+
it 'returns true' do
|
|
223
|
+
expect(store.available?).to be true
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
describe 'session isolation' do
|
|
228
|
+
it 'maintains separate events per session' do
|
|
229
|
+
# Use separate store instances to test isolation
|
|
230
|
+
store1 = EventSystem::Storage::MemoryStore.new('session1')
|
|
231
|
+
store2 = EventSystem::Storage::MemoryStore.new('session2')
|
|
232
|
+
|
|
233
|
+
event1 = create_test_event(:type1)
|
|
234
|
+
event2 = create_test_event(:type2)
|
|
235
|
+
|
|
236
|
+
store1.store(event1)
|
|
237
|
+
store2.store(event2)
|
|
238
|
+
|
|
239
|
+
session1_events = store1.load_session('session1')
|
|
240
|
+
session2_events = store2.load_session('session2')
|
|
241
|
+
|
|
242
|
+
expect(session1_events).to include(event1)
|
|
243
|
+
expect(session1_events).not_to include(event2)
|
|
244
|
+
expect(session2_events).to include(event2)
|
|
245
|
+
expect(session2_events).not_to include(event1)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|