euston-eventstore 1.0.2-java
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.
- data/Rakefile +126 -0
- data/euston-eventstore.gemspec +68 -0
- data/lib/euston-eventstore/commit.rb +77 -0
- data/lib/euston-eventstore/constants.rb +5 -0
- data/lib/euston-eventstore/dispatcher/asynchronous_dispatcher.rb +37 -0
- data/lib/euston-eventstore/dispatcher/null_dispatcher.rb +11 -0
- data/lib/euston-eventstore/dispatcher/synchronous_dispatcher.rb +21 -0
- data/lib/euston-eventstore/errors.rb +21 -0
- data/lib/euston-eventstore/event_message.rb +26 -0
- data/lib/euston-eventstore/optimistic_event_store.rb +68 -0
- data/lib/euston-eventstore/optimistic_event_stream.rb +106 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_commit.rb +82 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_commit_id.rb +16 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_config.rb +28 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_event_message.rb +31 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_persistence_engine.rb +167 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_persistence_factory.rb +31 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_snapshot.rb +32 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_stream_head.rb +29 -0
- data/lib/euston-eventstore/persistence/stream_head.rb +23 -0
- data/lib/euston-eventstore/snapshot.rb +21 -0
- data/lib/euston-eventstore/version.rb +5 -0
- data/lib/euston-eventstore.rb +7 -0
- data/spec/event_store/dispatcher/asynchronous_dispatcher_spec.rb +75 -0
- data/spec/event_store/dispatcher/synchronous_dispatcher_spec.rb +39 -0
- data/spec/event_store/optimistic_event_store_spec.rb +292 -0
- data/spec/event_store/optimistic_event_stream_spec.rb +318 -0
- data/spec/event_store/persistence/mongodb_spec.rb +301 -0
- data/spec/event_store/serialization/simple_message.rb +12 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/array_enumeration_counter.rb +20 -0
- metadata +178 -0
@@ -0,0 +1,292 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe Euston::EventStore do
|
4
|
+
let(:uuid) { Uuid }
|
5
|
+
let(:stream_id) { uuid.generate }
|
6
|
+
let(:persistence) { double('persistence').as_null_object }
|
7
|
+
let(:store) { Euston::EventStore::OptimisticEventStore.new persistence }
|
8
|
+
|
9
|
+
after { stream_id = uuid.generate }
|
10
|
+
|
11
|
+
describe 'optimistic event store' do
|
12
|
+
context 'when creating a stream' do
|
13
|
+
let(:stream) { store.create_stream stream_id }
|
14
|
+
|
15
|
+
it('returns a new stream') { stream.should_not be_nil }
|
16
|
+
it('returns a stream with the correct stream identifier') { stream.stream_id.should == stream_id }
|
17
|
+
it('returns a stream with a zero stream revision') { stream.stream_revision.should == 0 }
|
18
|
+
it('returns a stream with a zero commit sequence') { stream.commit_sequence.should == 0 }
|
19
|
+
it('returns a stream with no committed events') { stream.committed_events.should have(0).items }
|
20
|
+
it('returns a stream with no uncommitted events') { stream.uncommitted_events.should have(0).items }
|
21
|
+
it('returns a stream with no uncommitted headers') { stream.uncommitted_headers.should have(0).items }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when opening an empty stream starting at revision zero' do
|
25
|
+
before do
|
26
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
27
|
+
:min_revision => 0,
|
28
|
+
:max_revision => Euston::EventStore::FIXNUM_MAX }) { [] }
|
29
|
+
|
30
|
+
@stream = store.open_stream :stream_id => stream_id, :min_revision => 0, :max_revision => 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it('returns a new stream') { @stream.should_not be_nil }
|
34
|
+
it('returns a stream with the correct stream identifier') { @stream.stream_id.should == stream_id }
|
35
|
+
it('returns a stream with a zero stream revision') { @stream.stream_revision.should == 0 }
|
36
|
+
it('returns a stream with a zero commit sequence') { @stream.commit_sequence.should == 0 }
|
37
|
+
it('returns a stream with no committed events') { @stream.committed_events.should have(0).items }
|
38
|
+
it('returns a stream with no uncommitted events') { @stream.uncommitted_events.should have(0).items }
|
39
|
+
it('returns a stream with no uncommitted headers') { @stream.uncommitted_headers.should have(0).items }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when opening an empty stream starting above revision zero' do
|
43
|
+
let(:min_revision) { 1 }
|
44
|
+
|
45
|
+
before do
|
46
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
47
|
+
:min_revision => min_revision,
|
48
|
+
:max_revision => Euston::EventStore::FIXNUM_MAX }) { [] }
|
49
|
+
|
50
|
+
begin
|
51
|
+
store.open_stream :stream_id => stream_id,
|
52
|
+
:min_revision => min_revision,
|
53
|
+
:max_revision => Euston::EventStore::FIXNUM_MAX
|
54
|
+
rescue Exception => e
|
55
|
+
@caught = e
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it('throws a StreamNotFoundError') { @caught.should be_an(Euston::EventStore::StreamNotFoundError) }
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when opening a populated stream' do
|
63
|
+
let(:min_revision) { 17 }
|
64
|
+
let(:max_revision) { 42 }
|
65
|
+
let(:committed) { [ commit(:stream_revision => min_revision,
|
66
|
+
:commit_sequence => 1) ] }
|
67
|
+
|
68
|
+
before do
|
69
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
70
|
+
:min_revision => min_revision,
|
71
|
+
:max_revision => max_revision }) { @invoked = true; committed }
|
72
|
+
|
73
|
+
@stream = store.open_stream :stream_id => stream_id,
|
74
|
+
:min_revision => min_revision,
|
75
|
+
:max_revision => max_revision
|
76
|
+
end
|
77
|
+
|
78
|
+
it('invokes the underlying infrastructure with the values provided') { @invoked.should be_true }
|
79
|
+
it('returns an event stream containing the correct stream identifier') { @stream.stream_id.should == stream_id }
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when opening a populated stream' do
|
83
|
+
let(:min_revision) { 17 }
|
84
|
+
let(:max_revision) { 42 }
|
85
|
+
let(:committed) { [ commit(:stream_revision => min_revision) ] }
|
86
|
+
|
87
|
+
before do
|
88
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
89
|
+
:min_revision => min_revision,
|
90
|
+
:max_revision => max_revision }) { @invoked = true; committed }
|
91
|
+
|
92
|
+
@stream = store.open_stream :stream_id => stream_id,
|
93
|
+
:min_revision => min_revision,
|
94
|
+
:max_revision => max_revision
|
95
|
+
end
|
96
|
+
|
97
|
+
it('invokes the underlying infrastructure with the values provided') { @invoked.should be_true }
|
98
|
+
it('returns an event stream containing the correct stream identifier') { @stream.stream_id.should == stream_id }
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when opening a populated stream from a snapshot' do
|
102
|
+
let(:min_revision) { 42 }
|
103
|
+
let(:max_revision) { Euston::EventStore::FIXNUM_MAX }
|
104
|
+
let(:snapshot) { Euston::EventStore::Snapshot.new stream_id, min_revision, 'snapshot' }
|
105
|
+
let(:committed) { [ commit(:stream_revision => min_revision, :commit_sequence => 0) ] }
|
106
|
+
|
107
|
+
before do
|
108
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
109
|
+
:min_revision => min_revision,
|
110
|
+
:max_revision => max_revision }) { @invoked = true; committed }
|
111
|
+
|
112
|
+
store.open_stream :snapshot => snapshot,
|
113
|
+
:max_revision => max_revision
|
114
|
+
end
|
115
|
+
|
116
|
+
it('invokes the underlying infrastructure with the values provided') { @invoked.should be_true }
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'when opening a stream from a snapshot that is at the revision of the stream head' do
|
120
|
+
let(:head_stream_revision) { 42 }
|
121
|
+
let(:head_commit_sequence) { 15 }
|
122
|
+
let(:snapshot) { Euston::EventStore::Snapshot.new stream_id, head_stream_revision, 'snapshot' }
|
123
|
+
let(:committed) { Euston::EventStore::ArrayEnumerationCounter.new [ commit(:stream_revision => head_stream_revision,
|
124
|
+
:commit_sequence => head_commit_sequence) ] }
|
125
|
+
|
126
|
+
before do
|
127
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
128
|
+
:min_revision => head_stream_revision,
|
129
|
+
:max_revision => Euston::EventStore::FIXNUM_MAX }) { committed }
|
130
|
+
|
131
|
+
@stream = store.open_stream :snapshot => snapshot,
|
132
|
+
:max_revision => Euston::EventStore::FIXNUM_MAX
|
133
|
+
end
|
134
|
+
|
135
|
+
it('returns a stream with the correct stream identifier') { @stream.stream_id.should == stream_id }
|
136
|
+
it('returns a stream with the revision of the stream head') { @stream.stream_revision.should == head_stream_revision }
|
137
|
+
it('returns a stream with a commit sequence of the stream head') { @stream.commit_sequence.should == head_commit_sequence }
|
138
|
+
it('returns a stream with no committed events') { @stream.committed_events.should have(0).items }
|
139
|
+
it('returns a stream with no uncommitted events') { @stream.uncommitted_events.should have(0).items }
|
140
|
+
it('only enumerates the set of commits once') { committed.invocations.should == 1 }
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when reading from revision zero' do
|
144
|
+
before do
|
145
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
146
|
+
:min_revision => 0,
|
147
|
+
:max_revision => Euston::EventStore::FIXNUM_MAX }) { @invoked = true; [] }
|
148
|
+
|
149
|
+
store.get_from stream_id, 0, Euston::EventStore::FIXNUM_MAX
|
150
|
+
end
|
151
|
+
|
152
|
+
it('passes a revision range to the persistence infrastructure') { @invoked.should be_true }
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'when reading up to revision zero' do
|
156
|
+
let(:committed) { [ commit ] }
|
157
|
+
|
158
|
+
before do
|
159
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
160
|
+
:min_revision => 0,
|
161
|
+
:max_revision => Euston::EventStore::FIXNUM_MAX }) { @invoked = true; committed }
|
162
|
+
|
163
|
+
store.open_stream :stream_id => stream_id,
|
164
|
+
:min_revision => 0,
|
165
|
+
:max_revision => 0
|
166
|
+
end
|
167
|
+
|
168
|
+
it('passes the maximum possible revision to the persistence infrastructure') { @invoked.should be_true }
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when reading from a snapshot up to revision zero' do
|
172
|
+
let(:snapshot) { Euston::EventStore::Snapshot.new stream_id, 1, 'snapshot' }
|
173
|
+
let(:committed) { [ commit ] }
|
174
|
+
|
175
|
+
before do
|
176
|
+
persistence.stub(:get_from).with({ :stream_id => stream_id,
|
177
|
+
:min_revision => snapshot.stream_revision,
|
178
|
+
:max_revision => Euston::EventStore::FIXNUM_MAX }) { @invoked = true; committed }
|
179
|
+
|
180
|
+
store.open_stream :snapshot => snapshot,
|
181
|
+
:max_revision => 0
|
182
|
+
end
|
183
|
+
|
184
|
+
it('passes the maximum possible revision to the persistence infrastructure') { @invoked.should be_true }
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'when committing a null attempt back to the stream' do
|
188
|
+
before do
|
189
|
+
begin
|
190
|
+
store.commit nil
|
191
|
+
rescue Exception => e
|
192
|
+
@caught = e
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
it('throws an ArgumentError') { @caught.should be_an(ArgumentError) }
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'when committing with an unidentified attempt back to the stream' do
|
200
|
+
let(:empty_identifier) { nil }
|
201
|
+
let(:unidentified) { commit(:commit_id => empty_identifier, :events => [] ) }
|
202
|
+
|
203
|
+
before do
|
204
|
+
begin
|
205
|
+
store.commit unidentified
|
206
|
+
rescue Exception => e
|
207
|
+
@caught = e
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
it('throws an ArgumentError') { @caught.should be_an(ArgumentError) }
|
212
|
+
end
|
213
|
+
|
214
|
+
context 'when the number of commits is greater than the number of revisions' do
|
215
|
+
let(:stream_revision) { 1 }
|
216
|
+
let(:commit_sequence) { 2 }
|
217
|
+
let(:corrupt) { commit(:stream_revision => stream_revision, :commit_sequence => commit_sequence) }
|
218
|
+
|
219
|
+
before do
|
220
|
+
begin
|
221
|
+
store.commit corrupt
|
222
|
+
rescue Exception => e
|
223
|
+
@caught = e
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it('throws an ArgumentError') { @caught.should be_an(ArgumentError) }
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'when committing with a non-positive commit sequence back to the stream' do
|
231
|
+
let(:stream_revision) { 1 }
|
232
|
+
let(:invalid_commit_sequence) { 0 }
|
233
|
+
let(:invalid_commit) { commit(:stream_revision => stream_revision, :commit_sequence => invalid_commit_sequence) }
|
234
|
+
|
235
|
+
before do
|
236
|
+
begin
|
237
|
+
store.commit invalid_commit
|
238
|
+
rescue Exception => e
|
239
|
+
@caught = e
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
it('throw an ArgumentError') { @caught.should be_an(ArgumentError) }
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'when committing with a non-positive stream revision back to the stream' do
|
247
|
+
let(:invalid_stream_revision) { 0 }
|
248
|
+
let(:commit_sequence) { 1 }
|
249
|
+
let(:invalid_commit) { commit(:stream_revision => invalid_stream_revision, :commit_sequence => commit_sequence) }
|
250
|
+
|
251
|
+
before do
|
252
|
+
begin
|
253
|
+
store.commit invalid_commit
|
254
|
+
rescue Exception => e
|
255
|
+
@caught = e
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
it('throw an ArgumentError') { @caught.should be_an(ArgumentError) }
|
260
|
+
end
|
261
|
+
|
262
|
+
context 'when committing an empty attempt to a stream' do
|
263
|
+
let(:attempt_with_no_events) { commit }
|
264
|
+
|
265
|
+
before do
|
266
|
+
persistence.stub(:commit).with(attempt_with_no_events) { @invoked = true }
|
267
|
+
end
|
268
|
+
|
269
|
+
it('drops the commit provided') { @invoked.should be_nil }
|
270
|
+
end
|
271
|
+
|
272
|
+
context 'when committing with a valid and populated attempt to a stream' do
|
273
|
+
let(:populated_attempt) { commit }
|
274
|
+
|
275
|
+
before do
|
276
|
+
persistence.stub(:commit).with(populated_attempt) { @commit_invoked = true }
|
277
|
+
|
278
|
+
store.commit populated_attempt
|
279
|
+
end
|
280
|
+
|
281
|
+
it('provides the commit attempt to the configured persistence mechanism') { @commit_invoked.should be_true }
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def commit(options = {})
|
286
|
+
defaults = { :stream_id => stream_id,
|
287
|
+
:commit_id => uuid.generate,
|
288
|
+
:events => [ Euston::EventStore::EventMessage.new ]}
|
289
|
+
|
290
|
+
Euston::EventStore::Commit.new(defaults.merge options)
|
291
|
+
end
|
292
|
+
end
|
@@ -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
|