Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/ci_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: build
on:
workflow_dispatch:
push:
branches:
- main
- 'releases/**'
pull_request:
branches: [ main ]
paths:
Expand Down
4 changes: 4 additions & 0 deletions src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="F23.Kernel.Tests" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\F23.Kernel\F23.Kernel.csproj" />
</ItemGroup>
Expand Down
141 changes: 141 additions & 0 deletions src/F23.Kernel.Tests/EventSourcing/EventStreamTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using F23.Kernel.EventSourcing;
using F23.Kernel.Results;
using F23.Kernel.Tests.EventSourcing.Mocks;

namespace F23.Kernel.Tests.EventSourcing;

public class EventStreamTests
{
[Fact]
public void EventStream_Ctor_Snapshot()
{
// Arrange
var snapshot = new TestAggregateRoot();

// Act
var eventStream = new TestEventStream<TestAggregateRoot>(snapshot);

// Assert
Assert.Equal(snapshot, eventStream.Snapshot);
Assert.Equal(snapshot, eventStream.LastCommittedSnapshot);
Assert.Equal(snapshot.Id, eventStream.Id);
Assert.Empty(eventStream.CommittedEvents);
Assert.Empty(eventStream.UncommittedEvents);
Assert.Empty(eventStream.AllEvents);
}

[Fact]
public void EventStream_Ctor_SnapshotAndEvents()
{
// Arrange
var snapshot = new TestAggregateRoot();
var events = new List<IEvent>
{
new TestEvent()
};

// Act
var eventStream = new TestEventStream<TestAggregateRoot>(snapshot, events);

// Assert
Assert.Equal(snapshot, eventStream.Snapshot);
Assert.Equal(snapshot, eventStream.LastCommittedSnapshot);
Assert.Equal(snapshot.Id, eventStream.Id);
Assert.Equal(events, eventStream.CommittedEvents);
Assert.Empty(eventStream.UncommittedEvents);
Assert.Equal(events, eventStream.AllEvents);
}

[Fact]
public void EventStream_Apply_CreationEvent_Throws()
{
// Arrange
var snapshot = new TestAggregateRoot();
var eventStream = new TestEventStream<TestAggregateRoot>(snapshot);
var creationEvent = new TestCreationEvent();

// Act
void Act() => eventStream.Apply(creationEvent);

// Assert
Assert.Throws<InvalidOperationException>(Act);
}

[Fact]
public void EventStream_Apply_ValidEvent()
{
// Arrange
var snapshot = new TestAggregateRoot();
var eventStream = new TestEventStream<TestAggregateRoot>(snapshot, [new TestCreationEvent()]);
var validEvent = new TestEvent();

// Act
eventStream.Apply(validEvent);

// Assert
Assert.Equal(snapshot, eventStream.Snapshot);
Assert.Equal(snapshot, eventStream.LastCommittedSnapshot);
Assert.Equal(snapshot.Id, eventStream.Id);
Assert.Single(eventStream.CommittedEvents);
Assert.Single(eventStream.UncommittedEvents);
Assert.Equal(2, eventStream.AllEvents.Count());
}

[Fact]
public void EventStream_Commit()
{
// Arrange
var snapshot = new TestAggregateRoot();
var eventStream = new TestEventStream<TestAggregateRoot>(snapshot, [new TestCreationEvent()]);
var validEvent = new TestEvent();
eventStream.Apply(validEvent);

// Act
eventStream.Commit();

// Assert
Assert.Equal(snapshot, eventStream.Snapshot);
Assert.NotSame(snapshot, eventStream.Snapshot);
Assert.Equal(snapshot, eventStream.LastCommittedSnapshot);
Assert.NotSame(snapshot, eventStream.LastCommittedSnapshot);
Assert.Equal(snapshot.Id, eventStream.Id);
Assert.Equal(2, eventStream.CommittedEvents.Count);
Assert.Empty(eventStream.UncommittedEvents);
Assert.Equal(2, eventStream.AllEvents.Count());
}

[Fact]
public void EventStream_Rollback()
{
// Arrange
var snapshot = new TestAggregateRoot();
var eventStream = new TestEventStream<TestAggregateRoot>(snapshot, [new TestCreationEvent()]);
var validEvent = new TestEvent();
eventStream.Apply(validEvent);

// Act
eventStream.Rollback();

// Assert
Assert.Same(snapshot, eventStream.Snapshot);
Assert.Same(snapshot, eventStream.LastCommittedSnapshot);
Assert.Equal(snapshot.Id, eventStream.Id);
Assert.Single(eventStream.CommittedEvents);
Assert.Empty(eventStream.UncommittedEvents);
Assert.Single(eventStream.AllEvents);
}

private class TestEventStream<T> : EventStream<T>
where T : IAggregateRoot
{
public TestEventStream(T snapshot) : base(snapshot)
{
}

public TestEventStream(T snapshot, IEnumerable<IEvent> events) : base(snapshot, events)
{
}

public T LastCommittedSnapshot => LastCommittedSnapshot_FOR_UNIT_TESTING;
}
}
67 changes: 67 additions & 0 deletions src/F23.Kernel.Tests/EventSourcing/EventValidatorSwitcherTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using F23.Kernel.EventSourcing;
using F23.Kernel.Tests.EventSourcing.Mocks;
using Microsoft.Extensions.DependencyInjection;

