From 77abd9ce36784f7a770f2e92475b971ecf7f292e Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Sat, 3 Dec 2022 20:08:56 +0100 Subject: [PATCH] Added unit tests for Group Checkout --- Core/Extensions/DictionaryExtensions.cs | 14 +- .../GroupCheckoutTests.CheckIn.cs | 18 +++ ...koutTests.RecordGuestCheckoutCompletion.cs | 131 +++++++++++++++++ ...heckoutTests.RecordGuestCheckoutFailure.cs | 136 ++++++++++++++++++ ...outTests.RecordGuestCheckoutsInitiation.cs | 28 ++++ .../GroupCheckouts/GroupCheckoutTests.cs | 28 ++++ .../GroupCheckouts/GroupCheckout.cs | 35 +++-- 7 files changed, 374 insertions(+), 16 deletions(-) create mode 100644 Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.CheckIn.cs create mode 100644 Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutCompletion.cs create mode 100644 Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutFailure.cs create mode 100644 Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutsInitiation.cs create mode 100644 Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.cs diff --git a/Core/Extensions/DictionaryExtensions.cs b/Core/Extensions/DictionaryExtensions.cs index 82085e9a4..88e916c18 100644 --- a/Core/Extensions/DictionaryExtensions.cs +++ b/Core/Extensions/DictionaryExtensions.cs @@ -2,7 +2,6 @@ namespace Core.Extensions; public static class DictionaryExtensions { - public static Dictionary Merge( Dictionary first, Dictionary second @@ -14,4 +13,17 @@ public static Dictionary Merge( IEnumerable> second ) where TKey : notnull => new(first.Union(second)); + + public static Dictionary With( + this Dictionary first, + TKey key, + TValue value + ) where TKey : notnull + { + var newDictionary = first.ToDictionary(ks => ks.Key, vs => vs.Value); + + newDictionary[key] = value; + + return newDictionary; + } } diff --git a/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.CheckIn.cs b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.CheckIn.cs new file mode 100644 index 000000000..571cc9484 --- /dev/null +++ b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.CheckIn.cs @@ -0,0 +1,18 @@ +using HotelManagement.GroupCheckouts; +using Ogooreck.BusinessLogic; +using Xunit; + +namespace HotelManagement.Tests.GroupCheckouts; + +public partial class GroupCheckoutTests +{ + [Fact] + public void GivenNonExistingGroupCheckout_WhenInitiate_ThenSucceeds() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given() + .When(_ => GroupCheckout.Initiate(groupCheckoutId, clerkId, guestStaysIds, now)) + .Then(new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now)); + } +} diff --git a/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutCompletion.cs b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutCompletion.cs new file mode 100644 index 000000000..d43e3bcf4 --- /dev/null +++ b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutCompletion.cs @@ -0,0 +1,131 @@ +using HotelManagement.GroupCheckouts; +using Ogooreck.BusinessLogic; +using Xunit; + +namespace HotelManagement.Tests.GroupCheckouts; + +public partial class GroupCheckoutTests +{ + [Fact] + public void GivenNonExistingGroupCheckout_WhenRecordGuestCheckoutCompletion_ThenIgnores() + { + var guestStaysId = Guid.NewGuid(); + + Spec.Given() + .When(state => state.RecordGuestCheckoutCompletion(guestStaysId, now).IsPresent) + .Then(false); + } + + [Fact] + public void GivenInitiatedGroupCheckout_WhenRecordGuestCheckoutCompletion_ThenSucceeds() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now) + ) + .When(state => state.RecordGuestCheckoutCompletion(guestStaysIds[0], now).GetOrThrow()) + .Then(new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now)); + } + + [Fact] + public void GivenInitiatedGroupCheckout_WhenRecordGuestCheckoutCompletionTwice_ThenIgnores() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now) + ) + .When(state => state.RecordGuestCheckoutCompletion(guestStaysIds[0], now).IsPresent) + .Then(false); + } + + [Fact] + public void GivenInitiatedGroupCheckout_WhenRecordLastGuestCheckoutCompletion_ThenCompletes() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[1], now) + ) + .When(state => state.RecordGuestCheckoutCompletion(guestStaysIds[2], now).GetOrThrow()) + .Then( + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[2], now), + new GroupCheckoutCompleted(groupCheckoutId, guestStaysIds, now) + ); + } + + + [Fact] + public void GivenInitiatedGroupCheckoutWithFailure_WhenRecordLastGuestCheckoutCompletion_ThenCompletesWithFailure() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now), + new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[1], now) + ) + .When(state => state.RecordGuestCheckoutCompletion(guestStaysIds[2], now).GetOrThrow()) + .Then( + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[2], now), + new GroupCheckoutFailed( + groupCheckoutId, + new[] { guestStaysIds[0], guestStaysIds[2] }, + new[] { guestStaysIds[1] }, + now + ) + ); + } + + [Fact] + public void GivenCompletedGroupCheckoutWithFailure_WhenRecordGuestCheckoutCompletion_ThenIgnores() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now), + new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[1], now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[2], now), + new GroupCheckoutFailed( + groupCheckoutId, + new[] { guestStaysIds[0], guestStaysIds[2] }, + new[] { guestStaysIds[1] }, + now + ) + ) + .When(state => state.RecordGuestCheckoutCompletion(guestStaysIds[2], now).IsPresent) + .Then(false); + } + + [Fact] + public void GivenCompletedGroupCheckout_WhenRecordGuestCheckoutCompletion_ThenIgnores() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[1], now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[2], now), + new GroupCheckoutFailed( + groupCheckoutId, + new[] { guestStaysIds[0], guestStaysIds[2] }, + new[] { guestStaysIds[1] }, + now + ) + ) + .When(state => state.RecordGuestCheckoutCompletion(guestStaysIds[2], now).IsPresent) + .Then(false); + } +} diff --git a/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutFailure.cs b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutFailure.cs new file mode 100644 index 000000000..4f18ecffd --- /dev/null +++ b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutFailure.cs @@ -0,0 +1,136 @@ +using HotelManagement.GroupCheckouts; +using Ogooreck.BusinessLogic; +using Xunit; + +namespace HotelManagement.Tests.GroupCheckouts; + +public partial class GroupCheckoutTests +{ + [Fact] + public void GivenNonExistingGroupCheckout_WhenRecordGuestCheckoutFailure_ThenIgnores() + { + var guestStaysId = Guid.NewGuid(); + + Spec.Given() + .When(state => state.RecordGuestCheckoutFailure(guestStaysId, now).IsPresent) + .Then(false); + } + + [Fact] + public void GivenInitiatedGroupCheckout_WhenRecordGuestCheckoutFailure_ThenSucceeds() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now) + ) + .When(state => state.RecordGuestCheckoutFailure(guestStaysIds[0], now).GetOrThrow()) + .Then(new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[0], now)); + } + + [Fact] + public void GivenInitiatedGroupCheckout_WhenRecordGuestCheckoutFailureTwice_ThenIgnores() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[0], now) + ) + .When(state => state.RecordGuestCheckoutFailure(guestStaysIds[0], now).IsPresent) + .Then(false); + } + + [Fact] + public void GivenInitiatedGroupCheckout_WhenRecordLastGuestCheckoutFailure_ThenCompletesWithFailure() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[0], now), + new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[1], now) + ) + .When(state => state.RecordGuestCheckoutFailure(guestStaysIds[2], now).GetOrThrow()) + .Then( + new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[2], now), + new GroupCheckoutFailed( + groupCheckoutId, + Array.Empty(), + guestStaysIds, + now + ) + ); + } + + + [Fact] + public void GivenInitiatedGroupCheckoutWithFailure_WhenRecordLastGuestCheckoutFailure_ThenCompletesWithFailure() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now), + new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[1], now) + ) + .When(state => state.RecordGuestCheckoutFailure(guestStaysIds[2], now).GetOrThrow()) + .Then( + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[2], now), + new GroupCheckoutFailed( + groupCheckoutId, + new[] { guestStaysIds[0] }, + new[] { guestStaysIds[1], guestStaysIds[2] }, + now + ) + ); + } + + [Fact] + public void GivenCompletedGroupCheckoutWithFailure_WhenRecordGuestCheckoutFailure_ThenIgnores() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[1], now), + new GuestCheckoutFailed(groupCheckoutId, guestStaysIds[2], now), + new GroupCheckoutFailed( + groupCheckoutId, + new[] { guestStaysIds[0], guestStaysIds[1] }, + new[] { guestStaysIds[2] }, + now + ) + ) + .When(state => state.RecordGuestCheckoutFailure(guestStaysIds[2], now).IsPresent) + .Then(false); + } + + [Fact] + public void GivenCompletedGroupCheckout_WhenRecordGuestCheckoutFailure_ThenIgnores() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given( + new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now), + new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[0], now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[1], now), + new GuestCheckoutCompleted(groupCheckoutId, guestStaysIds[2], now), + new GroupCheckoutFailed( + groupCheckoutId, + new[] { guestStaysIds[0], guestStaysIds[2] }, + new[] { guestStaysIds[1] }, + now + ) + ) + .When(state => state.RecordGuestCheckoutFailure(guestStaysIds[2], now).IsPresent) + .Then(false); + } +} diff --git a/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutsInitiation.cs b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutsInitiation.cs new file mode 100644 index 000000000..169b71bae --- /dev/null +++ b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.RecordGuestCheckoutsInitiation.cs @@ -0,0 +1,28 @@ +using HotelManagement.GroupCheckouts; +using Ogooreck.BusinessLogic; +using Xunit; + +namespace HotelManagement.Tests.GroupCheckouts; + +public partial class GroupCheckoutTests +{ + [Fact] + public void GivenNonExistingGroupCheckout_WhenRecordGuestCheckoutsInitiation_ThenIgnores() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given() + .When(state => state.RecordGuestCheckoutsInitiation(guestStaysIds, now).IsPresent) + .Then(false); + } + + [Fact] + public void GivenInitiatedGroupCheckout_WhenRecordGuestCheckoutsInitiation_ThenSucceeds() + { + var guestStaysIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + + Spec.Given(new GroupCheckoutInitiated(groupCheckoutId, clerkId, guestStaysIds, now)) + .When(state => state.RecordGuestCheckoutsInitiation(guestStaysIds, now).GetOrThrow()) + .Then(new GuestCheckoutsInitiated(groupCheckoutId, guestStaysIds, now)); + } +} diff --git a/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.cs b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.cs new file mode 100644 index 000000000..d0df83a70 --- /dev/null +++ b/Sample/HotelManagement/HotelManagement.Tests/GroupCheckouts/GroupCheckoutTests.cs @@ -0,0 +1,28 @@ +using Bogus; +using HotelManagement.GroupCheckouts; +using Ogooreck.BusinessLogic; + +namespace HotelManagement.Tests.GroupCheckouts; + +public partial class GroupCheckoutTests +{ + private readonly HandlerSpecification Spec = Specification.For(Evolve); + private readonly DateTimeOffset now = DateTimeOffset.UtcNow; + private readonly Guid groupCheckoutId = Guid.NewGuid(); + private readonly Guid clerkId = Guid.NewGuid(); + private readonly Faker faker = new(); + + private static GroupCheckout Evolve(GroupCheckout groupCheckout, object @event) + { + return @event switch + { + GroupCheckoutInitiated groupCheckoutInitiated => GroupCheckout.Create(groupCheckoutInitiated), + GuestCheckoutsInitiated guestCheckoutsInitiated => groupCheckout.Apply(guestCheckoutsInitiated), + GuestCheckoutCompleted guestCheckoutCompleted => groupCheckout.Apply(guestCheckoutCompleted), + GuestCheckoutFailed guestCheckoutFailed => groupCheckout.Apply(guestCheckoutFailed), + GroupCheckoutCompleted groupCheckoutCompleted => groupCheckout.Apply(groupCheckoutCompleted), + GroupCheckoutFailed groupCheckoutFailed => groupCheckout.Apply(groupCheckoutFailed), + _ => groupCheckout + }; + } +} diff --git a/Sample/HotelManagement/HotelManagement/GroupCheckouts/GroupCheckout.cs b/Sample/HotelManagement/HotelManagement/GroupCheckouts/GroupCheckout.cs index d107441d6..18354b359 100644 --- a/Sample/HotelManagement/HotelManagement/GroupCheckouts/GroupCheckout.cs +++ b/Sample/HotelManagement/HotelManagement/GroupCheckouts/GroupCheckout.cs @@ -1,3 +1,4 @@ +using Core.Extensions; using Core.Structures; namespace HotelManagement.GroupCheckouts; @@ -73,9 +74,11 @@ DateTimeOffset now { var guestCheckoutCompleted = new GuestCheckoutCompleted(Id, guestStayId, now); - return AreAnyOngoingCheckouts + var guestStayCheckouts = GuestStayCheckouts.With(guestStayId, CheckoutStatus.Completed); + + return AreAnyOngoingCheckouts(guestStayCheckouts) ? new object[] { guestCheckoutCompleted } - : new[] { guestCheckoutCompleted, Finalize(now) }; + : new[] { guestCheckoutCompleted, Finalize(guestStayCheckouts, now) }; }); public Maybe RecordGuestCheckoutFailure( @@ -88,35 +91,37 @@ DateTimeOffset now { var guestCheckoutFailed = new GuestCheckoutFailed(Id, guestStayId, now); - return AreAnyOngoingCheckouts + var guestStayCheckouts = GuestStayCheckouts.With(guestStayId, CheckoutStatus.Failed); + + return AreAnyOngoingCheckouts(guestStayCheckouts) ? new object[] { guestCheckoutFailed } - : new[] { guestCheckoutFailed, Finalize(now) }; + : new[] { guestCheckoutFailed, Finalize(guestStayCheckouts, now) }; }); - private object Finalize(DateTimeOffset now) => - !AreAnyFailedCheckouts + private object Finalize(Dictionary guestStayCheckouts, DateTimeOffset now) => + !AreAnyFailedCheckouts(guestStayCheckouts) ? new GroupCheckoutCompleted ( Id, - CheckoutsWith(CheckoutStatus.Failed), + CheckoutsWith(guestStayCheckouts, CheckoutStatus.Completed), now ) : new GroupCheckoutFailed ( Id, - CheckoutsWith(CheckoutStatus.Completed), - CheckoutsWith(CheckoutStatus.Failed), + CheckoutsWith(guestStayCheckouts, CheckoutStatus.Completed), + CheckoutsWith(guestStayCheckouts, CheckoutStatus.Failed), now ); - private bool AreAnyOngoingCheckouts => - GuestStayCheckouts.Values.Any(status => status is CheckoutStatus.Initiated or CheckoutStatus.Pending); + private static bool AreAnyOngoingCheckouts(Dictionary guestStayCheckouts) => + guestStayCheckouts.Values.Any(status => status is CheckoutStatus.Initiated or CheckoutStatus.Pending); - private bool AreAnyFailedCheckouts => - GuestStayCheckouts.Values.Any(status => status is CheckoutStatus.Failed); + private static bool AreAnyFailedCheckouts(Dictionary guestStayCheckouts) => + guestStayCheckouts.Values.Any(status => status is CheckoutStatus.Failed); - private Guid[] CheckoutsWith(CheckoutStatus status) => - GuestStayCheckouts + private static Guid[] CheckoutsWith(Dictionary guestStayCheckouts, CheckoutStatus status) => + guestStayCheckouts .Where(pair => pair.Value == status) .Select(pair => pair.Key) .ToArray();