diff --git a/src/HelloShop.ApiService/Extensions/OpenApiConfigureOptions.cs b/src/HelloShop.ApiService/Extensions/OpenApiConfigureOptions.cs
index 221a2d3..15cdae8 100644
--- a/src/HelloShop.ApiService/Extensions/OpenApiConfigureOptions.cs
+++ b/src/HelloShop.ApiService/Extensions/OpenApiConfigureOptions.cs
@@ -21,7 +21,8 @@ public class OpenApiConfigureOptions(IConfiguredServiceEndPointResolver serviceR
try
{
- HttpResponseMessage response = httpClient.GetAsync(uriBuilder.Uri).GetAwaiter().GetResult();
+ HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri) { Version = new Version(2, 0) };
+ HttpResponseMessage response = httpClient.SendAsync(request).GetAwaiter().GetResult();
if (response.IsSuccessStatusCode)
{
urlDescriptors.Add(new UrlDescriptor
diff --git a/src/HelloShop.ApiService/HelloShop.ApiService.csproj b/src/HelloShop.ApiService/HelloShop.ApiService.csproj
index f6b7233..66e34c5 100644
--- a/src/HelloShop.ApiService/HelloShop.ApiService.csproj
+++ b/src/HelloShop.ApiService/HelloShop.ApiService.csproj
@@ -9,6 +9,6 @@
-
+
\ No newline at end of file
diff --git a/src/HelloShop.AppHost/HelloShop.AppHost.csproj b/src/HelloShop.AppHost/HelloShop.AppHost.csproj
index 2a4cfa8..0b4dd96 100644
--- a/src/HelloShop.AppHost/HelloShop.AppHost.csproj
+++ b/src/HelloShop.AppHost/HelloShop.AppHost.csproj
@@ -15,6 +15,6 @@
-
+
\ No newline at end of file
diff --git a/src/HelloShop.BasketService/AutoMapper/BasketsMapConfiguration.cs b/src/HelloShop.BasketService/AutoMapper/BasketsMapConfiguration.cs
new file mode 100644
index 0000000..75d3627
--- /dev/null
+++ b/src/HelloShop.BasketService/AutoMapper/BasketsMapConfiguration.cs
@@ -0,0 +1,19 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+using AutoMapper;
+using HelloShop.BasketService.Entities;
+using HelloShop.BasketService.Protos;
+
+namespace HelloShop.BasketService.AutoMapper
+{
+ public class BasketsMapConfiguration : Profile
+ {
+ public BasketsMapConfiguration()
+ {
+ CreateMap();
+ CreateMap().ReverseMap();
+ CreateMap();
+ }
+ }
+}
diff --git a/src/HelloShop.BasketService/Entities/BasketItem.cs b/src/HelloShop.BasketService/Entities/BasketItem.cs
new file mode 100644
index 0000000..408bcb5
--- /dev/null
+++ b/src/HelloShop.BasketService/Entities/BasketItem.cs
@@ -0,0 +1,12 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+namespace HelloShop.BasketService.Entities
+{
+ public class BasketItem
+ {
+ public int ProductId { get; set; }
+
+ public int Quantity { get; set; }
+ }
+}
diff --git a/src/HelloShop.BasketService/Entities/CustomerBasket.cs b/src/HelloShop.BasketService/Entities/CustomerBasket.cs
new file mode 100644
index 0000000..dec2512
--- /dev/null
+++ b/src/HelloShop.BasketService/Entities/CustomerBasket.cs
@@ -0,0 +1,12 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+namespace HelloShop.BasketService.Entities
+{
+ public class CustomerBasket
+ {
+ public int BuyerId { get; set; }
+
+ public List Items { get; set; } = [];
+ }
+}
diff --git a/src/HelloShop.BasketService/HelloShop.BasketService.csproj b/src/HelloShop.BasketService/HelloShop.BasketService.csproj
index f7f620c..f555e61 100644
--- a/src/HelloShop.BasketService/HelloShop.BasketService.csproj
+++ b/src/HelloShop.BasketService/HelloShop.BasketService.csproj
@@ -1,16 +1,24 @@
-
-
- net8.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
+
+
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/HelloShop.BasketService/Program.cs b/src/HelloShop.BasketService/Program.cs
index c23d283..e0f29d5 100644
--- a/src/HelloShop.BasketService/Program.cs
+++ b/src/HelloShop.BasketService/Program.cs
@@ -1,22 +1,61 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
+using Calzolari.Grpc.AspNetCore.Validation;
+using HelloShop.BasketService.Repositories;
using HelloShop.BasketService.Services;
using HelloShop.ServiceDefaults.Extensions;
+using Microsoft.IdentityModel.Tokens;
+using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
-// Add services to the container.
-builder.Services.AddGrpc();
+// Add extensions services to the container.
+
+const string issuerSigningKey = HelloShop.ServiceDefaults.Constants.IdentityConstants.IssuerSigningKey;
+
+builder.Services.AddAuthentication().AddJwtBearer(options =>
+{
+ options.TokenValidationParameters.ValidateIssuer = false;
+ options.TokenValidationParameters.ValidateAudience = false;
+ options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(issuerSigningKey));
+});
+
+builder.Services.AddHttpContextAccessor();
+builder.Services.AddDistributedMemoryCache();
+builder.Services.AddSingleton();
+
+builder.Services.AddGrpc(options => options.EnableMessageValidation()).AddJsonTranscoding();
+builder.Services.AddGrpcSwagger();
+builder.Services.AddOpenApi();
+
+builder.Services.AddDataSeedingProviders();
+builder.Services.AddCustomLocalization();
+builder.Services.AddModelMapper().AddModelValidator();
+builder.Services.AddLocalization().AddPermissionDefinitions();
+builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization();
+builder.Services.AddGrpcValidation();
+builder.Services.AddCors();
+// End addd extensions services to the container.
var app = builder.Build();
+app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
+
app.MapDefaultEndpoints();
// Configure the HTTP request pipeline.
-app.MapGrpcService();
-app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
+app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.").WithTags("Welcome");
+// Configure extensions request pipeline.
+app.UseAuthentication().UseAuthorization();
+app.MapGrpcService();
+app.MapGrpcService();
+app.UseDataSeedingProviders();
+app.UseCustomLocalization();
+app.UseOpenApi();
+app.MapGroup("api/Permissions").MapPermissionDefinitions("Permissions");
+// End configure extensions request pipeline.
app.Run();
diff --git a/src/HelloShop.BasketService/Properties/launchSettings.json b/src/HelloShop.BasketService/Properties/launchSettings.json
index 4938073..eae0e29 100644
--- a/src/HelloShop.BasketService/Properties/launchSettings.json
+++ b/src/HelloShop.BasketService/Properties/launchSettings.json
@@ -4,7 +4,8 @@
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
- "launchBrowser": false,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
"applicationUrl": "http://localhost:8004",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
@@ -13,7 +14,8 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
- "launchBrowser": false,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
"applicationUrl": "https://localhost:8104;http://localhost:8004",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
diff --git a/src/HelloShop.BasketService/Protos/basket.proto b/src/HelloShop.BasketService/Protos/basket.proto
new file mode 100644
index 0000000..b09d21e
--- /dev/null
+++ b/src/HelloShop.BasketService/Protos/basket.proto
@@ -0,0 +1,42 @@
+syntax = "proto3";
+
+import "google/protobuf/empty.proto";
+import "google/api/annotations.proto";
+
+option csharp_namespace = "HelloShop.BasketService.Protos";
+
+package basket;
+
+service Basket {
+ rpc GetBasket(google.protobuf.Empty) returns (CustomerBasketResponse) {
+ option (google.api.http) = {
+ get: "/api/basket"
+ };
+ }
+
+ rpc UpdateBasket(UpdateBasketRequest) returns (CustomerBasketResponse) {
+ option (google.api.http) = {
+ put: "/api/basket"
+ body: "*"
+ };
+ }
+
+ rpc DeleteBasket(google.protobuf.Empty) returns (google.protobuf.Empty) {
+ option (google.api.http) = {
+ delete: "/api/basket"
+ };
+ }
+}
+
+message CustomerBasketResponse {
+ repeated BasketListItem items = 1;
+}
+
+message BasketListItem {
+ int32 product_id = 1;
+ int32 quantity = 2;
+}
+
+message UpdateBasketRequest {
+ repeated BasketListItem items = 1;
+}
\ No newline at end of file
diff --git a/src/HelloShop.BasketService/Protos/greet.proto b/src/HelloShop.BasketService/Protos/greet.proto
index b8334fb..72eed6f 100644
--- a/src/HelloShop.BasketService/Protos/greet.proto
+++ b/src/HelloShop.BasketService/Protos/greet.proto
@@ -1,13 +1,20 @@
syntax = "proto3";
-option csharp_namespace = "HelloShop.BasketService";
+option csharp_namespace = "HelloShop.BasketService.Protos";
+
+import "google/api/annotations.proto";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
- rpc SayHello (HelloRequest) returns (HelloReply);
+ rpc SayHello (HelloRequest) returns (HelloReply){
+ option (google.api.http) = {
+ post: "/api/greet"
+ body: "*"
+ };
+ }
}
// The request message containing the user's name.
diff --git a/src/HelloShop.BasketService/Repositories/DistributedCacheBasketRepository.cs b/src/HelloShop.BasketService/Repositories/DistributedCacheBasketRepository.cs
new file mode 100644
index 0000000..135609f
--- /dev/null
+++ b/src/HelloShop.BasketService/Repositories/DistributedCacheBasketRepository.cs
@@ -0,0 +1,28 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+using HelloShop.BasketService.Entities;
+using Microsoft.Extensions.Caching.Distributed;
+
+namespace HelloShop.BasketService.Repositories
+{
+ public class DistributedCacheBasketRepository(IDistributedCache cache) : IBasketRepository
+ {
+ private const string BasketKeyPrefix = "basket";
+
+ private static string GetBasketKey(int customerId) => $"{BasketKeyPrefix}:{customerId}";
+
+ public async Task DeleteBasketAsync(int customerId, CancellationToken token = default) => await cache.RemoveAsync(GetBasketKey(customerId), token);
+
+ public async Task GetBasketAsync(int customerId, CancellationToken token = default) => await cache.GetObjectAsync(GetBasketKey(customerId), token);
+
+ public async Task UpdateBasketAsync(CustomerBasket basket, CancellationToken token = default)
+ {
+ DistributedCacheEntryOptions options = new() { SlidingExpiration = TimeSpan.MaxValue };
+
+ await cache.SetObjectAsync(GetBasketKey(basket.BuyerId), basket, options, token);
+
+ return await GetBasketAsync(basket.BuyerId, token);
+ }
+ }
+}
diff --git a/src/HelloShop.BasketService/Repositories/IBasketRepository.cs b/src/HelloShop.BasketService/Repositories/IBasketRepository.cs
new file mode 100644
index 0000000..835a026
--- /dev/null
+++ b/src/HelloShop.BasketService/Repositories/IBasketRepository.cs
@@ -0,0 +1,16 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+using HelloShop.BasketService.Entities;
+
+namespace HelloShop.BasketService.Repositories
+{
+ public interface IBasketRepository
+ {
+ Task GetBasketAsync(int customerId, CancellationToken token = default);
+
+ Task UpdateBasketAsync(CustomerBasket basket, CancellationToken token = default);
+
+ Task DeleteBasketAsync(int customerId, CancellationToken token = default);
+ }
+}
diff --git a/src/HelloShop.BasketService/Services/CustomerBasketService.cs b/src/HelloShop.BasketService/Services/CustomerBasketService.cs
new file mode 100644
index 0000000..5d7739d
--- /dev/null
+++ b/src/HelloShop.BasketService/Services/CustomerBasketService.cs
@@ -0,0 +1,78 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+using AutoMapper;
+using Google.Protobuf.WellKnownTypes;
+using Grpc.Core;
+using HelloShop.BasketService.Entities;
+
+
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+using HelloShop.BasketService.Protos;
+using HelloShop.BasketService.Repositories;
+using Microsoft.AspNetCore.Authorization;
+using System.Security.Claims;
+
+namespace HelloShop.BasketService.Services
+{
+ [Authorize]
+ public class CustomerBasketService(IBasketRepository repository, ILogger logger, IMapper mapper) : Basket.BasketBase
+ {
+ public override async Task GetBasket(Empty request, ServerCallContext context)
+ {
+ string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
+
+ if (!int.TryParse(nameIdentifier, out int userId))
+ {
+ throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated."));
+ }
+
+ logger.LogDebug("Begin GetBasketById call from method {Method} for basket id {Id}", context.Method, userId);
+
+ CustomerBasket? basket = await repository.GetBasketAsync(userId, context.CancellationToken);
+
+ if (basket is not null)
+ {
+ mapper.Map(basket);
+ }
+
+ return new();
+ }
+
+ public override async Task UpdateBasket(UpdateBasketRequest request, ServerCallContext context)
+ {
+ string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
+
+ if (!int.TryParse(nameIdentifier, out int userId))
+ {
+ throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated."));
+ }
+
+ logger.LogDebug("Begin UpdateBasket call from method {Method} for basket id {Id}", context.Method, userId);
+
+ CustomerBasket customerBasket = mapper.Map(request, opts => opts.AfterMap((src, dest) => dest.BuyerId = userId));
+
+ CustomerBasket? result = await repository.UpdateBasketAsync(customerBasket, context.CancellationToken);
+
+ result = result ?? throw new RpcException(new Status(StatusCode.NotFound, $"Basket with buyer id {userId} does not exist"));
+
+ return mapper.Map(result);
+ }
+
+ public override async Task DeleteBasket(Empty request, ServerCallContext context)
+ {
+ string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
+
+ if (!int.TryParse(nameIdentifier, out int userId))
+ {
+ throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated."));
+ }
+
+ await repository.DeleteBasketAsync(userId, context.CancellationToken);
+
+ return new();
+ }
+ }
+}
diff --git a/src/HelloShop.BasketService/Services/GreeterService.cs b/src/HelloShop.BasketService/Services/GreeterService.cs
index 7722524..643df76 100644
--- a/src/HelloShop.BasketService/Services/GreeterService.cs
+++ b/src/HelloShop.BasketService/Services/GreeterService.cs
@@ -2,17 +2,12 @@
// See the license file in the project root for more information.
using Grpc.Core;
+using HelloShop.BasketService.Protos;
namespace HelloShop.BasketService.Services;
public class GreeterService : Greeter.GreeterBase
{
- private readonly ILogger _logger;
- public GreeterService(ILogger logger)
- {
- _logger = logger;
- }
-
public override Task SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
diff --git a/src/HelloShop.BasketService/Validations/Baskets/BasketListItemValidator.cs b/src/HelloShop.BasketService/Validations/Baskets/BasketListItemValidator.cs
new file mode 100644
index 0000000..ca5e69a
--- /dev/null
+++ b/src/HelloShop.BasketService/Validations/Baskets/BasketListItemValidator.cs
@@ -0,0 +1,18 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+using FluentValidation;
+using FluentValidation.Validators;
+using HelloShop.BasketService.Protos;
+
+namespace HelloShop.BasketService.Validations.Baskets
+{
+ public class BasketListItemValidator : AbstractValidator
+ {
+ public BasketListItemValidator()
+ {
+ RuleFor(x => x.ProductId).GreaterThan(0);
+ RuleFor(x => x.Quantity).GreaterThan(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/HelloShop.BasketService/Validations/Baskets/UpdateBasketRequestValidator.cs b/src/HelloShop.BasketService/Validations/Baskets/UpdateBasketRequestValidator.cs
new file mode 100644
index 0000000..9c1839c
--- /dev/null
+++ b/src/HelloShop.BasketService/Validations/Baskets/UpdateBasketRequestValidator.cs
@@ -0,0 +1,17 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+using FluentValidation;
+using HelloShop.BasketService.Protos;
+
+namespace HelloShop.BasketService.Validations.Baskets
+{
+ public class UpdateBasketRequestValidator : AbstractValidator
+ {
+ public UpdateBasketRequestValidator()
+ {
+ RuleFor(x => x.Items).NotNull();
+ RuleForEach(x => x.Items).SetValidator(new BasketListItemValidator());
+ }
+ }
+}
diff --git a/src/HelloShop.BasketService/Validations/Greeters/HelloRequestValidator.cs b/src/HelloShop.BasketService/Validations/Greeters/HelloRequestValidator.cs
new file mode 100644
index 0000000..ce3dd3b
--- /dev/null
+++ b/src/HelloShop.BasketService/Validations/Greeters/HelloRequestValidator.cs
@@ -0,0 +1,16 @@
+// Copyright (c) HelloShop Corporation. All rights reserved.
+// See the license file in the project root for more information.
+
+using FluentValidation;
+using HelloShop.BasketService.Protos;
+
+namespace HelloShop.BasketService.Validations.Greeters
+{
+ public class HelloRequestValidator : AbstractValidator
+ {
+ public HelloRequestValidator()
+ {
+ RuleFor(x => x.Name).NotNull().NotEmpty().Length(3, 20);
+ }
+ }
+}
diff --git a/src/HelloShop.BasketService/appsettings.json b/src/HelloShop.BasketService/appsettings.json
index ec04bc1..f097952 100644
--- a/src/HelloShop.BasketService/appsettings.json
+++ b/src/HelloShop.BasketService/appsettings.json
@@ -5,5 +5,10 @@
"Microsoft.AspNetCore": "Warning"
}
},
+ "Kestrel": {
+ "EndpointDefaults": {
+ "Protocols": "Http2"
+ }
+ },
"AllowedHosts": "*"
}
\ No newline at end of file
diff --git a/src/HelloShop.HybridApp/HelloShop.HybridApp.csproj b/src/HelloShop.HybridApp/HelloShop.HybridApp.csproj
index 5659c69..63578c4 100644
--- a/src/HelloShop.HybridApp/HelloShop.HybridApp.csproj
+++ b/src/HelloShop.HybridApp/HelloShop.HybridApp.csproj
@@ -58,9 +58,9 @@
-
-
-
+
+
+
diff --git a/src/HelloShop.IdentityService/HelloShop.IdentityService.csproj b/src/HelloShop.IdentityService/HelloShop.IdentityService.csproj
index a8605af..b610f84 100644
--- a/src/HelloShop.IdentityService/HelloShop.IdentityService.csproj
+++ b/src/HelloShop.IdentityService/HelloShop.IdentityService.csproj
@@ -8,9 +8,9 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/src/HelloShop.ProductService/HelloShop.ProductService.csproj b/src/HelloShop.ProductService/HelloShop.ProductService.csproj
index 7a3fbcb..af10394 100644
--- a/src/HelloShop.ProductService/HelloShop.ProductService.csproj
+++ b/src/HelloShop.ProductService/HelloShop.ProductService.csproj
@@ -8,8 +8,8 @@
-
-
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/src/HelloShop.ServiceDefaults/Extensions/OpenApiExtensions.cs b/src/HelloShop.ServiceDefaults/Extensions/OpenApiExtensions.cs
index 7d5ab3f..df7eb41 100644
--- a/src/HelloShop.ServiceDefaults/Extensions/OpenApiExtensions.cs
+++ b/src/HelloShop.ServiceDefaults/Extensions/OpenApiExtensions.cs
@@ -22,7 +22,7 @@ namespace HelloShop.ServiceDefaults.Extensions
services.Configure(options =>
{
- options.DocumentTitle = Assembly.GetExecutingAssembly().GetName().Name;
+ options.DocumentTitle = Assembly.GetEntryAssembly()?.GetName().Name;
options.InjectStylesheet("/ServiceDefaults/Resources/OpenApi/Custom.css");
});
diff --git a/src/HelloShop.ServiceDefaults/HelloShop.ServiceDefaults.csproj b/src/HelloShop.ServiceDefaults/HelloShop.ServiceDefaults.csproj
index 033d269..0c30ba5 100644
--- a/src/HelloShop.ServiceDefaults/HelloShop.ServiceDefaults.csproj
+++ b/src/HelloShop.ServiceDefaults/HelloShop.ServiceDefaults.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/tests/HelloShop.FunctionalTests/HelloShop.FunctionalTests.csproj b/tests/HelloShop.FunctionalTests/HelloShop.FunctionalTests.csproj
index 2706853..5396ee6 100644
--- a/tests/HelloShop.FunctionalTests/HelloShop.FunctionalTests.csproj
+++ b/tests/HelloShop.FunctionalTests/HelloShop.FunctionalTests.csproj
@@ -9,14 +9,14 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/HelloShop.ProductService.FunctionalTests/HelloShop.ProductService.FunctionalTests.csproj b/tests/HelloShop.ProductService.FunctionalTests/HelloShop.ProductService.FunctionalTests.csproj
index 6489c44..b1e92fe 100644
--- a/tests/HelloShop.ProductService.FunctionalTests/HelloShop.ProductService.FunctionalTests.csproj
+++ b/tests/HelloShop.ProductService.FunctionalTests/HelloShop.ProductService.FunctionalTests.csproj
@@ -14,11 +14,11 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/HelloShop.ProductService.UnitTests/HelloShop.ProductService.UnitTests.csproj b/tests/HelloShop.ProductService.UnitTests/HelloShop.ProductService.UnitTests.csproj
index d3cf251..1e39637 100644
--- a/tests/HelloShop.ProductService.UnitTests/HelloShop.ProductService.UnitTests.csproj
+++ b/tests/HelloShop.ProductService.UnitTests/HelloShop.ProductService.UnitTests.csproj
@@ -14,11 +14,11 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive