Skip to content

Commit

Permalink
Merge pull request #157 from binarymash/feature/#89-add-toggle-states…
Browse files Browse the repository at this point in the history
…-projection

Feature/#89 add toggle states projection
  • Loading branch information
binarymash committed Jan 14, 2019
2 parents fe2250a + 991f4e7 commit 7edbd5e
Show file tree
Hide file tree
Showing 24 changed files with 699 additions and 95 deletions.
2 changes: 1 addition & 1 deletion build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var netFrameworkUnitTestAssemblies = new []
@"./src/Evelyn.Storage.EventStore.Tests/bin/"+compileConfig+"/net461/Evelyn.Storage.EventStore.Tests.dll",
};
var openCoverSettings = new OpenCoverSettings();
var minCodeCoverage =87d;
var minCodeCoverage =89d;
var coverallsRepoToken = "coveralls-repo-token-evelyn";

// integration testing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
namespace Evelyn.Core.Tests.ReadModel.Projections.ToggleState.ProjectEvents
{
using System.Linq;
using System.Threading.Tasks;
using AutoFixture;
using Evelyn.Core.WriteModel.Project.Events;
using FluentAssertions;
using TestStack.BDDfy;
using Xunit;
using Projections = Evelyn.Core.ReadModel.Projections;

public class ToggleStateAddedSpecs : ProjectionBuilderHarness<ToggleStateAdded>
{
[Fact]
public void ProjectionDoesNotExist()
{
this.Given(_ => GivenThereIsNoProjection())
.When(_ => WhenWeHandleAToggleStateAddedEvent())
.Then(_ => ThenTheProjectionIsCreatedWithTheNewToggleState())
.Then(_ => ThenTheProjectionAuditIsSet())
.BDDfy();
}

[Fact]
public void Nominal()
{
this.Given(_ => GivenTheProjectionExists())
.And(_ => GivenTheProjectAlreadyHasAToggleState())
.When(_ => WhenWeHandleAToggleStateAddedEvent())
.Then(_ => ThenTheNewToggleStateIsAdded())
.And(_ => ThenTheProjectionAuditIsSet())
.BDDfy();
}

protected override async Task HandleEventImplementation()
{
await ProjectionBuilder.Handle(StreamPosition, Event, StoppingToken);
}

private void GivenTheProjectAlreadyHasAToggleState()
{
OriginalProjection.ToggleState.AddEnvironmentState(
DataFixture.Create<Projections.EventAudit>(),
DataFixture.Create<string>(),
DataFixture.Create<string>());
}

private async Task WhenWeHandleAToggleStateAddedEvent()
{
Event = DataFixture.Build<ToggleStateAdded>()
.With(pc => pc.Id, ProjectId)
.With(pc => pc.EnvironmentKey, EnvironmentKey)
.Create();

await WhenTheEventIsHandled();
}

private void ThenTheProjectionIsCreatedWithTheNewToggleState()
{
var environmentStates = UpdatedProjection.ToggleState.EnvironmentStates.ToList();
environmentStates.Count.Should().Be(1);

environmentStates.Should().Contain(ts =>
ts.Key == Event.EnvironmentKey &&
ts.Value == Event.Value);
}

private void ThenTheNewToggleStateIsAdded()
{
var environmentStates = UpdatedProjection.ToggleState.EnvironmentStates.ToList();
environmentStates.Count.Should().Be(OriginalProjection.ToggleState.EnvironmentStates.Count() + 1);

foreach (var environmentState in OriginalProjection.ToggleState.EnvironmentStates)
{
environmentStates.Should().Contain(ts =>
ts.Key == environmentState.Key &&
ts.Value == environmentState.Value);
}

environmentStates.Should().Contain(ts =>
ts.Key == Event.EnvironmentKey &&
ts.Value == Event.Value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
namespace Evelyn.Core.Tests.ReadModel.Projections.ToggleState.ProjectEvents
{
using System.Linq;
using System.Threading.Tasks;
using AutoFixture;
using Evelyn.Core.WriteModel.Project.Events;
using FluentAssertions;
using TestStack.BDDfy;
using Xunit;

public class ToggleStateChangedSpecs : ProjectionBuilderHarness<ToggleStateChanged>
{
[Fact]
public void ProjectionDoesNotExist()
{
this.Given(_ => GivenThereIsNoProjection())
.When(_ => WhenWeHandleAToggleStateChangedEvent())
.Then(_ => ThenAnExceptionIsThrown())
.BDDfy();
}

[Fact]
public void Nominal()
{
this.Given(_ => GivenTheProjectionExists())
.And(_ => GivenOurToggleStateIsOnTheProjection())
.And(_ => GivenTheProjectionHasOtherToggleStates())
.When(_ => WhenWeHandleAToggleStateChangedEvent())
.Then(_ => ThenOurToggleStateIsChanged())
.And(_ => ThenTheProjectionAuditIsSet())
.BDDfy();
}

protected override async Task HandleEventImplementation()
{
await ProjectionBuilder.Handle(StreamPosition, Event, StoppingToken);
}

private async Task WhenWeHandleAToggleStateChangedEvent()
{
Event = DataFixture.Build<ToggleStateChanged>()
.With(pc => pc.Id, ProjectId)
.With(pc => pc.EnvironmentKey, EnvironmentKey)
.With(pc => pc.ToggleKey, ToggleKey)
.Create();

await WhenTheEventIsHandled();
}

private void ThenOurToggleStateIsChanged()
{
var updatedEnvironmentStates = UpdatedProjection.ToggleState.EnvironmentStates.ToList();
updatedEnvironmentStates.Count.Should().Be(OriginalProjection.ToggleState.EnvironmentStates.Count());

foreach (var originalEnvironmentState in OriginalProjection.ToggleState.EnvironmentStates)
{
var expectedEnvironmentStateValue =
(originalEnvironmentState.Key == Event.EnvironmentKey)
? Event.Value
: originalEnvironmentState.Value;

updatedEnvironmentStates.Should().Contain(ts =>
ts.Key == originalEnvironmentState.Key &&
ts.Value == expectedEnvironmentStateValue);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
namespace Evelyn.Core.Tests.ReadModel.Projections.ToggleState.ProjectEvents
{
using System.Linq;
using System.Threading.Tasks;
using AutoFixture;
using Evelyn.Core.ReadModel.Projections.ToggleState;
using Evelyn.Core.WriteModel.Project.Events;
using FluentAssertions;
using NSubstitute;
using TestStack.BDDfy;
using Xunit;

public class ToggleStateDeletedSpecs : ProjectionBuilderHarness<ToggleStateDeleted>
{
[Fact]
public void ProjectionDoesNotExist()
{
this.Given(_ => GivenThereIsNoProjection())
.When(_ => WhenWeHandleAToggleStateDeletedEvent())
.Then(_ => ThenAnExceptionIsThrown())
.BDDfy();
}

[Fact]
public void MultipleToggleStatesExist()
{
this.Given(_ => GivenTheProjectionExists())
.And(_ => GivenOurToggleStateIsOnTheProjection())
.And(_ => GivenTheProjectionHasOtherToggleStates())
.When(_ => WhenWeHandleAToggleStateDeletedEvent())
.Then(_ => ThenOurToggleStateIsRemoved())
.And(_ => ThenTheProjectionAuditIsSet())
.BDDfy();
}

[Fact]
public void LastToggleStateIsDeleted()
{
this.Given(_ => GivenTheProjectionExists())
.And(_ => GivenOurToggleStateIsOnTheProjection())
.When(_ => WhenWeHandleAToggleStateDeletedEvent())
.Then(_ => ThenTheProjectionIsDeleted())
.BDDfy();
}

protected override async Task HandleEventImplementation()
{
await ProjectionBuilder.Handle(StreamPosition, Event, StoppingToken);
}

private async Task WhenWeHandleAToggleStateDeletedEvent()
{
Event = DataFixture.Build<ToggleStateDeleted>()
.With(pc => pc.Id, ProjectId)
.With(pc => pc.EnvironmentKey, EnvironmentKey)
.With(pc => pc.ToggleKey, ToggleKey)
.Create();

await WhenTheEventIsHandled();
}

private void ThenOurToggleStateIsRemoved()
{
var updatedEnvironmentStates = UpdatedProjection.ToggleState.EnvironmentStates.ToList();
updatedEnvironmentStates.Count.Should().Be(OriginalProjection.ToggleState.EnvironmentStates.Count() - 1);

foreach (var originalEnvironmentState in OriginalProjection.ToggleState.EnvironmentStates)
{
if (originalEnvironmentState.Key != Event.EnvironmentKey)
{
updatedEnvironmentStates.Should().Contain(ts =>
ts.Key == originalEnvironmentState.Key &&
ts.Value == originalEnvironmentState.Value);
}
}
}

private void ThenTheProjectionIsDeleted()
{
ProjectionStore.Received().Delete(Projection.StoreKey(ProjectId, EnvironmentKey));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace Evelyn.Core.Tests.ReadModel.Projections.ToggleState
{
using System;
using AutoFixture;
using Evelyn.Core.WriteModel;
using NSubstitute;
using Projections = Evelyn.Core.ReadModel.Projections;

public abstract class ProjectionBuilderHarness<TEvent> : ProjectionBuilderHarness<Projections.ToggleState.Projection, Projections.ToggleState.ProjectionBuilder, TEvent>
where TEvent : Event
{
public ProjectionBuilderHarness()
{
ProjectionBuilder = new Projections.ToggleState.ProjectionBuilder(ProjectionStore);
}

protected Guid ProjectId { get; set; }

protected string ToggleKey { get; private set; }

protected string EnvironmentKey { get; set; }

protected void GivenThereIsNoProjection()
{
ProjectId = DataFixture.Create<Guid>();
ToggleKey = DataFixture.Create<string>();

ProjectionStore.WhenForAnyArgs(ps => ps.Get(Arg.Any<string>()))
.Do(ci => throw new Evelyn.Core.ReadModel.ProjectionNotFoundException());
}

protected void GivenTheProjectionExists()
{
OriginalProjection = DataFixture.Create<Projections.ToggleState.Projection>();
ProjectId = DataFixture.Create<Guid>();
ToggleKey = DataFixture.Create<string>();
}

protected void GivenTheProjectionHasOtherToggleStates()
{
OriginalProjection.ToggleState.AddEnvironmentState(
DataFixture.Create<Projections.EventAudit>(),
DataFixture.Create<string>(),
DataFixture.Create<string>());
}

protected void GivenOurToggleStateIsOnTheProjection()
{
EnvironmentKey = DataFixture.Create<string>();

OriginalProjection.ToggleState.AddEnvironmentState(
DataFixture.Create<Projections.EventAudit>(),
EnvironmentKey,
DataFixture.Create<string>());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Evelyn.Core.Tests.ReadModel.Projections.ToggleState
{
using AutoFixture;
using Xunit;
using Projections = Core.ReadModel.Projections;

public class ProjectionSpecs : ProjectionHarness<Projections.ToggleState.Projection>
{
[Fact]
public void Serialization()
{
var dto = DataFixture.Create<Projections.ToggleState.Projection>();
AssertSerializationOf(dto);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public void Configure(EventStreamHandlerOptions options)
typeof(Evelyn.Core.ReadModel.Projections.EnvironmentState.ProjectionBuilder),
typeof(Evelyn.Core.ReadModel.Projections.ProjectDetails.ProjectionBuilder),
typeof(Evelyn.Core.ReadModel.Projections.ToggleDetails.ProjectionBuilder),
typeof(Evelyn.Core.ReadModel.Projections.ToggleState.ProjectionBuilder),
typeof(Evelyn.Core.ReadModel.Projections.ClientEnvironmentState.ProjectionBuilder),
});
}
Expand Down
1 change: 1 addition & 0 deletions src/Evelyn.Core/DependencyInjection/InProcessHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public static void SynchronouslyInProcess(this EventPublisherOptions parentOptio
parentOptions.Services.TryAddSingleton<Evelyn.Core.ReadModel.Projections.EnvironmentState.ProjectionBuilder>();
parentOptions.Services.TryAddSingleton<Evelyn.Core.ReadModel.Projections.ProjectDetails.ProjectionBuilder>();
parentOptions.Services.TryAddSingleton<Evelyn.Core.ReadModel.Projections.ToggleDetails.ProjectionBuilder>();
parentOptions.Services.TryAddSingleton<Evelyn.Core.ReadModel.Projections.ToggleState.ProjectionBuilder>();
parentOptions.Services.TryAddSingleton<Evelyn.Core.ReadModel.Projections.ClientEnvironmentState.ProjectionBuilder>();
}
}
Expand Down
13 changes: 8 additions & 5 deletions src/Evelyn.Core/ReadModel/DatabaseReadModelFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@
public class DatabaseReadModelFacade : IReadModelFacade
{
private readonly IProjectionStore<Projections.AccountProjects.Projection> _accountProjects;

private readonly IProjectionStore<Projections.ProjectDetails.Projection> _projectDetails;

private readonly IProjectionStore<Projections.EnvironmentDetails.Projection> _environmentDetails;

private readonly IProjectionStore<Projections.ToggleDetails.Projection> _toggleDetails;

private readonly IProjectionStore<Projections.EnvironmentState.Projection> _environmentStates;

private readonly IProjectionStore<Projections.ToggleState.Projection> _toggleStates;
private readonly IProjectionStore<Projections.ClientEnvironmentState.Projection> _clientEnvironmentStates;

public DatabaseReadModelFacade(
Expand All @@ -24,13 +20,15 @@ public class DatabaseReadModelFacade : IReadModelFacade
IProjectionStore<Projections.EnvironmentDetails.Projection> environmentDetails,
IProjectionStore<Projections.ToggleDetails.Projection> toggleDetails,
IProjectionStore<Projections.EnvironmentState.Projection> environmentStates,
IProjectionStore<Projections.ToggleState.Projection> toggleStates,
IProjectionStore<Projections.ClientEnvironmentState.Projection> clientEnvironmentStates)
{
_accountProjects = accountProjects;
_projectDetails = projectDetails;
_environmentDetails = environmentDetails;
_toggleDetails = toggleDetails;
_environmentStates = environmentStates;
_toggleStates = toggleStates;
_clientEnvironmentStates = clientEnvironmentStates;
}

Expand Down Expand Up @@ -59,6 +57,11 @@ public async Task<Projections.EnvironmentState.Projection> GetEnvironmentState(G
return await _environmentStates.Get(Projections.EnvironmentState.Projection.StoreKey(projectId, environmentName));
}

public async Task<Projections.ToggleState.Projection> GetToggleState(Guid projectId, string toggleKey)
{
return await _toggleStates.Get(Projections.ToggleState.Projection.StoreKey(projectId, toggleKey));
}

public async Task<Projections.ClientEnvironmentState.Projection> GetClientEnvironmentState(Guid projectId, string environmentName)
{
return await _clientEnvironmentStates.Get(Projections.ClientEnvironmentState.Projection.StoreKey(projectId, environmentName));
Expand Down
2 changes: 2 additions & 0 deletions src/Evelyn.Core/ReadModel/IReadModelFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface IReadModelFacade

Task<Projections.EnvironmentState.Projection> GetEnvironmentState(Guid projectId, string environmentName);

Task<Projections.ToggleState.Projection> GetToggleState(Guid projectId, string toggleKey);

Task<Projections.ClientEnvironmentState.Projection> GetClientEnvironmentState(Guid projectId, string environmentName);
}
}

0 comments on commit 7edbd5e

Please sign in to comment.