Skip to content

Commit

Permalink
Added Application layer tests
Browse files Browse the repository at this point in the history
  • Loading branch information
oskardudycz committed May 1, 2024
1 parent c07909f commit 6ea2ef6
Show file tree
Hide file tree
Showing 29 changed files with 1,212 additions and 28 deletions.
43 changes: 34 additions & 9 deletions Core/Validation/ValidationExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,67 @@
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace Core.Validation;

public static class ValidationExtensions
{
public static T AssertNotNull<T>(this T? value, [CallerArgumentExpression("value")] string? paramName = null)
public static T NotNull<T>(this T? value, [CallerArgumentExpression("value")] string? paramName = null)
where T : struct
{
if (value == null)
throw new ArgumentNullException(paramName);

return (T)value;
return value.Value;
}

public static string AssertNotEmpty(this string? value, [CallerArgumentExpression("value")] string? paramName = null)
public static T NotNull<T>(this T? value, [CallerArgumentExpression("value")] string? paramName = null)
where T : class
{
if (value == null)
throw new ArgumentNullException(paramName);

return value;
}

public static string NotEmpty(this string? value, [CallerArgumentExpression("value")] string? paramName = null)
=> !string.IsNullOrWhiteSpace(value) ? value : throw new ArgumentOutOfRangeException(paramName);

public static T AssertNotEmpty<T>(this T value, [CallerArgumentExpression("value")] string? paramName = null)
public static Guid NotEmpty(this Guid? value, [CallerArgumentExpression("value")] string? paramName = null)
=> value!= null && value != Guid.Empty ? value.Value : throw new ArgumentOutOfRangeException(paramName);


public static T NotEmpty<T>(this T value, [CallerArgumentExpression("value")] string? paramName = null)
where T : struct
=> AssertNotEmpty((T?)value, paramName);
=> NotEmpty((T?)value, paramName);

public static T AssertNotEmpty<T>(this T? value, [CallerArgumentExpression("value")] string? paramName = null)
public static T NotEmpty<T>(this T? value, [CallerArgumentExpression("value")] string? paramName = null)
where T : struct
{
var notNullValue = value.AssertNotNull(paramName);
var notNullValue = value.NotNull(paramName);

if (Equals(notNullValue, default(T)))
throw new ArgumentOutOfRangeException(paramName);

return notNullValue;
}

public static T AssertGreaterOrEqualThan<T>(this T value, T valueToCompare, [CallerArgumentExpression("value")] string? paramName = null)
public static T GreaterOrEqualThan<T>(this T value, T valueToCompare, [CallerArgumentExpression("value")] string? paramName = null)
where T : IComparable<T>
{
if (value.CompareTo(valueToCompare) < 0)
throw new ArgumentOutOfRangeException(paramName);

return value;

}

public static T Positive<T>(this T value, [CallerArgumentExpression("value")] string? paramName = null)
where T : INumber<T>
{
if (value == null || value.CompareTo(Convert.ChangeType(0, typeof(T))) <= 0)
throw new ArgumentOutOfRangeException(paramName);

return value;
}
}
14 changes: 14 additions & 0 deletions EventSourcing.NetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PointOfSales.Api", "Sample\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PointOfSales.Api.Tests", "Sample\ClosingTheBooks\PointOfSales.Api.Tests\PointOfSales.Api.Tests.csproj", "{42510FFD-04F5-4580-9B02-9CBA260718DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "08-ApplicationLogic.Marten", "Workshops\IntroductionToEventSourcing\08-ApplicationLogic.Marten\08-ApplicationLogic.Marten.csproj", "{48B5E0BE-81C3-4ECF-85C1-B22D9A245C5B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "08-ApplicationLogic.Marten.Tests", "Workshops\IntroductionToEventSourcing\08-ApplicationLogic.Marten.Tests\08-ApplicationLogic.Marten.Tests.csproj", "{A31878C3-8079-4A4E-AAAE-1000C5F34122}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -997,6 +1001,14 @@ Global
{42510FFD-04F5-4580-9B02-9CBA260718DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42510FFD-04F5-4580-9B02-9CBA260718DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42510FFD-04F5-4580-9B02-9CBA260718DC}.Release|Any CPU.Build.0 = Release|Any CPU
{48B5E0BE-81C3-4ECF-85C1-B22D9A245C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48B5E0BE-81C3-4ECF-85C1-B22D9A245C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48B5E0BE-81C3-4ECF-85C1-B22D9A245C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48B5E0BE-81C3-4ECF-85C1-B22D9A245C5B}.Release|Any CPU.Build.0 = Release|Any CPU
{A31878C3-8079-4A4E-AAAE-1000C5F34122}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A31878C3-8079-4A4E-AAAE-1000C5F34122}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A31878C3-8079-4A4E-AAAE-1000C5F34122}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A31878C3-8079-4A4E-AAAE-1000C5F34122}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1180,6 +1192,8 @@ Global
{F40DF11A-354C-4438-B674-B95B9A401C33} = {C8F02DB9-5FEA-46C8-95E3-BB4255CB0667}
{CE15C7EC-85CA-44B8-B13B-206E308E8EF8} = {C8F02DB9-5FEA-46C8-95E3-BB4255CB0667}
{42510FFD-04F5-4580-9B02-9CBA260718DC} = {C8F02DB9-5FEA-46C8-95E3-BB4255CB0667}
{48B5E0BE-81C3-4ECF-85C1-B22D9A245C5B} = {14C7B928-9D6C-441A-8A1F-0C49173E73EB}
{A31878C3-8079-4A4E-AAAE-1000C5F34122} = {14C7B928-9D6C-441A-8A1F-0C49173E73EB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A5F55604-2FF3-43B7-B657-4F18E6E95D3B}
Expand Down
2 changes: 1 addition & 1 deletion Sample/HotelManagement/Reservations/Guests/Guest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Reservations.Guests;
public record GuestExternalId(string Value)
{
public static GuestExternalId FromPrefix(string prefix, string externalId) =>
new($"{prefix.AssertNotEmpty()}/{externalId.AssertNotEmpty()}");
new($"{prefix.NotEmpty()}/{externalId.NotEmpty()}");
}

