Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ public interface IHoldHigherOrder
{
int HigherOrder { get; set; }

bool Visility { get; set; }
bool Visibility { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public IHoldHigherOrder Apply(IHoldHigherOrder state, EntitySoftDeleted @event)

public IHoldHigherOrder Apply(IHoldHigherOrder state, VisibilityEnabledEvent @event)
{
state.Visility = true;
state.Visibility = true;

return state;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal class EventStoreIntegrationContext
private static readonly IntegrationTestsSettings testsSettings = new IntegrationTestsSettings();
private readonly EventStoreRepository<string, IHoldHigherOrder> repository;
public readonly EventStoreReadOnlyRepository<string, IHoldHigherOrder> readOnlyRepository;
public readonly EventStoreEventReaderRepository<string> readEventsRepository;
private static IEventStoreConnection connection;
private static IDisposable eventStoreIsolation;

Expand All @@ -41,6 +42,7 @@ public EventStoreIntegrationContext(PassThroughValidator validator)

repository = new EventStoreRepository<string, IHoldHigherOrder>(validator, configuration, GetConnection(), DateTimeProvider);
readOnlyRepository = new EventStoreReadOnlyRepository<string, IHoldHigherOrder>(configuration, GetConnection());
readEventsRepository = new EventStoreEventReaderRepository<string>(configuration, GetConnection());
}

private static IEventStoreConnection GetConnection()
Expand Down Expand Up @@ -86,12 +88,12 @@ public async Task AppendEventsToCurrentStream(string id, IMyEvent[] events)
}
}

public Task HardDeleteStream(string id, int expectedVersion = -1)
=> GetConnection().DeleteStreamAsync(id, expectedVersion, hardDelete: true);

public Task SoftDeleteStream(string id)
=> repository.SoftDelete(id);

public Task HardDeleteStream(string id)
=> GetConnection().DeleteStreamAsync(id, -1, true);

public Task SoftDeleteByEvent(string id)
=> repository.SoftDeleteByEvent(id);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ internal class TestDataContext

internal string CurrentStreamId { get; set; }

internal int CurrentVersion { get; set; } = -1;

internal Guid RawStreamId { get; set; }

public Exception RecordedException { get; internal set; }
public IHoldHigherOrder LatestLoadedState { get; internal set; }
public IEnumerable<ItemWithType> LatestReadEvents { get; internal set; }

public Dictionary<string, IManageSessionOf<IHoldHigherOrder>> NamedSessions { get; internal set; } =
new Dictionary<string, IManageSessionOf<IHoldHigherOrder>>();
public Dictionary<string, List<Exception>> NamedSessionsExceptions { get; internal set; } =
Expand All @@ -29,6 +33,7 @@ internal void ResetStream(string categoryName = null)
RawStreamId = Guid.NewGuid();
StreamIdPrefix = !string.IsNullOrEmpty(categoryName) ? $"{StreamIdPrefix}_{categoryName}" : StreamIdPrefix;
CurrentStreamId = $"{StreamIdPrefix}-{RawStreamId}";
CurrentVersion = -1;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
Feature: ReadEventsSpecs
In order to support stream migration
As a user of this library
I want to be able to read all events from a particular stream

Scenario: Read all events from a stream
Given a new stream
And 3 new events
And I try to save the new events in the stream through their interface
When I read all the events back from the stream
Then the load process should succeed
And I should see all the events
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the load process should succeed" looks redundant given we are checking for "I should see all the events"

Tests below do not check for "the load process should succeed" and I think this is how it should be.


Scenario: Read events after a hard delete should return empty events
Given a new stream
And 3 new events
And I try to save the new events in the stream through their interface
When I hard-delete the stream
And I read all the events back from the stream
Then the stream should be empty

Scenario: Read events after a soft delete should return empty events
Given a new stream
And 3 new events
And I try to save the new events in the stream through their interface
When I soft-delete the stream
And I read all the events back from the stream
Then the stream should be empty

Scenario: Read events after a soft delete by event should return empty events
Given a new stream
And 3 new events
And I try to save the new events in the stream through their interface
When I soft-delete-by-event the stream
And I read all the events back from the stream
Then the stream should be empty

Scenario: Read events after a soft delete by event with subsequant events appended should return only appended events
Given a new stream
And 3 new events
And I try to save the new events in the stream through their interface
And I soft-delete-by-event the stream
And 5 new events
And I try to save the new events in the stream through their interface
When I read all the events back from the stream
Then the load process should succeed
And I should see all the appended events only

Scenario: Read events after a soft delete by custom event should return empty events
Given a new stream
And 3 new events
And I try to save the new events in the stream through their interface
When I soft-delete-by-custom-event the stream
And I read all the events back from the stream
Then the stream should be empty

Scenario: Read events after a soft delete by custom event with subsequant events appended should return only appended events
Given a new stream
And 3 new events
And I try to save the new events in the stream through their interface
And I soft-delete-by-custom-event the stream
And 5 new events
And I try to save the new events in the stream through their interface
When I read all the events back from the stream
Then the load process should succeed
And I should see all the appended events only
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ Scenario: Reconstitute state based on category with two event types up to a give
And I update the state of visible to be enabled as of '2020-09-22 11:10:00'
When I load all my entities as of '2020-09-20 11:10:00' for the streams category
Then the load process should succeed
And the visibilty should be disabled
And the visibility should be disabled

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public async Task GivenIUpdateTheStateOfVisibleToBeEnabled(DateTime datetime)
testDataContext.LastGeneratedEvents = new List<IMyEvent>{ visibleEvent };
session.AddEvent(visibleEvent);

