euston-eventstore 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/Rakefile +118 -0
  2. data/euston-eventstore.gemspec +66 -0
  3. data/lib/euston-eventstore.rb +7 -0
  4. data/lib/euston-eventstore/commit.rb +77 -0
  5. data/lib/euston-eventstore/constants.rb +5 -0
  6. data/lib/euston-eventstore/dispatcher/asynchronous_dispatcher.rb +37 -0
  7. data/lib/euston-eventstore/dispatcher/null_dispatcher.rb +11 -0
  8. data/lib/euston-eventstore/dispatcher/synchronous_dispatcher.rb +21 -0
  9. data/lib/euston-eventstore/errors.rb +21 -0
  10. data/lib/euston-eventstore/event_message.rb +26 -0
  11. data/lib/euston-eventstore/optimistic_event_store.rb +68 -0
  12. data/lib/euston-eventstore/optimistic_event_stream.rb +106 -0
  13. data/lib/euston-eventstore/persistence/mongodb/mongo_commit.rb +82 -0
  14. data/lib/euston-eventstore/persistence/mongodb/mongo_commit_id.rb +16 -0
  15. data/lib/euston-eventstore/persistence/mongodb/mongo_config.rb +28 -0
  16. data/lib/euston-eventstore/persistence/mongodb/mongo_event_message.rb +31 -0
  17. data/lib/euston-eventstore/persistence/mongodb/mongo_persistence_engine.rb +167 -0
  18. data/lib/euston-eventstore/persistence/mongodb/mongo_persistence_factory.rb +31 -0
  19. data/lib/euston-eventstore/persistence/mongodb/mongo_snapshot.rb +32 -0
  20. data/lib/euston-eventstore/persistence/mongodb/mongo_stream_head.rb +29 -0
  21. data/lib/euston-eventstore/persistence/stream_head.rb +23 -0
  22. data/lib/euston-eventstore/snapshot.rb +21 -0
  23. data/lib/euston-eventstore/version.rb +5 -0
  24. data/spec/event_store/dispatcher/asynchronous_dispatcher_spec.rb +75 -0
  25. data/spec/event_store/dispatcher/synchronous_dispatcher_spec.rb +39 -0
  26. data/spec/event_store/optimistic_event_store_spec.rb +292 -0
  27. data/spec/event_store/optimistic_event_stream_spec.rb +318 -0
  28. data/spec/event_store/persistence/mongodb_spec.rb +301 -0
  29. data/spec/event_store/serialization/simple_message.rb +12 -0
  30. data/spec/spec_helper.rb +39 -0
  31. data/spec/support/array_enumeration_counter.rb +20 -0
  32. metadata +189 -0