public record GuestId(string Value);
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ DateOnly To
DateOnly to
) =>
new(
roomType.AssertNotEmpty(),
from.AssertNotEmpty(),
to.AssertNotEmpty().AssertGreaterOrEqualThan(from)
roomType.NotEmpty(),
from.NotEmpty(),
to.NotEmpty().GreaterOrEqualThan(from)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ public static RoomReserved Handle(ReserveRoom command)
IReadOnlyList<DailyRoomTypeAvailability> dailyAvailability
) =>
new(
id.AssertNotEmpty(),
roomType.AssertNotEmpty(),
from.AssertNotEmpty(),
to.AssertNotEmpty().AssertGreaterOrEqualThan(from),
guestId.AssertNotEmpty(),
numberOfPeople.AssertNotEmpty(),
now.AssertNotEmpty(),
id.NotEmpty(),
roomType.NotEmpty(),
from.NotEmpty(),
to.NotEmpty().GreaterOrEqualThan(from),
guestId.NotEmpty(),
numberOfPeople.NotEmpty(),
now.NotEmpty(),
ReservationSource.Api,
dailyAvailability,
null
Expand All @@ -73,15 +73,15 @@ IReadOnlyList<DailyRoomTypeAvailability> dailyAvailability
DateTimeOffset now
) =>
new(
id.AssertNotEmpty(),
roomType.AssertNotEmpty(),
from.AssertNotEmpty(),
to.AssertNotEmpty().AssertGreaterOrEqualThan(from),
guestId.AssertNotEmpty(),
numberOfPeople.AssertNotEmpty(),
now.AssertNotEmpty(),
id.NotEmpty(),
roomType.NotEmpty(),
from.NotEmpty(),
to.NotEmpty().GreaterOrEqualThan(from),
guestId.NotEmpty(),
numberOfPeople.NotEmpty(),
now.NotEmpty(),
ReservationSource.External,
Array.Empty<DailyRoomTypeAvailability>(),
externalId.AssertNotEmpty()
externalId.NotEmpty()
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>ApplicationLogic.Marten.Tests</RootNamespace>
<AssemblyName>ApplicationLogic.Marten.Tests</AssemblyName>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.4" />
<PackageReference Include="Ogooreck" Version="0.8.0" />
<PackageReference Include="Bogus" Version="35.5.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\08-ApplicationLogic.Marten\08-ApplicationLogic.Marten.csproj" />
</ItemGroup>

<Import Project="..\..\..\Tests.Build.props" />

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System.Net;
using ApplicationLogic.Marten.Immutable.ShoppingCarts;
using Bogus;
using Ogooreck.API;
using Xunit;
using static Ogooreck.API.ApiSpecification;
using static ApplicationLogic.Marten.Tests.Incidents.Scenarios;
using static ApplicationLogic.Marten.Tests.Incidents.Endpoints;

namespace ApplicationLogic.Marten.Tests.Incidents;

public class AddProductItemToShoppingCartTests(ApiSpecification<Program> api):
IClassFixture<ApiSpecification<Program>>
{
[Theory]
[InlineData("immutable")]
public Task CantAddProductItemToNotExistingShoppingCart(string apiPrefix) =>
api.Given()
.When(
POST,
URI(ShoppingCartProductItems(apiPrefix, ClientId, NotExistingShoppingCartId)),
BODY(new AddProductRequest(ProductItem))
)
.Then(NOT_FOUND);

[Theory]
[InlineData("immutable")]
public Task AddsProductItemToEmptyShoppingCart(string apiPrefix) =>
api.Given(OpenedShoppingCart(apiPrefix, ClientId))
.When(
POST,
URI(ctx => ShoppingCartProductItems(apiPrefix, ClientId, ctx.GetCreatedId<Guid>())),
BODY(new AddProductRequest(ProductItem))
)
.Then(NO_CONTENT);

[Theory]
[InlineData("immutable")]
public Task AddsProductItemToNonEmptyShoppingCart(string apiPrefix) =>
api.Given(
OpenedShoppingCart(apiPrefix, ClientId),
WithProductItem(apiPrefix, ClientId, ProductItem)
)
.When(
POST,
URI(ctx => ShoppingCartProductItems(apiPrefix, ClientId, ctx.GetCreatedId<Guid>())),
BODY(new AddProductRequest(ProductItem))
)
.Then(NO_CONTENT);

[Theory]
[InlineData("immutable")]
public Task CantAddProductItemToConfirmedShoppingCart(string apiPrefix) =>
api.Given(
OpenedShoppingCart(apiPrefix, ClientId),
WithProductItem(apiPrefix, ClientId, ProductItem),
ThenConfirmed(apiPrefix, ClientId)
)
.When(
POST,
URI(ctx => ShoppingCartProductItems(apiPrefix, ClientId, ctx.GetCreatedId<Guid>())),
BODY(new AddProductRequest(ProductItem))
)
.Then(CONFLICT);

[Theory]
[InlineData("immutable")]
public Task CantAddProductItemToCanceledShoppingCart(string apiPrefix) =>
api.Given(
OpenedShoppingCart(apiPrefix, ClientId),
WithProductItem(apiPrefix, ClientId, ProductItem),
ThenCanceled(apiPrefix, ClientId)
)
.When(
POST,
URI(ctx => ShoppingCartProductItems(apiPrefix, ClientId, ctx.GetCreatedId<Guid>())),
BODY(new AddProductRequest(ProductItem))
)
.Then(CONFLICT);

[Theory]
[InlineData("immutable")]
public Task ReturnsNonEmptyShoppingCart(string apiPrefix) =>
api.Given(
OpenedShoppingCart(apiPrefix, ClientId),
WithProductItem(apiPrefix, ClientId, ProductItem)
)
.When(GET, URI(ctx => ShoppingCart(apiPrefix, ClientId, ctx.GetCreatedId<Guid>())))
.Then(OK);

private static readonly Faker Faker = new();
private readonly Guid NotExistingShoppingCartId = Guid.NewGuid();
private readonly Guid ClientId = Guid.NewGuid();
private readonly ProductItemRequest ProductItem = new(Guid.NewGuid(), Faker.Random.Number(1, 500));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System.Net;
using ApplicationLogic.Marten.Immutable.ShoppingCarts;
using Bogus;
using Ogooreck.API;
using Xunit;
using static Ogooreck.API.ApiSpecification;
using static ApplicationLogic.Marten.Tests.Incidents.Scenarios;
using static ApplicationLogic.Marten.Tests.Incidents.Endpoints;

namespace ApplicationLogic.Marten.Tests.Incidents;

public class CancelShoppingCartTests(ApiSpecification<Program> api):
IClassFixture<ApiSpecification<Program>>
{
[Theory]
[InlineData("immutable")]
public Task CantCancelNotExistingShoppingCart(string apiPrefix) =>
api.Given()
.When(
DELETE,
URI(ShoppingCart(apiPrefix, ClientId, NotExistingShoppingCartId))
)
.Then(NOT_FOUND);

[Theory]
[InlineData("immutable")]
public Task CantCancelEmptyShoppingCart(string apiPrefix) =>
api.Given(OpenedShoppingCart(apiPrefix, ClientId))
.When(
DELETE,
URI(ctx => ShoppingCart(apiPrefix, ClientId, ctx.GetCreatedId<Guid>()))
)
.Then(CONFLICT);

[Theory]
[InlineData("immutable")]
public Task CancelsNonEmptyShoppingCart(string apiPrefix) =>
api.Given(
OpenedShoppingCart(apiPrefix, ClientId),
WithProductItem(apiPrefix, ClientId, ProductItem)
)
.When(
DELETE,
URI(ctx => ShoppingCart(apiPrefix, ClientId, ctx.GetCreatedId<Guid>()))
)
.Then(NO_CONTENT);

[Theory]
[InlineData("immutable")]
public Task CantCancelAlreadyCanceledShoppingCart(string apiPrefix) =>
api.Given(
OpenedShoppingCart(apiPrefix, ClientId),
WithProductItem(apiPrefix, ClientId, ProductItem),
ThenCanceled(apiPrefix, ClientId)
)
.When(
DELETE,
URI(ctx => ShoppingCart(apiPrefix, ClientId, ctx.GetCreatedId<Guid>()))
)
.Then(CONFLICT);

[Theory]
[InlineData("immutable")]
public Task CantCancelConfirmedShoppingCart(string apiPrefix) =>
api.Given(
OpenedShoppingCart(apiPrefix, ClientId),
WithProductItem(apiPrefix, ClientId, ProductItem),
ThenConfirmed(apiPrefix, ClientId)
)
.When(
DELETE,
URI(ctx => ShoppingCart(apiPrefix, ClientId, ctx.GetCreatedId<Guid>()))
)
.Then(CONFLICT);

[Theory]
[InlineData("immutable")]
public Task ReturnsNonEmptyShoppingCart(string apiPrefix) =>
api.Given(
OpenedShoppingCart(apiPrefix, ClientId),
WithProductItem(apiPrefix, ClientId, ProductItem),
ThenCanceled(apiPrefix, ClientId)
)
.When(GET, URI(ctx => ShoppingCart(apiPrefix, ClientId, ctx.GetCreatedId<Guid>())))
.Then(OK);

private static readonly Faker Faker = new();
private readonly Guid NotExistingShoppingCartId = Guid.NewGuid();
private readonly Guid ClientId = Guid.NewGuid();
private readonly ProductItemRequest ProductItem = new(Guid.NewGuid(), Faker.Random.Number(1, 500));
}
Loading

0 comments on commit 6ea2ef6

Please sign in to comment.