namespace F23.Kernel.Tests.EventSourcing;

public class EventValidatorSwitcherTests
{
[Fact]
public async Task Validate_WithNoValidators_ReturnsPassedValidationResult()
{
// Arrange
var serviceProvider = new ServiceCollection().BuildServiceProvider();
var switcher = new EventValidatorSwitcher(serviceProvider);
var eventStream = TestEventStream.Create();
var e = new TestEvent();

// Act
var result = await switcher.Validate(eventStream, e);

// Assert
Assert.True(result.IsSuccess);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task Validate_WithOneValidator_ReturnsCorrectValidationResult(bool passed)
{
// Arrange
var serviceProvider = new ServiceCollection()
.AddTransient<IEventValidator<TestAggregateRoot, TestEvent>>(_ => new TestEventValidator(passed))
.BuildServiceProvider();
var switcher = new EventValidatorSwitcher(serviceProvider);
var eventStream = TestEventStream.Create();
var e = new TestEvent();

// Act
var result = await switcher.Validate(eventStream, e);

// Assert
Assert.Equal(passed, result.IsSuccess);
}

[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task Validate_WithMultipleValidators_ReturnsCorrectValidationResult(bool passed1, bool passed2)
{
// Arrange
var serviceProvider = new ServiceCollection()
.AddTransient<IEventValidator<TestAggregateRoot, TestEvent>>(_ => new TestEventValidator(passed1))
.AddTransient<IEventValidator<TestAggregateRoot, TestEvent>>(_ => new TestEventValidator(passed2))
.BuildServiceProvider();
var switcher = new EventValidatorSwitcher(serviceProvider);
var eventStream = TestEventStream.Create();
var e = new TestEvent();

// Act
var result = await switcher.Validate(eventStream, e);

// Assert
Assert.Equal(passed1 && passed2, result.IsSuccess);
}
}
23 changes: 23 additions & 0 deletions src/F23.Kernel.Tests/EventSourcing/Mocks/TestAggregateRoot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using F23.Kernel.EventSourcing;
using F23.Kernel.Results;

namespace F23.Kernel.Tests.EventSourcing.Mocks;

public record TestAggregateRoot :
IAggregateRoot,
IApplyEvent<TestAggregateRoot, TestEvent>
{
private readonly bool _isValid;

public TestAggregateRoot(bool isValid = true)
{
_isValid = isValid;
}

public string Id => "test-id";

public ValidationResult Validate() =>
_isValid ? ValidationResult.Passed() : ValidationResult.Failed("test", "test");

public TestAggregateRoot Apply(TestEvent e) => this with {};
}
16 changes: 16 additions & 0 deletions src/F23.Kernel.Tests/EventSourcing/Mocks/TestCreationEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using F23.Kernel.EventSourcing;
using F23.Kernel.Results;

namespace F23.Kernel.Tests.EventSourcing.Mocks;

public class TestCreationEvent(bool isValid = true) : ICreationEvent
{
public ValidationResult Validate() =>
isValid ? ValidationResult.Passed() : ValidationResult.Failed("test", "test");

public string EventType => "TestCreationEvent";

public string? UserProfileId { get; set; }

public DateTime OccurredAt { get; set; }
}
16 changes: 16 additions & 0 deletions src/F23.Kernel.Tests/EventSourcing/Mocks/TestEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using F23.Kernel.EventSourcing;
using F23.Kernel.Results;

namespace F23.Kernel.Tests.EventSourcing.Mocks;

public class TestEvent(bool isValid = true) : IEvent
{
public ValidationResult Validate() =>
isValid ? ValidationResult.Passed() : ValidationResult.Failed("test", "test");

public string EventType => "TestEvent";

public string? UserProfileId { get; set; }

public DateTime OccurredAt { get; set; }
}
9 changes: 9 additions & 0 deletions src/F23.Kernel.Tests/EventSourcing/Mocks/TestEventStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using F23.Kernel.EventSourcing;

namespace F23.Kernel.Tests.EventSourcing.Mocks;

public static class TestEventStream
{
public static EventStream<TestAggregateRoot> Create() =>
new(new TestAggregateRoot(), [new TestCreationEvent()]);
}
16 changes: 16 additions & 0 deletions src/F23.Kernel.Tests/EventSourcing/Mocks/TestEventValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using F23.Kernel.EventSourcing;
using F23.Kernel.Results;

namespace F23.Kernel.Tests.EventSourcing.Mocks;

public class TestEventValidator(bool passed) : IEventValidator<TestAggregateRoot, TestEvent>
{
public Task<ValidationResult> Validate(EventStream<TestAggregateRoot> eventStream, TestEvent e, CancellationToken cancellationToken = default)
{
var result = passed
? ValidationResult.Passed()
: ValidationResult.Failed(new List<ValidationError> { new("Test", "Test") });

return Task.FromResult(result);
}
}
42 changes: 42 additions & 0 deletions src/F23.Kernel.Tests/Results/AggregateResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using F23.Kernel.Results;

namespace F23.Kernel.Tests.Results;

public class AggregateResultTests
{
[Fact]
public void AggregateResult_AllSuccess_IsSuccess()
{
// Arrange
var results = new List<Result> { Result.Success(), Result.Success() };

// Act
var result = new AggregateResult(results);

// Assert
Assert.True(result.IsSuccess);
}

[Fact]
public void AggregateResult_OneFailure_IsFailure()
{
// Arrange
var results = new List<Result> { Result.Success(), Result.Unauthorized("YOU SHALL NOT PASS") };

// Act
var result = new AggregateResult(results);

// Assert
Assert.False(result.IsSuccess);
}

[Fact]
public void AggregateResult_EmptyResults_IsSuccess()
{
// Act
var result = new AggregateResult([]);

// Assert
Assert.True(result.IsSuccess);
}
}
32 changes: 32 additions & 0 deletions src/F23.Kernel.Tests/Results/AuthorizationResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using F23.Kernel.Results;

namespace F23.Kernel.Tests.Results;

public class AuthorizationResultTests
{
[Fact]
public void AuthorizationResult_Success_IsAuthorized()
{
// Act
var result = AuthorizationResult.Success();

// Assert
Assert.True(result.IsAuthorized);
Assert.IsType<SuccessfulAuthorizationResult>(result);
}

[Fact]
public void AuthorizationResult_Fail_IsNotAuthorized()
{
// Arrange
const string message = "YOU SHALL NOT PASS";

// Act
var result = AuthorizationResult.Fail(message);

// Assert
Assert.False(result.IsAuthorized);
var failedAuthorizationResult = Assert.IsType<FailedAuthorizationResult>(result);
Assert.Equal(message, failedAuthorizationResult.Message);
}
}
Loading
Loading