From d7edf54af1bf57d68e03c596f69a10054acc42e2 Mon Sep 17 00:00:00 2001 From: marcinkanardev Date: Sat, 5 Nov 2022 13:10:24 +0100 Subject: [PATCH 1/5] Added UpdateDetails and created basic classes for products testing. --- .../Events/ProductUpdated.cs | 3 + .../Products/Commands/CreateProduct.cs | 5 +- .../Products/Commands/DeleteProduct.cs | 50 +++++++++++++ .../Products/Commands/UpdateDetails.cs | 58 ++++++++++++++- .../Products.Core/Products.Core.csproj | 4 ++ .../DbConfigExtensions.cs | 24 +++++++ .../ProductApiFactory.cs | 71 +++++++++++++++++++ .../Products.IntegrationTests.csproj | 19 ++++- .../Products/DeleteProductsTests.cs | 40 +++++++++++ .../Products.IntegrationTests/TestBrands.cs | 28 ++++++++ .../TestCountries.cs | 31 ++++++++ .../Products.IntegrationTests/TestProducts.cs | 38 ++++++++++ 12 files changed, 366 insertions(+), 5 deletions(-) create mode 100644 src/Products/Products.Contracts/Events/ProductUpdated.cs create mode 100644 src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs create mode 100644 tests/Products/Products.IntegrationTests/DbConfigExtensions.cs create mode 100644 tests/Products/Products.IntegrationTests/ProductApiFactory.cs create mode 100644 tests/Products/Products.IntegrationTests/Products/DeleteProductsTests.cs create mode 100644 tests/Products/Products.IntegrationTests/TestBrands.cs create mode 100644 tests/Products/Products.IntegrationTests/TestCountries.cs create mode 100644 tests/Products/Products.IntegrationTests/TestProducts.cs diff --git a/src/Products/Products.Contracts/Events/ProductUpdated.cs b/src/Products/Products.Contracts/Events/ProductUpdated.cs new file mode 100644 index 0000000..02d7767 --- /dev/null +++ b/src/Products/Products.Contracts/Events/ProductUpdated.cs @@ -0,0 +1,3 @@ +namespace IGroceryStore.Products.Contracts.Events; + +public record class ProductUpdated(ulong Id, string Name); diff --git a/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs b/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs index 88b9ff8..ab4a22e 100644 --- a/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs +++ b/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs @@ -11,7 +11,6 @@ using IGroceryStore.Shared.Services; using IGroceryStore.Shared.Validation; using MassTransit; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; @@ -34,7 +33,9 @@ public void RegisterEndpoint(IEndpointRouteBuilder endpoints) => endpoints.MapPost("api/products") .RequireAuthorization() .AddEndpointFilter>() - .WithTags(SwaggerTags.Products); + .WithTags(SwaggerTags.Products) + .Produces(400) + .Produces(202); } internal class CreateProductHandler : ICommandHandler diff --git a/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs b/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs new file mode 100644 index 0000000..15b197d --- /dev/null +++ b/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs @@ -0,0 +1,50 @@ +using IGroceryStore.Products.Core.Entities; +using IGroceryStore.Products.Core.Exceptions; +using IGroceryStore.Products.Core.Persistence.Contexts; +using IGroceryStore.Shared.Abstraction.Commands; +using IGroceryStore.Shared.Abstraction.Common; +using IGroceryStore.Shared.Abstraction.Constants; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.EntityFrameworkCore; + +namespace IGroceryStore.Products.Core.Features.Products.Commands; + +internal record DeleteProduct(ulong Id) : IHttpCommand; + +public class DeleteProductEndpoint : IEndpoint +{ + public void RegisterEndpoint(IEndpointRouteBuilder endpoints) => + endpoints.MapDelete("products/{id}") + //.RequireAuthorization() + .WithTags(SwaggerTags.Products) + .Produces(204) + .Produces(400); + +} + +internal class DeleteProductHandler : ICommandHandler +{ + private readonly ProductsDbContext _productsDbContext; + + public DeleteProductHandler(ProductsDbContext productsDbContext) + { + _productsDbContext = productsDbContext; + } + + public async Task HandleAsync(DeleteProduct command, CancellationToken cancellationToken = default) + { + var products = await _productsDbContext.Products.ToListAsync(); + + var product = + await _productsDbContext.Products.FirstOrDefaultAsync(x => x.Id.Equals(command.Id), cancellationToken); + + if (product is null) throw new ProductNotFoundException(command.Id); + + _productsDbContext.Products.Remove(product); + await _productsDbContext.SaveChangesAsync(cancellationToken); + + return Results.NoContent(); + } +} diff --git a/src/Products/Products.Core/Features/Products/Commands/UpdateDetails.cs b/src/Products/Products.Core/Features/Products/Commands/UpdateDetails.cs index c5442fd..f026f7a 100644 --- a/src/Products/Products.Core/Features/Products/Commands/UpdateDetails.cs +++ b/src/Products/Products.Core/Features/Products/Commands/UpdateDetails.cs @@ -1,5 +1,59 @@ -namespace IGroceryStore.Products.Features.Products.Commands; +using IGroceryStore.Products.Core.Exceptions; +using IGroceryStore.Products.Core.Persistence.Contexts; +using IGroceryStore.Products.Contracts.Events; +using IGroceryStore.Shared.Abstraction.Commands; +using IGroceryStore.Shared.Abstraction.Common; +using IGroceryStore.Shared.Abstraction.Constants; +using MassTransit; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Routing; +using Microsoft.EntityFrameworkCore; -internal class UpdateDetails +namespace IGroceryStore.Products.Features.Products.Commands; + +internal record UpdateDetails(UpdateDetails.UpdateDetailsBody DetailsBody, ulong Id) : IHttpCommand +{ + internal record UpdateDetailsBody(string Name, + string Description + ); +} + +public class UpdateDetailsEndpoint : IEndpoint +{ + public void RegisterEndpoint(IEndpointRouteBuilder endpoints) + => endpoints.MapPut("api/products") + .WithTags(SwaggerTags.Products) + .Produces(204) + .Produces(400); +} + +internal class UpdateDetailsHandler : ICommandHandler { + private readonly ProductsDbContext _context; + private readonly IBus _bus; + + public UpdateDetailsHandler(ProductsDbContext context, IBus bus) + { + _context = context; + _bus = bus; + } + + public async Task HandleAsync(UpdateDetails command, CancellationToken cancellationToken = default) + { + var product = await _context.Products.FirstOrDefaultAsync(x => x.Id.Equals(command.Id), cancellationToken); + + if (product is null) throw new ProductNotFoundException(command.Id); + + var (name, description) = command.DetailsBody; + + product.Name = name; + product.Description = description; + _context.Update(product); + await _context.SaveChangesAsync(cancellationToken); + + await _bus.Publish(new ProductUpdated(command.Id, name), cancellationToken); + + return Results.NoContent(); + } } diff --git a/src/Products/Products.Core/Products.Core.csproj b/src/Products/Products.Core/Products.Core.csproj index cbe58e8..40af236 100644 --- a/src/Products/Products.Core/Products.Core.csproj +++ b/src/Products/Products.Core/Products.Core.csproj @@ -24,4 +24,8 @@ + + + + diff --git a/tests/Products/Products.IntegrationTests/DbConfigExtensions.cs b/tests/Products/Products.IntegrationTests/DbConfigExtensions.cs new file mode 100644 index 0000000..93fa395 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/DbConfigExtensions.cs @@ -0,0 +1,24 @@ +using DotNet.Testcontainers.Containers; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Products.IntegrationTests; + +public static class DbConfigExtensions +{ + public static void CleanDbContextOptions(this IServiceCollection services) + where T : DbContext + { + var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); + if (descriptor != null) services.Remove(descriptor); + services.RemoveAll(typeof(T)); + } + + public static void AddPostgresContext(this IServiceCollection services, TestcontainerDatabase dbContainer) + where T : DbContext + { + services.AddDbContext(ctx => + ctx.UseNpgsql(dbContainer.ConnectionString)); + } +} diff --git a/tests/Products/Products.IntegrationTests/ProductApiFactory.cs b/tests/Products/Products.IntegrationTests/ProductApiFactory.cs new file mode 100644 index 0000000..4d34c28 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/ProductApiFactory.cs @@ -0,0 +1,71 @@ +using Bogus; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Configurations; +using DotNet.Testcontainers.Containers; +using IGroceryStore.API; +using IGroceryStore.Baskets.Core.Persistence; +using IGroceryStore.Products.Core.Persistence.Contexts; +using IGroceryStore.Products.Contracts.Events; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; +using Xunit; +using MassTransit; +using Microsoft.AspNetCore.TestHost; + +namespace Products.IntegrationTests; + +public class ProductApiFactory : WebApplicationFactory, IAsyncLifetime +{ + private readonly TestcontainerDatabase _dbContainer = + new TestcontainersBuilder() + .WithDatabase(new PostgreSqlTestcontainerConfiguration + { + Database = "db", + Username = "postgres", + Password = "postgres" + }) + .WithAutoRemove(true) + .WithCleanUp(true) + .Build(); + + public ProductApiFactory() + { + Randomizer.Seed = new Random(420); + VerifierSettings.ScrubInlineGuids(); + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureLogging(logging => { logging.ClearProviders(); }); + + builder.ConfigureServices(services => + { + services.AddMassTransitTestHarness(x => + { + x.AddHandler(context => context.ConsumeCompleted); + }); + }); + + builder.ConfigureTestServices(services => + { + //services.CleanDbContextOptions(); + services.CleanDbContextOptions(); + services.CleanDbContextOptions(); + + //services.AddPostgresContext(_dbContainer); + services.AddPostgresContext(_dbContainer); + services.AddPostgresContext(_dbContainer); + }); + } + + public async Task InitializeAsync() + { + await _dbContainer.StartAsync(); + } + + async Task IAsyncLifetime.DisposeAsync() + { + await _dbContainer.DisposeAsync(); + } +} diff --git a/tests/Products/Products.IntegrationTests/Products.IntegrationTests.csproj b/tests/Products/Products.IntegrationTests/Products.IntegrationTests.csproj index 3ce04a7..b1274b0 100644 --- a/tests/Products/Products.IntegrationTests/Products.IntegrationTests.csproj +++ b/tests/Products/Products.IntegrationTests/Products.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -10,6 +10,23 @@ IGroceryStore.Products.IntegrationTests + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + diff --git a/tests/Products/Products.IntegrationTests/Products/DeleteProductsTests.cs b/tests/Products/Products.IntegrationTests/Products/DeleteProductsTests.cs new file mode 100644 index 0000000..da0baf7 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/Products/DeleteProductsTests.cs @@ -0,0 +1,40 @@ +using System.Net; +using System.Net.Http.Json; +using Microsoft.AspNetCore.Mvc.Testing; +using Xunit; +using FluentAssertions; + +namespace Products.IntegrationTests.Products; + +public class DeleteProductsTests : IClassFixture +{ + private readonly HttpClient _client; + private readonly ProductApiFactory _apiFactory; + + public DeleteProductsTests(ProductApiFactory productApiFactory) + { + _apiFactory = productApiFactory; + _client = productApiFactory.CreateClient(new WebApplicationFactoryClientOptions() + { + AllowAutoRedirect = false + }); + } + + [Fact] + public async Task CreateProduct_WhenDataIsValid() + { + // Arrange id = 228099032 + var createProductRequest = TestProducts.CreateProduct; + var responseWithProductLocation = + await _client.PostAsJsonAsync("products", createProductRequest.Body); + + + responseWithProductLocation.StatusCode.Should().Be(HttpStatusCode.Accepted); + + // Act + var response = await _client.DeleteAsync($"products/{responseWithProductLocation.Headers.Location}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NoContent); + } +} diff --git a/tests/Products/Products.IntegrationTests/TestBrands.cs b/tests/Products/Products.IntegrationTests/TestBrands.cs new file mode 100644 index 0000000..7ab7030 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/TestBrands.cs @@ -0,0 +1,28 @@ +using Bogus; +using IGroceryStore.Products.Core.Entities; +using IGroceryStore.Products.Core.ValueObjects; + +namespace Products.IntegrationTests; + +internal static class TestBrands +{ + private static readonly Faker ProductGenerator = new BrandFaker(); + + public static readonly List Brands = ProductGenerator.Generate(10); + public static readonly Brand Brand = Brands.First(); + + private sealed class BrandFaker : Faker + { + public BrandFaker() + { + CustomInstantiator(ResolveConstructor); + } + + private Brand ResolveConstructor(Faker faker) + { + var brand = new Brand { Id = new BrandId((ulong)faker.UniqueIndex), Name = faker.Company.CompanyName() }; + + return brand; + } + } +} diff --git a/tests/Products/Products.IntegrationTests/TestCountries.cs b/tests/Products/Products.IntegrationTests/TestCountries.cs new file mode 100644 index 0000000..a187aa6 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/TestCountries.cs @@ -0,0 +1,31 @@ +using Bogus; +using IGroceryStore.Products.Core.Entities; +using IGroceryStore.Products.Core.ValueObjects; + +namespace Products.IntegrationTests; + +internal static class TestCountries +{ + private static readonly Faker CountryGenerator = new CountryFaker(); + + public static readonly List Countries = CountryGenerator.Generate(10); + public static readonly Country Country = Countries.First(); + + private sealed class CountryFaker : Faker + { + public CountryFaker() + { + CustomInstantiator(ResolveConstructor); + } + + private Country ResolveConstructor(Faker faker) + { + return new Country + { + Id = new CountryId((ulong)faker.UniqueIndex), + Name = faker.Address.Country(), + Code = faker.Address.CountryCode() + }; + } + } +} diff --git a/tests/Products/Products.IntegrationTests/TestProducts.cs b/tests/Products/Products.IntegrationTests/TestProducts.cs new file mode 100644 index 0000000..1944670 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/TestProducts.cs @@ -0,0 +1,38 @@ +using Bogus; +using IGroceryStore.Products.Core.Features.Products.Commands; +using IGroceryStore.Products.Core.ReadModels; +using IGroceryStore.Products.Core.ValueObjects; +using IGroceryStore.Shops.Core.Entities; + +namespace Products.IntegrationTests; + +internal static class TestProducts +{ + private static readonly Faker CreateProductGenerator = new CreateProductFaker(); + + private static readonly List Products = CreateProductGenerator.Generate(10); + public static readonly CreateProduct CreateProduct = Products.First(); + + private sealed class CreateProductFaker : Faker + { + public CreateProductFaker() + { + CustomInstantiator(ResolveConstructor); + } + + private CreateProduct ResolveConstructor(Faker faker) + { + var units = new[] {Unit.Centimeter, Unit.Gram, Unit.Milliliter, Unit.Piece}; + + var body = new CreateProduct.CreateProductBody( + faker.Commerce.ProductName(), + new QuantityReadModel(faker.Random.UInt(1, 20) * 100, faker.PickRandom(units)), + new BrandId(TestBrands.Brand.Id), + new CountryId(TestCountries.Country.Id), + new CategoryId(TestCategories.Category.Id) + ); + + return new CreateProduct(body); + } + } +} From 48c190dbdfc225ef429eb9f2a117ef456db9b032 Mon Sep 17 00:00:00 2001 From: marcinkanardev Date: Sat, 5 Nov 2022 16:48:10 +0100 Subject: [PATCH 2/5] Refactor usings after rebase. --- src/API/Middlewares/ExceptionMiddleware.cs | 2 +- src/API/Program.cs | 8 ++++---- src/API/Properties/launchSettings.json | 2 +- src/API/appsettings.json | 2 +- src/Baskets/Baskets.Core/Baskets.Core.csproj | 4 ++++ .../Products/Commands/CreateProduct.cs | 1 + .../Products/Commands/DeleteProduct.cs | 6 ++---- .../Products/Commands/UpdateDetails.cs | 7 +++---- .../Products.Core/Products.Core.csproj | 12 +++++------ src/Users/Users.Core/Users.Core.csproj | 20 +++++++++---------- .../ProductApiFactory.cs | 8 +++----- .../Products.IntegrationTests.csproj | 6 +++--- .../Products.IntegrationTests/TestBrands.cs | 4 ++-- .../TestCountries.cs | 4 ++-- .../Products.IntegrationTests/TestProducts.cs | 7 +++---- 15 files changed, 46 insertions(+), 47 deletions(-) diff --git a/src/API/Middlewares/ExceptionMiddleware.cs b/src/API/Middlewares/ExceptionMiddleware.cs index 036aa43..d76cd9e 100644 --- a/src/API/Middlewares/ExceptionMiddleware.cs +++ b/src/API/Middlewares/ExceptionMiddleware.cs @@ -66,4 +66,4 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) } private static string ToUnderscoreCase(string value) => string.Concat(value.Select((x, i) => i > 0 && char.IsUpper(x) && !char.IsUpper(value[i-1]) ? $"_{x}" : x.ToString())).ToLower(); -} \ No newline at end of file +} diff --git a/src/API/Program.cs b/src/API/Program.cs index 5ab8d8f..866102a 100644 --- a/src/API/Program.cs +++ b/src/API/Program.cs @@ -25,10 +25,10 @@ } //AWS -if (!builder.Environment.IsDevelopment() && !builder.Environment.IsTestEnvironment()) -{ - builder.Configuration.AddSystemsManager("/Production/IGroceryStore", TimeSpan.FromSeconds(30)); -} +// if (!builder.Environment.IsDevelopment() && !builder.Environment.IsTestEnvironment()) +// { +// builder.Configuration.AddSystemsManager("/Production/IGroceryStore", TimeSpan.FromSeconds(30)); +// } //DateTime builder.Services.AddSingleton(); diff --git a/src/API/Properties/launchSettings.json b/src/API/Properties/launchSettings.json index fda9cd1..5edded3 100644 --- a/src/API/Properties/launchSettings.json +++ b/src/API/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Postman": { "commandName": "Project", - "launchBrowser": false, + "launchBrowser": true, "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/API/appsettings.json b/src/API/appsettings.json index 01c64cd..dc03290 100644 --- a/src/API/appsettings.json +++ b/src/API/appsettings.json @@ -14,7 +14,7 @@ ] }, "ElasticConfiguration": { - "Uri": "foo" + "Uri": "http://localhost:9200" }, "Postgres": { "ConnectionString": "foo", diff --git a/src/Baskets/Baskets.Core/Baskets.Core.csproj b/src/Baskets/Baskets.Core/Baskets.Core.csproj index 2f612bc..451e4e5 100644 --- a/src/Baskets/Baskets.Core/Baskets.Core.csproj +++ b/src/Baskets/Baskets.Core/Baskets.Core.csproj @@ -27,4 +27,8 @@ + + + + diff --git a/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs b/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs index ab4a22e..cd69f60 100644 --- a/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs +++ b/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs @@ -11,6 +11,7 @@ using IGroceryStore.Shared.Services; using IGroceryStore.Shared.Validation; using MassTransit; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; diff --git a/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs b/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs index 15b197d..ce07eac 100644 --- a/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs +++ b/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs @@ -1,10 +1,8 @@ -using IGroceryStore.Products.Core.Entities; -using IGroceryStore.Products.Core.Exceptions; -using IGroceryStore.Products.Core.Persistence.Contexts; +using IGroceryStore.Products.Exceptions; +using IGroceryStore.Products.Persistence.Contexts; using IGroceryStore.Shared.Abstraction.Commands; using IGroceryStore.Shared.Abstraction.Common; using IGroceryStore.Shared.Abstraction.Constants; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; diff --git a/src/Products/Products.Core/Features/Products/Commands/UpdateDetails.cs b/src/Products/Products.Core/Features/Products/Commands/UpdateDetails.cs index f026f7a..4fe1a81 100644 --- a/src/Products/Products.Core/Features/Products/Commands/UpdateDetails.cs +++ b/src/Products/Products.Core/Features/Products/Commands/UpdateDetails.cs @@ -1,12 +1,11 @@ -using IGroceryStore.Products.Core.Exceptions; -using IGroceryStore.Products.Core.Persistence.Contexts; -using IGroceryStore.Products.Contracts.Events; +using IGroceryStore.Products.Contracts.Events; +using IGroceryStore.Products.Exceptions; +using IGroceryStore.Products.Persistence.Contexts; using IGroceryStore.Shared.Abstraction.Commands; using IGroceryStore.Shared.Abstraction.Common; using IGroceryStore.Shared.Abstraction.Constants; using MassTransit; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; diff --git a/src/Products/Products.Core/Products.Core.csproj b/src/Products/Products.Core/Products.Core.csproj index 40af236..a06148c 100644 --- a/src/Products/Products.Core/Products.Core.csproj +++ b/src/Products/Products.Core/Products.Core.csproj @@ -10,18 +10,18 @@ - - + + - - + + - - + + diff --git a/src/Users/Users.Core/Users.Core.csproj b/src/Users/Users.Core/Users.Core.csproj index 32a309c..9c901bb 100644 --- a/src/Users/Users.Core/Users.Core.csproj +++ b/src/Users/Users.Core/Users.Core.csproj @@ -10,22 +10,22 @@ - - + + - - + + - - - - - - + + + + + + diff --git a/tests/Products/Products.IntegrationTests/ProductApiFactory.cs b/tests/Products/Products.IntegrationTests/ProductApiFactory.cs index 4d34c28..1ccda49 100644 --- a/tests/Products/Products.IntegrationTests/ProductApiFactory.cs +++ b/tests/Products/Products.IntegrationTests/ProductApiFactory.cs @@ -3,13 +3,11 @@ using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Containers; using IGroceryStore.API; -using IGroceryStore.Baskets.Core.Persistence; -using IGroceryStore.Products.Core.Persistence.Contexts; using IGroceryStore.Products.Contracts.Events; +using IGroceryStore.Products.Persistence.Contexts; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; -using Xunit; using MassTransit; using Microsoft.AspNetCore.TestHost; @@ -50,11 +48,11 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) builder.ConfigureTestServices(services => { //services.CleanDbContextOptions(); - services.CleanDbContextOptions(); + //services.CleanDbContextOptions(); services.CleanDbContextOptions(); //services.AddPostgresContext(_dbContainer); - services.AddPostgresContext(_dbContainer); + //services.AddPostgresContext(_dbContainer); services.AddPostgresContext(_dbContainer); }); } diff --git a/tests/Products/Products.IntegrationTests/Products.IntegrationTests.csproj b/tests/Products/Products.IntegrationTests/Products.IntegrationTests.csproj index b1274b0..bca1b3d 100644 --- a/tests/Products/Products.IntegrationTests/Products.IntegrationTests.csproj +++ b/tests/Products/Products.IntegrationTests/Products.IntegrationTests.csproj @@ -11,11 +11,11 @@ - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Products/Products.IntegrationTests/TestBrands.cs b/tests/Products/Products.IntegrationTests/TestBrands.cs index 7ab7030..ef2a80e 100644 --- a/tests/Products/Products.IntegrationTests/TestBrands.cs +++ b/tests/Products/Products.IntegrationTests/TestBrands.cs @@ -1,6 +1,6 @@ using Bogus; -using IGroceryStore.Products.Core.Entities; -using IGroceryStore.Products.Core.ValueObjects; +using IGroceryStore.Products.Entities; +using IGroceryStore.Products.ValueObjects; namespace Products.IntegrationTests; diff --git a/tests/Products/Products.IntegrationTests/TestCountries.cs b/tests/Products/Products.IntegrationTests/TestCountries.cs index a187aa6..0ca0ccf 100644 --- a/tests/Products/Products.IntegrationTests/TestCountries.cs +++ b/tests/Products/Products.IntegrationTests/TestCountries.cs @@ -1,6 +1,6 @@ using Bogus; -using IGroceryStore.Products.Core.Entities; -using IGroceryStore.Products.Core.ValueObjects; +using IGroceryStore.Products.Entities; +using IGroceryStore.Products.ValueObjects; namespace Products.IntegrationTests; diff --git a/tests/Products/Products.IntegrationTests/TestProducts.cs b/tests/Products/Products.IntegrationTests/TestProducts.cs index 1944670..e2f32b9 100644 --- a/tests/Products/Products.IntegrationTests/TestProducts.cs +++ b/tests/Products/Products.IntegrationTests/TestProducts.cs @@ -1,8 +1,7 @@ using Bogus; -using IGroceryStore.Products.Core.Features.Products.Commands; -using IGroceryStore.Products.Core.ReadModels; -using IGroceryStore.Products.Core.ValueObjects; -using IGroceryStore.Shops.Core.Entities; +using IGroceryStore.Products.Features.Products.Commands; +using IGroceryStore.Products.ReadModels; +using IGroceryStore.Products.ValueObjects; namespace Products.IntegrationTests; From f963e11e06c17bea30e69c2a9029f2a50a66adc2 Mon Sep 17 00:00:00 2001 From: marcinkanardev Date: Sat, 5 Nov 2022 19:03:54 +0100 Subject: [PATCH 3/5] Add TestCategories faker. --- .../TestCategories.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/Products/Products.IntegrationTests/TestCategories.cs diff --git a/tests/Products/Products.IntegrationTests/TestCategories.cs b/tests/Products/Products.IntegrationTests/TestCategories.cs new file mode 100644 index 0000000..5c155a4 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/TestCategories.cs @@ -0,0 +1,29 @@ +using Bogus; +using IGroceryStore.Products.Entities; +using IGroceryStore.Products.ValueObjects; + +namespace Products.IntegrationTests; + +internal static class TestCategories +{ + private static readonly Faker CategoryGenerator = new CategoryFaker(); + + public static readonly List Categories = CategoryGenerator.Generate(10); + public static readonly Category Category = Categories.First(); + + private sealed class CategoryFaker : Faker + { + public CategoryFaker() + { + CustomInstantiator(ResolveConstructor); + } + + private Category ResolveConstructor(Faker faker) + { + return new Category() + { + Id = new CategoryId((ulong)faker.UniqueIndex), Name = faker.Commerce.Categories(1).First() + }; + } + } +} From 097f33e6dad235116e69284133ca0c7edf3c7c41 Mon Sep 17 00:00:00 2001 From: marcinkanardev Date: Sun, 6 Nov 2022 11:21:28 +0100 Subject: [PATCH 4/5] Apply staged changes. --- IGroceryStore.sln.DotSettings.user | 6 ++--- .../Categories/Commands/UpdateCategory.cs | 6 ++--- .../Products/Commands/CreateProduct.cs | 23 +++++++++---------- .../Products.Core/modulesettings.json | 2 +- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/IGroceryStore.sln.DotSettings.user b/IGroceryStore.sln.DotSettings.user index 88d78ab..385822b 100644 --- a/IGroceryStore.sln.DotSettings.user +++ b/IGroceryStore.sln.DotSettings.user @@ -4,11 +4,11 @@ <PhysicalFolder Path="/Users/adrianfranczak/.nuget/packages/opentelemetry.instrumentation.http/1.0.0-rc9.5" Loaded="True" /> <PhysicalFolder Path="/Users/adrianfranczak/.nuget/packages/opentelemetry.instrumentation.http/1.0.0-rc9.6" Loaded="True" /> </AssemblyExplorer> - /Users/adrianfranczak/Library/Caches/JetBrains/Rider2022.2/resharper-host/temp/Rider/vAny/CoverageData/_IGroceryStore.1213299661/Snapshot/snapshot.utdcvr - <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;tests&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Project Location="/Users/adrianfranczak/Repos/Private/IGroceryStore" Presentation="&lt;tests&gt;" /> + + <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;tests&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Project Location="\Users\adrianfranczak\Repos\Private\IGroceryStore" Presentation="&lt;tests&gt;" /> </SessionState> True diff --git a/src/Products/Products.Core/Features/Categories/Commands/UpdateCategory.cs b/src/Products/Products.Core/Features/Categories/Commands/UpdateCategory.cs index 438864b..bbc79ce 100644 --- a/src/Products/Products.Core/Features/Categories/Commands/UpdateCategory.cs +++ b/src/Products/Products.Core/Features/Categories/Commands/UpdateCategory.cs @@ -22,7 +22,7 @@ public class UpdateCategoryEndpoint : IEndpoint { public void RegisterEndpoint(IEndpointRouteBuilder endpoints) => endpoints.MapPut("api/categories/{id}") - .AddEndpointFilter>() + .AddEndpointFilter>() .WithTags(SwaggerTags.Products) .Produces(202) .Produces(400); @@ -55,11 +55,11 @@ await _productsDbContext.Categories } } -internal class UpdateCategoryValidator : AbstractValidator +internal class UpdateCategoryValidator : AbstractValidator { public UpdateCategoryValidator() { - RuleFor(x => x.Name) + RuleFor(x => x.Body.Name) .MinimumLength(3) .NotEmpty(); } diff --git a/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs b/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs index cd69f60..95fceeb 100644 --- a/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs +++ b/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs @@ -11,7 +11,6 @@ using IGroceryStore.Shared.Services; using IGroceryStore.Shared.Validation; using MassTransit; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; @@ -32,11 +31,11 @@ public class CreateProductEndpoint : IEndpoint { public void RegisterEndpoint(IEndpointRouteBuilder endpoints) => endpoints.MapPost("api/products") - .RequireAuthorization() - .AddEndpointFilter>() + //.RequireAuthorization() + .AddEndpointFilter>() .WithTags(SwaggerTags.Products) .Produces(400) - .Produces(202); + .Produces(202);; } internal class CreateProductHandler : ICommandHandler @@ -83,32 +82,32 @@ public async Task HandleAsync(CreateProduct command, CancellationToken } } -internal class CreateProductValidator : AbstractValidator +internal class CreateProductValidator : AbstractValidator { public CreateProductValidator() { - RuleFor(x => x.Name) + RuleFor(x => x.Body.Name) .NotEmpty() .MinimumLength(3); - RuleFor(x => x.Quantity) + RuleFor(x => x.Body.Quantity) .NotNull() .DependentRules(() => { - RuleFor(x => x.Quantity.Amount) + RuleFor(x => x.Body.Quantity.Amount) .GreaterThan(0); - RuleFor(x => x.Quantity.Unit) + RuleFor(x => x.Body.Quantity.Unit) .NotEmpty(); }); - RuleFor(x => x.BrandId) + RuleFor(x => x.Body.BrandId) .NotEmpty(); - RuleFor(x => x.CountryId) + RuleFor(x => x.Body.CountryId) .NotEmpty(); - RuleFor(x => x.CategoryId) + RuleFor(x => x.Body.CategoryId) .NotEmpty(); } } diff --git a/src/Products/Products.Core/modulesettings.json b/src/Products/Products.Core/modulesettings.json index 91759e0..e6ecabb 100644 --- a/src/Products/Products.Core/modulesettings.json +++ b/src/Products/Products.Core/modulesettings.json @@ -1,5 +1,5 @@ { "Products": { - "ModuleEnabled": false + "ModuleEnabled": true } } \ No newline at end of file From 42ce0af211192ec05790a6f0f0211b5a9f3afe31 Mon Sep 17 00:00:00 2001 From: marcinkanardev Date: Sat, 19 Nov 2022 08:43:03 +0100 Subject: [PATCH 5/5] Added some tests for products and refactor products test structure. --- .../Products/Commands/CreateProduct.cs | 2 +- .../Products/Commands/DeleteProduct.cs | 2 +- .../ProductApiFactory.cs | 42 ++++++- .../ProductCollection.cs | 8 ++ .../Products/CreateProductTests.cs | 106 ++++++++++++++++++ .../Products/DeleteProductsTests.cs | 66 +++++++---- .../ProductsFakeSeeder.cs | 30 +++++ .../{ => TestModels}/TestBrands.cs | 4 +- .../{ => TestModels}/TestCategories.cs | 10 +- .../{ => TestModels}/TestCountries.cs | 2 +- .../TestModels/TestProducts.cs | 40 +++++++ .../Products.IntegrationTests/TestProducts.cs | 37 ------ 12 files changed, 280 insertions(+), 69 deletions(-) create mode 100644 tests/Products/Products.IntegrationTests/ProductCollection.cs create mode 100644 tests/Products/Products.IntegrationTests/Products/CreateProductTests.cs create mode 100644 tests/Products/Products.IntegrationTests/ProductsFakeSeeder.cs rename tests/Products/Products.IntegrationTests/{ => TestModels}/TestBrands.cs (92%) rename tests/Products/Products.IntegrationTests/{ => TestModels}/TestCategories.cs (70%) rename tests/Products/Products.IntegrationTests/{ => TestModels}/TestCountries.cs (93%) create mode 100644 tests/Products/Products.IntegrationTests/TestModels/TestProducts.cs delete mode 100644 tests/Products/Products.IntegrationTests/TestProducts.cs diff --git a/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs b/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs index 95fceeb..302fc66 100644 --- a/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs +++ b/src/Products/Products.Core/Features/Products/Commands/CreateProduct.cs @@ -35,7 +35,7 @@ public void RegisterEndpoint(IEndpointRouteBuilder endpoints) => .AddEndpointFilter>() .WithTags(SwaggerTags.Products) .Produces(400) - .Produces(202);; + .Produces(202); } internal class CreateProductHandler : ICommandHandler diff --git a/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs b/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs index ce07eac..b0b8be4 100644 --- a/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs +++ b/src/Products/Products.Core/Features/Products/Commands/DeleteProduct.cs @@ -14,7 +14,7 @@ internal record DeleteProduct(ulong Id) : IHttpCommand; public class DeleteProductEndpoint : IEndpoint { public void RegisterEndpoint(IEndpointRouteBuilder endpoints) => - endpoints.MapDelete("products/{id}") + endpoints.MapDelete("api/products/{id}") //.RequireAuthorization() .WithTags(SwaggerTags.Products) .Produces(204) diff --git a/tests/Products/Products.IntegrationTests/ProductApiFactory.cs b/tests/Products/Products.IntegrationTests/ProductApiFactory.cs index 1ccda49..26b4437 100644 --- a/tests/Products/Products.IntegrationTests/ProductApiFactory.cs +++ b/tests/Products/Products.IntegrationTests/ProductApiFactory.cs @@ -10,16 +10,27 @@ using Microsoft.Extensions.Logging; using MassTransit; using Microsoft.AspNetCore.TestHost; +using IGroceryStore.Shared.Tests.Auth; +using Respawn; +using System.Data.Common; +using IGroceryStore.Shared.Abstraction.Constants; +using System.Security.Claims; +using IGroceryStore.Shared.Services; +using Microsoft.Extensions.DependencyInjection; +using Npgsql; namespace Products.IntegrationTests; public class ProductApiFactory : WebApplicationFactory, IAsyncLifetime { + private readonly MockUser _user; + private Respawner _respawner = default!; + private DbConnection _dbConnection = default!; private readonly TestcontainerDatabase _dbContainer = new TestcontainersBuilder() .WithDatabase(new PostgreSqlTestcontainerConfiguration { - Database = "db", + Database = Guid.NewGuid().ToString(), Username = "postgres", Password = "postgres" }) @@ -29,14 +40,20 @@ public class ProductApiFactory : WebApplicationFactory, IAsyncLifeti public ProductApiFactory() { + _user = new MockUser(new Claim(Claims.Name.UserId, "1"), + new Claim(Claims.Name.Expire, DateTimeOffset.UtcNow.AddSeconds(2137).ToUnixTimeSeconds().ToString())); Randomizer.Seed = new Random(420); VerifierSettings.ScrubInlineGuids(); } - + + public HttpClient HttpClient { get; private set; } = default!; + protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureLogging(logging => { logging.ClearProviders(); }); + builder.UseEnvironment(EnvironmentService.TestEnvironment); + builder.ConfigureServices(services => { services.AddMassTransitTestHarness(x => @@ -48,18 +65,35 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) builder.ConfigureTestServices(services => { //services.CleanDbContextOptions(); - //services.CleanDbContextOptions(); services.CleanDbContextOptions(); //services.AddPostgresContext(_dbContainer); - //services.AddPostgresContext(_dbContainer); services.AddPostgresContext(_dbContainer); + + services.AddTestAuthentication(); + + services.AddSingleton(_ => _user); }); } public async Task InitializeAsync() { await _dbContainer.StartAsync(); + _dbConnection = new NpgsqlConnection(_dbContainer.ConnectionString); + HttpClient = CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); + await InitializeRespawner(); + } + + public async Task ResetDatabaseAsync() => await _respawner.ResetAsync(_dbConnection); + + private async Task InitializeRespawner() + { + await _dbConnection.OpenAsync(); + _respawner = await Respawner.CreateAsync(_dbConnection, new RespawnerOptions() + { + DbAdapter = DbAdapter.Postgres, + SchemasToInclude = new[] { "IGroceryStore.Products" }, + }); } async Task IAsyncLifetime.DisposeAsync() diff --git a/tests/Products/Products.IntegrationTests/ProductCollection.cs b/tests/Products/Products.IntegrationTests/ProductCollection.cs new file mode 100644 index 0000000..7668ac8 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/ProductCollection.cs @@ -0,0 +1,8 @@ +using Products.IntegrationTests; + +namespace IGroceryStore.Products.IntegrationTests; + +[CollectionDefinition("ProductCollection")] +public class ProductCollection : ICollectionFixture +{ +} diff --git a/tests/Products/Products.IntegrationTests/Products/CreateProductTests.cs b/tests/Products/Products.IntegrationTests/Products/CreateProductTests.cs new file mode 100644 index 0000000..bd50881 --- /dev/null +++ b/tests/Products/Products.IntegrationTests/Products/CreateProductTests.cs @@ -0,0 +1,106 @@ +using System.Net; +using System.Net.Http.Json; +using System.Security.Claims; +using Bogus; +using FluentAssertions; +using IGroceryStore.Products.Features.Products.Commands; +using IGroceryStore.Products.IntegrationTests.TestModels; +using IGroceryStore.Products.ReadModels; +using IGroceryStore.Products.ValueObjects; +using IGroceryStore.Shared.Abstraction.Constants; +using IGroceryStore.Shared.Tests.Auth; +using Microsoft.AspNetCore.TestHost; +using Products.IntegrationTests; + +namespace IGroceryStore.Products.IntegrationTests.Products; + +[UsesVerify] +[Collection("ProductCollection")] +public class CreateProductTests : IClassFixture, IAsyncLifetime +{ + private readonly HttpClient _client; + private readonly Func _resetDatabase; + private readonly ProductsFakeSeeder _productsFakeSeeder; + + public CreateProductTests(ProductApiFactory productApiFactory) + { + _client = productApiFactory.HttpClient; + _resetDatabase = productApiFactory.ResetDatabaseAsync; + productApiFactory + .WithWebHostBuilder(builder => + builder.ConfigureTestServices(services => + { + services.RegisterUser(new[] + { + new Claim(Claims.Name.UserId, "1"), + new Claim(Claims.Name.Expire, + DateTimeOffset.UtcNow.AddSeconds(2137).ToUnixTimeSeconds().ToString()) + }); + })); // override authorized user; + + _productsFakeSeeder = new ProductsFakeSeeder(productApiFactory); + //productApiFactory.SeedProductDb(); + } + + [Fact] + public async Task CreateProduct_WhenDataIsValid() + { + // Arrange + await _productsFakeSeeder.SeedData(); + var createProduct = GetFakeCreateProduct(); + + // Act + var response = await _client.PostAsJsonAsync($"api/products", createProduct); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.Accepted); + } + + [Fact] + public async Task CreateProduct_WhenCategoryNotExists_ShouldReturnNotFound() + { + // Arrange + await _productsFakeSeeder.SeedData(); + var createProduct = GetFakeCreateProduct(); + + await _client.DeleteAsync($"api/categories/{createProduct.CategoryId}"); + + // Act + var response = await _client.PostAsJsonAsync($"api/products", createProduct); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + private CreateProduct.CreateProductBody GetFakeCreateProduct() + { + var units = new[] { Unit.Centimeter, Unit.Gram, Unit.Milliliter, Unit.Piece }; + + var brandsIds = TestBrands.Brands + .Select(x => x.Id.Id) + .ToList(); + + var countriesIds = TestCountries.Countries + .Select(x => x.Id.Id) + .ToList(); + + var categoriesIds = TestCategories.Categories + .Select(x => x.Id.Id) + .ToList(); + + var faker = new Faker(); + + var body = new CreateProduct.CreateProductBody( + faker.Commerce.ProductName(), + new QuantityReadModel(faker.Random.UInt(1, 20) * 100, faker.PickRandom(units)), + faker.PickRandom(brandsIds), + faker.PickRandom(countriesIds), + faker.PickRandom(categoriesIds) + ); + + return body; + } + + public Task InitializeAsync() => Task.CompletedTask; + public Task DisposeAsync() => _resetDatabase(); +} diff --git a/tests/Products/Products.IntegrationTests/Products/DeleteProductsTests.cs b/tests/Products/Products.IntegrationTests/Products/DeleteProductsTests.cs index da0baf7..81791e5 100644 --- a/tests/Products/Products.IntegrationTests/Products/DeleteProductsTests.cs +++ b/tests/Products/Products.IntegrationTests/Products/DeleteProductsTests.cs @@ -1,40 +1,68 @@ using System.Net; -using System.Net.Http.Json; -using Microsoft.AspNetCore.Mvc.Testing; -using Xunit; using FluentAssertions; +using Microsoft.AspNetCore.TestHost; +using IGroceryStore.Shared.Tests.Auth; +using System.Security.Claims; +using IGroceryStore.Shared.Abstraction.Constants; +using IGroceryStore.Products.IntegrationTests; +using IGroceryStore.Products.IntegrationTests.TestModels; namespace Products.IntegrationTests.Products; -public class DeleteProductsTests : IClassFixture +[UsesVerify] +[Collection("ProductCollection")] +public class DeleteProductsTests : IClassFixture, IAsyncLifetime { private readonly HttpClient _client; - private readonly ProductApiFactory _apiFactory; + private readonly Func _resetDatabase; + private readonly ProductsFakeSeeder _productsFakeSeeder; public DeleteProductsTests(ProductApiFactory productApiFactory) { - _apiFactory = productApiFactory; - _client = productApiFactory.CreateClient(new WebApplicationFactoryClientOptions() - { - AllowAutoRedirect = false - }); + _client = productApiFactory.HttpClient; + _resetDatabase = productApiFactory.ResetDatabaseAsync; + productApiFactory + .WithWebHostBuilder(builder => + builder.ConfigureTestServices(services => + { + services.RegisterUser(new[] + { + new Claim(Claims.Name.UserId, "1"), + new Claim(Claims.Name.Expire, + DateTimeOffset.UtcNow.AddSeconds(2137).ToUnixTimeSeconds().ToString()) + }); + })); // override authorized user; + + _productsFakeSeeder = new ProductsFakeSeeder(productApiFactory); } [Fact] - public async Task CreateProduct_WhenDataIsValid() + public async Task DeleteProduct_WhenProductExists() { - // Arrange id = 228099032 - var createProductRequest = TestProducts.CreateProduct; - var responseWithProductLocation = - await _client.PostAsJsonAsync("products", createProductRequest.Body); - - - responseWithProductLocation.StatusCode.Should().Be(HttpStatusCode.Accepted); + // Arrange + await _productsFakeSeeder.SeedData(); + var product = TestProducts.Product; // Act - var response = await _client.DeleteAsync($"products/{responseWithProductLocation.Headers.Location}"); + var response = await _client.DeleteAsync($"api/products/{product.Id.Value}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NoContent); } + + [Fact] + public async Task ReturnNotFound_WhenProductNotExists() + { + // Arrange + var product = TestProducts.Product; + + // Act + var response = await _client.DeleteAsync($"api/products/{product.Id.Value}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + public Task InitializeAsync() => Task.CompletedTask; + public Task DisposeAsync() => _resetDatabase(); } diff --git a/tests/Products/Products.IntegrationTests/ProductsFakeSeeder.cs b/tests/Products/Products.IntegrationTests/ProductsFakeSeeder.cs new file mode 100644 index 0000000..471f98d --- /dev/null +++ b/tests/Products/Products.IntegrationTests/ProductsFakeSeeder.cs @@ -0,0 +1,30 @@ +using IGroceryStore.Products.IntegrationTests.TestModels; +using IGroceryStore.Products.Persistence.Contexts; +using Microsoft.Extensions.DependencyInjection; +using Products.IntegrationTests; + +namespace IGroceryStore.Products.IntegrationTests; + +public class ProductsFakeSeeder +{ + private ProductApiFactory _productApiFactory; + + public ProductsFakeSeeder(ProductApiFactory productApiFactory) + { + _productApiFactory = productApiFactory; + } + + public async Task SeedData() + { + //await _productApiFactory.ResetDatabaseAsync(); + + using var scope = _productApiFactory.Services.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + await context.Countries.AddRangeAsync(TestCountries.Countries); + await context.Brands.AddRangeAsync(TestBrands.Brands); + await context.Categories.AddRangeAsync(TestCategories.Categories); + await context.Products.AddRangeAsync(TestProducts.Products); + await context.SaveChangesAsync(); + } +} diff --git a/tests/Products/Products.IntegrationTests/TestBrands.cs b/tests/Products/Products.IntegrationTests/TestModels/TestBrands.cs similarity index 92% rename from tests/Products/Products.IntegrationTests/TestBrands.cs rename to tests/Products/Products.IntegrationTests/TestModels/TestBrands.cs index ef2a80e..cf6483d 100644 --- a/tests/Products/Products.IntegrationTests/TestBrands.cs +++ b/tests/Products/Products.IntegrationTests/TestModels/TestBrands.cs @@ -2,7 +2,7 @@ using IGroceryStore.Products.Entities; using IGroceryStore.Products.ValueObjects; -namespace Products.IntegrationTests; +namespace IGroceryStore.Products.IntegrationTests.TestModels; internal static class TestBrands { @@ -21,7 +21,7 @@ public BrandFaker() private Brand ResolveConstructor(Faker faker) { var brand = new Brand { Id = new BrandId((ulong)faker.UniqueIndex), Name = faker.Company.CompanyName() }; - + return brand; } } diff --git a/tests/Products/Products.IntegrationTests/TestCategories.cs b/tests/Products/Products.IntegrationTests/TestModels/TestCategories.cs similarity index 70% rename from tests/Products/Products.IntegrationTests/TestCategories.cs rename to tests/Products/Products.IntegrationTests/TestModels/TestCategories.cs index 5c155a4..5652d62 100644 --- a/tests/Products/Products.IntegrationTests/TestCategories.cs +++ b/tests/Products/Products.IntegrationTests/TestModels/TestCategories.cs @@ -1,13 +1,14 @@ using Bogus; using IGroceryStore.Products.Entities; +using IGroceryStore.Products.Features.Categories.Commands; using IGroceryStore.Products.ValueObjects; -namespace Products.IntegrationTests; +namespace IGroceryStore.Products.IntegrationTests.TestModels; internal static class TestCategories { private static readonly Faker CategoryGenerator = new CategoryFaker(); - + public static readonly List Categories = CategoryGenerator.Generate(10); public static readonly Category Category = Categories.First(); @@ -20,9 +21,10 @@ public CategoryFaker() private Category ResolveConstructor(Faker faker) { - return new Category() + return new Category { - Id = new CategoryId((ulong)faker.UniqueIndex), Name = faker.Commerce.Categories(1).First() + Id = new CategoryId((ulong)faker.UniqueIndex), + Name = faker.Commerce.Categories(1).First() }; } } diff --git a/tests/Products/Products.IntegrationTests/TestCountries.cs b/tests/Products/Products.IntegrationTests/TestModels/TestCountries.cs similarity index 93% rename from tests/Products/Products.IntegrationTests/TestCountries.cs rename to tests/Products/Products.IntegrationTests/TestModels/TestCountries.cs index 0ca0ccf..2f49073 100644 --- a/tests/Products/Products.IntegrationTests/TestCountries.cs +++ b/tests/Products/Products.IntegrationTests/TestModels/TestCountries.cs @@ -2,7 +2,7 @@ using IGroceryStore.Products.Entities; using IGroceryStore.Products.ValueObjects; -namespace Products.IntegrationTests; +namespace IGroceryStore.Products.IntegrationTests.TestModels; internal static class TestCountries { diff --git a/tests/Products/Products.IntegrationTests/TestModels/TestProducts.cs b/tests/Products/Products.IntegrationTests/TestModels/TestProducts.cs new file mode 100644 index 0000000..f2d1c6f --- /dev/null +++ b/tests/Products/Products.IntegrationTests/TestModels/TestProducts.cs @@ -0,0 +1,40 @@ +using Bogus; +using IGroceryStore.Products.Entities; +using IGroceryStore.Products.ValueObjects; +using IGroceryStore.Shared.ValueObjects; + +namespace IGroceryStore.Products.IntegrationTests.TestModels; + +internal static class TestProducts +{ + private static readonly Faker CreateProductGenerator = new ProductFaker(); + + public static readonly List Products = CreateProductGenerator.Generate(10); + public static readonly Product Product = Products.First(); + + private sealed class ProductFaker : Faker + { + public ProductFaker() + { + CustomInstantiator(ResolveConstructor); + } + + private Product ResolveConstructor(Faker faker) + { + var units = new[] { Unit.Centimeter, Unit.Gram, Unit.Milliliter, Unit.Piece }; + + return new Product + { + Id = new ProductId(faker.Random.UInt()), + Name = new ProductName(faker.Commerce.ProductName()), + Description = new Description(faker.Commerce.ProductDescription()), + Quantity = new Quantity(faker.Random.UInt(1, 20) * 100, faker.PickRandom(units)), + CountryId = new CountryId(TestCountries.Country.Id), + CategoryId = new CategoryId(TestCategories.Category.Id), + BrandId = new BrandId(TestBrands.Brand.Id), + ImageUrl = new Uri(faker.Internet.Url()), + BarCode = new BarCode(faker.Random.UInt(100_000_000, 900_000_000).ToString()) + }; + } + } +} diff --git a/tests/Products/Products.IntegrationTests/TestProducts.cs b/tests/Products/Products.IntegrationTests/TestProducts.cs deleted file mode 100644 index e2f32b9..0000000 --- a/tests/Products/Products.IntegrationTests/TestProducts.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Bogus; -using IGroceryStore.Products.Features.Products.Commands; -using IGroceryStore.Products.ReadModels; -using IGroceryStore.Products.ValueObjects; - -namespace Products.IntegrationTests; - -internal static class TestProducts -{ - private static readonly Faker CreateProductGenerator = new CreateProductFaker(); - - private static readonly List Products = CreateProductGenerator.Generate(10); - public static readonly CreateProduct CreateProduct = Products.First(); - - private sealed class CreateProductFaker : Faker - { - public CreateProductFaker() - { - CustomInstantiator(ResolveConstructor); - } - - private CreateProduct ResolveConstructor(Faker faker) - { - var units = new[] {Unit.Centimeter, Unit.Gram, Unit.Milliliter, Unit.Piece}; - - var body = new CreateProduct.CreateProductBody( - faker.Commerce.ProductName(), - new QuantityReadModel(faker.Random.UInt(1, 20) * 100, faker.PickRandom(units)), - new BrandId(TestBrands.Brand.Id), - new CountryId(TestCountries.Country.Id), - new CategoryId(TestCategories.Category.Id) - ); - - return new CreateProduct(body); - } - } -}