await session.SaveChanges();
testDataContext.CurrentVersion = await session.SaveChanges();
}
}

Expand Down Expand Up @@ -114,7 +114,7 @@ public async Task GivenITryToSaveTheNewEventsInTheStreamThroughTheirInterface()
});
}

await session.SaveChanges();
testDataContext.CurrentVersion = await session.SaveChanges();
}
});
}
Expand All @@ -141,12 +141,16 @@ public async Task WhenITryToSaveTheNewEventsInTheStream()
}

[Given(@"I soft-delete the stream")]
[When(@"I soft-delete the stream")]
public Task GivenISoft_DeleteTheStream()
=> eventStoreContainer.SoftDeleteStream(testDataContexts.First().CurrentStreamId);

[Given(@"I hard-delete the stream")]
[When(@"I hard-delete the stream")]
public Task GivenIHard_DeleteTheStream()
=> eventStoreContainer.HardDeleteStream(testDataContexts.First().CurrentStreamId);
=> eventStoreContainer.HardDeleteStream(
testDataContexts.First().CurrentStreamId,
testDataContexts.First().CurrentVersion);

[Given(@"I soft-delete-by-event the stream")]
[When(@"I soft-delete-by-event the stream")]
Expand Down Expand Up @@ -182,10 +186,10 @@ public void ThenHighOrderPropertyForStreamShouldBe(int streamNumber, int highest
States.ElementAt(--streamNumber).state.HigherOrder.Should().Be(highestOrderValue);
}

[Then(@"the visibilty should be disabled")]
public void ThenTheVisibiltyShouldBeEnabled()
[Then(@"the visibility should be disabled")]
public void ThenTheVisibilityShouldBeEnabled()
{
testDataContexts.First().LatestLoadedState.Visility.Should().BeFalse();
testDataContexts.First().LatestLoadedState.Visibility.Should().BeFalse();
}

[Then(@"the save process should fail")]
Expand Down Expand Up @@ -244,7 +248,7 @@ public async Task WhenITryToSave(string sessionName)
{
testDataContext.NamedSessionsExceptions.Add(sessionName, new List<Exception>());
}
var recordedException = await Record.ExceptionAsync(() => testDataContext.NamedSessions[sessionName].SaveChanges());
var recordedException = await Record.ExceptionAsync(async () => testDataContext.CurrentVersion = await testDataContext.NamedSessions[sessionName].SaveChanges());
if (recordedException != null)
{
testDataContext.NamedSessionsExceptions[sessionName].Add(recordedException);
Expand Down Expand Up @@ -273,6 +277,34 @@ public void ThenTheSaveProcessShouldFailFor(string sessionName)
testDataContexts.First().NamedSessionsExceptions[sessionName].Should().NotBeEmpty();
}

[When(@"I read all the events back from the stream")]
public async Task WhenIReadAllTheEventsBackFromTheStream()
{
var testDataContext = testDataContexts.First();

if (testDataContext.RecordedException != null) return;

testDataContext.RecordedException = await Record.ExceptionAsync(async () =>
{
var itemWithTypes = await eventStoreContainer.readEventsRepository.ReadFrom(testDataContext.CurrentStreamId.ToString());
testDataContext.LatestReadEvents = itemWithTypes;
});
}

[Then(@"I should see all the events")]
[Then(@"I should see all the appended events only")]
public void ThenIShouldSeeAllTheEvents()
{
testDataContexts.First().LatestReadEvents.Select(x => x.instance).Should().BeEquivalentTo(testDataContexts.First().LastGeneratedEvents);
}

[Then(@"the stream should be empty")]
public void ThenTheStreamShouldBeEmpty()
{
testDataContexts.First().LatestReadEvents.Should().BeEmpty();
}


private void AddStream()
{
var testDataContext = new TestDataContext();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace BullOak.Repositories.EventStore
{
using BullOak.Repositories.EventStore.Streams;
using global::EventStore.ClientAPI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class EventStoreEventReaderRepository<TId> : IReadEvents<TId>
{
private readonly IHoldAllConfiguration configs;
private readonly EventReader reader;

public EventStoreEventReaderRepository(IHoldAllConfiguration configs, IEventStoreConnection connection)
{
this.configs = configs ?? throw new ArgumentNullException(nameof(configs));

reader = new EventReader(connection, configs);
}

public async Task<IEnumerable<ItemWithType>> ReadFrom(TId id)
{
var streamData = await reader.ReadFrom(new ReadStreamBackwardsStrategy(id.ToString()));

return streamData.results.Select(x => x.Event); ;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class EventStoreReadOnlyRepository<TId, TState> : IReadQueryModels<TId, T

public EventStoreReadOnlyRepository(IHoldAllConfiguration configs, IEventStoreConnection connection)
{
this.configs = configs ?? throw new ArgumentNullException(nameof(connection));
this.configs = configs ?? throw new ArgumentNullException(nameof(configs));

reader = new EventReader(connection, configs);
}
Expand Down
10 changes: 10 additions & 0 deletions src/BullOak.Repositories.EventStore/IReadEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace BullOak.Repositories.EventStore
{
using System.Collections.Generic;
using System.Threading.Tasks;

public interface IReadEvents<TId>
{
Task<IEnumerable<ItemWithType>> ReadFrom(TId id);
}
}