@@ -0,0 +1,318 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Euston::EventStore do
4
+ let(:uuid) { Uuid }
5
+ let(:default_stream_revision) { 1 }
6
+ let(:default_commit_sequence) { 1 }
7
+ let(:stream_id) { uuid.generate }
8
+ let(:persistence) { double('persistence') }
9
+ let(:stream) { Euston::EventStore::OptimisticEventStream.new(:stream_id => stream_id, :persistence => persistence) }
10
+
11
+ after { stream_id = uuid.generate }
12
+
13
+ def build_commit_stub(stream_id, revision, sequence, length)
14
+ Euston::EventStore::Commit.new( :stream_id => stream_id,
15
+ :stream_revision => revision,
16
+ :commit_sequence => sequence,
17
+ :events => length.times.map{ Euston::EventStore::EventMessage.new })
18
+ end
19
+
20
+ describe 'optimistic event stream' do
21
+ context 'when constructing a new stream' do
22
+ let(:min_revision) { 2 }
23
+ let(:max_revision) { 7 }
24
+ let(:commit_length) { 2 }
25
+ let(:committed) { [
26
+ build_commit_stub(stream_id, 2, 1, commit_length),
27
+ build_commit_stub(stream_id, 4, 2, commit_length),
28
+ build_commit_stub(stream_id, 6, 3, commit_length),
29
+ build_commit_stub(stream_id, 8, 3, commit_length)
30
+ ] }
31
+
32
+ before do
33
+ persistence.stub(:get_from).with(stream_id, min_revision, max_revision) { committed }
34
+ @stream = Euston::EventStore::OptimisticEventStream.new(:stream_id => stream_id,
35
+ :persistence => persistence,
36
+ :min_revision => min_revision,
37
+ :max_revision => max_revision)
38
+ end
39
+
40
+ it 'has the correct stream identifier' do
41
+ @stream.stream_id.should == stream_id
42
+ end
43
+
44
+ it 'has the correct head stream revision' do
45
+ @stream.stream_revision.should == max_revision
46
+ end
47
+
48
+ it 'has the correct head commit sequence' do
49
+ @stream.commit_sequence.should == committed.last.commit_sequence
50
+ end
51
+
52
+ it 'does not include the event below the minimum revision indicated' do
53
+ @stream.committed_events.first.should == committed.first.events.last
54
+ end
55
+
56
+ it 'does not include events above the maximum revision indicated' do
57
+ @stream.committed_events.last.should == committed.last.events.first
58
+ end
59
+
60
+ it 'has all of the committed events up to the stream revision specified' do
61
+ @stream.committed_events.length.should == max_revision - min_revision + 1
62
+ end
63
+ end
64
+
65
+ context 'when constructing the head event revision is less than the max desired revision' do
66
+ let(:commit_length) { 2 }
67
+ let(:committed) { [
68
+ build_commit_stub(stream_id, 2, 1, commit_length),
69
+ build_commit_stub(stream_id, 4, 2, commit_length),
70
+ build_commit_stub(stream_id, 6, 3, commit_length),
71
+ build_commit_stub(stream_id, 8, 3, commit_length)
72
+ ] }
73
+
74
+ before do
75
+ persistence.stub(:get_from).with(stream_id, 0, Euston::EventStore::FIXNUM_MAX) { committed }
76
+ @stream = Euston::EventStore::OptimisticEventStream.new(:stream_id => stream_id,
77
+ :persistence => persistence,
78
+ :min_revision => 0,
79
+ :max_revision => Euston::EventStore::FIXNUM_MAX)
80
+ end
81
+
82
+ it 'sets the stream revision to the revision of the most recent event' do
83
+ @stream.stream_revision.should == committed.last.stream_revision
84
+ end
85
+ end
86
+
87
+ context 'when adding a null event message' do
88
+ before do
89
+ stream << nil
90
+ end
91
+
92
+ it 'is ignored' do
93
+ stream.uncommitted_events.should be_empty
94
+ end
95
+ end
96
+
97
+ context 'when adding an unpopulated event message' do
98
+ before do
99
+ stream << Euston::EventStore::EventMessage.new(nil)
100
+ end
101
+
102
+ it 'is ignored' do
103
+ stream.uncommitted_events.should be_empty
104
+ end
105
+ end
106
+
107
+ context 'when adding a fully populated event message' do
108
+ before do
109
+ stream << Euston::EventStore::EventMessage.new('populated')
110
+ end
111
+
112
+ it 'adds the event to the set of uncommitted events' do
113
+ stream.uncommitted_events.should have(1).items
114
+ end
115
+ end
116
+
117
+ context 'when adding multiple populated event messages' do
118
+ before do
119
+ stream << Euston::EventStore::EventMessage.new('populated')
120
+ stream << Euston::EventStore::EventMessage.new('also populated')
121
+ end
122
+
123
+ it 'adds all the events provided to the set of uncommitted events' do
124
+ stream.uncommitted_events.should have(2).items
125
+ end
126
+ end
127
+
128
+ context 'when adding a simple object as an event message' do
129
+ let(:my_event) { 'some event data' }
130
+
131
+ before do
132
+ stream << Euston::EventStore::EventMessage.new(my_event)
133
+ end
134
+
135
+ it 'adds the uncommitted event to the set of uncommitted events' do
136
+ stream.uncommitted_events.should have(1).items
137
+ end
138
+
139
+ it 'wraps the uncommitted event in an EventMessage object' do
140
+ stream.uncommitted_events.first.body.should == my_event
141
+ end
142
+ end
143
+
144
+ context 'when clearing any uncommitted changes' do
145
+ before do
146
+ stream << Euston::EventStore::EventMessage.new('')
147
+ stream.clear_changes
148
+ end
149
+
150
+ it 'clears all uncommitted events' do
151
+ stream.uncommitted_events.should be_empty
152
+ end
153
+ end
154
+
155
+ context 'when committing an empty changeset' do
156
+ before do
157
+ persistence.stub(:commit) { @persisted = true }
158
+ stream.commit_changes uuid.generate
159
+ end
160
+
161
+ it 'does not call the underlying infrastructure' do
162
+ @persisted.should be_nil
163
+ end
164
+
165
+ it 'does not increment the current stream revision' do
166
+ stream.stream_revision.should == 0
167
+ end
168
+
169
+ it 'does not increment the current commit sequence' do
170
+ stream.commit_sequence.should == 0
171
+ end
172
+ end
173
+
174
+ context 'when committing any uncommitted changes' do
175
+ let(:commit_id) { uuid.generate }
176
+ let(:uncommitted) { Euston::EventStore::EventMessage.new '' }
177
+ let(:headers) { { :key => :value } }
178
+
179
+ before do
180
+ persistence.stub(:commit) { |c| @constructed = c }
181
+ stream << uncommitted
182
+ headers.each { |key, value| stream.uncommitted_headers[key] = value }
183
+ stream.commit_changes commit_id
184
+ end
185
+
186
+ it 'provides a commit to the underlying infrastructure' do
187
+ @constructed.should_not be_nil
188
+ end
189
+
190
+ it 'builds the commit with the correct stream identifier' do
191
+ @constructed.stream_id.should == stream_id
192
+ end
193
+
194
+ it 'builds the commit with the correct stream revision' do
195
+ @constructed.stream_revision.should == default_stream_revision
196
+ end
197
+
198
+ it 'builds the commit with the correct commit identifier' do
199
+ @constructed.commit_id.should == commit_id
200
+ end
201
+
202
+ it 'builds the commit with an incremented commit sequence' do
203
+ @constructed.commit_sequence.should == default_commit_sequence
204
+ end
205
+
206
+ it 'builds the commit with the correct commit stamp' do
207
+ ((Time.now - @constructed.commit_timestamp) < 0.05).should be_true
208
+ end
209
+
210
+ it 'builds the commit with the headers provided' do
211
+ @constructed.headers.each do |key, value|
212
+ headers[key].should == value
213
+ end
214
+ @constructed.headers.keys.length.should == headers.keys.length
215
+ end
216
+
217
+ it 'builds the commit containing all uncommitted events' do
218
+ @constructed.events.should have(1).items
219
+ end
220
+
221
+ it 'builds the commit using the event messages provided' do
222
+ @constructed.events.first.should == uncommitted
223
+ end
224
+
225
+ it 'updates the stream revision' do
226
+ stream.stream_revision.should == @constructed.stream_revision
227
+ end
228
+
229
+ it 'updates the commit sequence' do
230
+ stream.commit_sequence.should == @constructed.commit_sequence
231
+ end
232
+
233
+ it 'adds the uncommitted events to the committed events' do
234
+ stream.committed_events.last.should == uncommitted
235
+ end
236
+
237
+ it 'clears the uncommitted events' do
238
+ stream.uncommitted_events.should have(0).items
239
+ end
240
+
241
+ it 'clears the uncommitted headers' do
242
+ stream.uncommitted_headers.should have(0).items
243
+ end
244
+ end
245
+
246
+ context 'when committing with an identifier that was previously read' do
247
+ let(:committed) { [ build_commit_stub(stream_id, 1, 1, 1) ] }
248
+ let(:duplicate_commit_id) { committed.first.commit_id }
249
+
250
+ before do
251
+ persistence.stub(:get_from).with(stream_id, 0, Euston::EventStore::FIXNUM_MAX) { committed }
252
+
253
+ @stream = Euston::EventStore::OptimisticEventStream.new(:stream_id => stream_id,
254
+ :persistence => persistence,
255
+ :min_revision => 0,
256
+ :max_revision => Euston::EventStore::FIXNUM_MAX)
257
+
258
+ begin
259
+ @stream.commit_changes duplicate_commit_id
260
+ rescue Exception => e
261
+ @thrown = e
262
+ end
263
+ end
264
+
265
+ it 'throws a DuplicateCommitError' do
266
+ @thrown.should be_a(Euston::EventStore::DuplicateCommitError)
267
+ end
268
+ end
269
+
270
+ context 'when committing after another thread or process has moved the stream head' do
271
+ let(:stream_revision) { 1 }
272
+ let(:committed) { [ build_commit_stub(stream_id, 1, 1, 1) ] }
273
+ let(:uncommitted) { Euston::EventStore::EventMessage.new '' }
274
+ let(:discovered_on_commit) { [ build_commit_stub(stream_id, 3, 2, 2) ] }
275
+
276
+ before do
277
+ persistence.stub(:commit) { raise Euston::EventStore::ConcurrencyError.new }
278
+ persistence.stub(:get_from).with(stream_id, stream_revision, Euston::EventStore::FIXNUM_MAX) { committed }
279
+ persistence.stub(:get_from).with(stream_id, stream_revision + 1, Euston::EventStore::FIXNUM_MAX) do
280
+ @queried_for_new_commits = true
281
+ discovered_on_commit
282
+ end
283
+
284
+ @stream = Euston::EventStore::OptimisticEventStream.new(:stream_id => stream_id,
285
+ :persistence => persistence,
286
+ :min_revision => stream_revision,
287
+ :max_revision => Euston::EventStore::FIXNUM_MAX)
288
+ @stream << uncommitted
289
+
290
+ begin
291
+ @stream.commit_changes uuid.generate
292
+ rescue Exception => e
293
+ @thrown = e
294
+ end
295
+ end
296
+
297
+ it 'throws a ConcurrencyError' do
298
+ @thrown.should be_a(Euston::EventStore::ConcurrencyError)
299
+ end
300
+
301
+ it 'queries the underlying storage to discover the new commits' do
302
+ @queried_for_new_commits.should be_true
303
+ end
304
+
305
+ it 'updates the stream revision accordingly' do
306
+ @stream.stream_revision.should == discovered_on_commit.first.stream_revision
307
+ end
308
+
309
+ it 'updates the commit sequence accordingly' do
310
+ @stream.commit_sequence.should == discovered_on_commit.first.commit_sequence
311
+ end
312
+
313
+ it 'add the newly discovered committed events to the set of committed events accordingly' do
314
+ @stream.committed_events.should have(discovered_on_commit.first.events.length + 1).items
315
+ end
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,301 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+
3
+ describe Euston::EventStore do
4
+ describe 'mongodb persistence' do
5
+ let(:uuid) { Uuid }
6
+ let(:database) { 'event_store_tests' }
7
+ let(:config) { Euston::EventStore::Persistence::Mongodb::Config }
8
+ let(:factory) { Euston::EventStore::Persistence::Mongodb::MongoPersistenceFactory }
9
+ let(:stream_id) { uuid.generate }
10
+
11
+ before do
12
+ config.instance.database = database
13
+
14
+ @persistence = factory.build
15
+ @persistence.init
16
+ end
17
+
18
+ context 'when a commit is successfully persisted' do
19
+ let(:now) { Time.now.utc + (60 * 60 * 24 * 7 * 52) }
20
+ let(:attempt) { new_attempt(:commit_timestamp => now) }
21
+
22
+ before do
23
+ @persistence.commit attempt
24
+
25
+ sleep 0.25
26
+
27
+ @persisted = @persistence.get_from(:stream_id => stream_id,
28
+ :min_revision => 0,
29
+ :max_revision => Euston::EventStore::FIXNUM_MAX).first
30
+ end
31
+
32
+ it('correctly persists the stream identifier') { @persisted.stream_id.should == attempt.stream_id }
33
+ it('correctly persists the stream revision') { @persisted.stream_revision.should == attempt.stream_revision }
34
+ it('correctly persists the commit identifier') { @persisted.commit_id.should == attempt.commit_id }
35
+ it('correctly persists the commit sequence') { @persisted.commit_sequence.should == attempt.commit_sequence }
36
+
37
+ # persistence engines have varying levels of precision with respect to time.
38
+ it('correctly persists the commit timestamp') { (@persisted.commit_timestamp - now.to_f).should be <= 1 }
39
+
40
+ it('correctly persists the headers') { @persisted.headers.should have(attempt.headers.length).items }
41
+ it('correctly persists the events') { @persisted.events.should have(attempt.events.length).items }
42
+ it('makes the commit available to be read from the stream') {
43
+ @persistence.get_from(:stream_id => stream_id,
44
+ :min_revision => 0,
45
+ :max_revision => Euston::EventStore::FIXNUM_MAX).first.commit_id.should == attempt.commit_id }
46
+
47
+ it('adds the commit to the set of undispatched commits') {
48
+ @persistence.get_undispatched_commits.detect { |x| x.commit_id == attempt.commit_id }.should_not be_nil }
49
+
50
+ it('causes the stream to be found in the list of streams to snapshot') {
51
+ @persistence.get_streams_to_snapshot(1).detect { |x| x.stream_id == stream_id }.should_not be_nil }
52
+ end
53
+
54
+ context 'when reading from a given revision' do
55
+ let(:load_from_commit_containing_revision) { 3 }
56
+ let(:up_to_commit_containing_revision) { 5 }
57
+ let(:oldest) { new_attempt }
58
+ let(:oldest2) { next_attempt(oldest) }
59
+ let(:oldest3) { next_attempt(oldest2) }
60
+ let(:newest) { next_attempt(oldest3) }
61
+
62
+ before do
63
+ @persistence.commit oldest
64
+ @persistence.commit oldest2
65
+ @persistence.commit oldest3
66
+ @persistence.commit newest
67
+
68
+ @committed = @persistence.get_from(:stream_id => stream_id,
69
+ :min_revision => load_from_commit_containing_revision,
70
+ :max_revision => up_to_commit_containing_revision).to_a
71
+
72
+ end
73
+
74
+ it('starts from the commit which contains the minimum stream revision specified') { @committed.first.commit_id.should == oldest2.commit_id }
75
+ it('reads up to the commit which contains the maximum stream revision specified') { @committed.last.commit_id.should == oldest3.commit_id }
76
+ end
77
+
78
+ context 'when committing a stream with the same revision' do
79
+ let(:persistence1) { factory.build }
80
+ let(:persistence2) { factory.build }
81
+ let(:attempt1) { new_attempt }
82
+ let(:attempt2) { new_attempt }
83
+
84
+ before do
85
+ persistence1.init
86
+ persistence2.init
87
+
88
+ persistence1.commit attempt1
89
+
90
+ begin
91
+ persistence2.commit attempt2
92
+ rescue Exception => e
93
+ @caught = e
94
+ end
95
+ end
96
+
97
+ it('raises a ConcurrencyError') { @caught.should be_an(Euston::EventStore::ConcurrencyError) }
98
+ end
99
+
100
+ context 'when committing a stream with the same sequence' do
101
+ let(:persistence1) { factory.build }
102
+ let(:persistence2) { factory.build }
103
+ let(:attempt1) { new_attempt }
104
+ let(:attempt2) { new_attempt }
105
+
106
+ before do
107
+ persistence1.init
108
+ persistence2.init
109
+
110
+ persistence1.commit attempt1
111
+
112
+ begin
113
+ persistence2.commit attempt2
114
+ rescue Exception => e
115
+ @caught = e
116
+ end
117
+ end
118
+
119
+ it('raises a ConcurrencyError') { @caught.should be_an(Euston::EventStore::ConcurrencyError) }
120
+ end
121
+
122
+ context 'when attempting to overwrite a committed sequence' do
123
+ let(:successful_attempt) { new_attempt }
124
+ let(:failed_attempt) { new_attempt }
125
+
126
+ before do
127
+ @persistence.commit successful_attempt
128
+
129
+ begin
130
+ @persistence.commit failed_attempt
131
+ rescue Exception => e
132
+ @caught = e
133
+ end
134
+ end
135
+
136
+ it('raises a ConcurrencyError') { @caught.should be_an(Euston::EventStore::ConcurrencyError) }
137
+ end
138
+
139
+ context 'when attempting to persist a commit twice' do
140
+ let(:attempt) { new_attempt }
141
+
142
+ before do
143
+ @persistence.commit attempt
144
+
145
+ begin
146
+ @persistence.commit attempt
147
+ rescue Exception => e
148
+ @caught = e
149
+ end
150
+ end
151
+
152
+ it('raises a DuplicateCommitError') { @caught.should be_an(Euston::EventStore::DuplicateCommitError) }
153
+ end
154
+
155
+ context 'when a commit has been marked as dispatched' do
156
+ let(:attempt) { new_attempt }
157
+
158
+ before do
159
+ @persistence.commit attempt
160
+ @persistence.mark_commit_as_dispatched attempt
161
+ end
162
+
163
+ it('is no longer found in the set of undispatched commits') {
164
+ @persistence.get_undispatched_commits.detect { |c| c.commit_id == attempt.commit_id }.should be_nil }
165
+ end
166
+
167
+ context 'when saving a snapshot' do
168
+ let(:snapshot) { Euston::EventStore::Snapshot.new stream_id, 1, { :key => :value } }
169
+
170
+ before do
171
+ @persistence.commit new_attempt
172
+
173
+ sleep 0.25
174
+
175
+ @added = @persistence.add_snapshot snapshot
176
+ end
177
+
178
+ it('indicates the snapshot was added') { @added.should be_true }
179
+ it('is able to retrieve the snapshot') { @persistence.get_snapshot(stream_id, snapshot.stream_revision).should_not be_nil }
180
+ end
181
+
182
+ context 'when retrieving a snapshot' do
183
+ let(:too_far_back) { Euston::EventStore::Snapshot.new stream_id, 1, {} }
184
+ let(:correct) { Euston::EventStore::Snapshot.new stream_id, 3, { 'key' => 'value' } }
185
+ let(:too_far_forward) { Euston::EventStore::Snapshot.new stream_id, 5, {} }
186
+ let(:commit1) { new_attempt }
187
+ let(:commit2) { next_attempt commit1 }
188
+ let(:commit3) { next_attempt commit2 }
189
+
190
+ before do
191
+ @persistence.commit commit1
192
+ @persistence.commit commit2
193
+ @persistence.commit commit3
194
+
195
+ sleep 0.25
196
+
197
+ @persistence.add_snapshot too_far_back
198
+ @persistence.add_snapshot correct
199
+ @persistence.add_snapshot too_far_forward
200
+
201
+ @snapshot = @persistence.get_snapshot stream_id, too_far_forward.stream_revision - 1
202
+ end
203
+
204
+ it('loads the most recent prior snapshot') { @snapshot.stream_revision.should == correct.stream_revision }
205
+ it('has the correct snapshot payload') { @snapshot.payload.should == correct.payload }
206
+ end
207
+
208
+ context 'when a snapshot has been added to the most recent commit of a stream' do
209
+ let(:oldest) { new_attempt }
210
+ let(:oldest2) { next_attempt oldest }
211
+ let(:newest) { next_attempt oldest2 }
212
+ let(:snapshot) { Euston::EventStore::Snapshot.new stream_id, newest.stream_revision, { :key => :value } }
213
+
214
+ before do
215
+ @persistence.commit oldest
216
+ @persistence.commit oldest2
217
+ @persistence.commit newest
218
+
219
+ sleep 0.25
220
+
221
+ @persistence.add_snapshot snapshot
222
+ end
223
+
224
+ it('no longer finds the stream in the set of streams to be snapshot') {
225
+ @persistence.get_streams_to_snapshot(1).detect { |x| x.stream_id == stream_id }.should be_nil }
226
+ end
227
+
228
+ # Timing issues with this one?
229
+ #
230
+ # context 'when adding a commit after a snapshot' do
231
+ # let(:within_threshold) { 2 }
232
+ # let(:over_threshold) { 3 }
233
+ # let(:snapshot_data) { { :key => :value } }
234
+ # let(:oldest) { new_attempt }
235
+ # let(:oldest2) { next_attempt oldest }
236
+ # let(:newest) { next_attempt oldest2 }
237
+
238
+ # before do
239
+ # @persistence.commit oldest
240
+ # @persistence.commit oldest2
241
+
242
+ # sleep 0.25
243
+
244
+ # @persistence.add_snapshot Euston::EventStore::Snapshot.new(stream_id, oldest2.stream_revision, snapshot_data)
245
+ # @persistence.commit newest
246
+ # end
247
+
248
+ # it 'finds the stream in the set of streams to be snapshot when within the threshold' do
249
+ # @persistence.get_streams_to_snapshot(within_threshold).detect { |x| x.stream_id == stream_id }.should_not be_nil
250
+ # end
251
+
252
+ # it 'does not find the stream in the set of stream to be snapshot when over the threshold' do
253
+ # @persistence.get_streams_to_snapshot(over_threshold).detect { |x| x.stream_id == stream_id }.should be_nil
254
+ # end
255
+ # end
256
+
257
+ context 'when reading all commits from a particular point in time' do
258
+ let(:now) { Time.now.utc + (60 * 60 * 24 * 7 * 52) }
259
+ let(:first) { new_attempt(:commit_timestamp => now + 1) }
260
+ let(:second) { next_attempt first }
261
+ let(:third) { next_attempt second }
262
+ let(:fourth) { next_attempt third }
263
+
264
+ before do
265
+ @persistence.commit first
266
+ @persistence.commit second
267
+ @persistence.commit third
268
+ @persistence.commit fourth
269
+
270
+ @committed = @persistence.get_from :timestamp => now
271
+ end
272
+
273
+ it('returns all commits on or after the point in time specified') { @committed.should have(4).items }
274
+ end
275
+
276
+ def new_attempt(options = {})
277
+ defaults = { :stream_id => stream_id,
278
+ :stream_revision => 2,
279
+ :commit_id => uuid.generate,
280
+ :commit_sequence => 1,
281
+ :commit_timestamp => Time.now.utc,
282
+ :headers => { 'A header' => 'A string value',
283
+ 'Another header' => 2 },
284
+ :events => [ Euston::EventStore::EventMessage.new(:some_property => 'test'),
285
+ Euston::EventStore::EventMessage.new(:some_property => 'test2') ] }
286
+
287
+ Euston::EventStore::Commit.new(defaults.merge options)
288
+ end
289
+
290
+ def next_attempt(attempt)
291
+ Euston::EventStore::Commit.new(:stream_id => attempt.stream_id,
292
+ :stream_revision => attempt.stream_revision + 2,
293
+ :commit_id => uuid.generate,
294
+ :commit_sequence => attempt.commit_sequence + 1,
295
+ :commit_timestamp => attempt.commit_timestamp,
296
+ :headers => {},
297
+ :events => [ Euston::EventStore::EventMessage.new(:some_property => 'Another test'),
298
+ Euston::EventStore::EventMessage.new(:some_property => 'Another test2') ])
299
+ end
300
+ end
301
+ end