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