diff --git a/README.md b/README.md index 59e888c..fc7ce57 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ This project demonstrates the development of the FakeStore API (https://github.com/keikaavousi/fake-store-api) based on Domain-Driven Design (DDD) documentation in ASP.NET Core, using AI to drive the implementation. +## AI Models Comparison + +Comparison images are located in `docs/ai-models` folder: + + ### [Documentation Index](docs/ai-models//README.md) + + - `docs/ai + ## Documentation The complete documentation is available in the `docs/` folder. @@ -16,9 +24,6 @@ The complete documentation is available in the `docs/` folder. - `docs/infrastructure` - `docs/roadmap` -## AI Models Comparison -Comparison images are located in `docs/ai-models` folder: -### [Documentation Index](docs/ai-models//README.md) diff --git a/docs/ai-models/coding-models.png b/docs/ai/coding-models.png similarity index 100% rename from docs/ai-models/coding-models.png rename to docs/ai/coding-models.png diff --git a/docs/ai-models/gpt-models.png b/docs/ai/gpt-models.png similarity index 100% rename from docs/ai-models/gpt-models.png rename to docs/ai/gpt-models.png diff --git a/docs/ai-models/README.md b/docs/ai/models.md similarity index 100% rename from docs/ai-models/README.md rename to docs/ai/models.md diff --git a/docs/ai-models/other-models.png b/docs/ai/other-models.png similarity index 100% rename from docs/ai-models/other-models.png rename to docs/ai/other-models.png diff --git a/docs/ai/prompt.md b/docs/ai/prompt.md new file mode 100644 index 0000000..8b9c607 --- /dev/null +++ b/docs/ai/prompt.md @@ -0,0 +1,29 @@ +## Generic Prompt + +> You are a Senior .NET developer with extensive expertise in C#, ASP.NET Core, and EF Core. + +> In this API project, you must use the following key NuGet packages: +> - MediatR for the command/query pipeline +> - AutoMapper for DTO mapping +> - OneOf for typed results +> - FluentValidation for command validations +> - Serilog for structured logging +> - Swagger (Swashbuckle) for documentation +> - HealthChecks for readiness probes + +> Every class, method, and property must include XML comments in accordance with Microsoft’s guidelines. + +> In controllers and endpoints, include these API documentation attributes: +> - [ProducesResponseType] +> - [SwaggerOperation] with a detailed description +> - Example: [SwaggerResponseExample] + +> Use DataAnnotations to validate request models. + +> Write unit and integration tests using xUnit, Shouldly, NSubstitute, and Bogus Faker. + +> In your tests, follow the Given-When-Then pattern and assign a meaningful DisplayName to each Fact/Theory. + +> Review the source code in `/src/` and the tests in `/tests/`. Also consult the design documents under `/docs/` (context-map, tactical/modeling, infrastructure/api). + +> Then implement the requirements specified in `work/00-file-name.md`, covering its Description, Acceptance Criteria, Technical Refinement, Test Cases (Gherkin), and Definition of Done. \ No newline at end of file diff --git a/src/FakeStoreNet.Api/FakeStoreNet.Api.csproj b/src/FakeStoreNet.Api/FakeStoreNet.Api.csproj index 7d448a5..6af007a 100644 --- a/src/FakeStoreNet.Api/FakeStoreNet.Api.csproj +++ b/src/FakeStoreNet.Api/FakeStoreNet.Api.csproj @@ -11,12 +11,13 @@ - + + diff --git a/src/FakeStoreNet.Api/Program.cs b/src/FakeStoreNet.Api/Program.cs index 4785501..d6edacf 100644 --- a/src/FakeStoreNet.Api/Program.cs +++ b/src/FakeStoreNet.Api/Program.cs @@ -1,4 +1,7 @@ +using FakeStoreNet.Application.Common; +using FakeStoreNet.Infrastructure.Caching; + namespace FakeStoreNet.Api { public class Program @@ -12,6 +15,9 @@ public static void Main(string[] args) builder.Services.AddSingleton(); builder.Services.AddControllers(); + builder.Services.AddMemoryCache(); + builder.Services.AddSingleton(); + builder.Services.Configure(builder.Configuration.GetSection("CacheSettings")); var app = builder.Build(); diff --git a/src/FakeStoreNet.Application/Common/CacheSettings.cs b/src/FakeStoreNet.Application/Common/CacheSettings.cs new file mode 100644 index 0000000..9e3fcc0 --- /dev/null +++ b/src/FakeStoreNet.Application/Common/CacheSettings.cs @@ -0,0 +1,13 @@ +namespace FakeStoreNet.Application.Common +{ + /// + /// Settings for cache expiration policies. + /// + public class CacheSettings + { + /// + /// Absolute expiration in seconds for GetAllProductsQuery cache. + /// + public int GetAllProductsAbsoluteExpirationInSeconds { get; set; } = 60; + } +} diff --git a/src/FakeStoreNet.Application/Common/ICacheService.cs b/src/FakeStoreNet.Application/Common/ICacheService.cs new file mode 100644 index 0000000..f615905 --- /dev/null +++ b/src/FakeStoreNet.Application/Common/ICacheService.cs @@ -0,0 +1,32 @@ +namespace FakeStoreNet.Application.Common +{ + /// + /// Defines methods for interacting with a cache store. + /// + public interface ICacheService + { + /// + /// Retrieves an entry from cache. + /// + /// Type of cached value. + /// Cache key. + /// Cached value or default if not found. + Task GetAsync(string key); + + /// + /// Stores an entry in cache. + /// + /// Type of value to cache. + /// Cache key. + /// Value to cache. + /// Absolute expiration timespan (optional). + /// Sliding expiration timespan (optional). + Task SetAsync(string key, T value, TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null); + + /// + /// Removes an entry from cache. + /// + /// Cache key. + Task RemoveAsync(string key); + } +} diff --git a/src/FakeStoreNet.Application/FakeStoreNet.Application.csproj b/src/FakeStoreNet.Application/FakeStoreNet.Application.csproj index f2d2deb..aa95125 100644 --- a/src/FakeStoreNet.Application/FakeStoreNet.Application.csproj +++ b/src/FakeStoreNet.Application/FakeStoreNet.Application.csproj @@ -13,7 +13,6 @@ - diff --git a/src/FakeStoreNet.Application/Features/Product/Commands/CreateProduct/CreateProductCommand.cs b/src/FakeStoreNet.Application/Features/Product/Commands/CreateProduct/CreateProductCommand.cs index 62ec8d7..663a489 100644 --- a/src/FakeStoreNet.Application/Features/Product/Commands/CreateProduct/CreateProductCommand.cs +++ b/src/FakeStoreNet.Application/Features/Product/Commands/CreateProduct/CreateProductCommand.cs @@ -1,6 +1,6 @@ +using FakeStoreNet.Domain.Exceptions; using MediatR; using OneOf; -using FakeStoreNet.Domain.Common; namespace FakeStoreNet.Application.Features.Product.Commands.CreateProduct { diff --git a/src/FakeStoreNet.Application/Features/Product/Commands/CreateProduct/CreateProductCommandHandler.cs b/src/FakeStoreNet.Application/Features/Product/Commands/CreateProduct/CreateProductCommandHandler.cs index d346647..64086d8 100644 --- a/src/FakeStoreNet.Application/Features/Product/Commands/CreateProduct/CreateProductCommandHandler.cs +++ b/src/FakeStoreNet.Application/Features/Product/Commands/CreateProduct/CreateProductCommandHandler.cs @@ -1,36 +1,24 @@ -using System.Threading; -using System.Threading.Tasks; using AutoMapper; -using MediatR; -using OneOf; +using FakeStoreNet.Application.Common; using FakeStoreNet.Domain.Common; -using FakeStoreNet.Domain.Entities; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using FakeStoreNet.Application.Features.Product.Commands.CreateProduct; +using MediatR; +using OneOf; namespace FakeStoreNet.Application.Features.Product.Commands.CreateProduct { /// /// Handles to create a new product. /// - public class CreateProductCommandHandler : IRequestHandler> + /// + /// Initializes a new instance of . + /// + /// Product repository. + public class CreateProductCommandHandler(IProductRepository repository, ICacheService cacheService) : IRequestHandler> { - private readonly IProductRepository _repository; - private readonly IMapper _mapper; - - /// - /// Initializes a new instance of . - /// - /// Product repository. - /// AutoMapper mapper. - public CreateProductCommandHandler(IProductRepository repository, IMapper mapper) - { - _repository = repository; - _mapper = mapper; - } - /// - public Task> Handle(CreateProductCommand request, CancellationToken cancellationToken) + public async Task> Handle(CreateProductCommand request, CancellationToken cancellationToken) { try { @@ -42,13 +30,14 @@ public Task> Handle(CreateProductCommand r request.Image, new Rating(request.Rate, request.Count)); - _repository.Add(product); + repository.Add(product); + await cacheService.RemoveAsync("GetAllProducts"); - return Task.FromResult>(product.Id); + return product.Id; } catch (DomainValidationException ex) { - return Task.FromResult>(ex); + return ex; } } } diff --git a/src/FakeStoreNet.Application/Features/Product/Commands/DeleteProduct/DeleteProductCommand.cs b/src/FakeStoreNet.Application/Features/Product/Commands/DeleteProduct/DeleteProductCommand.cs index 53f53c4..f3bcafe 100644 --- a/src/FakeStoreNet.Application/Features/Product/Commands/DeleteProduct/DeleteProductCommand.cs +++ b/src/FakeStoreNet.Application/Features/Product/Commands/DeleteProduct/DeleteProductCommand.cs @@ -1,6 +1,6 @@ +using FakeStoreNet.Domain.Exceptions; using MediatR; using OneOf; -using FakeStoreNet.Domain.Common; namespace FakeStoreNet.Application.Features.Product.Commands.DeleteProduct { diff --git a/src/FakeStoreNet.Application/Features/Product/Commands/DeleteProduct/DeleteProductCommandHandler.cs b/src/FakeStoreNet.Application/Features/Product/Commands/DeleteProduct/DeleteProductCommandHandler.cs index 84e1872..6be7cfa 100644 --- a/src/FakeStoreNet.Application/Features/Product/Commands/DeleteProduct/DeleteProductCommandHandler.cs +++ b/src/FakeStoreNet.Application/Features/Product/Commands/DeleteProduct/DeleteProductCommandHandler.cs @@ -1,39 +1,34 @@ -using System.Threading; -using System.Threading.Tasks; +using FakeStoreNet.Application.Common; +using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using MediatR; using OneOf; -using FakeStoreNet.Domain.Common; namespace FakeStoreNet.Application.Features.Product.Commands.DeleteProduct { /// /// Handles to delete an existing product. /// - public class DeleteProductCommandHandler : IRequestHandler> + /// + /// Initializes a new instance of . + /// + /// Product repository. + public class DeleteProductCommandHandler(IProductRepository repository, ICacheService cacheService) : IRequestHandler> { - private readonly IProductRepository _repository; - - /// - /// Initializes a new instance of . - /// - /// Product repository. - public DeleteProductCommandHandler(IProductRepository repository) - { - _repository = repository; - } /// - public Task> Handle(DeleteProductCommand request, CancellationToken cancellationToken) + public async Task> Handle(DeleteProductCommand request, CancellationToken cancellationToken) { try { - var product = _repository.GetById(request.Id); - _repository.Delete(product); - return Task.FromResult>(Unit.Value); + var product = repository.GetById(request.Id); + repository.Delete(product); + await cacheService.RemoveAsync("GetAllProducts"); + return Unit.Value; } catch (DomainValidationException ex) { - return Task.FromResult>(ex); + return ex; } } } diff --git a/src/FakeStoreNet.Application/Features/Product/Commands/UpdateProduct/UpdateProductCommand.cs b/src/FakeStoreNet.Application/Features/Product/Commands/UpdateProduct/UpdateProductCommand.cs index 9d8b4d6..e441229 100644 --- a/src/FakeStoreNet.Application/Features/Product/Commands/UpdateProduct/UpdateProductCommand.cs +++ b/src/FakeStoreNet.Application/Features/Product/Commands/UpdateProduct/UpdateProductCommand.cs @@ -1,6 +1,6 @@ +using FakeStoreNet.Domain.Exceptions; using MediatR; using OneOf; -using FakeStoreNet.Domain.Common; namespace FakeStoreNet.Application.Features.Product.Commands.UpdateProduct { diff --git a/src/FakeStoreNet.Application/Features/Product/Commands/UpdateProduct/UpdateProductCommandHandler.cs b/src/FakeStoreNet.Application/Features/Product/Commands/UpdateProduct/UpdateProductCommandHandler.cs index 69dff9b..1a32943 100644 --- a/src/FakeStoreNet.Application/Features/Product/Commands/UpdateProduct/UpdateProductCommandHandler.cs +++ b/src/FakeStoreNet.Application/Features/Product/Commands/UpdateProduct/UpdateProductCommandHandler.cs @@ -1,52 +1,45 @@ -using System.Threading; -using System.Threading.Tasks; -using AutoMapper; -using MediatR; -using OneOf; +using FakeStoreNet.Application.Common; using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using FakeStoreNet.Application.Features.Product.Commands.UpdateProduct; +using MediatR; +using OneOf; namespace FakeStoreNet.Application.Features.Product.Commands.UpdateProduct { /// /// Handles to update an existing product. /// - public class UpdateProductCommandHandler : IRequestHandler> + /// + /// Initializes a new instance of . + /// + /// Product repository. + /// Cache service. + public class UpdateProductCommandHandler(IProductRepository repository, ICacheService cacheService) : IRequestHandler> { - private readonly IProductRepository _repository; - - /// - /// Initializes a new instance of . - /// - /// Product repository. - public UpdateProductCommandHandler(IProductRepository repository) - { - _repository = repository; - } - /// - public Task> Handle(UpdateProductCommand request, CancellationToken cancellationToken) + public async Task> Handle(UpdateProductCommand request, CancellationToken cancellationToken) { try { if (string.IsNullOrWhiteSpace(request.Title)) throw new DomainValidationException("Title is required"); - var product = _repository.GetById(request.Id); + var product = repository.GetById(request.Id); product.UpdateDetails( request.Title, -new Money(request.Price, product.Price.Currency), + new Money(request.Price, product.Price.Currency), request.Description, request.Category, request.Image, new Rating(request.Rate, request.Count) ); - _repository.Update(product); - return Task.FromResult>(Unit.Value); + repository.Update(product); + await cacheService.RemoveAsync("GetAllProducts"); + return Unit.Value; } catch (DomainValidationException ex) { - return Task.FromResult>(ex); + return ex; } } } diff --git a/src/FakeStoreNet.Application/Features/Product/Queries/GetAllProducts/GetAllProductsQuery.cs b/src/FakeStoreNet.Application/Features/Product/Queries/GetAllProducts/GetAllProductsQuery.cs index 0ce2dac..f6f9084 100644 --- a/src/FakeStoreNet.Application/Features/Product/Queries/GetAllProducts/GetAllProductsQuery.cs +++ b/src/FakeStoreNet.Application/Features/Product/Queries/GetAllProducts/GetAllProductsQuery.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using MediatR; namespace FakeStoreNet.Application.Features.Product.Queries.GetAllProducts diff --git a/src/FakeStoreNet.Application/Features/Product/Queries/GetAllProducts/GetAllProductsQueryHandler.cs b/src/FakeStoreNet.Application/Features/Product/Queries/GetAllProducts/GetAllProductsQueryHandler.cs index 755c6cb..e91feb8 100644 --- a/src/FakeStoreNet.Application/Features/Product/Queries/GetAllProducts/GetAllProductsQueryHandler.cs +++ b/src/FakeStoreNet.Application/Features/Product/Queries/GetAllProducts/GetAllProductsQueryHandler.cs @@ -1,11 +1,8 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using AutoMapper; -using MediatR; -using FakeStoreNet.Application.Features.Product.Queries.GetAllProducts; -using FakeStoreNet.Application.Features.Product.Queries; +using FakeStoreNet.Application.Common; using FakeStoreNet.Domain.Common; +using MediatR; +using Microsoft.Extensions.Options; namespace FakeStoreNet.Application.Features.Product.Queries.GetAllProducts { @@ -16,24 +13,33 @@ public class GetAllProductsQueryHandler : IRequestHandler /// Initializes a new instance of . /// /// Product repository. /// AutoMapper mapper. - public GetAllProductsQueryHandler(IProductRepository repository, IMapper mapper) + /// Cache service. + /// Cache settings. + public GetAllProductsQueryHandler(IProductRepository repository, IMapper mapper, ICacheService cacheService, IOptions cacheSettings) { _repository = repository; _mapper = mapper; + _cacheService = cacheService; + _cacheSettings = cacheSettings.Value; } /// - public Task> Handle(GetAllProductsQuery request, CancellationToken cancellationToken) + public async Task> Handle(GetAllProductsQuery request, CancellationToken cancellationToken) { var products = _repository.GetAll(); var dtos = _mapper.Map>(products); - return Task.FromResult(dtos); + var cacheKey = "GetAllProducts"; + var expiration = TimeSpan.FromSeconds(_cacheSettings.GetAllProductsAbsoluteExpirationInSeconds); + await _cacheService.SetAsync(cacheKey, dtos, absoluteExpiration: expiration); + return dtos; } } } diff --git a/src/FakeStoreNet.Application/Features/Product/Queries/GetProductById/GetProductByIdQuery.cs b/src/FakeStoreNet.Application/Features/Product/Queries/GetProductById/GetProductByIdQuery.cs index c7f0e2a..5d09904 100644 --- a/src/FakeStoreNet.Application/Features/Product/Queries/GetProductById/GetProductByIdQuery.cs +++ b/src/FakeStoreNet.Application/Features/Product/Queries/GetProductById/GetProductByIdQuery.cs @@ -1,5 +1,4 @@ using MediatR; -using FakeStoreNet.Application.Features.Product.Queries; namespace FakeStoreNet.Application.Features.Product.Queries.GetProductById { diff --git a/src/FakeStoreNet.Application/Features/Product/Queries/GetProductById/GetProductByIdQueryHandler.cs b/src/FakeStoreNet.Application/Features/Product/Queries/GetProductById/GetProductByIdQueryHandler.cs index 05ed20d..dee30e9 100644 --- a/src/FakeStoreNet.Application/Features/Product/Queries/GetProductById/GetProductByIdQueryHandler.cs +++ b/src/FakeStoreNet.Application/Features/Product/Queries/GetProductById/GetProductByIdQueryHandler.cs @@ -1,10 +1,6 @@ -using System.Threading; -using System.Threading.Tasks; using AutoMapper; -using MediatR; -using FakeStoreNet.Application.Features.Product.Queries.GetProductById; -using FakeStoreNet.Application.Features.Product.Queries; using FakeStoreNet.Domain.Common; +using MediatR; namespace FakeStoreNet.Application.Features.Product.Queries.GetProductById { diff --git a/src/FakeStoreNet.Domain/Common/DomainValidationException.cs b/src/FakeStoreNet.Domain/Common/DomainValidationException.cs deleted file mode 100644 index f9ddde7..0000000 --- a/src/FakeStoreNet.Domain/Common/DomainValidationException.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FakeStoreNet.Domain.Common -{ - /// - /// Exception thrown when a domain invariant is violated. - /// - public class DomainValidationException : Exception - { - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public DomainValidationException(string message) - : base(message) - { - } - } -} diff --git a/src/FakeStoreNet.Domain/Common/Entity.cs b/src/FakeStoreNet.Domain/Common/Entity.cs index b4e2360..01617f0 100644 --- a/src/FakeStoreNet.Domain/Common/Entity.cs +++ b/src/FakeStoreNet.Domain/Common/Entity.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace FakeStoreNet.Domain.Common { /// @@ -24,7 +22,7 @@ public abstract class Entity /// /// The object to compare with this instance. /// True if the specified object is equal to this instance; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is not Entity other || GetType() != other.GetType()) return false; diff --git a/src/FakeStoreNet.Domain/Common/EventLog.cs b/src/FakeStoreNet.Domain/Common/EventLog.cs index cf76d63..26ccfc2 100644 --- a/src/FakeStoreNet.Domain/Common/EventLog.cs +++ b/src/FakeStoreNet.Domain/Common/EventLog.cs @@ -1,5 +1,3 @@ -using System; - namespace FakeStoreNet.Domain.Common { /// diff --git a/src/FakeStoreNet.Domain/Common/Events/ProductCreatedEvent.cs b/src/FakeStoreNet.Domain/Common/Events/ProductCreatedEvent.cs index 5df3068..b3b44e3 100644 --- a/src/FakeStoreNet.Domain/Common/Events/ProductCreatedEvent.cs +++ b/src/FakeStoreNet.Domain/Common/Events/ProductCreatedEvent.cs @@ -1,6 +1,3 @@ -using System; -using FakeStoreNet.Domain.Common; - namespace FakeStoreNet.Domain.Common.Events { /// diff --git a/src/FakeStoreNet.Domain/Common/Events/ProductUpdatedEvent.cs b/src/FakeStoreNet.Domain/Common/Events/ProductUpdatedEvent.cs index 040506c..573af6d 100644 --- a/src/FakeStoreNet.Domain/Common/Events/ProductUpdatedEvent.cs +++ b/src/FakeStoreNet.Domain/Common/Events/ProductUpdatedEvent.cs @@ -1,6 +1,3 @@ -using System; -using FakeStoreNet.Domain.Common; - namespace FakeStoreNet.Domain.Common.Events { /// diff --git a/src/FakeStoreNet.Domain/Common/IDomainEvent.cs b/src/FakeStoreNet.Domain/Common/IDomainEvent.cs index 71d4636..3c97cc7 100644 --- a/src/FakeStoreNet.Domain/Common/IDomainEvent.cs +++ b/src/FakeStoreNet.Domain/Common/IDomainEvent.cs @@ -1,5 +1,3 @@ -using System; - namespace FakeStoreNet.Domain.Common { /// diff --git a/src/FakeStoreNet.Domain/Common/IEventBus.cs b/src/FakeStoreNet.Domain/Common/IEventBus.cs index 1ce0b3f..02320c3 100644 --- a/src/FakeStoreNet.Domain/Common/IEventBus.cs +++ b/src/FakeStoreNet.Domain/Common/IEventBus.cs @@ -1,6 +1,3 @@ -using System; -using System.Threading.Tasks; - namespace FakeStoreNet.Domain.Common { /// diff --git a/src/FakeStoreNet.Domain/Common/IEventLogRepository.cs b/src/FakeStoreNet.Domain/Common/IEventLogRepository.cs index f6c0684..4624c62 100644 --- a/src/FakeStoreNet.Domain/Common/IEventLogRepository.cs +++ b/src/FakeStoreNet.Domain/Common/IEventLogRepository.cs @@ -1,5 +1,3 @@ -using System.Threading.Tasks; - namespace FakeStoreNet.Domain.Common { /// diff --git a/src/FakeStoreNet.Domain/Common/IProductRepository.cs b/src/FakeStoreNet.Domain/Common/IProductRepository.cs index 14321e6..1b68dc5 100644 --- a/src/FakeStoreNet.Domain/Common/IProductRepository.cs +++ b/src/FakeStoreNet.Domain/Common/IProductRepository.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FakeStoreNet.Domain.Entities; namespace FakeStoreNet.Domain.Common diff --git a/src/FakeStoreNet.Domain/Common/IUserRepository.cs b/src/FakeStoreNet.Domain/Common/IUserRepository.cs index 81668a6..0be5c6b 100644 --- a/src/FakeStoreNet.Domain/Common/IUserRepository.cs +++ b/src/FakeStoreNet.Domain/Common/IUserRepository.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FakeStoreNet.Domain.Entities; namespace FakeStoreNet.Domain.Common diff --git a/src/FakeStoreNet.Domain/Entities/Cart.cs b/src/FakeStoreNet.Domain/Entities/Cart.cs index 58bb5a2..24547e4 100644 --- a/src/FakeStoreNet.Domain/Entities/Cart.cs +++ b/src/FakeStoreNet.Domain/Entities/Cart.cs @@ -1,4 +1,5 @@ using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.Entities { @@ -35,7 +36,7 @@ public Cart(int userId) UserId = userId; Date = DateTime.UtcNow; - _items = new List(); + _items = []; } /// diff --git a/src/FakeStoreNet.Domain/Entities/CartItem.cs b/src/FakeStoreNet.Domain/Entities/CartItem.cs index 22657af..bd08fde 100644 --- a/src/FakeStoreNet.Domain/Entities/CartItem.cs +++ b/src/FakeStoreNet.Domain/Entities/CartItem.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; namespace FakeStoreNet.Domain.Entities diff --git a/src/FakeStoreNet.Domain/Entities/Order.cs b/src/FakeStoreNet.Domain/Entities/Order.cs index 98de291..50be0fc 100644 --- a/src/FakeStoreNet.Domain/Entities/Order.cs +++ b/src/FakeStoreNet.Domain/Entities/Order.cs @@ -1,4 +1,5 @@ using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.Entities { diff --git a/src/FakeStoreNet.Domain/Entities/OrderItem.cs b/src/FakeStoreNet.Domain/Entities/OrderItem.cs index 300d432..cc5ca77 100644 --- a/src/FakeStoreNet.Domain/Entities/OrderItem.cs +++ b/src/FakeStoreNet.Domain/Entities/OrderItem.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; namespace FakeStoreNet.Domain.Entities diff --git a/src/FakeStoreNet.Domain/Entities/Product.cs b/src/FakeStoreNet.Domain/Entities/Product.cs index 2b80820..3ef05a4 100644 --- a/src/FakeStoreNet.Domain/Entities/Product.cs +++ b/src/FakeStoreNet.Domain/Entities/Product.cs @@ -1,6 +1,7 @@ using FakeStoreNet.Domain.Common; -using FakeStoreNet.Domain.ValueObjects; using FakeStoreNet.Domain.Common.Events; +using FakeStoreNet.Domain.Exceptions; +using FakeStoreNet.Domain.ValueObjects; namespace FakeStoreNet.Domain.Entities { diff --git a/src/FakeStoreNet.Domain/Entities/User.cs b/src/FakeStoreNet.Domain/Entities/User.cs index 8e57dbd..ab070df 100644 --- a/src/FakeStoreNet.Domain/Entities/User.cs +++ b/src/FakeStoreNet.Domain/Entities/User.cs @@ -1,4 +1,5 @@ using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; namespace FakeStoreNet.Domain.Entities diff --git a/src/FakeStoreNet.Domain/Exceptions/DomainValidationException.cs b/src/FakeStoreNet.Domain/Exceptions/DomainValidationException.cs new file mode 100644 index 0000000..6f2feaf --- /dev/null +++ b/src/FakeStoreNet.Domain/Exceptions/DomainValidationException.cs @@ -0,0 +1,13 @@ +namespace FakeStoreNet.Domain.Exceptions +{ + /// + /// Exception thrown when a domain invariant is violated. + /// + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public class DomainValidationException(string message) : Exception(message) + { + } +} diff --git a/src/FakeStoreNet.Domain/ValueObjects/Address.cs b/src/FakeStoreNet.Domain/ValueObjects/Address.cs index 661b248..e42a852 100644 --- a/src/FakeStoreNet.Domain/ValueObjects/Address.cs +++ b/src/FakeStoreNet.Domain/ValueObjects/Address.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.ValueObjects { @@ -68,7 +68,7 @@ public Address(string street, string number, string city, string zipCode, Geoloc /// /// The object to compare. /// True if the values are equal; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is not Address other) return false; diff --git a/src/FakeStoreNet.Domain/ValueObjects/Geolocation.cs b/src/FakeStoreNet.Domain/ValueObjects/Geolocation.cs index 25faee2..d29007c 100644 --- a/src/FakeStoreNet.Domain/ValueObjects/Geolocation.cs +++ b/src/FakeStoreNet.Domain/ValueObjects/Geolocation.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.ValueObjects { @@ -40,7 +40,7 @@ public Geolocation(string latitude, string longitude) /// /// The object to compare. /// True if the values are equal; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is not Geolocation other) return false; diff --git a/src/FakeStoreNet.Domain/ValueObjects/Money.cs b/src/FakeStoreNet.Domain/ValueObjects/Money.cs index 9519a5b..d98b6b6 100644 --- a/src/FakeStoreNet.Domain/ValueObjects/Money.cs +++ b/src/FakeStoreNet.Domain/ValueObjects/Money.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.ValueObjects { @@ -40,7 +40,7 @@ public Money(decimal amount, string currency) /// /// The object to compare. /// True if the values are equal; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is not Money other) return false; diff --git a/src/FakeStoreNet.Domain/ValueObjects/Name.cs b/src/FakeStoreNet.Domain/ValueObjects/Name.cs index 24f2648..1cd446d 100644 --- a/src/FakeStoreNet.Domain/ValueObjects/Name.cs +++ b/src/FakeStoreNet.Domain/ValueObjects/Name.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.ValueObjects { @@ -40,7 +40,7 @@ public Name(string firstName, string lastName) /// /// The object to compare. /// True if the values are equal; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is not Name other) return false; diff --git a/src/FakeStoreNet.Domain/ValueObjects/Quantity.cs b/src/FakeStoreNet.Domain/ValueObjects/Quantity.cs index 73c7d27..309c2df 100644 --- a/src/FakeStoreNet.Domain/ValueObjects/Quantity.cs +++ b/src/FakeStoreNet.Domain/ValueObjects/Quantity.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.ValueObjects { @@ -30,7 +30,7 @@ public Quantity(int value) /// /// The object to compare. /// True if the values are equal; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is not Quantity other) return false; diff --git a/src/FakeStoreNet.Domain/ValueObjects/Rating.cs b/src/FakeStoreNet.Domain/ValueObjects/Rating.cs index f79a67b..008f0d4 100644 --- a/src/FakeStoreNet.Domain/ValueObjects/Rating.cs +++ b/src/FakeStoreNet.Domain/ValueObjects/Rating.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.ValueObjects { @@ -40,7 +40,7 @@ public Rating(double rate, int count) /// /// The object to compare. /// True if the values are equal; otherwise, false. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is not Rating other) return false; diff --git a/src/FakeStoreNet.Infrastructure/Caching/MemoryCacheService.cs b/src/FakeStoreNet.Infrastructure/Caching/MemoryCacheService.cs new file mode 100644 index 0000000..45ac4ca --- /dev/null +++ b/src/FakeStoreNet.Infrastructure/Caching/MemoryCacheService.cs @@ -0,0 +1,52 @@ +using FakeStoreNet.Application.Common; +using Microsoft.Extensions.Caching.Memory; + +namespace FakeStoreNet.Infrastructure.Caching +{ + /// + /// In-memory cache service implementation using IMemoryCache. + /// + public class MemoryCacheService : ICacheService + { + private readonly IMemoryCache _memoryCache; + + /// + /// Initializes a new instance of . + /// + /// The IMemoryCache instance. + public MemoryCacheService(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + /// + public Task GetAsync(string key) + { + _memoryCache.TryGetValue(key, out T? value); + return Task.FromResult(value); + } + + /// + public Task SetAsync(string key, T value, TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null) + { + var options = new MemoryCacheEntryOptions(); + if (absoluteExpiration.HasValue) + { + options.SetAbsoluteExpiration(absoluteExpiration.Value); + } + if (slidingExpiration.HasValue) + { + options.SetSlidingExpiration(slidingExpiration.Value); + } + _memoryCache.Set(key, value, options); + return Task.CompletedTask; + } + + /// + public Task RemoveAsync(string key) + { + _memoryCache.Remove(key); + return Task.CompletedTask; + } + } +} diff --git a/src/FakeStoreNet.Infrastructure/EventDispatching/InMemoryEventBus.cs b/src/FakeStoreNet.Infrastructure/EventDispatching/InMemoryEventBus.cs index 33a8803..5daa2b4 100644 --- a/src/FakeStoreNet.Infrastructure/EventDispatching/InMemoryEventBus.cs +++ b/src/FakeStoreNet.Infrastructure/EventDispatching/InMemoryEventBus.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; using FakeStoreNet.Domain.Common; +using System.Collections.Concurrent; namespace FakeStoreNet.Infrastructure.EventDispatching { diff --git a/src/FakeStoreNet.Infrastructure/EventDispatching/InMemoryEventLogRepository.cs b/src/FakeStoreNet.Infrastructure/EventDispatching/InMemoryEventLogRepository.cs index a662919..d045044 100644 --- a/src/FakeStoreNet.Infrastructure/EventDispatching/InMemoryEventLogRepository.cs +++ b/src/FakeStoreNet.Infrastructure/EventDispatching/InMemoryEventLogRepository.cs @@ -1,6 +1,5 @@ -using System.Collections.Concurrent; -using System.Threading.Tasks; using FakeStoreNet.Domain.Common; +using System.Collections.Concurrent; namespace FakeStoreNet.Infrastructure.EventDispatching { diff --git a/src/FakeStoreNet.Infrastructure/FakeStoreNet.Infrastructure.csproj b/src/FakeStoreNet.Infrastructure/FakeStoreNet.Infrastructure.csproj index 631a8f6..be95fb3 100644 --- a/src/FakeStoreNet.Infrastructure/FakeStoreNet.Infrastructure.csproj +++ b/src/FakeStoreNet.Infrastructure/FakeStoreNet.Infrastructure.csproj @@ -8,6 +8,10 @@ + + + + diff --git a/tests/FakeStoreNet.Api.Tests/FakeStoreNet.Api.Tests.csproj b/tests/FakeStoreNet.Api.Tests/FakeStoreNet.Api.Tests.csproj index 879433b..dabd59b 100644 --- a/tests/FakeStoreNet.Api.Tests/FakeStoreNet.Api.Tests.csproj +++ b/tests/FakeStoreNet.Api.Tests/FakeStoreNet.Api.Tests.csproj @@ -23,7 +23,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/FakeStoreNet.Api.Tests/Usings.cs b/tests/FakeStoreNet.Api.Tests/Usings.cs index 4c34d48..a611174 100644 --- a/tests/FakeStoreNet.Api.Tests/Usings.cs +++ b/tests/FakeStoreNet.Api.Tests/Usings.cs @@ -1,3 +1 @@ -global using Bogus; -global using Shouldly; -global using Xunit; \ No newline at end of file +global using Xunit; \ No newline at end of file diff --git a/tests/FakeStoreNet.Application.Tests/FakeStoreNet.Application.Tests.csproj b/tests/FakeStoreNet.Application.Tests/FakeStoreNet.Application.Tests.csproj index 27121cb..d83572c 100644 --- a/tests/FakeStoreNet.Application.Tests/FakeStoreNet.Application.Tests.csproj +++ b/tests/FakeStoreNet.Application.Tests/FakeStoreNet.Application.Tests.csproj @@ -23,7 +23,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/CreateProductCommandHandlerTests.cs b/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/CreateProductCommandHandlerTests.cs index 333cb6b..0896213 100644 --- a/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/CreateProductCommandHandlerTests.cs +++ b/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/CreateProductCommandHandlerTests.cs @@ -1,15 +1,8 @@ -using System.Threading; -using System.Threading.Tasks; -using Bogus; -using NSubstitute; -using OneOf; -using Shouldly; -using Xunit; +using FakeStoreNet.Application.Common; using FakeStoreNet.Application.Features.Product.Commands.CreateProduct; using FakeStoreNet.Domain.Common; -using FakeStoreNet.Domain.Common.Events; -using FakeStoreNet.Domain.Entities; -using FakeStoreNet.Domain.ValueObjects; +using FakeStoreNet.Domain.Exceptions; +using NSubstitute; namespace FakeStoreNet.Application.Tests.Features.Product.Commands { @@ -45,7 +38,8 @@ public async Task GivenValidCommand_WhenHandling_ThenReturnsNewProductId() capturedId = product.Id; }); - var handler = new CreateProductCommandHandler(_repository, null!); + var cacheService = Substitute.For(); + var handler = new CreateProductCommandHandler(_repository, cacheService); // Act var result = await handler.Handle(command, CancellationToken.None); @@ -99,7 +93,8 @@ public async Task GivenCommandWithDomainError_WhenHandling_ThenReturnsDomainVali .When(r => r.Add(Arg.Any())) .Do(callInfo => throw new DomainValidationException("Test error")); - var handler = new CreateProductCommandHandler(_repository, null!); + var cacheService = Substitute.For(); + var handler = new CreateProductCommandHandler(_repository, cacheService); // Act var result = await handler.Handle(command, CancellationToken.None); diff --git a/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/DeleteProductCommandHandlerTests.cs b/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/DeleteProductCommandHandlerTests.cs index b3560d7..4099ebc 100644 --- a/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/DeleteProductCommandHandlerTests.cs +++ b/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/DeleteProductCommandHandlerTests.cs @@ -1,12 +1,8 @@ -using System.Threading; -using System.Threading.Tasks; -using NSubstitute; -using OneOf; -using Shouldly; -using Xunit; +using FakeStoreNet.Application.Common; using FakeStoreNet.Application.Features.Product.Commands.DeleteProduct; using FakeStoreNet.Domain.Common; -using FakeStoreNet.Domain.Entities; +using FakeStoreNet.Domain.Exceptions; +using NSubstitute; using DomainProduct = FakeStoreNet.Domain.Entities.Product; namespace FakeStoreNet.Application.Tests.Features.Product.Commands @@ -24,7 +20,8 @@ public async Task GivenValidDeleteCommand_WhenHandling_ThenReturnsUnit() _repository.GetById(3).Returns(existing); var command = new DeleteProductCommand { Id = 3 }; - var handler = new DeleteProductCommandHandler(_repository); + var cacheService = Substitute.For(); + var handler = new DeleteProductCommandHandler(_repository, cacheService); // Act var result = await handler.Handle(command, CancellationToken.None); @@ -46,7 +43,8 @@ public async Task GivenRepositoryThrowsError_WhenHandling_ThenReturnsDomainValid .Do(call => throw new DomainValidationException("Delete error")); var command = new DeleteProductCommand { Id = 4 }; - var handler = new DeleteProductCommandHandler(_repository); + var cacheService = Substitute.For(); + var handler = new DeleteProductCommandHandler(_repository, cacheService); // Act var result = await handler.Handle(command, CancellationToken.None); diff --git a/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/UpdateProductCommandHandlerTests.cs b/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/UpdateProductCommandHandlerTests.cs index f061281..f294717 100644 --- a/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/UpdateProductCommandHandlerTests.cs +++ b/tests/FakeStoreNet.Application.Tests/Features/Product/Commands/UpdateProductCommandHandlerTests.cs @@ -1,14 +1,8 @@ -using System.Threading; -using System.Threading.Tasks; -using Bogus; -using NSubstitute; -using OneOf; -using Shouldly; -using Xunit; +using FakeStoreNet.Application.Common; using FakeStoreNet.Application.Features.Product.Commands.UpdateProduct; using FakeStoreNet.Domain.Common; -using FakeStoreNet.Domain.Entities; using FakeStoreNet.Domain.ValueObjects; +using NSubstitute; using DomainProduct = FakeStoreNet.Domain.Entities.Product; namespace FakeStoreNet.Application.Tests.Features.Product.Commands @@ -45,7 +39,8 @@ public async Task GivenValidUpdateCommand_WhenHandling_ThenReturnsUnit() Count = 50 }; - var handler = new UpdateProductCommandHandler(_repository); + var cacheService = Substitute.For(); + var handler = new UpdateProductCommandHandler(_repository, cacheService); // Act var result = await handler.Handle(command, CancellationToken.None); @@ -77,7 +72,8 @@ public async Task GivenInvalidUpdateCommand_WhenHandling_ThenReturnsDomainValida Count = -1 }; - var handler = new UpdateProductCommandHandler(_repository); + var cacheService = Substitute.For(); + var handler = new UpdateProductCommandHandler(_repository, cacheService); // Act var result = await handler.Handle(command, CancellationToken.None); diff --git a/tests/FakeStoreNet.Application.Tests/Features/Product/Queries/GetAllProductsQueryHandlerTests.cs b/tests/FakeStoreNet.Application.Tests/Features/Product/Queries/GetAllProductsQueryHandlerTests.cs index a095a4d..29880be 100644 --- a/tests/FakeStoreNet.Application.Tests/Features/Product/Queries/GetAllProductsQueryHandlerTests.cs +++ b/tests/FakeStoreNet.Application.Tests/Features/Product/Queries/GetAllProductsQueryHandlerTests.cs @@ -1,18 +1,12 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using AutoMapper; -using Bogus; -using NSubstitute; -using Shouldly; -using Xunit; -using FakeStoreNet.Application.Features.Product.Queries.GetAllProducts; -using FakeStoreNet.Application.Features.Product.Queries; using FakeStoreNet.Application.Common; +using FakeStoreNet.Application.Features.Product.Queries; +using FakeStoreNet.Application.Features.Product.Queries.GetAllProducts; using FakeStoreNet.Domain.Common; -using FakeStoreNet.Domain.Entities; -using DomainProduct = FakeStoreNet.Domain.Entities.Product; using FakeStoreNet.Domain.ValueObjects; +using Microsoft.Extensions.Options; +using NSubstitute; +using DomainProduct = FakeStoreNet.Domain.Entities.Product; namespace FakeStoreNet.Application.Tests.Features.Product.Queries { @@ -49,7 +43,9 @@ public async Task GivenRepositoryReturnsProducts_WhenHandlingQuery_ThenReturnsDt _repository.GetAll().Returns(products); var query = new GetAllProductsQuery(); - var handler = new GetAllProductsQueryHandler(_repository, _mapper); + var cacheService = Substitute.For(); + var cacheSettings = Options.Create(new CacheSettings { GetAllProductsAbsoluteExpirationInSeconds = 60 }); + var handler = new GetAllProductsQueryHandler(_repository, _mapper, cacheService, cacheSettings); // Act var dtos = await handler.Handle(query, CancellationToken.None); @@ -67,7 +63,9 @@ public async Task GivenRepositoryReturnsEmpty_WhenHandlingQuery_ThenReturnsEmpty _repository.GetAll().Returns(new List()); var query = new GetAllProductsQuery(); - var handler = new GetAllProductsQueryHandler(_repository, _mapper); + var cacheService = Substitute.For(); + var cacheSettings = Options.Create(new CacheSettings { GetAllProductsAbsoluteExpirationInSeconds = 60 }); + var handler = new GetAllProductsQueryHandler(_repository, _mapper, cacheService, cacheSettings); // Act var dtos = await handler.Handle(query, CancellationToken.None); diff --git a/tests/FakeStoreNet.Application.Tests/Features/Product/Queries/GetProductByIdQueryHandlerTests.cs b/tests/FakeStoreNet.Application.Tests/Features/Product/Queries/GetProductByIdQueryHandlerTests.cs index 13080f0..ec854e8 100644 --- a/tests/FakeStoreNet.Application.Tests/Features/Product/Queries/GetProductByIdQueryHandlerTests.cs +++ b/tests/FakeStoreNet.Application.Tests/Features/Product/Queries/GetProductByIdQueryHandlerTests.cs @@ -1,23 +1,17 @@ -using System.Threading; -using System.Threading.Tasks; using AutoMapper; -using Bogus; -using NSubstitute; -using Shouldly; -using Xunit; -using FakeStoreNet.Application.Features.Product.Queries.GetProductById; -using FakeStoreNet.Application.Features.Product.Queries; using FakeStoreNet.Application.Common; +using FakeStoreNet.Application.Features.Product.Queries.GetProductById; using FakeStoreNet.Domain.Common; -using FakeStoreNet.Domain.Entities; -using DomainProduct = FakeStoreNet.Domain.Entities.Product; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; +using NSubstitute; +using DomainProduct = FakeStoreNet.Domain.Entities.Product; namespace FakeStoreNet.Application.Tests.Features.Product.Queries { public class GetProductByIdQueryHandlerTests { - private readonly Faker _faker = new Faker(); + private readonly Faker _faker = new(); private readonly IProductRepository _repository = Substitute.For(); private readonly IMapper _mapper; diff --git a/tests/FakeStoreNet.Domain.Tests/AddressTests.cs b/tests/FakeStoreNet.Domain.Tests/AddressTests.cs index f843f5b..bbf62b1 100644 --- a/tests/FakeStoreNet.Domain.Tests/AddressTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/AddressTests.cs @@ -1,12 +1,11 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using Shouldly; namespace FakeStoreNet.Domain.Tests { public class AddressTests { - private readonly Geolocation ValidGeo = new Geolocation("12.34", "56.78"); + private readonly Geolocation ValidGeo = new("12.34", "56.78"); [Fact(DisplayName = "Given valid address data, when creating Address, then properties are assigned")] public void GivenValidAddress_WhenCreatingAddress_ThenPropertiesAreAssigned() @@ -42,8 +41,10 @@ public void GivenMissingField_WhenCreatingAddress_ThenDomainValidationException( [Fact(DisplayName = "Given null geolocation, when creating Address, then DomainValidationException is thrown")] public void GivenNullGeolocation_WhenCreatingAddress_ThenDomainValidationException() { +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. Should.Throw(() => new Address("St", "1", "City", "00000", null)) .Message.ShouldBe("Geolocation is required"); +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. } } } diff --git a/tests/FakeStoreNet.Domain.Tests/CartTests.cs b/tests/FakeStoreNet.Domain.Tests/CartTests.cs index 15fc8ce..1be9fa4 100644 --- a/tests/FakeStoreNet.Domain.Tests/CartTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/CartTests.cs @@ -1,6 +1,5 @@ -using FakeStoreNet.Domain.Common; using FakeStoreNet.Domain.Entities; -using Shouldly; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.Tests { diff --git a/tests/FakeStoreNet.Domain.Tests/FakeStoreNet.Domain.Tests.csproj b/tests/FakeStoreNet.Domain.Tests/FakeStoreNet.Domain.Tests.csproj index e8fda78..98e926c 100644 --- a/tests/FakeStoreNet.Domain.Tests/FakeStoreNet.Domain.Tests.csproj +++ b/tests/FakeStoreNet.Domain.Tests/FakeStoreNet.Domain.Tests.csproj @@ -23,7 +23,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/FakeStoreNet.Domain.Tests/GeolocationTests.cs b/tests/FakeStoreNet.Domain.Tests/GeolocationTests.cs index 01db947..bda6008 100644 --- a/tests/FakeStoreNet.Domain.Tests/GeolocationTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/GeolocationTests.cs @@ -1,6 +1,5 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using Shouldly; namespace FakeStoreNet.Domain.Tests { diff --git a/tests/FakeStoreNet.Domain.Tests/MoneyTests.cs b/tests/FakeStoreNet.Domain.Tests/MoneyTests.cs index d2af560..3753aa2 100644 --- a/tests/FakeStoreNet.Domain.Tests/MoneyTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/MoneyTests.cs @@ -1,4 +1,4 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; diff --git a/tests/FakeStoreNet.Domain.Tests/NameTests.cs b/tests/FakeStoreNet.Domain.Tests/NameTests.cs index 41d92b8..d86b34d 100644 --- a/tests/FakeStoreNet.Domain.Tests/NameTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/NameTests.cs @@ -1,6 +1,5 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using Shouldly; namespace FakeStoreNet.Domain.Tests { diff --git a/tests/FakeStoreNet.Domain.Tests/OrderItemTests.cs b/tests/FakeStoreNet.Domain.Tests/OrderItemTests.cs index 83115bc..111a05d 100644 --- a/tests/FakeStoreNet.Domain.Tests/OrderItemTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/OrderItemTests.cs @@ -1,7 +1,6 @@ -using FakeStoreNet.Domain.Common; using FakeStoreNet.Domain.Entities; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using Shouldly; namespace FakeStoreNet.Domain.Tests { diff --git a/tests/FakeStoreNet.Domain.Tests/OrderTests.cs b/tests/FakeStoreNet.Domain.Tests/OrderTests.cs index 8a1d977..8026430 100644 --- a/tests/FakeStoreNet.Domain.Tests/OrderTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/OrderTests.cs @@ -1,12 +1,11 @@ -using FakeStoreNet.Domain.Common; using FakeStoreNet.Domain.Entities; -using Shouldly; +using FakeStoreNet.Domain.Exceptions; namespace FakeStoreNet.Domain.Tests { public class OrderTests { - private readonly OrderItem _validItem = new OrderItem(1, new ValueObjects.Quantity(2)); + private readonly OrderItem _validItem = new(1, new ValueObjects.Quantity(2)); [Fact(DisplayName = "Given valid parameters, when creating Order, then properties are initialized")] public void GivenValidParameters_WhenCreatingOrder_ThenPropertiesAreInitialized() @@ -27,7 +26,7 @@ public void GivenValidParameters_WhenCreatingOrder_ThenPropertiesAreInitialized( [Fact(DisplayName = "Given invalid userId, when creating Order, then DomainValidationException is thrown")] public void GivenInvalidUserId_WhenCreatingOrder_ThenExceptionIsThrown() { - Should.Throw(() => new Order(0, new List { _validItem })) + Should.Throw(() => new Order(0, [_validItem])) .Message.ShouldBe("UserId must be greater than zero"); } @@ -41,14 +40,14 @@ public void GivenNullItems_WhenCreatingOrder_ThenExceptionIsThrown() [Fact(DisplayName = "Given empty items list, when creating Order, then DomainValidationException is thrown")] public void GivenEmptyItems_WhenCreatingOrder_ThenExceptionIsThrown() { - Should.Throw(() => new Order(1, new List())) + Should.Throw(() => new Order(1, [])) .Message.ShouldBe("At least one order item is required"); } [Fact(DisplayName = "Given valid order, when submitting Order, then IsSubmitted is true")] public void GivenValidOrder_WhenSubmittingOrder_ThenIsSubmittedIsTrue() { - var order = new Order(1, new List { _validItem }); + var order = new Order(1, [_validItem]); order.SubmitOrder(); @@ -58,7 +57,7 @@ public void GivenValidOrder_WhenSubmittingOrder_ThenIsSubmittedIsTrue() [Fact(DisplayName = "Given already submitted order, when submitting again, then DomainValidationException is thrown")] public void GivenAlreadySubmittedOrder_WhenSubmittingAgain_ThenExceptionIsThrown() { - var order = new Order(1, new List { _validItem }); + var order = new Order(1, [_validItem]); order.SubmitOrder(); Should.Throw(() => order.SubmitOrder()) diff --git a/tests/FakeStoreNet.Domain.Tests/ProductDomainEventTests.cs b/tests/FakeStoreNet.Domain.Tests/ProductDomainEventTests.cs index 881fb04..ecbdaa5 100644 --- a/tests/FakeStoreNet.Domain.Tests/ProductDomainEventTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/ProductDomainEventTests.cs @@ -1,9 +1,6 @@ -using System.Linq; using FakeStoreNet.Domain.Common.Events; using FakeStoreNet.Domain.Entities; using FakeStoreNet.Domain.ValueObjects; -using Shouldly; -using Xunit; namespace FakeStoreNet.Domain.Tests { diff --git a/tests/FakeStoreNet.Domain.Tests/ProductTests.cs b/tests/FakeStoreNet.Domain.Tests/ProductTests.cs index a5f33cb..c327936 100644 --- a/tests/FakeStoreNet.Domain.Tests/ProductTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/ProductTests.cs @@ -1,14 +1,13 @@ -using FakeStoreNet.Domain.Common; using FakeStoreNet.Domain.Entities; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using Shouldly; namespace FakeStoreNet.Domain.Tests { public class ProductTests { - private readonly Money ValidPrice = new Money(10m, "USD"); - private readonly Rating ValidRating = new Rating(4.5, 10); + private readonly Money ValidPrice = new(10m, "USD"); + private readonly Rating ValidRating = new(4.5, 10); [Fact(DisplayName = "Given valid parameters, when creating Product, then properties are assigned")] public void GivenValidParameters_WhenCreatingProduct_ThenPropertiesAreAssigned() diff --git a/tests/FakeStoreNet.Domain.Tests/QuantityTests.cs b/tests/FakeStoreNet.Domain.Tests/QuantityTests.cs index c38e074..bf5db78 100644 --- a/tests/FakeStoreNet.Domain.Tests/QuantityTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/QuantityTests.cs @@ -1,6 +1,5 @@ -using FakeStoreNet.Domain.Common; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using Shouldly; namespace FakeStoreNet.Domain.Tests { diff --git a/tests/FakeStoreNet.Domain.Tests/UserTests.cs b/tests/FakeStoreNet.Domain.Tests/UserTests.cs index 537a5cf..bc9aa9f 100644 --- a/tests/FakeStoreNet.Domain.Tests/UserTests.cs +++ b/tests/FakeStoreNet.Domain.Tests/UserTests.cs @@ -1,14 +1,13 @@ -using FakeStoreNet.Domain.Common; using FakeStoreNet.Domain.Entities; +using FakeStoreNet.Domain.Exceptions; using FakeStoreNet.Domain.ValueObjects; -using Shouldly; namespace FakeStoreNet.Domain.Tests { public class UserTests { - private readonly Name ValidName = new Name("Jane", "Doe"); - private readonly Address ValidAddress = new Address("Street", "1", "City", "00000", new Geolocation("0", "0")); + private readonly Name ValidName = new("Jane", "Doe"); + private readonly Address ValidAddress = new("Street", "1", "City", "00000", new Geolocation("0", "0")); [Fact(DisplayName = "Given valid parameters, when creating User, then properties are assigned")] public void GivenValidParameters_WhenCreatingUser_ThenPropertiesAreAssigned() diff --git a/tests/FakeStoreNet.Domain.Tests/Usings.cs b/tests/FakeStoreNet.Domain.Tests/Usings.cs index 4c34d48..b75bf43 100644 --- a/tests/FakeStoreNet.Domain.Tests/Usings.cs +++ b/tests/FakeStoreNet.Domain.Tests/Usings.cs @@ -1,3 +1,2 @@ -global using Bogus; -global using Shouldly; +global using Shouldly; global using Xunit; \ No newline at end of file diff --git a/tests/FakeStoreNet.Infrastructure.Tests/EventDispatchingTests.cs b/tests/FakeStoreNet.Infrastructure.Tests/EventDispatchingTests.cs index 21fa4cf..44460d2 100644 --- a/tests/FakeStoreNet.Infrastructure.Tests/EventDispatchingTests.cs +++ b/tests/FakeStoreNet.Infrastructure.Tests/EventDispatchingTests.cs @@ -1,11 +1,7 @@ -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; using FakeStoreNet.Domain.Common; using FakeStoreNet.Domain.Common.Events; using FakeStoreNet.Infrastructure.EventDispatching; -using Shouldly; -using Xunit; +using System.Text.Json; namespace FakeStoreNet.Infrastructure.Tests { diff --git a/tests/FakeStoreNet.Infrastructure.Tests/FakeStoreNet.Infrastructure.Tests.csproj b/tests/FakeStoreNet.Infrastructure.Tests/FakeStoreNet.Infrastructure.Tests.csproj index ecba406..d3d6d1d 100644 --- a/tests/FakeStoreNet.Infrastructure.Tests/FakeStoreNet.Infrastructure.Tests.csproj +++ b/tests/FakeStoreNet.Infrastructure.Tests/FakeStoreNet.Infrastructure.Tests.csproj @@ -10,7 +10,8 @@ - + + @@ -23,7 +24,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -36,4 +37,4 @@ - \ No newline at end of file + diff --git a/tests/FakeStoreNet.Infrastructure.Tests/MemoryCacheServiceTests.cs b/tests/FakeStoreNet.Infrastructure.Tests/MemoryCacheServiceTests.cs new file mode 100644 index 0000000..0725c7b --- /dev/null +++ b/tests/FakeStoreNet.Infrastructure.Tests/MemoryCacheServiceTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading.Tasks; +using FakeStoreNet.Application.Common; +using FakeStoreNet.Infrastructure.Caching; +using Microsoft.Extensions.Caching.Memory; +using Shouldly; +using Xunit; + +namespace FakeStoreNet.Infrastructure.Tests +{ + /// + /// Unit tests for MemoryCacheService to improve code coverage. + /// + public class MemoryCacheServiceTests + { + private readonly MemoryCacheService _cacheService; + private readonly IMemoryCache _memoryCache; + + public MemoryCacheServiceTests() + { + _memoryCache = new MemoryCache(new MemoryCacheOptions()); + _cacheService = new MemoryCacheService(_memoryCache); + } + + [Fact(DisplayName = "GetAsync returns null when key does not exist")] + public async Task GetAsync_WhenKeyDoesNotExist_ReturnsNull() + { + var result = await _cacheService.GetAsync("nonexistent"); + result.ShouldBeNull(); + } + + [Fact(DisplayName = "SetAsync and GetAsync store and retrieve a string value")] + public async Task SetAsync_And_GetAsync_ReturnsValue() + { + var key = "testKey"; + var value = "testValue"; + await _cacheService.SetAsync(key, value); + var result = await _cacheService.GetAsync(key); + result.ShouldBe(value); + } + + [Fact(DisplayName = "RemoveAsync removes the cached entry")] + public async Task RemoveAsync_RemovesValue() + { + var key = "toRemove"; + await _cacheService.SetAsync(key, 123); + var before = await _cacheService.GetAsync(key); + before.ShouldBe(123); + await _cacheService.RemoveAsync(key); + var after = await _cacheService.GetAsync(key); + after.ShouldBe(0); + } + + [Fact(DisplayName = "SetAsync with expirations sets entry options and value is retrievable before expiration")] + public async Task SetAsync_WithExpirations_DoesNotThrow_And_ReturnsValueBeforeExpiration() + { + var key = "expiring"; + var value = new { Name = "John" }; + var absoluteExpiration = TimeSpan.FromMinutes(1); + var slidingExpiration = TimeSpan.FromSeconds(30); + + await _cacheService.SetAsync(key, value, absoluteExpiration, slidingExpiration); + var result = await _cacheService.GetAsync(key); + result.ShouldNotBeNull(); + result.ShouldBe(value); + } + } +} diff --git a/tests/FakeStoreNet.Infrastructure.Tests/Usings.cs b/tests/FakeStoreNet.Infrastructure.Tests/Usings.cs index 4c34d48..b75bf43 100644 --- a/tests/FakeStoreNet.Infrastructure.Tests/Usings.cs +++ b/tests/FakeStoreNet.Infrastructure.Tests/Usings.cs @@ -1,3 +1,2 @@ -global using Bogus; -global using Shouldly; +global using Shouldly; global using Xunit; \ No newline at end of file