diff --git a/DentallApp.sln b/DentallApp.sln index 50c0742f..c2b47c32 100644 --- a/DentallApp.sln +++ b/DentallApp.sln @@ -19,9 +19,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.ChatBot", "src\Plugi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.ChatBot.IntegrationTests", "tests\ChatBot\Plugin.ChatBot.IntegrationTests.csproj", "{3897DDB9-5C4F-4664-94D7-756A5E673951}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.Twilio.WhatsApp", "src\Plugins\TwilioWhatsApp\Plugin.Twilio.WhatsApp.csproj", "{6B8BD4C1-00A7-429B-A22F-25851E7296B5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Twilio.WhatsApp", "src\Plugins\TwilioWhatsApp\Plugin.Twilio.WhatsApp.csproj", "{6B8BD4C1-00A7-429B-A22F-25851E7296B5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.SendGrid", "src\Plugins\SendGrid\Plugin.SendGrid.csproj", "{6917AD6C-E337-4DE6-88D8-B0990E9B8D56}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.SendGrid", "src\Plugins\SendGrid\Plugin.SendGrid.csproj", "{6917AD6C-E337-4DE6-88D8-B0990E9B8D56}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.IdentityDocument.Ecuador", "src\Plugins\IdentityDocumentEcuador\Plugin.IdentityDocument.Ecuador.csproj", "{390B0477-73DB-4C71-A1BE-4539E93EF55D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Elements", "Solution Elements", "{D66CD840-8EE9-4697-9FFA-B4E5B0F25D80}" ProjectSection(SolutionItems) = preProject @@ -42,14 +44,6 @@ Global {E354A181-1E6C-4F76-8D99-96E5A6824FC1}.Debug|Any CPU.Build.0 = Debug|Any CPU {E354A181-1E6C-4F76-8D99-96E5A6824FC1}.Release|Any CPU.ActiveCfg = Release|Any CPU {E354A181-1E6C-4F76-8D99-96E5A6824FC1}.Release|Any CPU.Build.0 = Release|Any CPU - {EACB8B54-AAFE-4A46-A1D9-1FB23ADDA41C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EACB8B54-AAFE-4A46-A1D9-1FB23ADDA41C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EACB8B54-AAFE-4A46-A1D9-1FB23ADDA41C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EACB8B54-AAFE-4A46-A1D9-1FB23ADDA41C}.Release|Any CPU.Build.0 = Release|Any CPU - {BFD8D9F8-AEF4-4661-B0DC-6D79F84F718E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BFD8D9F8-AEF4-4661-B0DC-6D79F84F718E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFD8D9F8-AEF4-4661-B0DC-6D79F84F718E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFD8D9F8-AEF4-4661-B0DC-6D79F84F718E}.Release|Any CPU.Build.0 = Release|Any CPU {1B8756B1-A59A-4724-B757-01AAECADF43C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B8756B1-A59A-4724-B757-01AAECADF43C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B8756B1-A59A-4724-B757-01AAECADF43C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -66,6 +60,14 @@ Global {BE6318A1-900B-4E8E-8AAE-C391F881C844}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE6318A1-900B-4E8E-8AAE-C391F881C844}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE6318A1-900B-4E8E-8AAE-C391F881C844}.Release|Any CPU.Build.0 = Release|Any CPU + {EACB8B54-AAFE-4A46-A1D9-1FB23ADDA41C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EACB8B54-AAFE-4A46-A1D9-1FB23ADDA41C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EACB8B54-AAFE-4A46-A1D9-1FB23ADDA41C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EACB8B54-AAFE-4A46-A1D9-1FB23ADDA41C}.Release|Any CPU.Build.0 = Release|Any CPU + {BFD8D9F8-AEF4-4661-B0DC-6D79F84F718E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFD8D9F8-AEF4-4661-B0DC-6D79F84F718E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFD8D9F8-AEF4-4661-B0DC-6D79F84F718E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFD8D9F8-AEF4-4661-B0DC-6D79F84F718E}.Release|Any CPU.Build.0 = Release|Any CPU {3897DDB9-5C4F-4664-94D7-756A5E673951}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3897DDB9-5C4F-4664-94D7-756A5E673951}.Debug|Any CPU.Build.0 = Debug|Any CPU {3897DDB9-5C4F-4664-94D7-756A5E673951}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -78,6 +80,10 @@ Global {6917AD6C-E337-4DE6-88D8-B0990E9B8D56}.Debug|Any CPU.Build.0 = Debug|Any CPU {6917AD6C-E337-4DE6-88D8-B0990E9B8D56}.Release|Any CPU.ActiveCfg = Release|Any CPU {6917AD6C-E337-4DE6-88D8-B0990E9B8D56}.Release|Any CPU.Build.0 = Release|Any CPU + {390B0477-73DB-4C71-A1BE-4539E93EF55D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {390B0477-73DB-4C71-A1BE-4539E93EF55D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {390B0477-73DB-4C71-A1BE-4539E93EF55D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {390B0477-73DB-4C71-A1BE-4539E93EF55D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Dockerfile b/Dockerfile index eefcb3b0..39668775 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ COPY ["src/Plugins/AppointmentReminders/*.csproj", "src/Plugins/AppointmentRemin COPY ["src/Plugins/ChatBot/*.csproj", "src/Plugins/ChatBot/"] COPY ["src/Plugins/SendGrid/*.csproj", "src/Plugins/SendGrid/"] COPY ["src/Plugins/TwilioWhatsApp/*.csproj", "src/Plugins/TwilioWhatsApp/"] +COPY ["src/Plugins/IdentityDocumentEcuador/*.csproj", "src/Plugins/IdentityDocumentEcuador/"] COPY ["src/Plugins/*.props", "src/Plugins/"] WORKDIR /app/src/Plugins/AppointmentReminders RUN dotnet restore @@ -29,6 +30,8 @@ WORKDIR /app/src/Plugins/SendGrid RUN dotnet restore WORKDIR /app/src/Plugins/TwilioWhatsApp RUN dotnet restore +WORKDIR /app/src/Plugins/IdentityDocumentEcuador +RUN dotnet restore # Copy everything else and build plugins COPY ["src/Shared/", "/app/src/Shared/"] @@ -41,6 +44,8 @@ WORKDIR /app/src/Plugins/SendGrid RUN dotnet build -c Release --no-restore WORKDIR /app/src/Plugins/TwilioWhatsApp RUN dotnet build -c Release --no-restore +WORKDIR /app/src/Plugins/IdentityDocumentEcuador +RUN dotnet build -c Release --no-restore # Copy everything else and build app COPY ["src/", "/app/src/"] diff --git a/README.md b/README.md index 6e3f4d63..cc057213 100644 --- a/README.md +++ b/README.md @@ -209,13 +209,15 @@ Request body: ### General architecture
-More details +Show diagram ![general-architecture](https://github.com/DentallApp/back-end/blob/dev/diagrams/general-architecture.png)
-#### Overview of each component +
+More details + - **Host Application.** Contains everything needed to run the application. It represents the entry point of the application. This layer performs other tasks such as: - Load plugins from a configuration file (.env) using the library called [CPlugin.Net](https://github.com/MrDave1999/CPlugin.Net). @@ -256,24 +258,33 @@ Request body: - **Twilio WhatsApp.** It is a plugin that allows to send messages by whatsapp in cases such as: - When an appointment is scheduled from the chatbot, the user is sent the appointment information to whatsapp. - When an employee needs to cancel an appointment, he/she should notify patients by whatsapp. +- **IdentityDocument.Ecuador.** It is a plugin that allows to validate identity documents registered in Ecuador. + This plugin uses an [algorithm](https://www.skypack.dev/view/udv-ec) to verify if the identity document is valid or not. + +
### Core layer
-More details +Show diagram ![core-layer](https://github.com/DentallApp/back-end/blob/dev/diagrams/core-layer.png)
+
+More details + The above diagram describes in more detail which feature modules are contained in the core layer. In the presented diagram it can be identified that the feature modules are not coupled to each other, the purpose of this is not to cause a [dependency hell](https://en.wikipedia.org/wiki/Dependency_hell), in order to maintain a dependency graph that is as simple as possible. The purpose is to make it easier to understand the parts of the backend application. +
+ ### Relational model
-More details +Show diagram ![relational-model](https://github.com/DentallApp/back-end/blob/dev/diagrams/relational-model.png) diff --git a/diagrams/general-architecture.png b/diagrams/general-architecture.png index 3ac6e20d..88e2808f 100644 Binary files a/diagrams/general-architecture.png and b/diagrams/general-architecture.png differ diff --git a/src/Core/Dependents/UseCases/Create.cs b/src/Core/Dependents/UseCases/Create.cs index e63bc38b..e44df7cb 100644 --- a/src/Core/Dependents/UseCases/Create.cs +++ b/src/Core/Dependents/UseCases/Create.cs @@ -35,9 +35,10 @@ public Dependent MapToDependent(int userId) public class CreateDependentValidator : AbstractValidator { - public CreateDependentValidator() + public CreateDependentValidator(IIdentityDocumentValidator documentValidator) { - RuleFor(request => request.Document).NotEmpty(); + RuleFor(request => request.Document) + .MustBeValidIdentityDocument(documentValidator); RuleFor(request => request.Names).NotEmpty(); RuleFor(request => request.LastNames).NotEmpty(); RuleFor(request => request.CellPhone).NotEmpty(); diff --git a/src/Core/Persons/UseCases/Create.cs b/src/Core/Persons/UseCases/Create.cs index 601e6d4a..57472acd 100644 --- a/src/Core/Persons/UseCases/Create.cs +++ b/src/Core/Persons/UseCases/Create.cs @@ -24,9 +24,10 @@ public class CreatePersonRequest public class CreatePersonValidator : AbstractValidator { - public CreatePersonValidator() + public CreatePersonValidator(IIdentityDocumentValidator documentValidator) { - RuleFor(request => request.Document).NotEmpty(); + RuleFor(request => request.Document) + .MustBeValidIdentityDocument(documentValidator); RuleFor(request => request.Names).NotEmpty(); RuleFor(request => request.LastNames).NotEmpty(); RuleFor(request => request.CellPhone).NotEmpty(); diff --git a/src/Core/Security/Employees/UseCases/Create.cs b/src/Core/Security/Employees/UseCases/Create.cs index 107e2335..1aba09bc 100644 --- a/src/Core/Security/Employees/UseCases/Create.cs +++ b/src/Core/Security/Employees/UseCases/Create.cs @@ -48,13 +48,14 @@ public Employee MapToEmployee(string password) public class CreateEmployeeValidator : AbstractValidator { - public CreateEmployeeValidator() + public CreateEmployeeValidator(IIdentityDocumentValidator documentValidator) { RuleFor(request => request.UserName) .NotEmpty() .EmailAddress(); RuleFor(request => request.Password).MustBeSecurePassword(); - RuleFor(request => request.Document).NotEmpty(); + RuleFor(request => request.Document) + .MustBeValidIdentityDocument(documentValidator); RuleFor(request => request.Names).NotEmpty(); RuleFor(request => request.LastNames).NotEmpty(); RuleFor(request => request.CellPhone).NotEmpty(); diff --git a/src/Core/Security/Employees/UseCases/UpdateAnyEmployee.cs b/src/Core/Security/Employees/UseCases/UpdateAnyEmployee.cs index 4cd12b06..8ffc4028 100644 --- a/src/Core/Security/Employees/UseCases/UpdateAnyEmployee.cs +++ b/src/Core/Security/Employees/UseCases/UpdateAnyEmployee.cs @@ -36,13 +36,14 @@ public void MapToEmployee(Employee employee) public class UpdateAnyEmployeeValidator : AbstractValidator { - public UpdateAnyEmployeeValidator() + public UpdateAnyEmployeeValidator(IIdentityDocumentValidator documentValidator) { RuleFor(request => request.Email) .NotEmpty() .EmailAddress(); RuleFor(request => request.Password).MustBeSecurePassword(); - RuleFor(request => request.Document).NotEmpty(); + RuleFor(request => request.Document) + .MustBeValidIdentityDocument(documentValidator); RuleFor(request => request.Names).NotEmpty(); RuleFor(request => request.LastNames).NotEmpty(); RuleFor(request => request.CellPhone).NotEmpty(); diff --git a/src/Core/Security/Users/UseCases/Create.cs b/src/Core/Security/Users/UseCases/Create.cs index c7528946..483d2731 100644 --- a/src/Core/Security/Users/UseCases/Create.cs +++ b/src/Core/Security/Users/UseCases/Create.cs @@ -35,13 +35,14 @@ public User MapToUser(string password) public class CreateBasicUserValidator : AbstractValidator { - public CreateBasicUserValidator() + public CreateBasicUserValidator(IIdentityDocumentValidator documentValidator) { RuleFor(request => request.UserName) .NotEmpty() .EmailAddress(); RuleFor(request => request.Password).MustBeSecurePassword(); - RuleFor(request => request.Document).NotEmpty(); + RuleFor(request => request.Document) + .MustBeValidIdentityDocument(documentValidator); RuleFor(request => request.Names).NotEmpty(); RuleFor(request => request.LastNames).NotEmpty(); RuleFor(request => request.CellPhone).NotEmpty(); diff --git a/src/HostApplication/PluginStartup.cs b/src/HostApplication/PluginStartup.cs index 5a7ed95b..f8af1fab 100644 --- a/src/HostApplication/PluginStartup.cs +++ b/src/HostApplication/PluginStartup.cs @@ -17,5 +17,6 @@ public static void ConfigureServices(WebApplicationBuilder builder) // These services are only added when no plugin registers its own implementation. builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); } } diff --git a/src/Infrastructure/Services/FakeIdentityDocument.cs b/src/Infrastructure/Services/FakeIdentityDocument.cs new file mode 100644 index 00000000..21641eef --- /dev/null +++ b/src/Infrastructure/Services/FakeIdentityDocument.cs @@ -0,0 +1,6 @@ +namespace DentallApp.Infrastructure.Services; + +public class FakeIdentityDocument : IIdentityDocumentValidator +{ + public Result IsValid(string document) => Result.Success(); +} diff --git a/src/Plugins/IdentityDocumentEcuador/DependencyServicesRegisterer.cs b/src/Plugins/IdentityDocumentEcuador/DependencyServicesRegisterer.cs new file mode 100644 index 00000000..a64856c8 --- /dev/null +++ b/src/Plugins/IdentityDocumentEcuador/DependencyServicesRegisterer.cs @@ -0,0 +1,11 @@ +[assembly: Plugin(typeof(DependencyServicesRegisterer))] + +namespace Plugin.IdentityDocument.Ecuador; + +public class DependencyServicesRegisterer : IDependencyServicesRegisterer +{ + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + } +} diff --git a/src/Plugins/IdentityDocumentEcuador/GlobalUsings.cs b/src/Plugins/IdentityDocumentEcuador/GlobalUsings.cs new file mode 100644 index 00000000..b270670d --- /dev/null +++ b/src/Plugins/IdentityDocumentEcuador/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using DentallApp.Shared.Interfaces; +global using DentallApp.Shared.Resources.ApiResponses; +global using SimpleResults; +global using CPlugin.Net; +global using Plugin.IdentityDocument.Ecuador; diff --git a/src/Plugins/IdentityDocumentEcuador/IdentityDocumentValidator.cs b/src/Plugins/IdentityDocumentEcuador/IdentityDocumentValidator.cs new file mode 100644 index 00000000..b93d3066 --- /dev/null +++ b/src/Plugins/IdentityDocumentEcuador/IdentityDocumentValidator.cs @@ -0,0 +1,68 @@ +namespace Plugin.IdentityDocument.Ecuador; + +/// +/// Represents a validator for Ecuadorian identity documents. +/// +/// +/// Link to the algorithm: +/// +public class IdentityDocumentValidator : IIdentityDocumentValidator +{ + public Result IsValid(string document) + { + if (string.IsNullOrWhiteSpace(document)) + return Result.Invalid(Messages.DocumentIsEmpty); + + if (document.Length != 10) + return Result.Invalid(string.Format(Messages.DocumentMaxCharacters, 10)); + + if(IsNotNumeric(document)) + return Result.Invalid(Messages.DocumentMustBeNumeric); + + if(HasInvalidRegionDigit(document)) + return Result.Invalid(Messages.DocumentIsInvalid); + + int verificationDigit = document[^1].ToInt(); + int total = 0; + bool isEvenPosition = false; + // The verification digit is not considered. + int len = document.Length - 1; + for (int position = 0; position < len; position++) + { + isEvenPosition = !isEvenPosition; + int coefficient = isEvenPosition ? 2 : 1; + int digit = document[position].ToInt() * coefficient; + int result = digit > 9 ? digit - 9 : digit; + total += result; + } + + bool isValidDocument = + (total % 10 == 0 && verificationDigit == 0) || + (10 - (total % 10) == verificationDigit); + + return isValidDocument ? + Result.Success() : + Result.Invalid(Messages.DocumentIsInvalid); + } + + private static bool IsNotNumeric(string document) + => document.Where(c => c is < '0' or > '9').Any(); + + private static bool HasInvalidRegionDigit(string document) + => !HasValidRegionDigit(document); + + private static bool HasValidRegionDigit(string document) + { + // The digit of the region is obtained, which are the first two digits. + int regionDigit = int.Parse(document[..2]); + + // It is validated if the region digit exists in Ecuador, which is divided into 24 regions. + // 30 is assigned for Ecuadorians abroad. + return regionDigit is (>= 1 and <= 24) or 30; + } +} + +public static class CharExtensions +{ + public static int ToInt(this char c) => c - '0'; +} diff --git a/src/Plugins/IdentityDocumentEcuador/Plugin.IdentityDocument.Ecuador.csproj b/src/Plugins/IdentityDocumentEcuador/Plugin.IdentityDocument.Ecuador.csproj new file mode 100644 index 00000000..bd13a38c --- /dev/null +++ b/src/Plugins/IdentityDocumentEcuador/Plugin.IdentityDocument.Ecuador.csproj @@ -0,0 +1,8 @@ + + + + Library + Plugin.IdentityDocument.Ecuador + + + diff --git a/src/Shared/Interfaces/IIdentityDocumentValidator.cs b/src/Shared/Interfaces/IIdentityDocumentValidator.cs new file mode 100644 index 00000000..e56f9841 --- /dev/null +++ b/src/Shared/Interfaces/IIdentityDocumentValidator.cs @@ -0,0 +1,13 @@ +namespace DentallApp.Shared.Interfaces; + +/// +/// Represents a validator to validate identity documents. +/// +public interface IIdentityDocumentValidator +{ + /// + /// Checks if an identity document is valid. + /// + /// The document to validate. + Result IsValid(string document); +} diff --git a/src/Shared/Resources/ApiResponses/Messages.Designer.cs b/src/Shared/Resources/ApiResponses/Messages.Designer.cs index 70631c97..e25b5ad8 100644 --- a/src/Shared/Resources/ApiResponses/Messages.Designer.cs +++ b/src/Shared/Resources/ApiResponses/Messages.Designer.cs @@ -300,6 +300,42 @@ public static string DirectLineTokenFailed { } } + /// + /// Looks up a localized string similar to El documento de identidad está vacío.. + /// + public static string DocumentIsEmpty { + get { + return ResourceManager.GetString("DocumentIsEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to El documento de identidad es inválido.. + /// + public static string DocumentIsInvalid { + get { + return ResourceManager.GetString("DocumentIsInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to El documento de identidad debe tener {0} caracteres.. + /// + public static string DocumentMaxCharacters { + get { + return ResourceManager.GetString("DocumentMaxCharacters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to El documento de identidad debe ser numérico.. + /// + public static string DocumentMustBeNumeric { + get { + return ResourceManager.GetString("DocumentMustBeNumeric", resourceCulture); + } + } + /// /// Looks up a localized string similar to El correo electrónico no está confirmado.. /// diff --git a/src/Shared/Resources/ApiResponses/Messages.resx b/src/Shared/Resources/ApiResponses/Messages.resx index 485c4f46..aa27321d 100644 --- a/src/Shared/Resources/ApiResponses/Messages.resx +++ b/src/Shared/Resources/ApiResponses/Messages.resx @@ -201,6 +201,18 @@ Error en la llamada a la API del token de Direct Line. + + El documento de identidad está vacío. + + + El documento de identidad es inválido. + + + El documento de identidad debe tener {0} caracteres. + + + El documento de identidad debe ser numérico. + El correo electrónico no está confirmado. diff --git a/src/Shared/ValidationRules/DocumentValidator.cs b/src/Shared/ValidationRules/DocumentValidator.cs new file mode 100644 index 00000000..1826e142 --- /dev/null +++ b/src/Shared/ValidationRules/DocumentValidator.cs @@ -0,0 +1,28 @@ +namespace DentallApp.Shared.ValidationRules; + +public static class DocumentValidator +{ + public static IRuleBuilderOptions MustBeValidIdentityDocument( + this IRuleBuilder ruleBuilder, + IIdentityDocumentValidator documentValidator) + { + return ruleBuilder.Must((rootObject, document, context) => + { + if (string.IsNullOrWhiteSpace(document)) + { + context.AddFailure(Messages.DocumentIsEmpty); + return false; + } + + Result result = documentValidator.IsValid(document); + if (result.IsSuccess) + return true; + + context.AddFailure(result.Message); + foreach (string error in result.Errors) + context.AddFailure(error); + + return false; + }); + } +} diff --git a/tests/UnitTests/DentallApp.UnitTests.csproj b/tests/UnitTests/DentallApp.UnitTests.csproj index 75b2c2dc..2bf78051 100644 --- a/tests/UnitTests/DentallApp.UnitTests.csproj +++ b/tests/UnitTests/DentallApp.UnitTests.csproj @@ -21,6 +21,7 @@ + diff --git a/tests/UnitTests/GlobalUsings.cs b/tests/UnitTests/GlobalUsings.cs index ff723664..b8c6e701 100644 --- a/tests/UnitTests/GlobalUsings.cs +++ b/tests/UnitTests/GlobalUsings.cs @@ -1,7 +1,6 @@ global using System.Collections; global using System.Text.Json; global using System.Text.Json.Serialization; -global using System.Security.Claims; global using NUnit.Framework; global using FluentAssertions; global using FluentValidation; @@ -9,6 +8,7 @@ global using Microsoft.Bot.Schema; global using Telerik.JustMock; global using DotEnv.Core; +global using SimpleResults; global using DentallApp.Core.Appointments.UseCases; global using DentallApp.Core.Appointments.UseCases.GetAvailableHours; @@ -25,7 +25,6 @@ global using DentallApp.Shared.Resources.Weekdays; global using DentallApp.Shared.Resources.ApiResponses; global using DentallApp.Shared.Reasons; -global using DentallApp.Shared.Constants; global using DentallApp.Shared.Configuration; global using DentallApp.Shared.ValidationRules; @@ -33,3 +32,4 @@ global using Plugin.ChatBot.Models; global using Plugin.ChatBot.DirectLine; global using Plugin.ChatBot.DirectLine.Services; +global using Plugin.IdentityDocument.Ecuador; diff --git a/tests/UnitTests/Plugins/IdentityDocumentEcuador/IdentityDocumentValidatorTests.cs b/tests/UnitTests/Plugins/IdentityDocumentEcuador/IdentityDocumentValidatorTests.cs new file mode 100644 index 00000000..1540166d --- /dev/null +++ b/tests/UnitTests/Plugins/IdentityDocumentEcuador/IdentityDocumentValidatorTests.cs @@ -0,0 +1,110 @@ +namespace UnitTests.Plugins.IdentityDocumentEcuador; + +public class IdentityDocumentValidatorTests +{ + [TestCase("")] + [TestCase(" ")] + [TestCase(null)] + public void IsValid_WhenDocumentIsEmpty_ShouldReturnsInvalidResult(string document) + { + // Arrange + var validator = new IdentityDocumentValidator(); + var expectedMessage = Messages.DocumentIsEmpty; + + // Act + Result result = validator.IsValid(document); + + // Asserts + result.IsSuccess.Should().BeFalse(); + result.Message.Should().Be(expectedMessage); + } + + [TestCase("12345")] + [TestCase("12345678910")] + public void IsValid_WhenDocumentDoesNotHaveTenCharacters_ShouldReturnsInvalidResult(string document) + { + // Arrange + var validator = new IdentityDocumentValidator(); + var expectedMessage = string.Format(Messages.DocumentMaxCharacters, 10); + + // Act + Result result = validator.IsValid(document); + + // Asserts + result.IsSuccess.Should().BeFalse(); + result.Message.Should().Be(expectedMessage); + } + + [TestCase("092361170a")] + [TestCase("09236A1701")] + [TestCase("09236$#a0-")] + public void IsValid_WhenDocumentIsNotNumeric_ShouldReturnsInvalidResult(string document) + { + // Arrange + var validator = new IdentityDocumentValidator(); + var expectedMessage = Messages.DocumentMustBeNumeric; + + // Act + Result result = validator.IsValid(document); + + // Asserts + result.IsSuccess.Should().BeFalse(); + result.Message.Should().Be(expectedMessage); + } + + [TestCase("0023611701")] + [TestCase("2523611701")] + [TestCase("2923611701")] + [TestCase("3123611701")] + [TestCase("0104132817")] + [TestCase("0108875282")] + [TestCase("0100704434")] + [TestCase("0100201244")] + [TestCase("0107304047")] + [TestCase("0106784415")] + [TestCase("0100194636")] + [TestCase("0106354840")] + [TestCase("0100924960")] + [TestCase("1404840463")] + [TestCase("1313821924")] + public void IsValid_WhenDocumentIsInvalid_ShouldReturnsInvalidResult(string document) + { + // Arrange + var validator = new IdentityDocumentValidator(); + var expectedMessage = Messages.DocumentIsInvalid; + + // Act + Result result = validator.IsValid(document); + + // Asserts + result.IsSuccess.Should().BeFalse(); + result.Message.Should().Be(expectedMessage); + } + + [TestCase("1713175071")] + [TestCase("1710034065")] + [TestCase("0923611701")] + [TestCase("0102813417")] + [TestCase("0105287882")] + [TestCase("0104470034")] + [TestCase("0101220044")] + [TestCase("0104030747")] + [TestCase("0104478615")] + [TestCase("0104619036")] + [TestCase("0104835640")] + [TestCase("0104992060")] + [TestCase("1400484463")] + [TestCase("1500449861")] + [TestCase("1311982324")] + public void IsValid_WhenDocumentIsValid_ShouldReturnsSuccessResult(string document) + { + // Arrange + var validator = new IdentityDocumentValidator(); + + // Act + Result result = validator.IsValid(document); + + // Assert + result.IsSuccess.Should().BeTrue(); + } +} diff --git a/tests/UnitTests/Shared/ValidationRules/DocumentValidatorTests.cs b/tests/UnitTests/Shared/ValidationRules/DocumentValidatorTests.cs new file mode 100644 index 00000000..13b6138c --- /dev/null +++ b/tests/UnitTests/Shared/ValidationRules/DocumentValidatorTests.cs @@ -0,0 +1,47 @@ +namespace UnitTests.Shared.ValidationRules; + +public class DocumentValidatorTests +{ + [Test] + public void ShouldHaveErrorWhenDocumentIsInvalid() + { + var userValidator = new UserValidator(new IdentityDocumentValidator()); + var model = new User { Document = "2523611701" }; + var result = userValidator.TestValidate(model); + result.ShouldHaveValidationErrorFor(user => user.Document); + } + + [Test] + public void ShouldNotHaveErrorWhenDocumentIsValid() + { + var userValidator = new UserValidator(new IdentityDocumentValidator()); + var model = new User { Document = "1311982324" }; + var result = userValidator.TestValidate(model); + result.ShouldNotHaveValidationErrorFor(user => user.Document); + } + + [TestCase("")] + [TestCase(" ")] + [TestCase(null)] + public void ShouldHaveErrorWhenDocumentIsEmpty(string document) + { + var userValidator = new UserValidator(new IdentityDocumentValidator()); + var model = new User { Document = document }; + var result = userValidator.TestValidate(model); + result.ShouldHaveValidationErrorFor(user => user.Document); + } + + private class User + { + public string Document { get; init; } + } + + private class UserValidator : AbstractValidator + { + public UserValidator(IIdentityDocumentValidator documentValidator) + { + RuleFor(user => user.Document) + .MustBeValidIdentityDocument(documentValidator); + } + } +} diff --git a/tests/UnitTests/Shared/ValidationRules/PasswordValidatorTests.cs b/tests/UnitTests/Shared/ValidationRules/PasswordValidatorTests.cs index 304bdb2e..2907c926 100644 --- a/tests/UnitTests/Shared/ValidationRules/PasswordValidatorTests.cs +++ b/tests/UnitTests/Shared/ValidationRules/PasswordValidatorTests.cs @@ -2,20 +2,6 @@ public class PasswordValidatorTests { - private class User - { - public string Password { get; init; } - } - - private class UserValidator : AbstractValidator - { - public UserValidator() - { - RuleFor(user => user.Password) - .MustBeSecurePassword(); - } - } - [TestCase("1234")] [TestCase("D234")] [TestCase("DD34")] @@ -63,4 +49,18 @@ public void ShouldHaveErrorWhenPasswordIsEmpty(string password) var result = validator.TestValidate(model); result.ShouldHaveValidationErrorFor(user => user.Password); } + + private class User + { + public string Password { get; init; } + } + + private class UserValidator : AbstractValidator + { + public UserValidator() + { + RuleFor(user => user.Password) + .MustBeSecurePassword(); + } + } }