From a8fa6472776cbf36493e6dfb5f0c430ddead3523 Mon Sep 17 00:00:00 2001 From: matthewtoghill <63318725+matthewtoghill@users.noreply.github.com> Date: Thu, 9 Mar 2023 14:35:09 +0000 Subject: [PATCH] FEAT: File support components testing, cleanup and refactors. (#44) ## Description - Refactor FileService to remove remaining dependency on System.Drawing - Fix BlobStorageService GetFileType() to use string.ToLower() - Add unit tests for GetFileType() - Add unit tests to FileServiceTests - Remove duplicate Trait declarations in test classes - General tidy up - Update packages ## Motivation and Context #31 replaced System.Drawing implementation, however FileService still had a dependency on System.Drawing for its Size class. As we are not using any specific functionality from the Size class and are just using it to represent data then we can replace it with a tuple. Other changes in this PR are to: - fix a bug with the GetFileType method that was not handling strings with uppercase characters properly. - correct some typos and apply consistent style in some areas - add unit tests ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist: - [x] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [x] I have read the **CONTRIBUTING** document. - [x] I have added tests to cover my changes. - [x] All new and existing tests passed. --- .../Solution/.template.config/template.json | 4 +- .../Monaco.Template.Backend.Api.csproj | 4 +- .../Commands/CompanyCommandsHandlersTests.cs | 3 +- .../CompanyCreateCommandValidatorTests.cs | 27 +-- .../CompanyDeleteCommandValidatorTests.cs | 4 +- .../CompanyEditCommandValidatorTests.cs | 210 ++++++++---------- .../Queries/CompanyQueriesHandlersTests.cs | 7 +- .../Queries/CountryQueriesHandlersTests.cs | 5 +- ....Template.Backend.Application.Tests.csproj | 2 +- .../Services/FileServiceTests.cs | 71 +++++- .../DTOs/CompanyDto.cs | 6 +- .../DTOs/CountryDto.cs | 2 +- .../DTOs/Extensions/CompanyExtensions.cs | 3 +- .../DTOs/Extensions/CountryExtensions.cs | 20 +- .../DTOs/Extensions/FileExtensions.cs | 68 +++--- .../DTOs/FileDownloadDto.cs | 4 +- .../DTOs/FileDto.cs | 6 +- ...Monaco.Template.Backend.Application.csproj | 4 +- .../Services/FileService.cs | 40 ++-- .../MediatorExtensions.cs | 32 ++- ...late.Backend.Common.Api.Application.csproj | 2 +- .../Auth/AuthExtensions.cs | 8 +- .../Behaviors/CommandValidationBehavior.cs | 4 +- ...Template.Backend.Common.Application.csproj | 2 +- .../Extensions/ValidatorsExtensions.cs | 12 +- .../BlobStorageServiceTests.cs | 51 +++++ ...te.Backend.Common.BlobStorage.Tests.csproj | 28 +++ .../BlobStorageService.cs | 18 +- ...Template.Backend.Common.BlobStorage.csproj | 2 +- .../DomainEventTests.cs | 4 +- .../EntityTests.cs | 20 +- .../EnumerationTests.cs | 9 +- ...emplate.Backend.Common.Domain.Tests.csproj | 6 +- .../PageTests.cs | 4 +- .../ValueObjectTests.cs | 5 +- ...naco.Template.Backend.Common.Domain.csproj | 2 +- .../Context/Extensions/FilterExtensions.cs | 40 ++-- .../Extensions/OperationsExtensions.cs | 11 +- ...plate.Backend.Common.Infrastructure.csproj | 2 +- ...onaco.Template.Backend.Common.Tests.csproj | 4 +- .../AddressTests.cs | 2 +- .../CompanyTests.cs | 3 +- .../CountryTests.cs | 2 +- ...onaco.Template.Backend.Domain.Tests.csproj | 2 +- .../Solution/Monaco.Template.Backend.sln | 8 +- src/Monaco.Template.nuspec | 2 +- 46 files changed, 422 insertions(+), 353 deletions(-) create mode 100644 src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage.Tests/BlobStorageServiceTests.cs create mode 100644 src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage.Tests/Monaco.Template.Backend.Common.BlobStorage.Tests.csproj diff --git a/src/Content/Backend/Solution/.template.config/template.json b/src/Content/Backend/Solution/.template.config/template.json index 1845bdc..4b8b6b4 100644 --- a/src/Content/Backend/Solution/.template.config/template.json +++ b/src/Content/Backend/Solution/.template.config/template.json @@ -159,6 +159,7 @@ "condition": "(!filesSupport)", "exclude": [ "Monaco.Template.Backend.Common.BlobStorage/**/*", + "Monaco.Template.Backend.Common.BlobStorage.Tests/**/*", "Monaco.Template.Backend.Api/Controllers/FilesController.cs", "Monaco.Template.Backend.Api/Controllers/ImagesController.cs", "Monaco.Template.Backend.Application/Features/File/**/*", @@ -547,6 +548,7 @@ "5a3893d1-1e36-310c-7633-8f36ffa26315", "a2689ae3-3643-6250-a748-8f055cc72da8", "be447a08-0a85-5779-8c65-cf15c2c9a5a8", - "c776f397-182b-6d0d-09f2-4e440dc093d3" + "c776f397-182b-6d0d-09f2-4e440dc093d3", + "d8623b90-59c1-4753-a0e6-f2dbd4305c9b" ] } \ No newline at end of file diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Api/Monaco.Template.Backend.Api.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Api/Monaco.Template.Backend.Api.csproj index 36e3c8c..96b10c8 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Api/Monaco.Template.Backend.Api.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Api/Monaco.Template.Backend.Api.csproj @@ -10,8 +10,8 @@ - - + + all diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/CompanyCommandsHandlersTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/CompanyCommandsHandlersTests.cs index 3934406..c6501a5 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/CompanyCommandsHandlersTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/CompanyCommandsHandlersTests.cs @@ -16,9 +16,9 @@ namespace Monaco.Template.Backend.Application.Tests.Features.Company.Commands; [ExcludeFromCodeCoverage] +[Trait("Application Commands", "Company Commands")] public class CompanyCommandsHandlersTests { - [Trait("Application Commands", "Company Commands")] [Theory(DisplayName = "Create new company succeeds")] [AnonymousData] public async Task CreateNewCompanySucceeds(Domain.Model.Country country) @@ -48,7 +48,6 @@ public async Task CreateNewCompanySucceeds(Domain.Model.Country country) result.ItemNotFound.Should().BeFalse(); } - [Trait("Application Commands", "Company Commands")] [Theory(DisplayName = "Edit company succeeds")] [AnonymousData] public async Task EditCompanySucceeds(Domain.Model.Country country) diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyCreateCommandValidatorTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyCreateCommandValidatorTests.cs index 650143a..e7f74fa 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyCreateCommandValidatorTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyCreateCommandValidatorTests.cs @@ -17,9 +17,9 @@ namespace Monaco.Template.Backend.Application.Tests.Features.Company.Commands.Validators; [ExcludeFromCodeCoverage] +[Trait("Application Validators", "Company Validators")] public class CompanyCreateCommandValidatorTests { - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Validator's rule level cascade mode is 'Stop'")] public void ValidatorRuleLevelCascadeModeIsStop() { @@ -28,7 +28,6 @@ public void ValidatorRuleLevelCascadeModeIsStop() sut.RuleLevelCascadeMode.Should().Be(CascadeMode.Stop); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Name being valid does not generate validation error")] public async Task NameDoesNotGenerateErrorWhenValid() { @@ -52,7 +51,6 @@ public async Task NameDoesNotGenerateErrorWhenValid() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.Name); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Name with empty value generates validation error")] public async Task NameIsEmptyGeneratesError() { @@ -74,7 +72,6 @@ public async Task NameIsEmptyGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Name with long value generates validation error")] public async Task NameWithLongValueGeneratesError() { @@ -97,7 +94,6 @@ public async Task NameWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Name which already exists generates validation error")] [AnonymousData] public async Task NameAlreadyExistsGeneratesError(Domain.Model.Company company) @@ -125,7 +121,6 @@ public async Task NameAlreadyExistsGeneratesError(Domain.Model.Company company) .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Email being valid does not generate validation error")] public async Task EmailIsValidDoesNotGenerateError() { @@ -149,7 +144,6 @@ public async Task EmailIsValidDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.Email); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Email with empty value generates validation error")] public async Task EmailIsEmptyGeneratesError() { @@ -171,7 +165,6 @@ public async Task EmailIsEmptyGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Email being invalid generates validation error")] [AnonymousData] public async Task EmailAddressIsInvalidGeneratesError(string email) @@ -194,7 +187,6 @@ public async Task EmailAddressIsInvalidGeneratesError(string email) .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Website URL with long value generates validation error")] public async Task WebsiteUrlWithLongValueGeneratesError() { @@ -217,7 +209,6 @@ public async Task WebsiteUrlWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Website URL with empty value does not generate validation error")] public async Task WebsiteUrlWithEmptyValueDoesNotGenerateError() { @@ -236,9 +227,8 @@ public async Task WebsiteUrlWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.WebSiteUrl); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Street with long value generates validation error")] - public async Task AddressWithLongValueGeneratesError() + public async Task StreetWithLongValueGeneratesError() { var cmdMock = new Mock(It.IsAny(), // Name It.IsAny(), // Email @@ -259,9 +249,8 @@ public async Task AddressWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Street with empty value does not generate validation error")] - public async Task AddressWithEmptyValueDoesNotGenerateError() + public async Task StreetWithEmptyValueDoesNotGenerateError() { var cmdMock = new Mock(It.IsAny(), // Name It.IsAny(), // Email @@ -278,7 +267,6 @@ public async Task AddressWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.Street); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "City with long value generates validation error")] public async Task CityWithLongValueGeneratesError() { @@ -301,7 +289,6 @@ public async Task CityWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "City with empty value does not generate validation error")] public async Task CityWithEmptyValueDoesNotGenerateError() { @@ -320,7 +307,6 @@ public async Task CityWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.City); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "County with long value generates validation error")] public async Task CountyWithLongValueGeneratesError() { @@ -343,7 +329,6 @@ public async Task CountyWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "County with empty value does not generate validation error")] public async Task CountyWithEmptyValueDoesNotGenerateError() { @@ -362,7 +347,6 @@ public async Task CountyWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.County); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Postcode with long value generates validation error")] public async Task PostcodeWithLongValueGeneratesError() { @@ -385,7 +369,6 @@ public async Task PostcodeWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Postcode with empty value does not generate validation error")] public async Task PostcodeWithEmptyValueDoesNotGenerateError() { @@ -404,7 +387,6 @@ public async Task PostcodeWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.PostCode); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Country being valid does not generate validation error")] [AnonymousData(true)] public async Task CountryIsValidDoesNotGenerateError(Domain.Model.Country country) @@ -436,7 +418,6 @@ public async Task CountryIsValidDoesNotGenerateError(Domain.Model.Country countr validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.CountryId); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Country with null value does not generate validation error when Address fields null")] public async Task CountryWithNullValueDoesNotGenerateErrorWhenAddressFieldsNull() { @@ -459,7 +440,6 @@ public async Task CountryWithNullValueDoesNotGenerateErrorWhenAddressFieldsNull( validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.CountryId); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Country with null value generates validation error when Address fields present")] public async Task CountryWithNullValueGeneratesErrorWhenAddressFieldsPresent() { @@ -482,7 +462,6 @@ public async Task CountryWithNullValueGeneratesErrorWhenAddressFieldsPresent() validationResult.ShouldHaveValidationErrorFor(cmd => cmd.CountryId); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Country that doesn't exist generates validation error")] [AnonymousData] public async Task CountryMustExistValidation(Domain.Model.Country country) diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyDeleteCommandValidatorTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyDeleteCommandValidatorTests.cs index 2c29733..3a7ba57 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyDeleteCommandValidatorTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyDeleteCommandValidatorTests.cs @@ -18,9 +18,9 @@ namespace Monaco.Template.Backend.Application.Tests.Features.Company.Commands.Validators; [ExcludeFromCodeCoverage] +[Trait("Application Validators", "Company Validators")] public class CompanyDeleteCommandValidatorTests { - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Validator's rule level cascade mode is 'Stop'")] public void ValidatorRuleLevelCascadeModeIsStop() { @@ -29,7 +29,6 @@ public void ValidatorRuleLevelCascadeModeIsStop() sut.RuleLevelCascadeMode.Should().Be(CascadeMode.Stop); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Existing company passes validation correctly")] [AnonymousData] public async Task ExistingCompanyPassesValidationCorrectly(Domain.Model.Company company) @@ -46,7 +45,6 @@ public async Task ExistingCompanyPassesValidationCorrectly(Domain.Model.Company validationResult.ShouldNotHaveAnyValidationErrors(); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Non existing company passes validation correctly")] [AnonymousData] public async Task NonExistingCompanyPassesValidationCorrectly(Domain.Model.Company company, Guid id) diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyEditCommandValidatorTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyEditCommandValidatorTests.cs index 7c1e5b9..1c8702f 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyEditCommandValidatorTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Commands/Validators/CompanyEditCommandValidatorTests.cs @@ -18,9 +18,9 @@ namespace Monaco.Template.Backend.Application.Tests.Features.Company.Commands.Validators; [ExcludeFromCodeCoverage] +[Trait("Application Validators", "Company Validators")] public class CompanyEditCommandValidatorTests { - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Validator's rule level cascade mode is 'Stop'")] public void ValidatorRuleLevelCascadeModeIsStop() { @@ -29,7 +29,6 @@ public void ValidatorRuleLevelCascadeModeIsStop() sut.RuleLevelCascadeMode.Should().Be(CascadeMode.Stop); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Existing company passes validation correctly")] [AnonymousData] public async Task ExistingCompanyPassesValidationCorrectly(Domain.Model.Company company) @@ -54,7 +53,6 @@ public async Task ExistingCompanyPassesValidationCorrectly(Domain.Model.Company validationResult.ShouldNotHaveAnyValidationErrors(); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Non existing company passes validation correctly")] [AnonymousData] public async Task NonExistingCompanyPassesValidationCorrectly(Domain.Model.Company company, Guid id) @@ -79,7 +77,6 @@ public async Task NonExistingCompanyPassesValidationCorrectly(Domain.Model.Compa validationResult.ShouldHaveValidationErrorFor(x => x.Id); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Name being valid does not generate validation error")] public async Task NameDoesNotGenerateErrorWhenValid() { @@ -88,15 +85,15 @@ public async Task NameDoesNotGenerateErrorWhenValid() var companyDbSetMock = new List().AsQueryable().BuildMockDbSet(); dbContextMock.Setup(x => x.Set()).Returns(companyDbSetMock.Object); - var cmdMock = new Mock(It.IsAny(), + var cmdMock = new Mock(It.IsAny(), // Id new string(It.IsAny(), 100), // same Name as the already-existing Company - It.IsAny(), // Email - It.IsAny(), // WebSiteUrl - It.IsAny(), // Street - It.IsAny(), // City - It.IsAny(), // County - It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny(), // Email + It.IsAny(), // WebSiteUrl + It.IsAny(), // Street + It.IsAny(), // City + It.IsAny(), // County + It.IsAny(), // PostCode + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(dbContextMock.Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.Name)); @@ -104,19 +101,18 @@ public async Task NameDoesNotGenerateErrorWhenValid() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.Name); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Name with empty value generates validation error")] public async Task NameIsEmptyGeneratesError() { - var cmdMock = new Mock(It.IsAny(), - string.Empty, // Name + var cmdMock = new Mock(It.IsAny(), // Id + string.Empty, // Name It.IsAny(), // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City It.IsAny(), // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.Name)); @@ -127,18 +123,17 @@ public async Task NameIsEmptyGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Name with long value generates validation error")] public async Task NameWithLongValueGeneratesError() { - var cmdMock = new Mock(It.IsAny(), + var cmdMock = new Mock(It.IsAny(), // Id new string(It.IsAny(), 101), // Name - It.IsAny(), // Email - It.IsAny(), // WebSiteUrl - It.IsAny(), // Street - It.IsAny(), // City - It.IsAny(), // County - It.IsAny(), // PostCode + It.IsAny(), // Email + It.IsAny(), // WebSiteUrl + It.IsAny(), // Street + It.IsAny(), // City + It.IsAny(), // County + It.IsAny(), // PostCode It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); @@ -151,7 +146,6 @@ public async Task NameWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Name which already exists generates validation error")] [AnonymousData] public async Task NameAlreadyExistsGeneratesError(Domain.Model.Company company, Guid id) @@ -161,15 +155,15 @@ public async Task NameAlreadyExistsGeneratesError(Domain.Model.Company company, var companyDbSetMock = new List { company }.AsQueryable().BuildMockDbSet(); dbContextMock.Setup(x => x.Set()).Returns(companyDbSetMock.Object); - var cmdMock = new Mock(id, - company.Name, // same Name as the already-existing Company + var cmdMock = new Mock(id, // Id + company.Name, // same Name as the already-existing Company It.IsAny(), // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City - It.IsAny(), // County + It.IsAny(), // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(dbContextMock.Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.Name)); @@ -180,7 +174,6 @@ public async Task NameAlreadyExistsGeneratesError(Domain.Model.Company company, .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Email being valid does not generate validation error")] public async Task EmailIsValidDoesNotGenerateError() { @@ -189,15 +182,15 @@ public async Task EmailIsValidDoesNotGenerateError() var companyDbSetMock = new List().AsQueryable().BuildMockDbSet(); dbContextMock.Setup(x => x.Set()).Returns(companyDbSetMock.Object); - var cmdMock = new Mock(It.IsAny(), + var cmdMock = new Mock(It.IsAny(), // Id It.IsAny(), // same Name as the already-existing Company - "valid@email.com", // Email + "valid@email.com", // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City It.IsAny(), // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(dbContextMock.Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.Email)); @@ -205,19 +198,18 @@ public async Task EmailIsValidDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.Email); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Email with empty value generates validation error")] public async Task EmailIsEmptyGeneratesError() { - var cmdMock = new Mock(It.IsAny(), + var cmdMock = new Mock(It.IsAny(), // Id It.IsAny(), // Name - string.Empty, // Email + string.Empty, // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City It.IsAny(), // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.Email)); @@ -228,20 +220,19 @@ public async Task EmailIsEmptyGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Email being invalid generates validation error")] [AnonymousData] public async Task EmailAddressIsInvalidGeneratesError(string email) { - var cmdMock = new Mock(It.IsAny(), + var cmdMock = new Mock(It.IsAny(), // Id It.IsAny(), // Name - email, // Email + email, // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City It.IsAny(), // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.Email)); @@ -252,18 +243,17 @@ public async Task EmailAddressIsInvalidGeneratesError(string email) .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Website URL with long value generates validation error")] public async Task WebsiteUrlWithLongValueGeneratesError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name - It.IsAny(), // Email + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name + It.IsAny(), // Email new string(It.IsAny(), 301), // WebSiteUrl - It.IsAny(), // Street - It.IsAny(), // City - It.IsAny(), // County - It.IsAny(), // PostCode + It.IsAny(), // Street + It.IsAny(), // City + It.IsAny(), // County + It.IsAny(), // PostCode It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); @@ -276,19 +266,18 @@ public async Task WebsiteUrlWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Website URL with empty value does not generate validation error")] public async Task WebsiteUrlWithEmptyValueDoesNotGenerateError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name It.IsAny(), // Email - string.Empty, // WebSiteUrl + string.Empty, // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City It.IsAny(), // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.WebSiteUrl)); @@ -296,18 +285,17 @@ public async Task WebsiteUrlWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.WebSiteUrl); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Street with long value generates validation error")] public async Task AddressWithLongValueGeneratesError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name - It.IsAny(), // Email - It.IsAny(), // WebSiteUrl + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name + It.IsAny(), // Email + It.IsAny(), // WebSiteUrl new string(It.IsAny(), 101), // Street - It.IsAny(), // City - It.IsAny(), // County - It.IsAny(), // PostCode + It.IsAny(), // City + It.IsAny(), // County + It.IsAny(), // PostCode It.IsAny()); // country.Id var validator = new CompanyEditCommandValidator(new Mock().Object); @@ -320,19 +308,18 @@ public async Task AddressWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Street with empty value does not generate validation error")] public async Task AddressWithEmptyValueDoesNotGenerateError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name It.IsAny(), // Email It.IsAny(), // WebSiteUrl - string.Empty, // Street + string.Empty, // Street It.IsAny(), // City It.IsAny(), // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.Street)); @@ -340,18 +327,17 @@ public async Task AddressWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.Street); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "City with long value generates validation error")] public async Task CityWithLongValueGeneratesError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name - It.IsAny(), // Email - It.IsAny(), // WebSiteUrl - It.IsAny(), // Street + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name + It.IsAny(), // Email + It.IsAny(), // WebSiteUrl + It.IsAny(), // Street new string(It.IsAny(), 101), // City - It.IsAny(), // County - It.IsAny(), // PostCode + It.IsAny(), // County + It.IsAny(), // PostCode It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); @@ -364,19 +350,18 @@ public async Task CityWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "City with empty value does not generate validation error")] public async Task CityWithEmptyValueDoesNotGenerateError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name It.IsAny(), // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street - string.Empty, // City + string.Empty, // City It.IsAny(), // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.City)); @@ -384,18 +369,17 @@ public async Task CityWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.City); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "County with long value generates validation error")] public async Task CountyWithLongValueGeneratesError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name - It.IsAny(), // Email - It.IsAny(), // WebSiteUrl - It.IsAny(), // Street - It.IsAny(), // City + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name + It.IsAny(), // Email + It.IsAny(), // WebSiteUrl + It.IsAny(), // Street + It.IsAny(), // City new string(It.IsAny(), 101), // County - It.IsAny(), // PostCode + It.IsAny(), // PostCode It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); @@ -408,19 +392,18 @@ public async Task CountyWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "County with empty value does not generate validation error")] public async Task CountyWithEmptyValueDoesNotGenerateError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name It.IsAny(), // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City - string.Empty, // County + string.Empty, // County It.IsAny(), // PostCode - It.IsAny()); // country.Id + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.County)); @@ -428,18 +411,17 @@ public async Task CountyWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.County); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Postcode with long value generates validation error")] public async Task PostcodeWithLongValueGeneratesError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name - It.IsAny(), // Email - It.IsAny(), // WebSiteUrl - It.IsAny(), // Street - It.IsAny(), // City - It.IsAny(), // County - new string(It.IsAny(), 11), // PostCode + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name + It.IsAny(), // Email + It.IsAny(), // WebSiteUrl + It.IsAny(), // Street + It.IsAny(), // City + It.IsAny(), // County + new string(It.IsAny(), 11), // PostCode It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); @@ -452,19 +434,18 @@ public async Task PostcodeWithLongValueGeneratesError() .HaveCount(1); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Postcode with empty value does not generate validation error")] public async Task PostcodeWithEmptyValueDoesNotGenerateError() { - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name It.IsAny(), // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City It.IsAny(), // County - string.Empty, // PostCode - It.IsAny()); // country.Id + string.Empty, // PostCode + It.IsAny()); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.PostCode)); @@ -472,7 +453,6 @@ public async Task PostcodeWithEmptyValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.PostCode); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Country being valid does not generate validation error")] [AnonymousData(true)] public async Task CountryIsValidDoesNotGenerateError(Domain.Model.Country country) @@ -485,7 +465,7 @@ public async Task CountryIsValidDoesNotGenerateError(Domain.Model.Country countr var countryDbSetMock = new List() { country }.AsQueryable().BuildMockDbSet(); dbContextMock.Setup(x => x.Set()).Returns(countryDbSetMock.Object); - var cmdMock = new Mock(It.IsAny(), + var cmdMock = new Mock(It.IsAny(), // Id It.IsAny(), // Name It.IsAny(), // Email It.IsAny(), // WebSiteUrl @@ -501,11 +481,10 @@ public async Task CountryIsValidDoesNotGenerateError(Domain.Model.Country countr validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.CountryId); } - [Trait("Application Validators", "Company Validators")] [Fact(DisplayName = "Country with null value does not generate validation error")] public async Task CountryWithNullValueDoesNotGenerateError() { - var cmdMock = new Mock(It.IsAny(), + var cmdMock = new Mock(It.IsAny(), // Id It.IsAny(), // Name It.IsAny(), // Email It.IsAny(), // WebSiteUrl @@ -513,7 +492,7 @@ public async Task CountryWithNullValueDoesNotGenerateError() It.IsAny(), // City It.IsAny(), // County It.IsAny(), // PostCode - null); // country.Id + null); // country.Id var sut = new CompanyEditCommandValidator(new Mock().Object); var validationResult = await sut.TestValidateAsync(cmdMock.Object, strategy => strategy.IncludeProperties(cmd => cmd.CountryId)); @@ -521,7 +500,6 @@ public async Task CountryWithNullValueDoesNotGenerateError() validationResult.ShouldNotHaveValidationErrorFor(cmd => cmd.CountryId); } - [Trait("Application Validators", "Company Validators")] [Theory(DisplayName = "Country that doesn't exist generates validation error")] [AnonymousData] public async Task CountryMustExistValidation(Domain.Model.Country country) @@ -534,14 +512,14 @@ public async Task CountryMustExistValidation(Domain.Model.Country country) var countryDbSetMock = new List() { country }.AsQueryable().BuildMockDbSet(); dbContextMock.Setup(x => x.Set()).Returns(countryDbSetMock.Object); - var cmdMock = new Mock(It.IsAny(), - It.IsAny(), // Name + var cmdMock = new Mock(It.IsAny(), // Id + It.IsAny(), // Name It.IsAny(), // Email It.IsAny(), // WebSiteUrl It.IsAny(), // Street It.IsAny(), // City It.IsAny(), // County - It.IsAny(), // PostCode + It.IsAny(), // PostCode Guid.NewGuid()); // country.Id var sut = new CompanyEditCommandValidator(dbContextMock.Object); diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Queries/CompanyQueriesHandlersTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Queries/CompanyQueriesHandlersTests.cs index 1821ef6..0fa30ec 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Queries/CompanyQueriesHandlersTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Company/Queries/CompanyQueriesHandlersTests.cs @@ -18,9 +18,9 @@ namespace Monaco.Template.Backend.Application.Tests.Features.Company.Queries; [ExcludeFromCodeCoverage] +[Trait("Application Queries", "Company Queries")] public class CompanyQueriesHandlersTests { - [Trait("Application Queries", "Company Queries")] [Theory(DisplayName = "Get company page without params succeeds")] [AnonymousData] public async Task GetCompanyPageWithoutParamsSucceeds(List companies) @@ -40,7 +40,6 @@ public async Task GetCompanyPageWithoutParamsSucceeds(List .BeInAscendingOrder(x => x.Name); } - [Trait("Application Queries", "Company Queries")] [Theory(DisplayName = "Get company page with params succeeds")] [AnonymousData] public async Task GetCompanyPageWithParamsSucceeds(List companies) @@ -82,9 +81,8 @@ public async Task GetExistingCompanyByIdSucceeds() result!.Name.Should().Be(company.Name); } - [Trait("Application Queries", "Company Queries")] [Fact(DisplayName = "Get non-existing company by Id fails")] - public async Task GetNonExistingCountryByIdFails() + public async Task GetNonExistingCompanyByIdFails() { var companies = CompanyFactory.CreateMany().ToList(); var dbContextMock = SetupMock(companies); @@ -103,7 +101,6 @@ private static Mock SetupMock(IEnumerable co dbContextMock.Setup(x => x.Set()) .Returns(dbSetMock.Object); - return dbContextMock; } } \ No newline at end of file diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Country/Queries/CountryQueriesHandlersTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Country/Queries/CountryQueriesHandlersTests.cs index 9267134..7d63b8c 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Country/Queries/CountryQueriesHandlersTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Features/Country/Queries/CountryQueriesHandlersTests.cs @@ -18,9 +18,9 @@ namespace Monaco.Template.Backend.Application.Tests.Features.Country.Queries; [ExcludeFromCodeCoverage] +[Trait("Application Queries", "Country Queries")] public class CountryQueriesHandlersTests { - [Trait("Application Queries", "Country Queries")] [Theory(DisplayName = "Get country list without params succeeds")] [AnonymousData] public async Task GetCountryListWithoutParamsSucceeds(List countries) @@ -37,7 +37,6 @@ public async Task GetCountryListWithoutParamsSucceeds(List .BeInAscendingOrder(x => x.Name); } - [Trait("Application Queries", "Country Queries")] [Theory(DisplayName = "Get country list with params succeeds")] [AnonymousData] public async Task GetCountryListWithParamsSucceeds(List countries) @@ -61,7 +60,6 @@ public async Task GetCountryListWithParamsSucceeds(List co .BeInDescendingOrder(x => x.Name); } - [Trait("Application Queries", "Country Queries")] [Fact(DisplayName = "Get existing country by Id succeeds")] public async Task GetExistingCountryByIdSucceeds() { @@ -77,7 +75,6 @@ public async Task GetExistingCountryByIdSucceeds() result!.Name.Should().Be(country.Name); } - [Trait("Application Queries", "Country Queries")] [Fact(DisplayName = "Get non-existing country by Id fails")] public async Task GetNonExistingCountryByIdFails() { diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Monaco.Template.Backend.Application.Tests.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Monaco.Template.Backend.Application.Tests.csproj index 6b90d42..daf387f 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Monaco.Template.Backend.Application.Tests.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Monaco.Template.Backend.Application.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Services/FileServiceTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Services/FileServiceTests.cs index 77d838e..ab0d7c1 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Services/FileServiceTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application.Tests/Services/FileServiceTests.cs @@ -1,6 +1,7 @@ using MockQueryable.Moq; using Monaco.Template.Backend.Application.Infrastructure.Context; using Monaco.Template.Backend.Application.Services; +using Monaco.Template.Backend.Common.BlobStorage; using Monaco.Template.Backend.Common.BlobStorage.Contracts; using Monaco.Template.Backend.Domain.Model; using Moq; @@ -16,9 +17,9 @@ namespace Monaco.Template.Backend.Application.Tests.Services; [ExcludeFromCodeCoverage] +[Trait("Application Services", "File Service")] public class FileServiceTests { - [Trait("Application Services", "File Service")] [Fact(DisplayName = "Upload image succeeds")] public async Task UploadImageSucceeds() { @@ -40,4 +41,72 @@ public async Task UploadImageSucceeds() dbContextMock.Verify(x => x.Set().AddAsync(It.IsAny(), It.IsAny())); dbContextMock.Verify(x => x.SaveEntitiesAsync(It.IsAny())); } + + [Fact(DisplayName = "Upload document succeeds")] + public async Task UploadDocumentSucceeds() + { + var dbContextMock = new Mock(); + var documentDbSetMock = new List().AsQueryable().BuildMockDbSet(); + dbContextMock.Setup(x => x.Set()) + .Returns(documentDbSetMock.Object); + var blobStorageServiceMock = new Mock(); + + var sut = new FileService(dbContextMock.Object, blobStorageServiceMock.Object); + + const string txtBase64 = "TW9uYWNvIFVuaXQgVGVzdCBGaWxlIGZvciBVcGxvYWQgZG9jdW1lbnQgc3VjY2VlZHMu"; + + await using var stream = new MemoryStream(Convert.FromBase64String(txtBase64)); + await sut.UploadDocument(stream, "sample-text-file.txt", "text/plain", CancellationToken.None); + stream.Close(); + + blobStorageServiceMock.Verify(x => x.UploadTempFileAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + dbContextMock.Verify(x => x.Set().AddAsync(It.IsAny(), It.IsAny())); + dbContextMock.Verify(x => x.SaveEntitiesAsync(It.IsAny())); + } + + [Fact(DisplayName = "Uploading an image file uploads an image succeeds")] + public async Task UploadingImageFileUploadsImageSucceeds() + { + var dbContextMock = new Mock(); + var imageDbSetMock = new List().AsQueryable().BuildMockDbSet(); + dbContextMock.Setup(x => x.Set()) + .Returns(imageDbSetMock.Object); + var blobStorageServiceMock = new Mock(); + blobStorageServiceMock.Setup(x => x.GetFileType(It.IsAny())) + .Returns(FileTypeEnum.Image); + + var sut = new FileService(dbContextMock.Object, blobStorageServiceMock.Object); + + const string imgBase64 = "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"; + + await using var stream = new MemoryStream(Convert.FromBase64String(imgBase64)); + await sut.Upload(stream, "sample-image.png", "image/png", CancellationToken.None); + stream.Close(); + + blobStorageServiceMock.Verify(x => x.UploadTempFileAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + dbContextMock.Verify(x => x.Set().AddAsync(It.IsAny(), It.IsAny())); + dbContextMock.Verify(x => x.SaveEntitiesAsync(It.IsAny())); + } + + [Fact(DisplayName = "Uploading a text file uploads a document succeeds")] + public async Task UploadingTextFileUploadsDocumentSucceeds() + { + var dbContextMock = new Mock(); + var documentDbSetMock = new List().AsQueryable().BuildMockDbSet(); + dbContextMock.Setup(x => x.Set()) + .Returns(documentDbSetMock.Object); + var blobStorageServiceMock = new Mock(); + + var sut = new FileService(dbContextMock.Object, blobStorageServiceMock.Object); + + const string txtBase64 = "TW9uYWNvIFVuaXQgVGVzdCBGaWxlIGZvciBVcGxvYWQgZG9jdW1lbnQgc3VjY2VlZHMu"; + + await using var stream = new MemoryStream(Convert.FromBase64String(txtBase64)); + await sut.Upload(stream, "sample-text-file.txt", "text/plain", CancellationToken.None); + stream.Close(); + + blobStorageServiceMock.Verify(x => x.UploadTempFileAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); + dbContextMock.Verify(x => x.Set().AddAsync(It.IsAny(), It.IsAny())); + dbContextMock.Verify(x => x.SaveEntitiesAsync(It.IsAny())); + } } \ No newline at end of file diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/CompanyDto.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/CompanyDto.cs index 2a267e1..ab22306 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/CompanyDto.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/CompanyDto.cs @@ -3,9 +3,9 @@ public class CompanyDto { public Guid Id { get; set; } - public string Name { get; set; } - public string Email { get; set; } - public string WebSiteUrl { get; set; } + public string Name { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string WebSiteUrl { get; set; } = string.Empty; public string? Street { get; set; } public string? City { get; set; } public string? County { get; set; } diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/CountryDto.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/CountryDto.cs index 9794d16..b860e7b 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/CountryDto.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/CountryDto.cs @@ -3,5 +3,5 @@ public class CountryDto { public Guid Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/CompanyExtensions.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/CompanyExtensions.cs index eee9209..d0108c8 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/CompanyExtensions.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/CompanyExtensions.cs @@ -1,5 +1,4 @@ -using Monaco.Template.Backend.Application.DTOs.Extensions; -using Monaco.Template.Backend.Application.Features.Company.Commands; +using Monaco.Template.Backend.Application.Features.Company.Commands; using Monaco.Template.Backend.Domain.Model; using System.Linq.Expressions; diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/CountryExtensions.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/CountryExtensions.cs index f0e415a..b9092ab 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/CountryExtensions.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/CountryExtensions.cs @@ -5,18 +5,14 @@ namespace Monaco.Template.Backend.Application.DTOs.Extensions; public static class CountryExtensions { - public static CountryDto? Map(this Country? value) - { - if (value == null) - return null; - - var dto = new CountryDto - { - Id = value.Id, - Name = value.Name - }; - return dto; - } + public static CountryDto? Map(this Country? value) => + value is null + ? null + : new() + { + Id = value.Id, + Name = value.Name + }; public static Dictionary>> GetMappedFields() => new() diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/FileExtensions.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/FileExtensions.cs index f962fe4..cdade0c 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/FileExtensions.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/Extensions/FileExtensions.cs @@ -7,44 +7,38 @@ namespace Monaco.Template.Backend.Application.DTOs.Extensions; public static class FileExtensions { - public static FileDto? Map(this File? value) - { - if (value == null) - return null; + public static FileDto? Map(this File? value) => + value is null + ? null + : new() + { + Id = value.Id, + Name = value.Name, + Extension = value.Extension, + ContentType = value.ContentType, + Size = value.Size, + UploadedOn = value.UploadedOn, + IsTemp = value.IsTemp + }; - return new FileDto - { - Id = value.Id, - Name = value.Name, - Extension = value.Extension, - ContentType = value.ContentType, - Size = value.Size, - UploadedOn = value.UploadedOn, - IsTemp = value.IsTemp - }; - } - - public static ImageDto? Map(this Image? value) - { - if (value == null) - return null; - - return new ImageDto - { - DateTaken = value.DateTaken, - Width = value.Width, - Height = value.Height, - ThumbnailId = value.ThumbnailId, - Thumbnail = value.ThumbnailId.HasValue ? value.Thumbnail.Map() : null, - Id = value.Id, - Name = value.Name, - Extension = value.Extension, - ContentType = value.ContentType, - Size = value.Size, - UploadedOn = value.UploadedOn, - IsTemp = value.IsTemp - }; - } + public static ImageDto? Map(this Image? value) => + value is null + ? null + : new() + { + DateTaken = value.DateTaken, + Width = value.Width, + Height = value.Height, + ThumbnailId = value.ThumbnailId, + Thumbnail = value.ThumbnailId.HasValue ? value.Thumbnail.Map() : null, + Id = value.Id, + Name = value.Name, + Extension = value.Extension, + ContentType = value.ContentType, + Size = value.Size, + UploadedOn = value.UploadedOn, + IsTemp = value.IsTemp + }; public static File Map(this FileCreateCommand value, Guid id, FileTypeEnum fileType) => fileType switch diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/FileDownloadDto.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/FileDownloadDto.cs index 255fcfb..c3617e5 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/FileDownloadDto.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/FileDownloadDto.cs @@ -3,6 +3,6 @@ public class FileDownloadDto { public Stream FileContent { get; set; } - public string FileName { get; set; } - public string ContentType { get; set; } + public string FileName { get; set; } = string.Empty; + public string ContentType { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/FileDto.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/FileDto.cs index 716b851..85ba975 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/FileDto.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/DTOs/FileDto.cs @@ -3,9 +3,9 @@ public class FileDto { public Guid Id { get; set; } - public string Name { get; set; } - public string Extension { get; set; } - public string ContentType { get; set; } + public string Name { get; set; } = string.Empty; + public string Extension { get; set; } = string.Empty; + public string ContentType { get; set; } = string.Empty; public long Size { get; set; } public DateTime UploadedOn { get; set; } public bool IsTemp { get; set; } diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/Monaco.Template.Backend.Application.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/Monaco.Template.Backend.Application.csproj index 43ba611..79556e3 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/Monaco.Template.Backend.Application.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/Monaco.Template.Backend.Application.csproj @@ -15,9 +15,9 @@ - + - + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/Services/FileService.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/Services/FileService.cs index ea09305..044d6d2 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Application/Services/FileService.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Application/Services/FileService.cs @@ -5,7 +5,6 @@ using Monaco.Template.Backend.Common.BlobStorage.Contracts; using Monaco.Template.Backend.Domain.Model; using SkiaSharp; -using System.Drawing; using File = Monaco.Template.Backend.Domain.Model.File; using Image = Monaco.Template.Backend.Domain.Model.Image; @@ -66,7 +65,7 @@ public async Task UploadImage(Stream stream, string fileName, string cont var dateTaken = metadata.Get(ExifTag.DateTimeOriginal)?.Value; var gpsLat = metadata.Get(ExifTag.GPSLatitude)?.ToFloat(); var gpsLong = metadata.Get(ExifTag.GPSLongitude)?.ToFloat(); - stream.Position = 0; //Reset streams position to read from beginning + stream.Position = 0; // Reset streams position to read from beginning thumbStream.Position = 0; var imageIds = await Task.WhenAll(_blobStorageService.UploadTempFileAsync(stream, fileName, @@ -85,8 +84,8 @@ public async Task UploadImage(Stream stream, string fileName, string cont contentType, stream.Length, thumbStream.Length, - new Size(image.Width, image.Height), - new Size(thumb.Width, thumb.Height), + (image.Width, image.Height), + (thumb.Width, thumb.Height), dateTaken, gpsLat, gpsLong, @@ -178,9 +177,10 @@ public async Task CopyFile(Guid id, CancellationToken cancellationToken) switch (file) { case Image image: - Guid? thumbCopyId = null; - if (image.ThumbnailId.HasValue) - thumbCopyId = image.ThumbnailId.HasValue ? await _blobStorageService.CopyAsync(image.ThumbnailId.Value, image.IsTemp, cancellationToken) : null; + Guid? thumbCopyId = image.ThumbnailId.HasValue + ? await _blobStorageService.CopyAsync(image.ThumbnailId.Value, image.IsTemp, cancellationToken) + : null; + return await SaveImage(copyId, thumbCopyId, image.Name, @@ -188,8 +188,8 @@ public async Task CopyFile(Guid id, CancellationToken cancellationToken) image.ContentType, image.Size, image.Thumbnail?.Size, - new Size(image.Width, image.Height), - image.ThumbnailId.HasValue ? new Size(image.Thumbnail!.Width, image.Thumbnail.Height) : null, + (image.Width, image.Height), + image.ThumbnailId.HasValue ? (image.Thumbnail!.Width, image.Thumbnail.Height) : null, image.DateTaken, image.GpsLatitude, image.GpsLongitude, @@ -213,12 +213,12 @@ public ExifPropertyCollection GetMetadata(Stream stream) public SKImage GetThumbnail(SKImage image, int thumbnailWidth, int thumbnailHeight) { - //Calculates the proper scale to shrink the image so the aspect ratio remains the same for the thumbnail as well + // Calculates the proper scale to shrink the image so the aspect ratio remains the same for the thumbnail as well var scale = Math.Min(thumbnailWidth / (float)image.Height, thumbnailHeight / (float)image.Width); - if (scale > 1) //If scale is bigger than 1, it means that the thumbnail would end up being bigger than the original image - scale = 1; //So we reset it to 1 so both image and thumbnail are the same size at worst. - //Finally, we use the scale to calculate the final width and height to use for the thumbnail + if (scale > 1) // If scale is bigger than 1, it means that the thumbnail would end up being bigger than the original image + scale = 1; // So we reset it to 1 so both image and thumbnail are the same size at worst. + // Finally, we use the scale to calculate the final width and height to use for the thumbnail var sourceBitmap = SKBitmap.FromImage(image); using var scaledBitmap = sourceBitmap.Resize(new SKImageInfo((int)(image.Width * scale), (int)(image.Height * scale)), @@ -238,8 +238,8 @@ private async Task SaveImage(Guid imageId, string contentType, long imageSize, long? thumbSize, - Size imageDimentions, - Size? thumbDimentions, + (int Height, int Width) imageDimensions, + (int Height, int Width)? thumbDimensions, DateTime? dateTaken, float? gpsLatitude, float? gpsLongitude, @@ -252,20 +252,21 @@ private async Task SaveImage(Guid imageId, thumbSize!.Value, contentType, true, - thumbDimentions!.Value.Height, - thumbDimentions!.Value.Width, + thumbDimensions!.Value.Height, + thumbDimensions!.Value.Width, dateTaken, gpsLatitude, gpsLongitude) : null; + var item = new Image(imageId, name, extension, imageSize, contentType, true, - imageDimentions.Height, - imageDimentions.Width, + imageDimensions.Height, + imageDimensions.Width, dateTaken, gpsLatitude, gpsLongitude, @@ -287,6 +288,7 @@ private async Task SaveDocument(Guid id, size, contentType, true); + return await Save(item, cancellationToken); } diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api.Application/MediatorExtensions.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api.Application/MediatorExtensions.cs index fdfc167..9ba7dcd 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api.Application/MediatorExtensions.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api.Application/MediatorExtensions.cs @@ -18,15 +18,13 @@ public static class MediatorExtensions /// /// /// - public static async Task> ExecuteQueryAsync(this IMediator mediator, - QueryBase query) + public static async Task> ExecuteQueryAsync(this IMediator mediator, QueryBase query) { var result = await mediator.Send(query); - if (result == null) - return new NotFoundResult(); - - return new OkObjectResult(result); + return result is null + ? new NotFoundResult() + : new OkObjectResult(result); } /// @@ -36,15 +34,13 @@ public static async Task> ExecuteQueryAsync(this /// /// /// - public static async Task>> ExecuteQueryAsync(this IMediator mediator, - QueryPagedBase query) + public static async Task>> ExecuteQueryAsync(this IMediator mediator, QueryPagedBase query) { var result = await mediator.Send(query); - if (result == null) - return new NotFoundResult(); - - return new OkObjectResult(result); + return result is null + ? new NotFoundResult() + : new OkObjectResult(result); } /// @@ -54,15 +50,13 @@ public static async Task>> ExecuteQueryAsync /// /// /// - public static async Task> ExecuteQueryAsync(this IMediator mediator, - QueryByIdBase query) + public static async Task> ExecuteQueryAsync(this IMediator mediator, QueryByIdBase query) { - var item = await mediator.Send(query); - - if (item == null) - return new NotFoundResult(); + var result = await mediator.Send(query); - return new OkObjectResult(item); + return result is null + ? new NotFoundResult() + : new OkObjectResult(result); } /// diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api.Application/Monaco.Template.Backend.Common.Api.Application.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api.Application/Monaco.Template.Backend.Common.Api.Application.csproj index 9c56153..8d3f17d 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api.Application/Monaco.Template.Backend.Common.Api.Application.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api.Application/Monaco.Template.Backend.Common.Api.Application.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api/Auth/AuthExtensions.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api/Auth/AuthExtensions.cs index 23bf241..c9b4513 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api/Auth/AuthExtensions.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Api/Auth/AuthExtensions.cs @@ -12,10 +12,10 @@ public static class AuthExtensions public static IServiceCollection AddAuthorizationWithPolicies(this IServiceCollection services, List scopes) => services.AddAuthorization(cfg => - { //DefaultPolicy will require at least authenticated user by default + { // DefaultPolicy will require at least authenticated user by default cfg.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build(); - //Register all listed scopes as policies requiring the existance of such scope in User claims + // Register all listed scopes as policies requiring the existence of such scope in User claims scopes.ForEach(s => cfg.AddPolicy(s, p => p.RequireScope(s))); }); @@ -23,9 +23,9 @@ public static AuthenticationBuilder AddJwtBearerAuthentication(this IServiceColl string authority, string audience, bool requireHttpsMetadata) => - services.AddTransient() //Add transformer to map scopes correctly in ClaimsPrincipal/Identity + services.AddTransient() // Add transformer to map scopes correctly in ClaimsPrincipal/Identity .AddAuthentication() - .AddJwtBearer(options => //Configure validation settings for JWT bearer + .AddJwtBearer(options => // Configure validation settings for JWT bearer { options.Authority = authority; options.Audience = audience; diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Commands/Behaviors/CommandValidationBehavior.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Commands/Behaviors/CommandValidationBehavior.cs index 509febf..b64330f 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Commands/Behaviors/CommandValidationBehavior.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Commands/Behaviors/CommandValidationBehavior.cs @@ -55,7 +55,7 @@ public CommandValidationBehavior(IValidator validator) } /// -/// Behavior to validate the existance of the entity represented by the Command Id. +/// Behavior to validate the existence of the entity represented by the Command Id. /// /// The type of the Command to process public class CommandValidationExistsBehavior : IPipelineBehavior where TCommand : CommandBase @@ -78,7 +78,7 @@ public virtual async Task Handle(TCommand request, RequestHandle } /// -/// Behavior to validate the existance of the entity represented by the Command Id. +/// Behavior to validate the existence of the entity represented by the Command Id. /// /// The type of the Command to process /// The type of data to return along with the CommandResult diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Monaco.Template.Backend.Common.Application.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Monaco.Template.Backend.Common.Application.csproj index bc5a3fd..64e44d2 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Monaco.Template.Backend.Common.Application.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Monaco.Template.Backend.Common.Application.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Validators/Extensions/ValidatorsExtensions.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Validators/Extensions/ValidatorsExtensions.cs index 9b677ee..23b7ed7 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Validators/Extensions/ValidatorsExtensions.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Application/Validators/Extensions/ValidatorsExtensions.cs @@ -13,11 +13,13 @@ public static class ValidatorsExtensions public static void CheckIfExists(this AbstractValidator validator, BaseDbContext dbContext) where TCommand : CommandBase where TEntity : Entity => - validator.RuleSet(ExistsRulesetName, () => validator.RuleFor(x => x.Id).MustExistAsync(dbContext)); + validator.RuleSet(ExistsRulesetName, () => validator.RuleFor(x => x.Id) + .MustExistAsync(dbContext)); public static void CheckIfExists(this AbstractValidator validator, BaseDbContext dbContext) where TCommand : CommandBase where TEntity : Entity => - validator.RuleSet(ExistsRulesetName, () => validator.RuleFor(x => x.Id).MustExistAsync(dbContext)); + validator.RuleSet(ExistsRulesetName, () => validator.RuleFor(x => x.Id) + .MustExistAsync(dbContext)); public static void CheckIfExists(this AbstractValidator validator, Func> predicate) where TCommand : CommandBase => @@ -45,9 +47,9 @@ public static void CheckIfExists(this AbstractValidator validator.RuleSet(ExistsRulesetName, () => validator.RuleFor(selector) .MustAsync(predicate)); - public static void CheckIfExists(this AbstractValidator validator, - Expression> selector, - Func> predicate) where TComomand : CommandBase => + public static void CheckIfExists(this AbstractValidator validator, + Expression> selector, + Func> predicate) where TCommand : CommandBase => validator.RuleSet(ExistsRulesetName, () => validator.RuleFor(selector) .MustAsync(predicate)); diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage.Tests/BlobStorageServiceTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage.Tests/BlobStorageServiceTests.cs new file mode 100644 index 0000000..b7e4388 --- /dev/null +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage.Tests/BlobStorageServiceTests.cs @@ -0,0 +1,51 @@ +using Azure.Storage.Blobs; +using FluentAssertions; +using Moq; +using Xunit; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Monaco.Template.Backend.Common.BlobStorage.Tests; + +[ExcludeFromCodeCoverage] +[Trait("Common Application Services", "Blob Storage Service")] +public class BlobStorageServiceTests +{ + private readonly Mock _blobServiceClientMock; + private readonly BlobStorageService _blobStorageService; + + public BlobStorageServiceTests() + { + _blobServiceClientMock = new Mock(); + _blobStorageService = new(_blobServiceClientMock.Object, It.IsAny()); + } + + [Theory(DisplayName = "Get file type succeeds")] + [MemberData(nameof(GetFileTypeTestData))] + public void GetFileTypeSucceeds(string fileExtension, FileTypeEnum expected) + { + var fileType = _blobStorageService.GetFileType(fileExtension); + fileType.Should().Be(expected); + } + + public static IEnumerable GetFileTypeTestData => + new List + { + new object[] { ".doc", FileTypeEnum.Document }, + new object[] { ".docx", FileTypeEnum.Document }, + new object[] { ".pdf", FileTypeEnum.Document }, + new object[] { ".rtf", FileTypeEnum.Document }, + new object[] { ".txt", FileTypeEnum.Document }, + new object[] { ".xls", FileTypeEnum.Document }, + new object[] { ".xlsx", FileTypeEnum.Document }, + new object[] { ".xlsm", FileTypeEnum.Document }, + new object[] { ".jpg", FileTypeEnum.Image }, + new object[] { ".jpeg", FileTypeEnum.Image }, + new object[] { ".png", FileTypeEnum.Image }, + new object[] { ".bmp", FileTypeEnum.Image }, + new object[] { ".gif", FileTypeEnum.Image }, + new object[] { ".tif", FileTypeEnum.Image }, + new object[] { ".tiff", FileTypeEnum.Image }, + new object[] { ".other", FileTypeEnum.Others } + }; +} \ No newline at end of file diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage.Tests/Monaco.Template.Backend.Common.BlobStorage.Tests.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage.Tests/Monaco.Template.Backend.Common.BlobStorage.Tests.csproj new file mode 100644 index 0000000..e1cec6b --- /dev/null +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage.Tests/Monaco.Template.Backend.Common.BlobStorage.Tests.csproj @@ -0,0 +1,28 @@ + + + + net7.0 + enable + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage/BlobStorageService.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage/BlobStorageService.cs index 051df50..197c87a 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage/BlobStorageService.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage/BlobStorageService.cs @@ -61,15 +61,13 @@ public async Task CopyAsync(Guid fileName, bool isTemp, CancellationToken return destId; } - public FileTypeEnum GetFileType(string fileExtension) - { - return fileExtension.Trim('.') switch + public FileTypeEnum GetFileType(string fileExtension) => + fileExtension.Trim('.').ToLower() switch { "doc" or "docx" or "pdf" or "rtf" or "txt" or "xls" or "xlsx" or "xlsm" => FileTypeEnum.Document, - "jpg" or "jpeg" or "png" or "bmp" or "gif" or "tiff" => FileTypeEnum.Image, + "jpg" or "jpeg" or "png" or "bmp" or "gif" or "tif" or "tiff" => FileTypeEnum.Image, _ => FileTypeEnum.Others, }; - } private static string GetTempPath(string blobFileName) => $"temp/{blobFileName}"; @@ -78,10 +76,10 @@ public FileTypeEnum GetFileType(string fileExtension) private Dictionary GetMetadata(string fileName, string contentType) => new() { - { BlobMetadata.Name, HttpUtility.UrlEncode(Path.GetFileNameWithoutExtension(fileName)) }, - { BlobMetadata.Extension, HttpUtility.UrlEncode(Path.GetExtension(fileName)) }, - { BlobMetadata.ContentType, contentType }, - { BlobMetadata.UploadedOn, DateTime.UtcNow.ToString("O") }, - { BlobMetadata.FileType, GetFileType(Path.GetExtension(fileName)).ToString()} + [BlobMetadata.Name] = HttpUtility.UrlEncode(Path.GetFileNameWithoutExtension(fileName)), + [BlobMetadata.Extension] = HttpUtility.UrlEncode(Path.GetExtension(fileName)), + [BlobMetadata.ContentType] = contentType, + [BlobMetadata.UploadedOn] = DateTime.UtcNow.ToString("O"), + [BlobMetadata.FileType] = GetFileType(Path.GetExtension(fileName)).ToString() }; } \ No newline at end of file diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage/Monaco.Template.Backend.Common.BlobStorage.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage/Monaco.Template.Backend.Common.BlobStorage.csproj index 2e50374..4edf12d 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage/Monaco.Template.Backend.Common.BlobStorage.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.BlobStorage/Monaco.Template.Backend.Common.BlobStorage.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/DomainEventTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/DomainEventTests.cs index a4400b1..678da62 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/DomainEventTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/DomainEventTests.cs @@ -8,12 +8,12 @@ namespace Monaco.Template.Backend.Common.Domain.Tests; [ExcludeFromCodeCoverage] +[Trait("Common Domain Entities", "Domain Event Entity")] public class DomainEventTests { - [Trait("Common Domain Entities", "Domain Event Entity")] [Theory(DisplayName = "New domain event succeeds")] [AnonymousData] - public void NewEntityWithoutParametersSucceeds(DomainEvent sut) + public void NewDomainEventWithoutParametersSucceeds(DomainEvent sut) { sut.DateOccurred.Should().BeCloseTo(DateTime.UtcNow, new TimeSpan(0, 0, 5)); } diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/EntityTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/EntityTests.cs index e9fb776..dbcd066 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/EntityTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/EntityTests.cs @@ -12,9 +12,9 @@ namespace Monaco.Template.Backend.Common.Domain.Tests; [ExcludeFromCodeCoverage] +[Trait("Common Domain Entities", "Base Entity")] public class EntityTests { - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "New entity without parameters succeeds")] [AnonymousData] public void NewEntityWithoutParametersSucceeds(Entity sut) @@ -23,7 +23,6 @@ public void NewEntityWithoutParametersSucceeds(Entity sut) sut.DomainEvents.Should().BeEmpty(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "New entity with parameters succeeds")] [AnonymousData] public void NewEntityWithParametersSucceeds(Guid id) @@ -37,7 +36,6 @@ public void NewEntityWithParametersSucceeds(Guid id) sut.DomainEvents.Should().BeEmpty(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Add Domain Event succeeds")] [AnonymousData] public void AddDomainEventSucceeds(Entity sut, DomainEvent domainEvent) @@ -47,7 +45,6 @@ public void AddDomainEventSucceeds(Entity sut, DomainEvent domainEvent) sut.DomainEvents.Should().HaveCount(1).And.Contain(domainEvent); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Add duplicated domain event allowing duplicates succeeds")] [AnonymousData] public void AddDuplicatedDomainEventAllowingDuplicatesSucceeds(Entity sut, DomainEvent domainEvent) @@ -58,7 +55,6 @@ public void AddDuplicatedDomainEventAllowingDuplicatesSucceeds(Entity sut, Domai sut.DomainEvents.Should().HaveCount(2).And.Contain(new[] { domainEvent, domainEvent }); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Add duplicated domain event not allowing duplicates adds only one")] [AnonymousData] public void AddDuplicatedDomainEventNotAllowingDuplicatesAddsOnlyOne(Entity sut, DomainEvent domainEvent) @@ -69,7 +65,6 @@ public void AddDuplicatedDomainEventNotAllowingDuplicatesAddsOnlyOne(Entity sut, sut.DomainEvents.Should().HaveCount(1).And.Contain(domainEvent); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Add duplicated type of domain event not allowing duplicates adds only one")] [AnonymousData] public void AddDuplicatedTypeOfDomainEventNotAllowingDuplicatesAddsOnlyOne(Entity sut, DomainEvent domainEvent1, DomainEvent domainEvent2) @@ -80,7 +75,6 @@ public void AddDuplicatedTypeOfDomainEventNotAllowingDuplicatesAddsOnlyOne(Entit sut.DomainEvents.Should().HaveCount(1).And.Contain(domainEvent1); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Remove Domain Event succeeds")] [AnonymousData] public void RemoveDomainEventSucceeds(Entity sut, List domainEvents) @@ -92,7 +86,6 @@ public void RemoveDomainEventSucceeds(Entity sut, List domainEvents sut.DomainEvents.Should().HaveCount(2).And.Contain(new[] { domainEvents[1], domainEvents[2] }); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Remove Domain Event succeeds")] [AnonymousData] public void RemoveNonExistingDomainEventSucceeds(Entity sut, List domainEvents) @@ -107,7 +100,6 @@ public void RemoveNonExistingDomainEventSucceeds(Entity sut, List d sut.DomainEvents.Should().HaveCount(2).And.Contain(new[] { domainEvents[0], domainEvents[1] }); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Clear Domain Events succeeds")] [AnonymousData] public void ClearDomainEventsSucceeds(Entity sut, List domainEvents) @@ -121,7 +113,6 @@ public void ClearDomainEventsSucceeds(Entity sut, List domainEvents sut.DomainEvents.Should().BeEmpty(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Entity instance equals (method) itself succeeds")] [AnonymousData] public void EntityInstanceEqualsMethodItselfSucceeds(Entity sut) @@ -129,7 +120,6 @@ public void EntityInstanceEqualsMethodItselfSucceeds(Entity sut) sut.Equals(sut).Should().BeTrue(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Entity instance equals (method) another instance same Id as same succeeds")] [AnonymousData] public void EntityInstanceEqualsMethodAnotherInstanceSameIdAsSameSucceeds(Guid id) @@ -143,7 +133,6 @@ public void EntityInstanceEqualsMethodAnotherInstanceSameIdAsSameSucceeds(Guid i sut.Equals(instance).Should().BeTrue(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Entity instance equals (method) another instance as different succeeds")] [AnonymousData] public void EntityInstanceEqualsMethodAnotherInstanceAsDifferentSucceeds(Entity sut, Entity instance) @@ -151,7 +140,6 @@ public void EntityInstanceEqualsMethodAnotherInstanceAsDifferentSucceeds(Entity sut.Equals(instance).Should().BeFalse(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Entity instance equals (method) another different object as different succeeds ")] [AnonymousData] public void EntityInstanceEqualsMethodAnotherDifferentObjectAsDifferentSucceeds(Entity sut, object instance) @@ -159,7 +147,6 @@ public void EntityInstanceEqualsMethodAnotherDifferentObjectAsDifferentSucceeds( sut.Equals(instance).Should().BeFalse(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Entity instance equals (method) another different not proxy object as different succeeds ")] [AnonymousData] public void EntityInstanceEqualsMethodAnotherDifferentNotProxyObjectAsDifferentSucceeds(Entity sut) @@ -167,7 +154,6 @@ public void EntityInstanceEqualsMethodAnotherDifferentNotProxyObjectAsDifferentS sut.Equals(new object()).Should().BeFalse(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Entity instance equals (operator) itself succeeds")] [AnonymousData] public void EntityInstanceEqualsOperatorItselfSucceeds(Entity sut) @@ -178,7 +164,6 @@ public void EntityInstanceEqualsOperatorItselfSucceeds(Entity sut) #pragma warning restore CS1718 // Comparison made to same variable } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Entity instance equals (operator) as different succeeds")] [AnonymousData] public void EntityInstanceEqualsOperatorNullAsDifferentSucceeds(Entity sut) @@ -186,14 +171,12 @@ public void EntityInstanceEqualsOperatorNullAsDifferentSucceeds(Entity sut) (sut == null).Should().BeFalse(); } - [Trait("Common Domain Entities", "Base Entity")] [Fact(DisplayName = "Entity instance null equals (operator) null as same succeeds")] public void EntityInstanceNullEqualsOperatorNullAsSameSucceeds() { (null as Entity == null).Should().BeTrue(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Entity instance equals (operator) null as different succeeds")] [AnonymousData] public void EntityInstanceNotEqualsOperatorNullAsDifferentSucceeds(Entity? sut) @@ -201,7 +184,6 @@ public void EntityInstanceNotEqualsOperatorNullAsDifferentSucceeds(Entity? sut) (sut != null).Should().BeTrue(); } - [Trait("Common Domain Entities", "Base Entity")] [Theory(DisplayName = "Get Entity hash code succeeds")] [AnonymousData] public void EntityGetHashCodeSucceeds(Entity sut) diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/EnumerationTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/EnumerationTests.cs index a35924c..238d83a 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/EnumerationTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/EnumerationTests.cs @@ -10,9 +10,9 @@ namespace Monaco.Template.Backend.Common.Domain.Tests; [ExcludeFromCodeCoverage] +[Trait("Common Domain Entities", "Enumeration Entity")] public class EnumerationTests { - [Trait("Common Domain Entities", "Enumeration Entity")] [Theory(DisplayName = "New enumeration instance succeeds")] [AnonymousData] public void NewEnumerationInstanceSucceeds(Guid id, string name) @@ -26,7 +26,6 @@ public void NewEnumerationInstanceSucceeds(Guid id, string name) sut.Name.Should().Be(name); } - [Trait("Common Domain Entities", "Enumeration Entity")] [Theory(DisplayName = "Enumeration to string is name")] [AnonymousData] public void EnumerationToStringIsName(Guid id, string name) @@ -39,7 +38,6 @@ public void EnumerationToStringIsName(Guid id, string name) sut.ToString().Should().Be(name); } - [Trait("Common Domain Entities", "Enumeration Entity")] [Fact(DisplayName = "Get all items from Enumeration succeeds")] public void GetAllItemsFromEnumerationSucceeds() { @@ -47,7 +45,6 @@ public void GetAllItemsFromEnumerationSucceeds() .Should().HaveCount(2).And.Contain(new[] { DummyEnumerationDerived.Item3, DummyEnumerationDerived.Item4 }); } - [Trait("Common Domain Entities", "Enumeration Entity")] [Fact(DisplayName = "Get an enumeration item from its value succeeds")] public void GetAnEnumerationItemFromItsValueSucceeds() { @@ -58,7 +55,6 @@ public void GetAnEnumerationItemFromItsValueSucceeds() result.Should().Be(DummyEnumeration.Item1); } - [Trait("Common Domain Entities", "Enumeration Entity")] [Theory(DisplayName = "Get an enumeration item from invalid value fails")] [AnonymousData] public void GetAnEnumerationItemFromInvalidValueFails(Guid id) @@ -68,7 +64,6 @@ public void GetAnEnumerationItemFromInvalidValueFails(Guid id) action.Should().Throw().WithMessage($"'{id}' is not a valid value in {typeof(DummyEnumeration)}"); } - [Trait("Common Domain Entities", "Enumeration Entity")] [Fact(DisplayName = "Get an enumeration item from its name succeeds")] public void GetAnEnumerationItemFromItsNameSucceeds() { @@ -79,7 +74,6 @@ public void GetAnEnumerationItemFromItsNameSucceeds() result.Should().Be(DummyEnumeration.Item1); } - [Trait("Common Domain Entities", "Enumeration Entity")] [Theory(DisplayName = "Get an enumeration item from invalid value fails")] [AnonymousData] public void GetAnEnumerationItemFromInvalidNameFails(string name) @@ -89,7 +83,6 @@ public void GetAnEnumerationItemFromInvalidNameFails(string name) action.Should().Throw().WithMessage($"'{name}' is not a valid display name in {typeof(DummyEnumeration)}"); } - [Trait("Common Domain Entities", "Enumeration Entity")] [Fact(DisplayName = "Enumeration compare succeeds")] public void EnumerationCompareSucceds() { diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/Monaco.Template.Backend.Common.Domain.Tests.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/Monaco.Template.Backend.Common.Domain.Tests.csproj index 7ecb5d3..67f46b7 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/Monaco.Template.Backend.Common.Domain.Tests.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/Monaco.Template.Backend.Common.Domain.Tests.csproj @@ -8,10 +8,10 @@ - - + + - + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/PageTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/PageTests.cs index c44de78..2065ccc 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/PageTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/PageTests.cs @@ -13,7 +13,7 @@ public class PageTests [Trait("Common Domain Entities", "Page Entity")] [Theory(DisplayName = "Create a new page succeeds")] [AnonymousData] - public void Create_a_new_page(List results, int offset, int limit, long count) + public void CreateNewPageSucceeds(List results, int offset, int limit, long count) { var sut = new Page(results, offset, limit, count); @@ -24,7 +24,7 @@ public void Create_a_new_page(List results, int offset, int limit, long [Trait("Common Domain Entities", "Pager Entity")] [Theory(DisplayName = "Create a new pager succeeds")] [AnonymousData] - public void Create_a_new_pager(int offset, int limit, long count) + public void CreateNewPagerSucceeds(int offset, int limit, long count) { var sut = new Pager(offset, limit, count); diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/ValueObjectTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/ValueObjectTests.cs index 1013a91..f265f79 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/ValueObjectTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain.Tests/ValueObjectTests.cs @@ -8,9 +8,9 @@ namespace Monaco.Template.Backend.Common.Domain.Tests; [ExcludeFromCodeCoverage] +[Trait("Common Domain entities", "ValueObject")] public class ValueObjectTests { - [Trait("Common Domain entities", "ValueObject")] [Theory(DisplayName = "New ValueObject instance succeeds")] [AnonymousData] public void NewValueObjectInstanceSucceeds(string field1, string? field2) @@ -21,7 +21,6 @@ public void NewValueObjectInstanceSucceeds(string field1, string? field2) sut.Field2.Should().Be(field2); } - [Trait("Common Domain entities", "ValueObject")] [Theory(DisplayName = "Different ValueObject instances with same values are equal")] [AnonymousData] public void DifferentValueObjectInstancesWithSameValuesAreEqual(string field1, string? field2) @@ -33,7 +32,6 @@ public void DifferentValueObjectInstancesWithSameValuesAreEqual(string field1, s val1.Equals(val2).Should().BeTrue(); } - [Trait("Common Domain entities", "ValueObject")] [Theory(DisplayName = "Different ValueObject instances with same values are not equal")] [AnonymousData] public void DifferentValueObjectInstancesWithDifferentValuesAreNotEqual(string field1, string? field2, string? field3) @@ -45,7 +43,6 @@ public void DifferentValueObjectInstancesWithDifferentValuesAreNotEqual(string f val1.Equals(val2).Should().BeFalse(); } - [Trait("Common Domain entities", "ValueObject")] [Theory(DisplayName = "Different ValueObject instances with same values are not equal")] [AnonymousData] public void ValueObjectComparedAgainstNullIsNotEqual(string field1, string? field2) diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain/Monaco.Template.Backend.Common.Domain.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain/Monaco.Template.Backend.Common.Domain.csproj index 2546c40..b1c3857 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain/Monaco.Template.Backend.Common.Domain.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Domain/Monaco.Template.Backend.Common.Domain.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Context/Extensions/FilterExtensions.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Context/Extensions/FilterExtensions.cs index 31ce890..2a4f247 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Context/Extensions/FilterExtensions.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Context/Extensions/FilterExtensions.cs @@ -59,13 +59,13 @@ public static IEnumerable ApplyFilter(this IEnumerable source, var (filterMapLower, filterList, predicate) = GetData(queryString, filterMap, defaultCondition); - foreach (var (key, values) in filterList) //and while looping through the list of valid ones to use + foreach (var (key, values) in filterList) // and while looping through the list of valid ones to use { - var predicateKey = PredicateBuilder.New(false); //Declare a PredicateBuilder for the current key values + var predicateKey = PredicateBuilder.New(false); // Declare a PredicateBuilder for the current key values predicateKey = values.Where(value => ValidateDataType(value, GetBodyExpression(filterMapLower[key]).Type)) - .Select(value => GetOperationExpression(key, filterMapLower[key], value, true)) //then generate the expresion for each value - .Aggregate(predicateKey, (current, expr) => current.Or(expr)); //and chain them all with an OR operator - predicate = allConditions ? predicate.And(predicateKey) : predicate.Or(predicateKey); //then add the resulting expresion to the more general predicate + .Select(value => GetOperationExpression(key, filterMapLower[key], value, true)) // then generate the expression for each value + .Aggregate(predicateKey, (current, expr) => current.Or(expr)); // and chain them all with an OR operator + predicate = allConditions ? predicate.And(predicateKey) : predicate.Or(predicateKey); // then add the resulting expression to the more general predicate } return source.Where(predicate); @@ -78,13 +78,13 @@ private static (Dictionary>> filterMapLower, Dictionary>> filterMap, bool defaultCondition) { - //convert to Dictionary with Keys in Lowercase to ease search + // convert to Dictionary with Keys in Lowercase to ease search var filterMapLower = filterMap.ToDictionary(x => x.Key.ToLower(), x => x.Value); - //convert field list to filter in a lowercase list, then filter the ones that don't exist and the null ones + // convert field list to filter in a lowercase list, then filter the ones that don't exist and the null ones var filterList = queryString.ToDictionary(q => q.Key.ToLower(), q => q.Value.Select(v => v?.ToLower())) .Where(x => filterMapLower.ContainsKey(x.Key.ToLower())); - var predicate = PredicateBuilder.New(defaultCondition); //Generate a PredicateBuilder (True to return all by default) + var predicate = PredicateBuilder.New(defaultCondition); // Generate a PredicateBuilder (True to return all by default) return (filterMapLower, filterList, predicate); } @@ -94,21 +94,21 @@ private static Expression GetBodyExpression(Expression> expre private static Expression> GetOperationExpression(string fieldKey, Expression> fieldMap, object? value, bool toLowerCase = false) { - //Obtain expression Type and based on it y choose the method for the operation on the DB depending on if it's a String or any other type + // Obtain expression Type and based on it choose the method for the operation on the DB depending on if it's a String or any other type var bodyExpression = GetBodyExpression(fieldMap); var type = bodyExpression.Type; - //Create the call to the method using the selected expression, the method and the value to search. If it's a string and it´s working on an IEnumerable, first apply ToLower + // Create the call to the method using the selected expression, the method and the value to search. If it's a string and it´s working on an IEnumerable, first apply ToLower Expression expression; - if ((value as string) is not { Length: > 0 }) //Handles comparison against null values + if ((value as string) is not { Length: > 0 }) // Handles comparison against null values expression = Expression.Equal(type == typeof(string) || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) ? bodyExpression //for strings : Expression.Convert(bodyExpression, typeof(Nullable<>).MakeGenericType(type)), //for all others Expression.Constant(null)); - else if (type == typeof(string)) //Handles string values + else if (type == typeof(string)) // Handles string values { - expression = toLowerCase //If it's needed, applies lower case to entire string to ignore casing differences + expression = toLowerCase // If needed, applies lower case to entire string to ignore casing differences ? Expression.Call(bodyExpression, type.GetMethod(nameof(string.ToLower), Type.EmptyTypes)!) : bodyExpression; @@ -116,14 +116,14 @@ private static Expression> GetOperationExpression(string fieldK var not = strValue is ['!', ..]; if (not) strValue = strValue[1..]; - if (strValue is ['"', .., '"']) //quoted strings searches as exactly the same + if (strValue is ['"', .., '"']) // quoted strings searches as exactly the same { var constExpr = Expression.Constant(Convert.ChangeType(strValue[1..^1], type)); expression = not ? Expression.NotEqual(expression, constExpr) : Expression.Equal(expression, constExpr); } - else //otherwise searches with Contains + else // otherwise searches with Contains { expression = Expression.Call(expression, type.GetMethod(nameof(string.Contains), new[] { type })!, @@ -133,15 +133,15 @@ private static Expression> GetOperationExpression(string fieldK } else if (type.IsEnum) expression = Expression.Equal(bodyExpression, Expression.Constant(Enum.Parse(type, (string.IsNullOrWhiteSpace(value?.ToString()) ? 0.ToString() : value!.ToString()) ?? 0.ToString()))); - else if (type.IsAssignableFrom(typeof(Guid))) //Handles Guid values + else if (type.IsAssignableFrom(typeof(Guid))) // Handles Guid values expression = value.ToString() is ['!', ..] ? Expression.NotEqual(bodyExpression, Expression.Constant(Guid.Parse(value.ToString()![1..]), type)) : Expression.Equal(bodyExpression, Expression.Constant(Guid.Parse(value.ToString()!), type)); - else if (type.IsAssignableFrom(typeof(DateTime)) && fieldKey.EndsWith("from")) //Handles DateTime values whose param name ends with From (range start) + else if (type.IsAssignableFrom(typeof(DateTime)) && fieldKey.EndsWith("from")) // Handles DateTime values whose param name ends with From (range start) expression = Expression.GreaterThanOrEqual(bodyExpression, Expression.Constant(DateTime.Parse(value.ToString()!), type)); - else if (type.IsAssignableFrom(typeof(DateTime)) && fieldKey.EndsWith("to")) //Handles DateTime values whose param name ends with To (range end) + else if (type.IsAssignableFrom(typeof(DateTime)) && fieldKey.EndsWith("to")) // Handles DateTime values whose param name ends with To (range end) expression = Expression.LessThanOrEqual(bodyExpression, Expression.Constant(DateTime.Parse(value.ToString()!), type)); - else //Handles all other generic cases (numbers, booleans, etc) + else // Handles all other generic cases (numbers, booleans, etc) { var underlyingType = Nullable.GetUnderlyingType(type); expression = value.ToString() is ['!', ..] @@ -149,7 +149,7 @@ private static Expression> GetOperationExpression(string fieldK : Expression.Equal(Expression.Convert(bodyExpression, underlyingType ?? type), Expression.Constant(Convert.ChangeType(value, underlyingType ?? type))); } - //Create and return the lambda expression that represents the operation to run + // Create and return the lambda expression that represents the operation to run return Expression.Lambda>(expression, fieldMap.Parameters); } diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Context/Extensions/OperationsExtensions.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Context/Extensions/OperationsExtensions.cs index 3f587bf..2978a23 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Context/Extensions/OperationsExtensions.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Context/Extensions/OperationsExtensions.cs @@ -13,7 +13,7 @@ public static Task ExistsAsync(this DbContext dbContext, public static Task ExistsAsync(this DbContext dbContext, Expression> predicate, - CancellationToken cancellationToken) where T : class => + CancellationToken cancellationToken) where T : class => dbContext.Set().AnyAsync(predicate, cancellationToken); public static async Task GetAsync(this DbContext dbContext, @@ -33,4 +33,13 @@ public static IQueryable Set(this DbContext context, Type t) = .GetMethod("Set", Type.EmptyTypes)? .MakeGenericMethod(t) .Invoke(context, Array.Empty())!; + + public static async Task> GetListByIdsAsync(this DbContext dbContext, + IEnumerable items, + CancellationToken cancellationToken) where T : Entity => + items?.Any() == true + ? await dbContext.Set() + .Where(x => items.Contains(x.Id)) + .ToListAsync(cancellationToken) + : new(); } \ No newline at end of file diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Monaco.Template.Backend.Common.Infrastructure.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Monaco.Template.Backend.Common.Infrastructure.csproj index 85a7980..bf811e9 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Monaco.Template.Backend.Common.Infrastructure.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Infrastructure/Monaco.Template.Backend.Common.Infrastructure.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Tests/Monaco.Template.Backend.Common.Tests.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Tests/Monaco.Template.Backend.Common.Tests.csproj index 279f811..bc3fb28 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Tests/Monaco.Template.Backend.Common.Tests.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Common.Tests/Monaco.Template.Backend.Common.Tests.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/AddressTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/AddressTests.cs index f347dde..216e489 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/AddressTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/AddressTests.cs @@ -7,9 +7,9 @@ namespace Monaco.Template.Backend.Domain.Tests; [ExcludeFromCodeCoverage] +[Trait("Core Domain Entities", "Address Entity")] public class AddressTests { - [Trait("Core Domain Entities", "Address Entity")] [Theory(DisplayName = "New address succeeds")] [AnonymousData] public void NewAddressSucceeds(string? street, diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/CompanyTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/CompanyTests.cs index 11554fd..c15865b 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/CompanyTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/CompanyTests.cs @@ -7,9 +7,9 @@ namespace Monaco.Template.Backend.Domain.Tests; [ExcludeFromCodeCoverage] +[Trait("Core Domain Entities", "Company Entity")] public class CompanyTests { - [Trait("Core Domain Entities", "Company Entity")] [Theory(DisplayName = "New company succeeds")] [AnonymousData] public void NewCompanySucceeds(string name, @@ -29,7 +29,6 @@ public void NewCompanySucceeds(string name, sut.Version.Should().BeNull(); } - [Trait("Core Domain Entities", "Company Entity")] [Theory(DisplayName = "New company succeeds")] [AnonymousData] public void UpdateCompanySucceeds(Company sut, diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/CountryTests.cs b/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/CountryTests.cs index 86d5eb1..e13274f 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/CountryTests.cs +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/CountryTests.cs @@ -7,9 +7,9 @@ namespace Monaco.Template.Backend.Domain.Tests; [ExcludeFromCodeCoverage] +[Trait("Core Domain Entities", "Country Entity")] public class CountryTests { - [Trait("Core Domain Entities", "Country Entity")] [Theory(DisplayName = "New country succeeds")] [AnonymousData] public void NewCountrySucceeds(string name) diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/Monaco.Template.Backend.Domain.Tests.csproj b/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/Monaco.Template.Backend.Domain.Tests.csproj index 8b21c55..e5812c1 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/Monaco.Template.Backend.Domain.Tests.csproj +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.Domain.Tests/Monaco.Template.Backend.Domain.Tests.csproj @@ -8,7 +8,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Content/Backend/Solution/Monaco.Template.Backend.sln b/src/Content/Backend/Solution/Monaco.Template.Backend.sln index 2a93f46..702c527 100644 --- a/src/Content/Backend/Solution/Monaco.Template.Backend.sln +++ b/src/Content/Backend/Solution/Monaco.Template.Backend.sln @@ -64,6 +64,8 @@ EndProject #if (filesSupport) Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Monaco.Template.Backend.Common.BlobStorage", "Monaco.Template.Backend.Common.BlobStorage\Monaco.Template.Backend.Common.BlobStorage.csproj", "{42E51D47-B82F-4A92-B1E5-CD8BE44DE6F0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Monaco.Template.Backend.Common.BlobStorage.Tests", "Monaco.Template.Backend.Common.BlobStorage.Tests\Monaco.Template.Backend.Common.BlobStorage.Tests.csproj", "{D8623B90-59C1-4753-A0E6-F2DBD4305C9B}" +EndProject #endif Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -150,6 +152,10 @@ Global {42E51D47-B82F-4A92-B1E5-CD8BE44DE6F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {42E51D47-B82F-4A92-B1E5-CD8BE44DE6F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {42E51D47-B82F-4A92-B1E5-CD8BE44DE6F0}.Release|Any CPU.Build.0 = Release|Any CPU + {D8623B90-59C1-4753-A0E6-F2DBD4305C9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8623B90-59C1-4753-A0E6-F2DBD4305C9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8623B90-59C1-4753-A0E6-F2DBD4305C9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8623B90-59C1-4753-A0E6-F2DBD4305C9B}.Release|Any CPU.Build.0 = Release|Any CPU #endif EndGlobalSection GlobalSection(SolutionProperties) = preSolution @@ -173,11 +179,11 @@ Global #if (apiGateway) {815EF0B0-5FDA-4D1E-BACE-DA7E440D5D34} = {8BFE9C37-2620-4156-88B8-286537954C5E} #endif - {426A6AB4-95F6-46AC-AB6D-98C4612F74E5} = {35484293-234F-40DA-B430-A95170EDE449} {E5781B96-E0CA-4762-9412-C4202E0CA08F} = {920A23AF-59DA-4453-B825-0549B1C04F5B} {B09E0906-8522-4B70-8C55-958415DAF21D} = {920A23AF-59DA-4453-B825-0549B1C04F5B} #if (filesSupport) {42E51D47-B82F-4A92-B1E5-CD8BE44DE6F0} = {8BFE9C37-2620-4156-88B8-286537954C5E} + {D8623B90-59C1-4753-A0E6-F2DBD4305C9B} = {35484293-234F-40DA-B430-A95170EDE449} #endif EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/src/Monaco.Template.nuspec b/src/Monaco.Template.nuspec index 25f21dd..68e0883 100644 --- a/src/Monaco.Template.nuspec +++ b/src/Monaco.Template.nuspec @@ -2,7 +2,7 @@ Monaco.Template - 2.0.2 + 2.0.3 Monaco Template Templates for .NET projects