diff --git a/SchengenVisaApi/ApplicationLayer/Constants.cs b/SchengenVisaApi/ApplicationLayer/Constants.cs index 2cf42e8..c6b3e62 100644 --- a/SchengenVisaApi/ApplicationLayer/Constants.cs +++ b/SchengenVisaApi/ApplicationLayer/Constants.cs @@ -6,7 +6,7 @@ namespace ApplicationLayer { public readonly static Regex EnglishWordRegex = new("^[a-zA-Z]*$"); - public readonly static Regex EnglishPhraseRegex = new(@"^[a-zA-Zā„–0-9?><;,{}[\]\-_+=!@#$%\^&*|']*$"); + public readonly static Regex EnglishPhraseRegex = new(@"^[a-zA-Z ā„–0-9;,\-_+=#*']*$"); public readonly static Regex PhoneNumRegex = new(@"^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$"); } diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PlaceOfWorkModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PlaceOfWorkModelValidator.cs index dc5449b..b1f30aa 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PlaceOfWorkModelValidator.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PlaceOfWorkModelValidator.cs @@ -60,7 +60,7 @@ public class PlaceOfWorkModelValidator : AbstractValidator .WithMessage("Building of place of work can not be empty") .Matches(Constants.EnglishPhraseRegex) .WithMessage("Place of work building field can contain only english letters, digits and special symbols") - .MaximumLength(ConfigurationConstraints.CountryNameLength) + .MaximumLength(ConfigurationConstraints.BuildingNumberLength) .WithMessage($"Building of place of work length must be less than {ConfigurationConstraints.BuildingNumberLength}"); } } diff --git a/SchengenVisaApi/BlazorWebAssemblyVisaApiClient/Constants.cs b/SchengenVisaApi/BlazorWebAssemblyVisaApiClient/Constants.cs index 9131708..907fa52 100644 --- a/SchengenVisaApi/BlazorWebAssemblyVisaApiClient/Constants.cs +++ b/SchengenVisaApi/BlazorWebAssemblyVisaApiClient/Constants.cs @@ -7,7 +7,7 @@ namespace BlazorWebAssemblyVisaApiClient { public readonly static Regex EnglishWordRegex = new("^[a-zA-Z]*$"); - public readonly static Regex EnglishPhraseRegex = new(@"^[a-zA-Zā„–0-9?><;,{}[\]\-_+=!@#$%\^&*|']*$"); + public readonly static Regex EnglishPhraseRegex = new(@"^[a-z A-Zā„–0-9?><;,{}[\]\-_+=!@#$%\^&*|']*$"); public readonly static Regex PhoneNumRegex = new(@"^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$"); diff --git a/SchengenVisaApi/VisaApiTests/Fakers/Applicants/ApplicantFaker.cs b/SchengenVisaApi/VisaApiTests/Fakers/Applicants/ApplicantFaker.cs index d25e22d..ddb8503 100644 --- a/SchengenVisaApi/VisaApiTests/Fakers/Applicants/ApplicantFaker.cs +++ b/SchengenVisaApi/VisaApiTests/Fakers/Applicants/ApplicantFaker.cs @@ -31,7 +31,7 @@ namespace VisaApi.Fakers.Applicants => new Passport { Issuer = f.Company.CompanyName(), - Number = f.Random.String(ConfigurationConstraints.PasswordLength), + Number = f.Random.String(ConfigurationConstraints.PasswordLength, 'a', 'z'), ExpirationDate = f.Date.Future(4, dateTimeProvider.Now()), IssueDate = f.Date.Past(4, dateTimeProvider.Now()) }); diff --git a/SchengenVisaApi/VisaApiTests/Fakers/Applicants/Requests/PassportModelFaker.cs b/SchengenVisaApi/VisaApiTests/Fakers/Applicants/Requests/PassportModelFaker.cs new file mode 100644 index 0000000..c923a9c --- /dev/null +++ b/SchengenVisaApi/VisaApiTests/Fakers/Applicants/Requests/PassportModelFaker.cs @@ -0,0 +1,24 @@ +using ApplicationLayer.InfrastructureServicesInterfaces; +using ApplicationLayer.Services.Applicants.Models; +using Bogus; +using Domains; + +namespace VisaApi.Fakers.Applicants.Requests +{ + public sealed class PassportModelFaker : Faker + { + public PassportModelFaker(IDateTimeProvider dateTimeProvider) + { + RuleFor(m => m.Issuer, f => f.Company.CompanyName()); + + RuleFor(m => m.Number, + f => f.Random.String(ConfigurationConstraints.PassportNumberLength, 'a', 'z')); + + RuleFor(m => m.ExpirationDate, + f => f.Date.Future(4, dateTimeProvider.Now())); + + RuleFor(m => m.IssueDate, + f => f.Date.Past(4, dateTimeProvider.Now())); + } + } +} diff --git a/SchengenVisaApi/VisaApiTests/Fakers/Applicants/Requests/PlaceOfWorkModelFaker.cs b/SchengenVisaApi/VisaApiTests/Fakers/Applicants/Requests/PlaceOfWorkModelFaker.cs new file mode 100644 index 0000000..d4df87a --- /dev/null +++ b/SchengenVisaApi/VisaApiTests/Fakers/Applicants/Requests/PlaceOfWorkModelFaker.cs @@ -0,0 +1,25 @@ +using ApplicationLayer.Services.Applicants.Models; +using Bogus; +using Domains.ApplicantDomain; + +namespace VisaApi.Fakers.Applicants.Requests +{ + public sealed class PlaceOfWorkModelFaker : Faker + { + public PlaceOfWorkModelFaker() + { + RuleFor(m => m.Name, f => f.Company.CompanyName()); + + RuleFor(m => m.PhoneNum, f => f.Phone.PhoneNumber("###########")); + + RuleFor(m => m.Address, + f => new AddressModel + { + Country = f.Address.Country(), + City = f.Address.City(), + Street = f.Address.StreetName(), + Building = f.Address.BuildingNumber() + }); + } + } +} diff --git a/SchengenVisaApi/VisaApiTests/Fakers/VisaApplications/ReentryPermitFaker.cs b/SchengenVisaApi/VisaApiTests/Fakers/VisaApplications/ReentryPermitFaker.cs index 6ea3009..f4a3c96 100644 --- a/SchengenVisaApi/VisaApiTests/Fakers/VisaApplications/ReentryPermitFaker.cs +++ b/SchengenVisaApi/VisaApiTests/Fakers/VisaApplications/ReentryPermitFaker.cs @@ -13,7 +13,7 @@ namespace VisaApi.Fakers.VisaApplications public ReentryPermitFaker(IDateTimeProvider dateTimeProvider) { RuleFor(p => p.Number, - f => f.Random.String(ConfigurationConstraints.ReentryPermitNumberLength)); + f => f.Random.String(ConfigurationConstraints.ReentryPermitNumberLength, 'a', 'z')); RuleFor(p => p.ExpirationDate, f => f.Date.Future(4, dateTimeProvider.Now())); diff --git a/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/NameValidatorTests.cs b/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/NameModelValidatorTests.cs similarity index 99% rename from SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/NameValidatorTests.cs rename to SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/NameModelValidatorTests.cs index 2f91c47..ec552e4 100644 --- a/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/NameValidatorTests.cs +++ b/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/NameModelValidatorTests.cs @@ -9,7 +9,7 @@ using Xunit; namespace VisaApi.Tests.Application.Validation.Applicants { - public class NameValidatorTests + public class NameModelValidatorTests { private static IValidator validator = new NameModelValidator(); private static NameModelFaker faker = new(); diff --git a/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/PassportModelValidatorTests.cs b/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/PassportModelValidatorTests.cs new file mode 100644 index 0000000..e398186 --- /dev/null +++ b/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/PassportModelValidatorTests.cs @@ -0,0 +1,175 @@ +using System.Text; +using ApplicationLayer.InfrastructureServicesInterfaces; +using ApplicationLayer.Services.Applicants.Models; +using ApplicationLayer.Services.Applicants.Models.Validation; +using Domains; +using FluentAssertions; +using FluentValidation; +using VisaApi.Fakers.Applicants.Requests; +using VisaApi.Services; +using Xunit; + +namespace VisaApi.Tests.Application.Validation.Applicants +{ + public class PassportModelValidatorTests + { + private static IDateTimeProvider dateTimeProvider = new TestDateTimeProvider(); + private static IValidator validator = new PassportModelValidator(dateTimeProvider); + private static PassportModelFaker faker = new(dateTimeProvider); + + /// + /// Test for validator that should return error for empty number + /// + [Fact] + private async Task ValidateForEmptyNumberShouldReturnError() + { + var model = faker.Generate(); + model.Number = string.Empty; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Number)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for too long number + /// + [Fact] + private async Task ValidateForLongNumberShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('d', ConfigurationConstraints.PassportNumberLength + 1); + model.Number = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Number)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for not valid number + /// + [Fact] + private async Task ValidateForNotValidNumberShouldReturnError() + { + var model = faker.Generate(); + model.Number = "&?%$24asd\\]|"; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Number)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for empty issuer + /// + [Fact] + private async Task ValidateForEmptyIssuerShouldReturnError() + { + var model = faker.Generate(); + model.Issuer = string.Empty; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Issuer)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for too long issuer + /// + [Fact] + private async Task ValidateForLongIssuerShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('d', ConfigurationConstraints.IssuerNameLength + 1); + model.Issuer = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Issuer)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for not valid issuer + /// + [Fact] + private async Task ValidateForNotValidIssuerShouldReturnError() + { + var model = faker.Generate(); + model.Issuer = "&?%$24asd\\]|"; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Issuer)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for expired passport + /// + [Fact] + private async Task ValidateForExpiredPassportShouldReturnError() + { + var model = faker.Generate(); + model.ExpirationDate = dateTimeProvider.Now().AddDays(-10); + model.IssueDate = model.ExpirationDate.AddDays(-10); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.ExpirationDate)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for passport from future + /// + [Fact] + private async Task ValidateForPassportFromFutureShouldReturnError() + { + var model = faker.Generate(); + model.ExpirationDate = dateTimeProvider.Now().AddDays(10); + model.IssueDate = model.ExpirationDate.AddDays(-3); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.IssueDate)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for passport that expired before issue + /// + [Fact] + private async Task ValidateForPassportExpiredBeforeIssueShouldReturnError() + { + var model = faker.Generate(); + model.ExpirationDate = dateTimeProvider.Now().AddDays(10); + model.IssueDate = model.ExpirationDate.AddDays(3); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.IssueDate)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return no errors for valid passport + /// + [Fact] + private async Task ValidateForValidPassportShouldReturnNoErrors() + { + var model = faker.Generate(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Should().BeEmpty(); + } + } +} diff --git a/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/PlaceOfWorkModelValidatorTests.cs b/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/PlaceOfWorkModelValidatorTests.cs new file mode 100644 index 0000000..aa15179 --- /dev/null +++ b/SchengenVisaApi/VisaApiTests/Tests/Application/Validation/Applicants/PlaceOfWorkModelValidatorTests.cs @@ -0,0 +1,354 @@ +using System.Text; +using ApplicationLayer.Services.Applicants.Models; +using ApplicationLayer.Services.Applicants.Models.Validation; +using Domains; +using FluentAssertions; +using FluentValidation; +using VisaApi.Fakers.Applicants.Requests; +using Xunit; + +namespace VisaApi.Tests.Application.Validation.Applicants +{ + public class PlaceOfWorkModelValidatorTests + { + private static IValidator validator = new PlaceOfWorkModelValidator(); + private static PlaceOfWorkModelFaker faker = new(); + + /// + /// Test for validator that should return error for empty phone num + /// + [Fact] + private async Task ValidateForEmptyPhoneNumShouldReturnError() + { + var model = faker.Generate(); + model.PhoneNum = string.Empty; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.PhoneNum)) + .Should().NotBeEmpty(); + } + + /// + /// Test for validator that should return error for long phone num + /// + [Fact] + private async Task ValidateForLongPhoneNumShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('8', ConfigurationConstraints.PhoneNumberLength + 1); + model.PhoneNum = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.PhoneNum)) + .Should().NotBeEmpty(); + } + + /// + /// Test for validator that should return error for short phone num + /// + [Fact] + private async Task ValidateForShortPhoneNumShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('8', ConfigurationConstraints.PhoneNumberMinLength - 1); + model.PhoneNum = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.PhoneNum)) + .Should() + .HaveCount(1); + } + + /// + /// Test for validator that should return error for not valid phone num + /// + [Fact] + private async Task ValidateForNotValidPhoneNumShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('a', ConfigurationConstraints.PhoneNumberMinLength); + model.PhoneNum = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.PhoneNum)) + .Should() + .HaveCount(1); + } + + /// + /// Test for validator that should throw exception for null address + /// + [Fact] + private async Task ValidateForEmptyAddressShouldThrow() + { + var model = faker.Generate(); + model.Address = null!; + NullReferenceException? result = null; + + try + { + await validator.ValidateAsync(model); + } + catch (Exception e) + { + result = e as NullReferenceException; + } + + result.Should().NotBeNull(); + } + + /// + /// Test for validator that should return error for empty Country + /// + [Fact] + private async Task ValidateForEmptyCountryShouldReturnError() + { + var model = faker.Generate(); + model.Address.Country = ""; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Country") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for too long Country + /// + [Fact] + private async Task ValidateForLongCountryShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('a', ConfigurationConstraints.CountryNameLength + 1); + model.Address.Country = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Country") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for not valid Country + /// + [Fact] + private async Task ValidateForNotValidCountryShouldReturnError() + { + var model = faker.Generate(); + model.Address.Country = "|&%"; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Country") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for empty City + /// + [Fact] + private async Task ValidateForEmptyCityShouldReturnError() + { + var model = faker.Generate(); + model.Address.City = ""; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.City") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for too long city + /// + [Fact] + private async Task ValidateForLongCityShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('a', ConfigurationConstraints.CityNameLength + 1); + model.Address.City = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.City") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for not valid city + /// + [Fact] + private async Task ValidateForNotValidCityShouldReturnError() + { + var model = faker.Generate(); + model.Address.City = "|&%"; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.City") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for empty street + /// + [Fact] + private async Task ValidateForEmptyStreetShouldReturnError() + { + var model = faker.Generate(); + model.Address.Street = ""; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Street") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for too long street + /// + [Fact] + private async Task ValidateForLongStreetShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('a', ConfigurationConstraints.StreetNameLength + 1); + model.Address.Street = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Street") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for not valid street + /// + [Fact] + private async Task ValidateForNotValidStreetShouldReturnError() + { + var model = faker.Generate(); + model.Address.Street = "|&%"; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Street") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for empty building /// + [Fact] + private async Task ValidateForEmptyBuildingShouldReturnError() + { + var model = faker.Generate(); + model.Address.Building = ""; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Building") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for too long building + /// + [Fact] + private async Task ValidateForLongBuildingShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('a', ConfigurationConstraints.BuildingNumberLength + 1); + model.Address.Building = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Building") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for not valid building + /// + [Fact] + private async Task ValidateForNotValidBuildingShouldReturnError() + { + var model = faker.Generate(); + model.Address.Building = "|&%"; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == "Address.Building") + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for empty name + /// + [Fact] + private async Task ValidateForEmptyNameShouldReturnError() + { + var model = faker.Generate(); + model.Name = ""; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Name)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for too long name + /// + [Fact] + private async Task ValidateForTooLongNameShouldReturnError() + { + var model = faker.Generate(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append('g', ConfigurationConstraints.PlaceOfWorkNameLength + 1); + model.Name = stringBuilder.ToString(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Name)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return error for not valid name + /// + [Fact] + private async Task ValidateForNotValidNameShouldReturnError() + { + var model = faker.Generate(); + model.Name = "@$%&|"; + + var result = await validator.ValidateAsync(model); + + result.Errors.Where(error => error.PropertyName == nameof(model.Name)) + .Should().HaveCount(1); + } + + /// + /// Test for validator that should return no errors for valid model + /// + [Fact] + private async Task ValidateForValidShouldReturnNoErrors() + { + var model = faker.Generate(); + + var result = await validator.ValidateAsync(model); + + result.Errors.Should().BeEmpty(); + } + } +}