From 84994c565685b6eb229f89e7d81780d49361a3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=BAc=20Ph=E1=BA=A1m=20H=E1=BB=93ng?= Date: Tue, 6 Aug 2024 17:00:26 +0700 Subject: [PATCH] test: update unit test for domain layer --- BuildingBlocks.sln | 9 + .../EventBusSubscriptionsManager.cs | 4 +- Core/BuildingBlock.Core.Domain/BaseEntity.cs | 5 +- .../Exceptions/EntityNotFoundException.cs | 2 +- .../Rules/Abstractions/IBusinessRule.cs | 2 +- .../EmailAddressMustFollowPattern.cs | 10 +- .../StringCanNotBeEmptyOrWhiteSpaceRule.cs | 12 +- .../Shared/Utils/ConfigurationUtilities.cs | 15 - .../Shared/Utils/Optional.cs | 22 +- .../ValueObjects/Abstractions/ValueObject.cs | 12 +- .../Extensions/FilterExtension.cs | 2 +- .../EventBusRabbitMQ.cs | 2 +- Tests/Core/Domain/BaseEntityTest.cs | 130 +++++ .../Domain/Constants/CacheKeyRegistryTest.cs | 51 ++ .../EmailAddressMustFollowPatternTest.cs | 77 +++ ...StringCanNotBeEmptyOrWhiteSpaceRuleTest.cs | 78 +++ .../Utils/ConfigurationUtilitiesTest.cs | 191 +++++++ .../Domain/Shared/Utils/EntityHelperTest.cs | 313 ++++++++++++ .../Core/Domain/Shared/Utils/OptionalTest.cs | 475 ++++++++++++++++++ .../Abstractions/AndSpecificationTest.cs | 43 ++ .../Abstractions/OrSpecificationTest.cs | 43 ++ .../EntityIdNotEqualSpecificationTest.cs | 42 ++ .../EntityIdSpecificationTest.cs | 42 ++ .../Specifications/TestSpecification.cs | 19 + Tests/Core/Domain/TestDomainEvent.cs | 7 + Tests/Core/Domain/TestEntity.cs | 11 + .../Abstractions/ValueObjectTest.cs | 89 ++++ Tests/Tests.csproj | 33 ++ 28 files changed, 1688 insertions(+), 53 deletions(-) create mode 100644 Tests/Core/Domain/BaseEntityTest.cs create mode 100644 Tests/Core/Domain/Constants/CacheKeyRegistryTest.cs create mode 100644 Tests/Core/Domain/Rules/EmailAddressMustFollowPatternTest.cs create mode 100644 Tests/Core/Domain/Rules/StringCanNotBeEmptyOrWhiteSpaceRuleTest.cs create mode 100644 Tests/Core/Domain/Shared/Utils/ConfigurationUtilitiesTest.cs create mode 100644 Tests/Core/Domain/Shared/Utils/EntityHelperTest.cs create mode 100644 Tests/Core/Domain/Shared/Utils/OptionalTest.cs create mode 100644 Tests/Core/Domain/Specifications/Abstractions/AndSpecificationTest.cs create mode 100644 Tests/Core/Domain/Specifications/Abstractions/OrSpecificationTest.cs create mode 100644 Tests/Core/Domain/Specifications/Implementations/EntityIdNotEqualSpecificationTest.cs create mode 100644 Tests/Core/Domain/Specifications/Implementations/EntityIdSpecificationTest.cs create mode 100644 Tests/Core/Domain/Specifications/TestSpecification.cs create mode 100644 Tests/Core/Domain/TestDomainEvent.cs create mode 100644 Tests/Core/Domain/TestEntity.cs create mode 100644 Tests/Core/Domain/ValueObjects/Abstractions/ValueObjectTest.cs create mode 100644 Tests/Tests.csproj diff --git a/BuildingBlocks.sln b/BuildingBlocks.sln index 79da048..90209b5 100644 --- a/BuildingBlocks.sln +++ b/BuildingBlocks.sln @@ -24,6 +24,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{CFE5A713-D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{9BFF2EA3-E081-4A2B-8297-A028DDD73E4A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{DC3F11D0-5517-44CC-B4D8-D2FB6282B874}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{06A00771-DEB6-4D06-A233-2B10B091295D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -66,6 +70,10 @@ Global {C9891FE2-E5C6-4773-BCA0-919FDCC132D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9891FE2-E5C6-4773-BCA0-919FDCC132D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {C9891FE2-E5C6-4773-BCA0-919FDCC132D2}.Release|Any CPU.Build.0 = Release|Any CPU + {DC3F11D0-5517-44CC-B4D8-D2FB6282B874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC3F11D0-5517-44CC-B4D8-D2FB6282B874}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC3F11D0-5517-44CC-B4D8-D2FB6282B874}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC3F11D0-5517-44CC-B4D8-D2FB6282B874}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0C2BE5AF-98DD-4E1D-A385-98E6B2D8A3A2} = {37ABC0F6-39B9-4057-955A-BA2CFDB64811} @@ -77,5 +85,6 @@ Global {4E1A0628-01F4-4876-B943-AC9FAFB19238} = {9BFF2EA3-E081-4A2B-8297-A028DDD73E4A} {2667E7B6-8451-4842-9CC5-0663C8CAE5F4} = {9BFF2EA3-E081-4A2B-8297-A028DDD73E4A} {C9891FE2-E5C6-4773-BCA0-919FDCC132D2} = {9BFF2EA3-E081-4A2B-8297-A028DDD73E4A} + {DC3F11D0-5517-44CC-B4D8-D2FB6282B874} = {06A00771-DEB6-4D06-A233-2B10B091295D} EndGlobalSection EndGlobal diff --git a/Core/BuildingBlock.Core.Application/EventBus/Implementations/EventBusSubscriptionsManager.cs b/Core/BuildingBlock.Core.Application/EventBus/Implementations/EventBusSubscriptionsManager.cs index e1df0f5..9c72f94 100644 --- a/Core/BuildingBlock.Core.Application/EventBus/Implementations/EventBusSubscriptionsManager.cs +++ b/Core/BuildingBlock.Core.Application/EventBus/Implementations/EventBusSubscriptionsManager.cs @@ -6,7 +6,7 @@ namespace BuildingBlock.Core.Application.EventBus.Implementations; public class EventBusSubscriptionsManager : IEventBusSubscriptionsManager { - private readonly List _eventTypes = new(); + private readonly List _eventTypes = []; private readonly Dictionary> _handlers = new(); public bool IsEmpty => _handlers is { Count: 0 }; @@ -63,7 +63,7 @@ public string GetEventKey() private void DoAddSubscription(Type handlerType, string eventName) { - if (!HasSubscriptionsForEvent(eventName)) _handlers.Add(eventName, new List()); + if (!HasSubscriptionsForEvent(eventName)) _handlers.Add(eventName, []); if (_handlers[eventName].Any(type => type == handlerType)) throw new ArgumentException( diff --git a/Core/BuildingBlock.Core.Domain/BaseEntity.cs b/Core/BuildingBlock.Core.Domain/BaseEntity.cs index 2fb9342..2cb9537 100644 --- a/Core/BuildingBlock.Core.Domain/BaseEntity.cs +++ b/Core/BuildingBlock.Core.Domain/BaseEntity.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Reflection; @@ -91,7 +92,7 @@ private void DeleteProperty(PropertyInfo propertyInfo, DateTime? deletedAt, stri foreach (var value in propertyValues) { - object?[] parameters = { deletedAt, deletedBy }; + object?[] parameters = [deletedAt, deletedBy]; deleteMethod.Invoke(value, parameters); } } @@ -118,7 +119,7 @@ private static bool IsAGenericList(Type type) public abstract class AggregateRoot : Entity, IAggregateRoot { - public List DomainEvents { get; } = new(); + public List DomainEvents { get; } = []; public void AddDomainEvent(IDomainEvent domainEvent) { diff --git a/Core/BuildingBlock.Core.Domain/Exceptions/EntityNotFoundException.cs b/Core/BuildingBlock.Core.Domain/Exceptions/EntityNotFoundException.cs index f88ed6f..82bfa19 100644 --- a/Core/BuildingBlock.Core.Domain/Exceptions/EntityNotFoundException.cs +++ b/Core/BuildingBlock.Core.Domain/Exceptions/EntityNotFoundException.cs @@ -17,7 +17,7 @@ protected EntityNotFoundException(string entity, string id) : base( { } - public EntityNotFoundException(string message) : base(message) + protected EntityNotFoundException(string message) : base(message) { } } \ No newline at end of file diff --git a/Core/BuildingBlock.Core.Domain/Rules/Abstractions/IBusinessRule.cs b/Core/BuildingBlock.Core.Domain/Rules/Abstractions/IBusinessRule.cs index 410ce18..da849c3 100644 --- a/Core/BuildingBlock.Core.Domain/Rules/Abstractions/IBusinessRule.cs +++ b/Core/BuildingBlock.Core.Domain/Rules/Abstractions/IBusinessRule.cs @@ -2,6 +2,6 @@ namespace BuildingBlock.Core.Domain.Rules.Abstractions; public interface IBusinessRule { - string Message { get; } + string? Message { get; } bool IsBroken(); } \ No newline at end of file diff --git a/Core/BuildingBlock.Core.Domain/Rules/Implementations/EmailAddressMustFollowPattern.cs b/Core/BuildingBlock.Core.Domain/Rules/Implementations/EmailAddressMustFollowPattern.cs index 2bc736f..9f7ae21 100644 --- a/Core/BuildingBlock.Core.Domain/Rules/Implementations/EmailAddressMustFollowPattern.cs +++ b/Core/BuildingBlock.Core.Domain/Rules/Implementations/EmailAddressMustFollowPattern.cs @@ -12,6 +12,8 @@ public EmailAddressMustFollowPattern(string email) _email = email; } + public string? Message { get; private set; } + public bool IsBroken() { try @@ -21,9 +23,13 @@ public bool IsBroken() } catch (FormatException) { + Message = $"Email address: '{_email}' is not valid."; + return true; + } + catch (ArgumentException) + { + Message = $"Email address: '{_email}' can not be empty."; return true; } } - - public string Message => $"Email address: '{_email}' is not valid."; } \ No newline at end of file diff --git a/Core/BuildingBlock.Core.Domain/Rules/Implementations/StringCanNotBeEmptyOrWhiteSpaceRule.cs b/Core/BuildingBlock.Core.Domain/Rules/Implementations/StringCanNotBeEmptyOrWhiteSpaceRule.cs index 9e65a39..26110c5 100644 --- a/Core/BuildingBlock.Core.Domain/Rules/Implementations/StringCanNotBeEmptyOrWhiteSpaceRule.cs +++ b/Core/BuildingBlock.Core.Domain/Rules/Implementations/StringCanNotBeEmptyOrWhiteSpaceRule.cs @@ -7,16 +7,20 @@ public class StringCanNotBeEmptyOrWhiteSpaceRule : IBusinessRule private readonly string _name; private readonly string? _value; - protected StringCanNotBeEmptyOrWhiteSpaceRule(string? value, string name) + + public StringCanNotBeEmptyOrWhiteSpaceRule(string? value, string name) { _value = value; _name = name; } + public string? Message { get; private set; } + public bool IsBroken() { - return string.IsNullOrEmpty(_value) || string.IsNullOrWhiteSpace(_value); - } + if (!string.IsNullOrEmpty(_value) && !string.IsNullOrWhiteSpace(_value)) return false; - public string Message => $"{_name} can not be empty or contain only white spaces."; + Message = $"{_name} with value: {_value} can not be empty or contain only white spaces."; + return true; + } } \ No newline at end of file diff --git a/Core/BuildingBlock.Core.Domain/Shared/Utils/ConfigurationUtilities.cs b/Core/BuildingBlock.Core.Domain/Shared/Utils/ConfigurationUtilities.cs index 9942293..6da110f 100644 --- a/Core/BuildingBlock.Core.Domain/Shared/Utils/ConfigurationUtilities.cs +++ b/Core/BuildingBlock.Core.Domain/Shared/Utils/ConfigurationUtilities.cs @@ -25,21 +25,6 @@ public static T BindAndGetConfig(this IConfiguration configuration, string se if (config == null) throw new Exception($"{sectionName} configuration is not provided."); - CheckForNullProperties(config, sectionName); - return config; } - - private static void CheckForNullProperties(object obj, string sectionName) - { - var properties = obj.GetType().GetProperties(); - - foreach (var property in properties) - { - var value = property.GetValue(obj, null); - - if (value == null) - throw new Exception($"Property '{property.Name}' in section '{sectionName}' is missing."); - } - } } \ No newline at end of file diff --git a/Core/BuildingBlock.Core.Domain/Shared/Utils/Optional.cs b/Core/BuildingBlock.Core.Domain/Shared/Utils/Optional.cs index 79d9756..a4d7583 100644 --- a/Core/BuildingBlock.Core.Domain/Shared/Utils/Optional.cs +++ b/Core/BuildingBlock.Core.Domain/Shared/Utils/Optional.cs @@ -31,30 +31,26 @@ public Optional ThrowIfNotExist(Exception exception) public T Get() { - CheckIfInstanceIsNotNull(); + CheckIfExist(); return _instance!; } - public Optional ThrowIfNotEqual(T value, Exception exception) + public Optional ThrowIfNotEqual(T? value, Exception exception) { - CheckIfInstanceIsNotNull(); + if (Equals(_instance, value)) return this; - if (!_instance!.Equals(value)) throw exception; - - return this; + throw exception; } - public Optional ThrowIfEqual(T value, Exception exception) + public Optional ThrowIfEqual(T? value, Exception exception) { - CheckIfInstanceIsNotNull(); + if (!Equals(_instance, value)) return this; - if (_instance!.Equals(value)) throw exception; - - return this; + throw exception; } - private void CheckIfInstanceIsNotNull() + private void CheckIfExist() { - if (_instance == null) throw new InvalidOperationException("No value present"); + ThrowIfNotExist(new InvalidOperationException("Instance is null.")); } } \ No newline at end of file diff --git a/Core/BuildingBlock.Core.Domain/ValueObjects/Abstractions/ValueObject.cs b/Core/BuildingBlock.Core.Domain/ValueObjects/Abstractions/ValueObject.cs index 733781a..318f095 100644 --- a/Core/BuildingBlock.Core.Domain/ValueObjects/Abstractions/ValueObject.cs +++ b/Core/BuildingBlock.Core.Domain/ValueObjects/Abstractions/ValueObject.cs @@ -12,16 +12,6 @@ public bool Equals(ValueObject? other) protected abstract IEnumerable GetValues(); - public override bool Equals(object? obj) - { - return obj is ValueObject other && EqualsTo(other); - } - - public override int GetHashCode() - { - return GetValues().Aggregate(default(int), HashCode.Combine); - } - private bool EqualsTo(ValueObject other) { return GetValues().SequenceEqual(other.GetValues()); @@ -29,6 +19,6 @@ private bool EqualsTo(ValueObject other) public static void CheckRule(IBusinessRule rule) { - if (rule.IsBroken()) throw new ValidationException(rule.Message); + if (rule.IsBroken()) throw new ValidationException(rule.Message!); } } \ No newline at end of file diff --git a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/FilterExtension.cs b/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/FilterExtension.cs index 04d96c6..6d1ef40 100644 --- a/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/FilterExtension.cs +++ b/Infrastructure/BuildingBlock.Infrastructure.EntityFrameworkCore/Extensions/FilterExtension.cs @@ -13,7 +13,7 @@ public static class FilterExtension public static void SetSoftDeleteFilter(this ModelBuilder modelBuilder, Type entityType) { SetSoftDeleteFilterMethod.MakeGenericMethod(entityType) - .Invoke(null, new object[] { modelBuilder }); + .Invoke(null, [modelBuilder]); } public static void SetSoftDeleteFilter(this ModelBuilder modelBuilder) diff --git a/Infrastructure/BuildingBlock.Infrastructure.RabbitMQ/EventBusRabbitMQ.cs b/Infrastructure/BuildingBlock.Infrastructure.RabbitMQ/EventBusRabbitMQ.cs index e3df890..778fba8 100644 --- a/Infrastructure/BuildingBlock.Infrastructure.RabbitMQ/EventBusRabbitMQ.cs +++ b/Infrastructure/BuildingBlock.Infrastructure.RabbitMQ/EventBusRabbitMQ.cs @@ -209,7 +209,7 @@ private async Task ProcessEvent(string eventName, string message) await Task.Yield(); - await (Task)concreteType.GetMethod("HandleAsync")?.Invoke(handler, new[] { integrationEvent })!; + await (Task)concreteType.GetMethod("HandleAsync")?.Invoke(handler, [integrationEvent])!; } } else diff --git a/Tests/Core/Domain/BaseEntityTest.cs b/Tests/Core/Domain/BaseEntityTest.cs new file mode 100644 index 0000000..c66674b --- /dev/null +++ b/Tests/Core/Domain/BaseEntityTest.cs @@ -0,0 +1,130 @@ +using BuildingBlock.Core.Domain; +using FluentAssertions; + +namespace Tests.Core.Domain; + +public class EntityTest +{ + public class TestEntity : Entity + { + public TestEntity() + { + UpdatedAt = DateTime.Now; + UpdatedBy = "Test"; + } + + public List TestEntities { get; set; } = []; + public List TestInts { get; set; } = [1, 2, 3]; + public List TestStrings { get; set; } = []; + public TestEntity ChildEntity { get; set; } = null!; + } + + public class ResetUpdatedTimeStamp + { + public class ShouldResetUpdatedTimeStamp + { + [Fact] + public void WhenInvoke() + { + // Arrange + var entity = new TestEntity(); + + // Act + entity.ResetUpdatedTimeStamp(); + + // Assert + entity.UpdatedAt.Should().BeNull(); + entity.UpdatedBy.Should().BeNull(); + } + } + } +} + +public class AggregateRootTest +{ + public class AddDomainEvent + { + public class ShouldAddDomainEvent + { + [Fact] + public void WhenInvoke() + { + // Arrange + var aggregateRoot = new TestAggregateRoot(); + var domainEvent = new TestDomainEvent(); + + // Act + aggregateRoot.AddDomainEvent(domainEvent); + + // Assert + aggregateRoot.DomainEvents.Should().Contain(domainEvent); + aggregateRoot.DomainEvents.Should().HaveCount(1); + } + } + } + + public class RemoveDomainEvent + { + public class ShouldRemoveDomainEvent + { + [Fact] + public void WhenThatDomainEventIsExist() + { + // Arrange + var aggregateRoot = new TestAggregateRoot(); + var domainEvent = new TestDomainEvent(); + aggregateRoot.AddDomainEvent(domainEvent); + + // Act + aggregateRoot.RemoveDomainEvent(domainEvent); + + // Assert + aggregateRoot.DomainEvents.Should().NotContain(domainEvent); + aggregateRoot.DomainEvents.Should().HaveCount(0); + } + } + + public class ShouldDoNothing + { + [Fact] + public void WhenThatDomainEventIsNotExist() + { + // Arrange + var aggregateRoot = new TestAggregateRoot(); + var domainEvent = new TestDomainEvent(); + + // Act + aggregateRoot.RemoveDomainEvent(domainEvent); + + // Assert + aggregateRoot.DomainEvents.Should().NotContain(domainEvent); + aggregateRoot.DomainEvents.Should().HaveCount(0); + } + } + } + + public class ClearDomainEvent + { + public class ShouldRemoveAllDomainEvents + { + [Fact] + public void WhenInvoke() + { + // Arrange + var aggregateRoot = new TestAggregateRoot(); + var domainEvent = new TestDomainEvent(); + aggregateRoot.AddDomainEvent(domainEvent); + aggregateRoot.AddDomainEvent(domainEvent); + aggregateRoot.AddDomainEvent(domainEvent); + aggregateRoot.AddDomainEvent(domainEvent); + + // Act + aggregateRoot.ClearDomainEvents(); + + // Assert + aggregateRoot.DomainEvents.Should().NotContain(domainEvent); + aggregateRoot.DomainEvents.Should().HaveCount(0); + } + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Constants/CacheKeyRegistryTest.cs b/Tests/Core/Domain/Constants/CacheKeyRegistryTest.cs new file mode 100644 index 0000000..8a51b0a --- /dev/null +++ b/Tests/Core/Domain/Constants/CacheKeyRegistryTest.cs @@ -0,0 +1,51 @@ +using BuildingBlock.Core.Domain.Shared.Constants; + +namespace Tests.Core.Domain.Constants; + +public class CacheKeyRegistryTest +{ + public class ShouldSetValueToUpperCase + { + [Fact] + public void CaseGetEmailConfirmationByUserIdKey() + { + // Arrange + const string value = "value"; + var recordKey = CacheKeyRegistry.GetEmailConfirmationByUserIdKey(value); + + // Act + var result = recordKey.Value; + + // Assert + Assert.Equal($"email-{value}".ToUpper(), result); + } + + [Fact] + public void CaseGetRolesByUserIdKey() + { + // Arrange + var value = "value"; + var recordKey = CacheKeyRegistry.GetRolesByUserIdKey(value); + + // Act + var result = recordKey.Value; + + // Assert + Assert.Equal($"role-{value}".ToUpper(), result); + } + + [Fact] + public void CaseGetPermissionsByRoleNameKey() + { + // Arrange + var value = "value"; + var recordKey = CacheKeyRegistry.GetPermissionsByRoleNameKey(value); + + // Act + var result = recordKey.Value; + + // Assert + Assert.Equal($"permission-{value}".ToUpper(), result); + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Rules/EmailAddressMustFollowPatternTest.cs b/Tests/Core/Domain/Rules/EmailAddressMustFollowPatternTest.cs new file mode 100644 index 0000000..b4a0797 --- /dev/null +++ b/Tests/Core/Domain/Rules/EmailAddressMustFollowPatternTest.cs @@ -0,0 +1,77 @@ +using BuildingBlock.Core.Domain.Rules.Implementations; +using FluentAssertions; + +namespace Tests.Core.Domain.Rules; + +public class EmailAddressMustFollowPatternTest +{ + public class ShouldReturnTrue + { + [Fact] + public void WhenEmailIsEmpty() + { + // Arrange + const string email = ""; + var emailAddressMustFollowPattern = new EmailAddressMustFollowPattern(email); + + // Act + var result = emailAddressMustFollowPattern.IsBroken(); + + // Assert + result.Should().BeTrue(); + emailAddressMustFollowPattern.Message.Should().Be($"Email address: '{email}' can not be empty."); + } + + public class WhenEmailIsInvalid + { + [Fact] + public void Case1() + { + // Arrange + const string email = "invalid"; + var emailAddressMustFollowPattern = new EmailAddressMustFollowPattern(email); + + // Act + var result = emailAddressMustFollowPattern.IsBroken(); + + // Assert + result.Should().BeTrue(); + emailAddressMustFollowPattern.Message.Should().Be($"Email address: '{email}' is not valid."); + } + } + } + + public class ShouldReturnFalse + { + public class WhenEmailIsValid + { + [Fact] + public void Case1() + { + // Arrange + var emailAddressMustFollowPattern = new EmailAddressMustFollowPattern("invalid@123"); + + // Act + var result = emailAddressMustFollowPattern.IsBroken(); + + // Assert + result.Should().BeFalse(); + emailAddressMustFollowPattern.Message.Should().BeNull(); + } + + [Fact] + public void Case2() + { + // Arrange + var emailAddressMustFollowPattern = new EmailAddressMustFollowPattern("invalid@abc.def"); + + // Act + var result = emailAddressMustFollowPattern.IsBroken(); + + // Assert + result.Should().BeFalse(); + emailAddressMustFollowPattern.Message.Should().BeNull(); + } + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Rules/StringCanNotBeEmptyOrWhiteSpaceRuleTest.cs b/Tests/Core/Domain/Rules/StringCanNotBeEmptyOrWhiteSpaceRuleTest.cs new file mode 100644 index 0000000..d98be4e --- /dev/null +++ b/Tests/Core/Domain/Rules/StringCanNotBeEmptyOrWhiteSpaceRuleTest.cs @@ -0,0 +1,78 @@ +using BuildingBlock.Core.Domain.Rules.Implementations; +using FluentAssertions; + +namespace Tests.Core.Domain.Rules; + +public class StringCanNotBeEmptyOrWhiteSpaceRuleTest +{ + private const string Name = "String"; + + public class ShouldReturnTrue + { + [Fact] + public void WhenStringIsEmpty() + { + // Arrange + var value = string.Empty; + var stringCanNotBeEmptyOrWhiteSpaceRule = new StringCanNotBeEmptyOrWhiteSpaceRule(value, Name); + + // Act + var result = stringCanNotBeEmptyOrWhiteSpaceRule.IsBroken(); + + // Assert + result.Should().BeTrue(); + stringCanNotBeEmptyOrWhiteSpaceRule.Message.Should() + .Be($"{Name} with value: {value} can not be empty or contain only white spaces."); + } + + [Fact] + public void WhenStringIsNull() + { + // Arrange + string? value = null; + var stringCanNotBeEmptyOrWhiteSpaceRule = new StringCanNotBeEmptyOrWhiteSpaceRule(value, Name); + + // Act + var result = stringCanNotBeEmptyOrWhiteSpaceRule.IsBroken(); + + // Assert + result.Should().BeTrue(); + stringCanNotBeEmptyOrWhiteSpaceRule.Message.Should() + .Be($"{Name} with value: {value} can not be empty or contain only white spaces."); + } + + [Fact] + public void WhenStringContainsOnlyWhiteSpaces() + { + // Arrange + var value = " "; + var stringCanNotBeEmptyOrWhiteSpaceRule = new StringCanNotBeEmptyOrWhiteSpaceRule(value, Name); + + // Act + var result = stringCanNotBeEmptyOrWhiteSpaceRule.IsBroken(); + + // Assert + result.Should().BeTrue(); + stringCanNotBeEmptyOrWhiteSpaceRule.Message.Should() + .Be($"{Name} with value: {value} can not be empty or contain only white spaces."); + } + } + + public class ShouldReturnFalse + { + [Fact] + public void WhenStringContainsCharacters() + { + // Arrange + var value = "test"; + var stringCanNotBeEmptyOrWhiteSpaceRule = new StringCanNotBeEmptyOrWhiteSpaceRule(value, Name); + + // Act + var result = stringCanNotBeEmptyOrWhiteSpaceRule.IsBroken(); + + // Assert + result.Should().BeFalse(); + stringCanNotBeEmptyOrWhiteSpaceRule.Message.Should().BeNull(); + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Shared/Utils/ConfigurationUtilitiesTest.cs b/Tests/Core/Domain/Shared/Utils/ConfigurationUtilitiesTest.cs new file mode 100644 index 0000000..06c9622 --- /dev/null +++ b/Tests/Core/Domain/Shared/Utils/ConfigurationUtilitiesTest.cs @@ -0,0 +1,191 @@ +using BuildingBlock.Core.Domain.Shared.Utils; +using FluentAssertions; +using Microsoft.Extensions.Configuration; + +namespace Tests.Core.Domain.Shared.Utils; + +public class ConfigurationUtilitiesTest +{ + public class GetRequiredValue + { + private const string Name = "Name"; + + public class ShouldThrowInvalidOperationException + { + [Fact] + public void WhenConfigurationValueIsEmpty() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + + // Act + var exception = Assert.Throws(() => configuration.GetRequiredValue(Name)); + + // Assert + Assert.Equal($"Configuration missing value for: {Name}", exception.Message); + } + } + + public class ShouldReturnValue + { + [Fact] + public void WhenConfigurationValueIsNotEmpty() + { + // Arrange + const string value = "Value"; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { { Name, value } }!).Build(); + + // Act + var result = configuration.GetRequiredValue(Name); + + // Assert + Assert.Equal(value, result); + } + } + } + + public class GetRequiredConnectionString + { + private const string Value = "Value"; + private const string Name = "Name"; + + + public class ShouldThrowInvalidOperationException + { + [Fact] + public void WhenConfigurationDontHaveConnectionStringsSection() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + + // Act + var exception = + Assert.Throws(() => configuration.GetRequiredConnectionString(Name)); + + // Assert + Assert.Equal($"Configuration missing value for: ConnectionStrings: {Name}", exception.Message); + } + + [Fact] + public void WhenNameDoesntExistInConnectionStringsSection() + { + // Arrange + var inMemorySettings = new Dictionary + { + { "ConnectionStrings:ABC", "Value" } + }; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(inMemorySettings!) + .Build(); + + // Act + var exception = + Assert.Throws(() => configuration.GetRequiredConnectionString(Name)); + + // Assert + Assert.Equal($"Configuration missing value for: ConnectionStrings: {Name}", exception.Message); + } + } + + public class ShouldReturnValue + { + [Fact] + public void WhenNameExistInConnectionStringsSection() + { + // Arrange + var inMemorySettings = new Dictionary + { + { "ConnectionStrings:Name", "Value" } + }; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(inMemorySettings!) + .Build(); + + // Act + var result = configuration.GetRequiredConnectionString(Name); + + // Assert + Assert.Equal(Value, result); + } + } + } + + public class BindAndGetConfig + { + private const string SectionName = "Jwt"; + + private class JwtConfiguration + { + public string Audience { get; set; } = null!; + + public string Issuer { get; set; } = null!; + + public string AccessTokenSecurityKey { get; set; } = null!; + + public int AccessTokenLifeTimeInMinute { get; set; } + + public string RefreshTokenSecurityKey { get; set; } = null!; + + public int RefreshTokenLifeTimeInMinute { get; set; } + } + + public class ShouldReturnException + { + [Fact] + public void WhenSectionIsNotProvided() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + + // Act + Action act = () => configuration.BindAndGetConfig(SectionName); + + // Assert + act.Should().Throw().WithMessage($"{SectionName} configuration is not provided."); + } + } + + public class ShouldReturnConfiguration + { + [Fact] + public void WhenSectionIsProvided() + { + var jwtConfiguration = new JwtConfiguration + { + Audience = "Audience", + Issuer = "Issuer", + AccessTokenSecurityKey = "AccessTokenSecurityKey", + AccessTokenLifeTimeInMinute = 1, + RefreshTokenSecurityKey = "RefreshTokenSecurityKey", + RefreshTokenLifeTimeInMinute = 1 + }; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { $"{SectionName}:Audience", jwtConfiguration.Audience }, + { $"{SectionName}:Issuer", jwtConfiguration.Issuer }, + { $"{SectionName}:AccessTokenSecurityKey", jwtConfiguration.AccessTokenSecurityKey }, + { + $"{SectionName}:AccessTokenLifeTimeInMinute", + jwtConfiguration.AccessTokenLifeTimeInMinute.ToString() + }, + { $"{SectionName}:RefreshTokenSecurityKey", jwtConfiguration.RefreshTokenSecurityKey }, + { + $"{SectionName}:RefreshTokenLifeTimeInMinute", + jwtConfiguration.RefreshTokenLifeTimeInMinute.ToString() + } + }!).Build(); + + // Act + var result = configuration.BindAndGetConfig(SectionName); + + // Assert + result.Should().BeEquivalentTo(jwtConfiguration); + } + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Shared/Utils/EntityHelperTest.cs b/Tests/Core/Domain/Shared/Utils/EntityHelperTest.cs new file mode 100644 index 0000000..3ed03bc --- /dev/null +++ b/Tests/Core/Domain/Shared/Utils/EntityHelperTest.cs @@ -0,0 +1,313 @@ +using BuildingBlock.Core.Domain; +using BuildingBlock.Core.Domain.Exceptions; +using BuildingBlock.Core.Domain.Repositories; +using BuildingBlock.Core.Domain.Shared.Utils; +using BuildingBlock.Core.Domain.Specifications.Abstractions; +using FluentAssertions; +using Moq; + +namespace Tests.Core.Domain.Shared.Utils; + +public class EntityHelperTest +{ + public class TestEntityNotFoundException : EntityNotFoundException + { + public TestEntityNotFoundException(string message) : base(message) + { + } + } + + public class TestEntityConflictException : EntityConflictException + { + public TestEntityConflictException(string message) : base(message) + { + } + } + + public class GetOrThrowAsync + { + public class ShouldThrowException + { + public class WhenEntityIsNull + { + private readonly Mock> _mockEntityRepositoryMock; + + public WhenEntityIsNull() + { + _mockEntityRepositoryMock = new Mock>(); + } + + [Fact] + public void CaseSpecification() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.GetAnyAsync(It.IsAny>(), null, false, false)) + .ReturnsAsync((TestEntity?)null); + const string exceptionMessage = "Entity not found."; + + // Act + Func act = async () => await EntityHelper.GetOrThrowAsync(null, + new TestEntityNotFoundException(exceptionMessage), _mockEntityRepositoryMock.Object); + + // Assert + act.Should().ThrowAsync().WithMessage(exceptionMessage); + } + + [Fact] + public void CaseId() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.GetAnyAsync(It.IsAny>(), null, false, false)) + .ReturnsAsync((TestEntity?)null); + const string exceptionMessage = "Entity not found."; + + // Act + Func act = async () => await EntityHelper.GetOrThrowAsync(Guid.NewGuid(), + new TestEntityNotFoundException(exceptionMessage), _mockEntityRepositoryMock.Object); + + // Assert + act.Should().ThrowAsync().WithMessage(exceptionMessage); + } + } + + public class ShouldReturnValue + { + public class WhenEntityIsNotNull + { + private readonly Mock> _mockEntityRepositoryMock; + + public WhenEntityIsNotNull() + { + _mockEntityRepositoryMock = new Mock>(); + } + + [Fact] + public async Task CaseSpecification() + { + // Arrange + var entity = new TestEntity(); + _mockEntityRepositoryMock.Setup(repo => + repo.GetAnyAsync(It.IsAny>(), null, false, false)) + .ReturnsAsync(entity); + + // Act + var result = await EntityHelper.GetOrThrowAsync(null, + new TestEntityNotFoundException("Entity not found."), _mockEntityRepositoryMock.Object); + + // Assert + result.Should().Be(entity); + } + + [Fact] + public async Task CaseId() + { + // Arrange + var entity = new TestEntity(); + _mockEntityRepositoryMock.Setup(repo => + repo.GetAnyAsync(It.IsAny>(), null, false, false)) + .ReturnsAsync(entity); + + // Act + var result = await EntityHelper.GetOrThrowAsync(Guid.NewGuid(), + new TestEntityNotFoundException("Entity not found."), _mockEntityRepositoryMock.Object); + + // Assert + result.Should().Be(entity); + } + } + } + } + } + + public class ThrowIfNotExistAsync + { + public class ShouldThrowException + { + public class WhenEntityIsNotExist + { + private readonly Mock> _mockEntityRepositoryMock; + + public WhenEntityIsNotExist() + { + _mockEntityRepositoryMock = new Mock>(); + } + + [Fact] + public void CaseSpecification() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.CheckIfExistAsync(It.IsAny>(), false)) + .ReturnsAsync(false); + const string exceptionMessage = "Entity not found."; + + // Act + var act = async () => await EntityHelper.ThrowIfNotExistAsync(null, + new TestEntityNotFoundException(exceptionMessage), _mockEntityRepositoryMock.Object); + + // Assert + act.Should().ThrowAsync().WithMessage(exceptionMessage); + } + + [Fact] + public void CaseId() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.CheckIfExistAsync(It.IsAny>(), false)) + .ReturnsAsync(false); + const string exceptionMessage = "Entity not found."; + + // Act + var act = async () => await EntityHelper.ThrowIfNotExistAsync(Guid.NewGuid(), + new TestEntityNotFoundException(exceptionMessage), _mockEntityRepositoryMock.Object); + + // Assert + act.Should().ThrowAsync().WithMessage(exceptionMessage); + } + } + } + + public class ShouldNotThrowException + { + public class WhenEntityIsExist + { + private readonly Mock> _mockEntityRepositoryMock; + + public WhenEntityIsExist() + { + _mockEntityRepositoryMock = new Mock>(); + } + + [Fact] + public async Task CaseSpecification() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.CheckIfExistAsync(It.IsAny>(), false)) + .ReturnsAsync(true); + + // Act + var act = async () => await EntityHelper.ThrowIfNotExistAsync(null, + new TestEntityNotFoundException("Entity not found."), _mockEntityRepositoryMock.Object); + + // Assert + await act.Should().NotThrowAsync(); + } + + [Fact] + public async Task CaseId() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.CheckIfExistAsync(It.IsAny>(), false)) + .ReturnsAsync(true); + + // Act + var act = async () => await EntityHelper.ThrowIfNotExistAsync(Guid.NewGuid(), + new TestEntityNotFoundException("Entity not found."), _mockEntityRepositoryMock.Object); + + // Assert + await act.Should().NotThrowAsync(); + } + } + } + } + + public class ThrowIfExistAsync + { + public class ShouldThrowException + { + public class WhenEntityIsExist + { + private readonly Mock> _mockEntityRepositoryMock; + + public WhenEntityIsExist() + { + _mockEntityRepositoryMock = new Mock>(); + } + + [Fact] + public void CaseSpecification() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.CheckIfExistAsync(It.IsAny>(), false)) + .ReturnsAsync(true); + const string exceptionMessage = "Entity already exist."; + + // Act + var act = async () => await EntityHelper.ThrowIfExistAsync(null, + new TestEntityConflictException(exceptionMessage), _mockEntityRepositoryMock.Object); + + // Assert + act.Should().ThrowAsync().WithMessage(exceptionMessage); + } + + [Fact] + public void CaseId() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.CheckIfExistAsync(It.IsAny>(), false)) + .ReturnsAsync(true); + const string exceptionMessage = "Entity already exist."; + + // Act + var act = async () => await EntityHelper.ThrowIfExistAsync(Guid.NewGuid(), + new TestEntityConflictException(exceptionMessage), _mockEntityRepositoryMock.Object); + + // Assert + act.Should().ThrowAsync().WithMessage(exceptionMessage); + } + } + } + + public class ShouldNotThrowExeption + { + public class WhenEntityIsNotExist + { + private readonly Mock> _mockEntityRepositoryMock; + + public WhenEntityIsNotExist() + { + _mockEntityRepositoryMock = new Mock>(); + } + + [Fact] + public async Task CaseSpecification() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.CheckIfExistAsync(It.IsAny>(), false)) + .ReturnsAsync(false); + + // Act + var act = async () => await EntityHelper.ThrowIfExistAsync(null, + new TestEntityConflictException("Entity already exist."), _mockEntityRepositoryMock.Object); + + // Assert + await act.Should().NotThrowAsync(); + } + + [Fact] + public async Task CaseId() + { + // Arrange + _mockEntityRepositoryMock.Setup(repo => + repo.CheckIfExistAsync(It.IsAny>(), false)) + .ReturnsAsync(false); + + // Act + var act = async () => await EntityHelper.ThrowIfExistAsync(Guid.NewGuid(), + new TestEntityConflictException("Entity already exist."), _mockEntityRepositoryMock.Object); + + // Assert + await act.Should().NotThrowAsync(); + } + } + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Shared/Utils/OptionalTest.cs b/Tests/Core/Domain/Shared/Utils/OptionalTest.cs new file mode 100644 index 0000000..6f0bd01 --- /dev/null +++ b/Tests/Core/Domain/Shared/Utils/OptionalTest.cs @@ -0,0 +1,475 @@ +using BuildingBlock.Core.Domain.Shared.Utils; +using FluentAssertions; + +namespace Tests.Core.Domain.Shared.Utils; + +public class OptionalTest +{ + public class ThrowIfExist + { + public class ShouldThrowException + { + [Fact] + public void WhenInstanceIsNotNull() + { + // Arrange + var optional = Optional.Of(new object()); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfExist(new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void WhenInstanceIsTrue() + { + // Arrange + var optional = Optional.Of(true); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfExist(new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + } + + public class ShouldReturnInstance + { + [Fact] + public void WhenInstanceIsNull() + { + // Arrange + var optional = Optional.Of(null); + + // Act + var result = optional.ThrowIfExist(new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void WhenInstanceIsFalse() + { + // Arrange + var optional = Optional.Of(false); + + // Act + var result = optional.ThrowIfExist(new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + } + } + + public class ThrowIfNotExist + { + public class ShouldThrowException + { + [Fact] + public void WhenInstanceIsNull() + { + // Arrange + var optional = Optional.Of(null); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfNotExist(new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void WhenInstanceIsFalse() + { + // Arrange + var optional = Optional.Of(false); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfNotExist(new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + } + + public class ShouldReturnInstance + { + [Fact] + public void WhenInstanceIsNotNull() + { + // Arrange + var optional = Optional.Of(new object()); + + // Act + var result = optional.ThrowIfNotExist(new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void WhenInstanceIsTrue() + { + // Arrange + var optional = Optional.Of(true); + + // Act + var result = optional.ThrowIfNotExist(new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + } + } + + public class ThrowIfEqual + { + public class ShouldThrowException + { + public class WhenInstanceIsEqual + { + [Fact] + public void InstanceIsBoolean() + { + // Arrange + var optional = Optional.Of(true); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfEqual(true, new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void InstanceIsNumber() + { + // Arrange + var optional = Optional.Of(1); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfEqual(1, new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void InstanceIsString() + { + // Arrange + var optional = Optional.Of("test"); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfEqual("test", new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void InstanceIsObject() + { + // Arrange + var optional = Optional.Of(new { Name = "test", Description = "description" }); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfEqual(new { Name = "test", Description = "description" }, + new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void InstanceIsObjectWithNull() + { + // Arrange + var optional = Optional.Of(null); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfEqual(null, new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + } + + public class ShouldReturnInstance + { + public class WhenInstanceIsNotEqual + { + [Fact] + public void InstanceIsBoolean() + { + // Arrange + var optional = Optional.Of(true); + + // Act + var result = optional.ThrowIfEqual(false, new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void InstanceIsNumber() + { + // Arrange + var optional = Optional.Of(1); + + // Act + var result = optional.ThrowIfEqual(2, new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void InstanceIsString() + { + // Arrange + var optional = Optional.Of("test"); + + // Act + var result = optional.ThrowIfEqual("test1", new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void InstanceIsObject() + { + // Arrange + var optional = Optional.Of(new { Name = "test", Description = "description" }); + + // Act + var result = optional.ThrowIfEqual(new { Name = "test1", Description = "description" }, + new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void InstanceIsObjectWithNull() + { + // Arrange + var optional = Optional.Of(null); + + // Act + var result = optional.ThrowIfEqual(new { Name = "test1", Description = "description" }, + new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + } + } + } + } + + public class ThrowIfNotEqual + { + public class ShouldThrowException + { + public class WhenInstanceIsNotEqual + { + [Fact] + public void InstanceIsBoolean() + { + // Arrange + var optional = Optional.Of(true); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfNotEqual(false, new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void InstanceIsNumber() + { + // Arrange + var optional = Optional.Of(1); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfNotEqual(2, new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void InstanceIsString() + { + // Arrange + var optional = Optional.Of("test"); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfNotEqual("test1", new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void InstanceIsObject() + { + // Arrange + var optional = Optional.Of(new { Name = "test", Description = "description" }); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfNotEqual(new { Name = "test1", Description = "description" }, + new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + + [Fact] + public void InstanceIsObjectWithNull() + { + // Arrange + var optional = Optional.Of(null); + const string exceptionMessage = "123"; + + // Act + Action act = () => optional.ThrowIfNotEqual(new { Name = "test1", Description = "description" }, + new Exception(exceptionMessage)); + + // Assert + act.Should().Throw().WithMessage(exceptionMessage); + } + } + } + + public class ShouldReturnInstance + { + public class WhenInstanceIsEqual + { + [Fact] + public void InstanceIsBoolean() + { + // Arrange + var optional = Optional.Of(true); + + // Act + var result = optional.ThrowIfNotEqual(true, new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void InstanceIsNumber() + { + // Arrange + var optional = Optional.Of(1); + + // Act + var result = optional.ThrowIfNotEqual(1, new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void InstanceIsString() + { + // Arrange + var optional = Optional.Of("test"); + + // Act + var result = optional.ThrowIfNotEqual("test", new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void InstanceIsObject() + { + // Arrange + var optional = Optional.Of(new { Name = "test", Description = "description" }); + + // Act + var result = optional.ThrowIfNotEqual(new { Name = "test", Description = "description" }, + new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + + [Fact] + public void InstanceIsObjectWithNull() + { + // Arrange + var optional = Optional.Of(null); + + // Act + var result = optional.ThrowIfNotEqual(null, new Exception()); + + // Assert + result.Should().BeSameAs(optional); + } + } + } + } + + public class Get + { + public class ShouldThrowException + { + [Fact] + public void WhenInstanceIsNull() + { + // Arrange + var optional = Optional.Of(null); + + // Act + Action act = () => optional.Get(); + + // Assert + act.Should().Throw().WithMessage("Instance is null."); + } + } + + public class ShouldReturnValue + { + [Fact] + public void WhenInstanceIsNotNull() + { + // Arrange + var value = new object(); + var optional = Optional.Of(value); + + // Act + var result = optional.Get(); + + // Assert + result.Should().BeSameAs(value); + } + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Specifications/Abstractions/AndSpecificationTest.cs b/Tests/Core/Domain/Specifications/Abstractions/AndSpecificationTest.cs new file mode 100644 index 0000000..4e82bb1 --- /dev/null +++ b/Tests/Core/Domain/Specifications/Abstractions/AndSpecificationTest.cs @@ -0,0 +1,43 @@ +using BuildingBlock.Core.Domain.Specifications.Abstractions; +using FluentAssertions; + +namespace Tests.Core.Domain.Specifications.Abstractions; + +public class AndSpecificationTest +{ + public class ShouldReturnTrue + { + [Fact] + public void WhenBothSpecificationsIsTrue() + { + // Arrange + var spec1 = new TestSpecification(true); + var spec2 = new TestSpecification(true); + var andSpec = new AndSpecification(spec1, spec2); + + // Act + var result = andSpec.ToExpression().Compile()(new TestEntity()); + + // Assert + result.Should().BeTrue(); + } + } + + public class ShouldReturnFalse + { + [Fact] + public void WhenOneSpecificationIsFalse() + { + // Arrange + var spec1 = new TestSpecification(true); + var spec2 = new TestSpecification(false); + var andSpec = new AndSpecification(spec1, spec2); + + // Act + var result = andSpec.ToExpression().Compile()(new TestEntity()); + + // Assert + result.Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Specifications/Abstractions/OrSpecificationTest.cs b/Tests/Core/Domain/Specifications/Abstractions/OrSpecificationTest.cs new file mode 100644 index 0000000..af35693 --- /dev/null +++ b/Tests/Core/Domain/Specifications/Abstractions/OrSpecificationTest.cs @@ -0,0 +1,43 @@ +using BuildingBlock.Core.Domain.Specifications.Abstractions; +using FluentAssertions; + +namespace Tests.Core.Domain.Specifications.Abstractions; + +public class OrSpecificationTest +{ + public class ShouldReturnTrue + { + [Fact] + public void WhenOneSpecificationIsTrue() + { + // Arrange + var spec1 = new TestSpecification(true); + var spec2 = new TestSpecification(false); + var orSpec = new OrSpecification(spec1, spec2); + + // Act + var result = orSpec.ToExpression().Compile()(new TestEntity()); + + // Assert + result.Should().BeTrue(); + } + } + + public class ShouldReturnFalse + { + [Fact] + public void WhenBothSpecificationsIsFalse() + { + // Arrange + var spec1 = new TestSpecification(false); + var spec2 = new TestSpecification(false); + var orSpec = new OrSpecification(spec1, spec2); + + // Act + var result = orSpec.ToExpression().Compile()(new TestEntity()); + + // Assert + result.Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Specifications/Implementations/EntityIdNotEqualSpecificationTest.cs b/Tests/Core/Domain/Specifications/Implementations/EntityIdNotEqualSpecificationTest.cs new file mode 100644 index 0000000..fc8de9f --- /dev/null +++ b/Tests/Core/Domain/Specifications/Implementations/EntityIdNotEqualSpecificationTest.cs @@ -0,0 +1,42 @@ +using BuildingBlock.Core.Domain.Specifications.Implementations; +using FluentAssertions; + +namespace Tests.Core.Domain.Specifications.Implementations; + +public class EntityIdNotEqualSpecificationTest +{ + public class ShouldReturnTrue + { + [Fact] + public void WhenEntityIdIsNotEqual() + { + // Arrange + var entity = new TestEntity { Id = Guid.NewGuid() }; + var entityIdSpecification = new EntityIdNotEqualSpecification(Guid.NewGuid()); + + // Act + var result = entityIdSpecification.ToExpression().Compile()(entity); + + // Assert + result.Should().BeTrue(); + } + } + + public class ShouldReturnFalse + { + [Fact] + public void WhenEntityIdIsEqual() + { + // Arrange + var id = Guid.NewGuid(); + var entity = new TestEntity { Id = id }; + var entityIdSpecification = new EntityIdNotEqualSpecification(id); + + // Act + var result = entityIdSpecification.ToExpression().Compile()(entity); + + // Assert + result.Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Specifications/Implementations/EntityIdSpecificationTest.cs b/Tests/Core/Domain/Specifications/Implementations/EntityIdSpecificationTest.cs new file mode 100644 index 0000000..0778cf7 --- /dev/null +++ b/Tests/Core/Domain/Specifications/Implementations/EntityIdSpecificationTest.cs @@ -0,0 +1,42 @@ +using BuildingBlock.Core.Domain.Specifications.Implementations; +using FluentAssertions; + +namespace Tests.Core.Domain.Specifications.Implementations; + +public class EntityIdSpecificationTest +{ + public class ShouldReturnTrue + { + [Fact] + public void WhenEntityIdIsEqual() + { + // Arrange + var id = Guid.NewGuid(); + var entity = new TestEntity { Id = id }; + var entityIdSpecification = new EntityIdSpecification(id); + + // Act + var result = entityIdSpecification.ToExpression().Compile()(entity); + + // Assert + result.Should().BeTrue(); + } + } + + public class ShouldReturnFalse + { + [Fact] + public void WhenEntityIdIsNotEqual() + { + // Arrange + var entity = new TestEntity { Id = Guid.NewGuid() }; + var entityIdSpecification = new EntityIdSpecification(Guid.NewGuid()); + + // Act + var result = entityIdSpecification.ToExpression().Compile()(entity); + + // Assert + result.Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/Specifications/TestSpecification.cs b/Tests/Core/Domain/Specifications/TestSpecification.cs new file mode 100644 index 0000000..eaad257 --- /dev/null +++ b/Tests/Core/Domain/Specifications/TestSpecification.cs @@ -0,0 +1,19 @@ +using System.Linq.Expressions; +using BuildingBlock.Core.Domain.Specifications.Abstractions; + +namespace Tests.Core.Domain.Specifications; + +public class TestSpecification : Specification +{ + private readonly bool _result; + + public TestSpecification(bool result) + { + _result = result; + } + + public override Expression> ToExpression() + { + return entity => _result; + } +} \ No newline at end of file diff --git a/Tests/Core/Domain/TestDomainEvent.cs b/Tests/Core/Domain/TestDomainEvent.cs new file mode 100644 index 0000000..f31ca3f --- /dev/null +++ b/Tests/Core/Domain/TestDomainEvent.cs @@ -0,0 +1,7 @@ +using BuildingBlock.Core.Domain.DomainEvents; + +namespace Tests.Core.Domain; + +public class TestDomainEvent : IDomainEvent +{ +} \ No newline at end of file diff --git a/Tests/Core/Domain/TestEntity.cs b/Tests/Core/Domain/TestEntity.cs new file mode 100644 index 0000000..2d13aaa --- /dev/null +++ b/Tests/Core/Domain/TestEntity.cs @@ -0,0 +1,11 @@ +using BuildingBlock.Core.Domain; + +namespace Tests.Core.Domain; + +public class TestEntity : Entity +{ +} + +public class TestAggregateRoot : AggregateRoot +{ +} \ No newline at end of file diff --git a/Tests/Core/Domain/ValueObjects/Abstractions/ValueObjectTest.cs b/Tests/Core/Domain/ValueObjects/Abstractions/ValueObjectTest.cs new file mode 100644 index 0000000..6ba1ddb --- /dev/null +++ b/Tests/Core/Domain/ValueObjects/Abstractions/ValueObjectTest.cs @@ -0,0 +1,89 @@ +using BuildingBlock.Core.Domain.Exceptions; +using BuildingBlock.Core.Domain.Rules.Implementations; +using BuildingBlock.Core.Domain.ValueObjects.Abstractions; +using BuildingBlock.Core.Domain.ValueObjects.Implementations; +using FluentAssertions; + +namespace Tests.Core.Domain.ValueObjects.Abstractions; + +public class ValueObjectTest +{ + public class Equals + { + public class ShouldReturnTrue + { + [Fact] + public void WhenTwoValueObjectsAreEqual() + { + // Arrange + var valueObject1 = new EmailAddress("string@123"); + var valueObject2 = new EmailAddress("string@123"); + + // Act + var actualData = valueObject1.Equals(valueObject2); + + // Assert + actualData.Should().BeTrue(); + } + } + + public class ShouldReturnFalse + { + [Fact] + public void WhenTwoValueObjectsAreNotEqual() + { + // Arrange + var valueObject1 = new EmailAddress("string@123"); + var valueObject2 = new EmailAddress("string2@123"); + + // Act + var actualData = valueObject1.Equals(valueObject2); + + // Assert + actualData.Should().BeFalse(); + } + } + } + + public class CheckRule + { + public class ShouldThrowValidationException + { + [Fact] + public void WhenRuleIsBroken() + { + // Arrange + const string name = "string"; + const string value = ""; + var rule = new StringCanNotBeEmptyOrWhiteSpaceRule(value, name); + + // Act + var action = () => ValueObject.CheckRule(rule); + + // Assert + action.Should().Throw(); + rule.Message.Should().Be($"{name} with value: {value} can not be empty or contain only white spaces."); + } + } + } + + public class ShouldDoNothing + { + public class WhenRuleIsNotBroken + { + [Fact] + public void ShouldDoNothingWhenRuleIsNotBroken() + { + // Arrange + var rule = new StringCanNotBeEmptyOrWhiteSpaceRule("string", "string"); + + // Act + var action = new Action(() => ValueObject.CheckRule(rule)); + + // Assert + action.Should().NotThrow(); + rule.Message.Should().BeNull(); + } + } + } +} \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj new file mode 100644 index 0000000..3a84762 --- /dev/null +++ b/Tests/Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + +