Skip to content

Commit e7f9250

Browse files
authored
Merge pull request #650 from JackHerring/Rebuild-Async-ReadModels
Rebuild async ReadModels
2 parents ce69e5d + 5222002 commit e7f9250

11 files changed

+271
-168
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
```
3434
* Fix: ASP.NET Core `AddRequestHeadersMetadataProvider` doesn't throw when
3535
HttpContext is null.
36+
* Fix: `ReadModelRepopulator` now correctly populates `IAmAsyncReadModelFor`
3637

3738
### New in 0.72.3914 (released 2019-05-28)
3839

Source/EventFlow.Tests/UnitTests/ReadStores/AggregateReadStoreManagerTests.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ namespace EventFlow.Tests.UnitTests.ReadStores
4444
public class AggregateReadStoreManagerTests : ReadStoreManagerTestSuite<AggregateReadStoreManager<
4545
ThingyAggregate,
4646
ThingyId,
47-
IReadModelStore<TReadModel>,
48-
TReadModel>>
47+
IReadModelStore<TestReadModel>,
48+
TestReadModel>>
4949
{
5050
private Mock<IEventStore> _eventStoreMock;
5151

@@ -64,9 +64,9 @@ public async Task EventsAreApplied()
6464
{
6565
ToDomainEvent(thingyId, A<ThingyPingEvent>(), 3),
6666
};
67-
Arrange_ReadModelStore_UpdateAsync(ReadModelEnvelope<TReadModel>.With(
67+
Arrange_ReadModelStore_UpdateAsync(ReadModelEnvelope<TestReadModel>.With(
6868
thingyId.Value,
69-
A<TReadModel>(),
69+
A<TestReadModel>(),
7070
2));
7171

7272
// Act
@@ -86,9 +86,9 @@ public async Task AlreadyAppliedEventsAreNotApplied()
8686
{
8787
ToDomainEvent(thingyId, A<ThingyPingEvent>(), 3),
8888
};
89-
var resultingReadModelUpdates = Arrange_ReadModelStore_UpdateAsync(ReadModelEnvelope<TReadModel>.With(
89+
var resultingReadModelUpdates = Arrange_ReadModelStore_UpdateAsync(ReadModelEnvelope<TestReadModel>.With(
9090
thingyId.Value,
91-
A<TReadModel>(),
91+
A<TestReadModel>(),
9292
3));
9393

9494
// Act
@@ -108,9 +108,9 @@ public async Task OutdatedEventsAreNotApplied()
108108
{
109109
ToDomainEvent(thingyId, A<ThingyPingEvent>(), 1),
110110
};
111-
Arrange_ReadModelStore_UpdateAsync(ReadModelEnvelope<TReadModel>.With(
111+
Arrange_ReadModelStore_UpdateAsync(ReadModelEnvelope<TestReadModel>.With(
112112
thingyId.Value,
113-
A<TReadModel>(),
113+
A<TestReadModel>(),
114114
3));
115115

116116
// Act
@@ -138,9 +138,9 @@ public async Task StoredEventsAreAppliedIfThereAreMissingEvents()
138138
.Concat(missingEvents)
139139
.Concat(emittedEvents)
140140
.ToArray();
141-
Arrange_ReadModelStore_UpdateAsync(ReadModelEnvelope<TReadModel>.With(
141+
Arrange_ReadModelStore_UpdateAsync(ReadModelEnvelope<TestReadModel>.With(
142142
thingyId.Value,
143-
A<TReadModel>(),
143+
A<TestReadModel>(),
144144
1));
145145
_eventStoreMock.Arrange_LoadEventsAsync(storedEvents);
146146

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2015-2019 Rasmus Mikkelsen
4+
// Copyright (c) 2015-2019 eBay Software Foundation
5+
// https://github.com/eventflow/EventFlow
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
8+
// this software and associated documentation files (the "Software"), to deal in
9+
// the Software without restriction, including without limitation the rights to
10+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11+
// the Software, and to permit persons to whom the Software is furnished to do so,
12+
// subject to the following conditions:
13+
//
14+
// The above copyright notice and this permission notice shall be included in all
15+
// copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
24+
namespace EventFlow.Tests.UnitTests.ReadStores
25+
{
26+
public class AsyncReadModelPopulatorTests : BaseReadModelTests<TestAsyncReadModel>
27+
{
28+
}
29+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2015-2019 Rasmus Mikkelsen
4+
// Copyright (c) 2015-2019 eBay Software Foundation
5+
// https://github.com/eventflow/EventFlow
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
8+
// this software and associated documentation files (the "Software"), to deal in
9+
// the Software without restriction, including without limitation the rights to
10+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11+
// the Software, and to permit persons to whom the Software is furnished to do so,
12+
// subject to the following conditions:
13+
//
14+
// The above copyright notice and this permission notice shall be included in all
15+
// copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
24+
using System;
25+
using System.Collections.Generic;
26+
using System.Linq;
27+
using System.Threading;
28+
using System.Threading.Tasks;
29+
using EventFlow.Aggregates;
30+
using EventFlow.Configuration;
31+
using EventFlow.EventStores;
32+
using EventFlow.ReadStores;
33+
using EventFlow.TestHelpers;
34+
using EventFlow.TestHelpers.Aggregates.Events;
35+
using Moq;
36+
using NUnit.Framework;
37+
38+
namespace EventFlow.Tests.UnitTests.ReadStores
39+
{
40+
[Timeout(5000)]
41+
[Category(Categories.Unit)]
42+
public abstract class BaseReadModelTests<TReadModel> : TestsFor<ReadModelPopulator>
43+
where TReadModel : class, IReadModel
44+
{
45+
private const int ReadModelPageSize = 3;
46+
47+
private Mock<IReadModelStore<TReadModel>> _readModelStoreMock;
48+
private Mock<IReadStoreManager<TReadModel>> _readStoreManagerMock;
49+
private Mock<IEventFlowConfiguration> _eventFlowConfigurationMock;
50+
private Mock<IEventStore> _eventStoreMock;
51+
private Mock<IResolver> _resolverMock;
52+
private List<IDomainEvent> _eventStoreData;
53+
54+
[SetUp]
55+
public void SetUp()
56+
{
57+
_eventStoreMock = InjectMock<IEventStore>();
58+
_eventStoreData = null;
59+
_resolverMock = InjectMock<IResolver>();
60+
_readModelStoreMock = new Mock<IReadModelStore<TReadModel>>();
61+
_readStoreManagerMock = new Mock<IReadStoreManager<TReadModel>>();
62+
_eventFlowConfigurationMock = InjectMock<IEventFlowConfiguration>();
63+
64+
_resolverMock
65+
.Setup(r => r.Resolve<IEnumerable<IReadStoreManager>>())
66+
.Returns(new[] { _readStoreManagerMock.Object });
67+
_resolverMock
68+
.Setup(r => r.ResolveAll(typeof(IReadModelStore<TReadModel>)))
69+
.Returns(new[] { _readModelStoreMock.Object });
70+
71+
_eventFlowConfigurationMock
72+
.Setup(c => c.PopulateReadModelEventPageSize)
73+
.Returns(ReadModelPageSize);
74+
75+
_eventStoreMock
76+
.Setup(s => s.LoadAllEventsAsync(It.IsAny<GlobalPosition>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
77+
.Returns<GlobalPosition, int, CancellationToken>((s, p, c) => Task.FromResult(GetEvents(s, p)));
78+
_readStoreManagerMock
79+
.Setup(m => m.ReadModelType)
80+
.Returns(typeof(TReadModel));
81+
}
82+
83+
[Test]
84+
public async Task PurgeIsCalled()
85+
{
86+
// Act
87+
await Sut.PurgeAsync<TReadModel>(CancellationToken.None).ConfigureAwait(false);
88+
89+
// Assert
90+
_readModelStoreMock.Verify(s => s.DeleteAllAsync(It.IsAny<CancellationToken>()), Times.Once);
91+
}
92+
93+
[Test]
94+
public async Task PopulateCallsApplyDomainEvents()
95+
{
96+
// Arrange
97+
ArrangeEventStore(Many<ThingyPingEvent>(6));
98+
99+
// Act
100+
await Sut.PopulateAsync<TReadModel>(CancellationToken.None).ConfigureAwait(false);
101+
102+
// Assert
103+
_readStoreManagerMock.Verify(
104+
s => s.UpdateReadStoresAsync(
105+
It.Is<IReadOnlyCollection<IDomainEvent>>(l => l.Count == ReadModelPageSize),
106+
It.IsAny<CancellationToken>()),
107+
Times.Exactly(2));
108+
}
109+
110+
[Test]
111+
public async Task UnwantedEventsAreFiltered()
112+
{
113+
// Arrange
114+
var events = new IAggregateEvent[]
115+
{
116+
A<ThingyPingEvent>(),
117+
A<ThingyDomainErrorAfterFirstEvent>(),
118+
A<ThingyPingEvent>(),
119+
};
120+
ArrangeEventStore(events);
121+
122+
// Act
123+
await Sut.PopulateAsync<TReadModel>(CancellationToken.None).ConfigureAwait(false);
124+
125+
// Assert
126+
_readStoreManagerMock
127+
.Verify(
128+
s => s.UpdateReadStoresAsync(
129+
It.Is<IReadOnlyCollection<IDomainEvent>>(l => l.Count == 2 && l.All(e => e.EventType == typeof(ThingyPingEvent))),
130+
It.IsAny<CancellationToken>()),
131+
Times.Once);
132+
}
133+
134+
private AllEventsPage GetEvents(GlobalPosition globalPosition, int pageSize)
135+
{
136+
var startPosition = globalPosition.IsStart
137+
? 1
138+
: int.Parse(globalPosition.Value);
139+
140+
var events = _eventStoreData
141+
.Skip(Math.Max(startPosition - 1, 0))
142+
.Take(pageSize)
143+
.ToList();
144+
145+
var nextPosition = Math.Min(Math.Max(startPosition, 1) + pageSize, _eventStoreData.Count + 1);
146+
147+
return new AllEventsPage(new GlobalPosition(nextPosition.ToString()), events);
148+
}
149+
150+
private void ArrangeEventStore(IEnumerable<IAggregateEvent> aggregateEvents)
151+
{
152+
ArrangeEventStore(aggregateEvents.Select(e => ToDomainEvent(e)));
153+
}
154+
155+
private void ArrangeEventStore(IEnumerable<IDomainEvent> domainEvents)
156+
{
157+
_eventStoreData = domainEvents.ToList();
158+
}
159+
}
160+
}

Source/EventFlow.Tests/UnitTests/ReadStores/MultipleAggregateReadStoreManagerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
namespace EventFlow.Tests.UnitTests.ReadStores
3737
{
3838
[Category(Categories.Unit)]
39-
public class MultipleAggregateReadStoreManagerTests : ReadStoreManagerTestSuite<MultipleAggregateReadStoreManager<IReadModelStore<TReadModel>, TReadModel, IReadModelLocator>>
39+
public class MultipleAggregateReadStoreManagerTests : ReadStoreManagerTestSuite<MultipleAggregateReadStoreManager<IReadModelStore<TestReadModel>, TestReadModel, IReadModelLocator>>
4040
{
4141
private Mock<IReadModelLocator> _readModelLocator;
4242

@@ -100,7 +100,7 @@ public async Task IfNoReadModelIdsAreReturned_ThenDontInvokeTheReadModelStore()
100100
s => s.UpdateAsync(
101101
It.IsAny<IReadOnlyCollection<ReadModelUpdate>>(),
102102
It.IsAny<IReadModelContextFactory>(),
103-
It.IsAny<Func<IReadModelContext, IReadOnlyCollection<IDomainEvent>, ReadModelEnvelope<TReadModel>, CancellationToken, Task<ReadModelUpdateResult<TReadModel>>>>(),
103+
It.IsAny<Func<IReadModelContext, IReadOnlyCollection<IDomainEvent>, ReadModelEnvelope<TestReadModel>, CancellationToken, Task<ReadModelUpdateResult<TestReadModel>>>>(),
104104
It.IsAny<CancellationToken>()),
105105
Times.Never);
106106
}

0 commit comments

Comments
 (0)