diff --git a/.editorconfig b/.editorconfig index 88b30b0..459d752 100644 --- a/.editorconfig +++ b/.editorconfig @@ -141,4 +141,10 @@ csharp_preserve_single_line_blocks = true ############################### [*.vb] # Modifier preferences -visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion \ No newline at end of file +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion############################### +###################################### +# Configure Nullable Reference Types # +###################################### +[{**/*Dto.cs,**/*Request.cs,**/*Response.cs}] +# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable. +dotnet_diagnostic.CS8618.severity = none diff --git a/Directory.Packages.props b/Directory.Packages.props index a719109..a4eebe2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,7 @@ true + true net7.0 diff --git a/Everything.sln b/Everything.sln new file mode 100644 index 0000000..037da0d --- /dev/null +++ b/Everything.sln @@ -0,0 +1,92 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplicationCore", "src\ApplicationCore\ApplicationCore.csproj", "{1A5759FF-9990-4CF5-AD78-528452C5EFCC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorAdmin", "src\BlazorAdmin\BlazorAdmin.csproj", "{7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorShared", "src\BlazorShared\BlazorShared.csproj", "{6FD75683-D186-4BE3-ABD0-2324650B46B5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{35457566-83CE-44FC-A650-265CC9C544DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublicApi", "src\PublicApi\PublicApi.csproj", "{7F226129-E8B0-4274-87A7-347AA4F7D374}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj", "{7559FA9E-7CFC-4615-8D09-3CDEFC765455}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BAA5312D-B54C-42D6-A3B9-504DD12F8250}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionalTests", "tests\FunctionalTests\FunctionalTests.csproj", "{020545FF-D985-4274-9FDB-FD8B9B32D2ED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "tests\IntegrationTests\IntegrationTests.csproj", "{D6829485-DD9C-42CE-BEDE-4EB0E81021AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublicApiIntegrationTests", "tests\PublicApiIntegrationTests\PublicApiIntegrationTests.csproj", "{698594AE-78D3-429F-B5CC-3A6F6BCE397A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "tests\UnitTests\UnitTests.csproj", "{EAD6CF0B-2979-462C-BBB9-AF723B1EB570}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A5759FF-9990-4CF5-AD78-528452C5EFCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A5759FF-9990-4CF5-AD78-528452C5EFCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A5759FF-9990-4CF5-AD78-528452C5EFCC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A5759FF-9990-4CF5-AD78-528452C5EFCC}.Release|Any CPU.Build.0 = Release|Any CPU + {7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970}.Release|Any CPU.Build.0 = Release|Any CPU + {6FD75683-D186-4BE3-ABD0-2324650B46B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FD75683-D186-4BE3-ABD0-2324650B46B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FD75683-D186-4BE3-ABD0-2324650B46B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FD75683-D186-4BE3-ABD0-2324650B46B5}.Release|Any CPU.Build.0 = Release|Any CPU + {35457566-83CE-44FC-A650-265CC9C544DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35457566-83CE-44FC-A650-265CC9C544DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35457566-83CE-44FC-A650-265CC9C544DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35457566-83CE-44FC-A650-265CC9C544DC}.Release|Any CPU.Build.0 = Release|Any CPU + {7F226129-E8B0-4274-87A7-347AA4F7D374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F226129-E8B0-4274-87A7-347AA4F7D374}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F226129-E8B0-4274-87A7-347AA4F7D374}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F226129-E8B0-4274-87A7-347AA4F7D374}.Release|Any CPU.Build.0 = Release|Any CPU + {7559FA9E-7CFC-4615-8D09-3CDEFC765455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7559FA9E-7CFC-4615-8D09-3CDEFC765455}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7559FA9E-7CFC-4615-8D09-3CDEFC765455}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7559FA9E-7CFC-4615-8D09-3CDEFC765455}.Release|Any CPU.Build.0 = Release|Any CPU + {020545FF-D985-4274-9FDB-FD8B9B32D2ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {020545FF-D985-4274-9FDB-FD8B9B32D2ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {020545FF-D985-4274-9FDB-FD8B9B32D2ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {020545FF-D985-4274-9FDB-FD8B9B32D2ED}.Release|Any CPU.Build.0 = Release|Any CPU + {D6829485-DD9C-42CE-BEDE-4EB0E81021AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6829485-DD9C-42CE-BEDE-4EB0E81021AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6829485-DD9C-42CE-BEDE-4EB0E81021AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6829485-DD9C-42CE-BEDE-4EB0E81021AC}.Release|Any CPU.Build.0 = Release|Any CPU + {698594AE-78D3-429F-B5CC-3A6F6BCE397A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {698594AE-78D3-429F-B5CC-3A6F6BCE397A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {698594AE-78D3-429F-B5CC-3A6F6BCE397A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {698594AE-78D3-429F-B5CC-3A6F6BCE397A}.Release|Any CPU.Build.0 = Release|Any CPU + {EAD6CF0B-2979-462C-BBB9-AF723B1EB570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAD6CF0B-2979-462C-BBB9-AF723B1EB570}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAD6CF0B-2979-462C-BBB9-AF723B1EB570}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAD6CF0B-2979-462C-BBB9-AF723B1EB570}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1A5759FF-9990-4CF5-AD78-528452C5EFCC} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7} + {7D7D0B73-4153-4E9B-BBD1-C9D7C8AEE970} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7} + {6FD75683-D186-4BE3-ABD0-2324650B46B5} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7} + {35457566-83CE-44FC-A650-265CC9C544DC} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7} + {7F226129-E8B0-4274-87A7-347AA4F7D374} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7} + {7559FA9E-7CFC-4615-8D09-3CDEFC765455} = {8FF16BDB-352E-42A2-A25F-0B5BC3A17FD7} + {020545FF-D985-4274-9FDB-FD8B9B32D2ED} = {BAA5312D-B54C-42D6-A3B9-504DD12F8250} + {D6829485-DD9C-42CE-BEDE-4EB0E81021AC} = {BAA5312D-B54C-42D6-A3B9-504DD12F8250} + {698594AE-78D3-429F-B5CC-3A6F6BCE397A} = {BAA5312D-B54C-42D6-A3B9-504DD12F8250} + {EAD6CF0B-2979-462C-BBB9-AF723B1EB570} = {BAA5312D-B54C-42D6-A3B9-504DD12F8250} + EndGlobalSection +EndGlobal diff --git a/src/Infrastructure/Dependencies.cs b/src/Infrastructure/Dependencies.cs index d049a96..9645676 100644 --- a/src/Infrastructure/Dependencies.cs +++ b/src/Infrastructure/Dependencies.cs @@ -10,10 +10,10 @@ public static class Dependencies { public static void ConfigureServices(IConfiguration configuration, IServiceCollection services) { - var useOnlyInMemoryDatabase = false; + bool useOnlyInMemoryDatabase = false; if (configuration["UseOnlyInMemoryDatabase"] != null) { - useOnlyInMemoryDatabase = bool.Parse(configuration["UseOnlyInMemoryDatabase"]); + useOnlyInMemoryDatabase = bool.Parse(configuration["UseOnlyInMemoryDatabase"]!); } if (useOnlyInMemoryDatabase) diff --git a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs index 3531005..8be12f6 100644 --- a/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs +++ b/src/Infrastructure/Identity/AppIdentityDbContextSeed.cs @@ -24,6 +24,9 @@ public class AppIdentityDbContextSeed var adminUser = new ApplicationUser { UserName = adminUserName, Email = adminUserName }; await userManager.CreateAsync(adminUser, AuthorizationConstants.DEFAULT_PASSWORD); adminUser = await userManager.FindByNameAsync(adminUserName); - await userManager.AddToRoleAsync(adminUser, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + if (adminUser != null) + { + await userManager.AddToRoleAsync(adminUser, BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS); + } } } diff --git a/src/Infrastructure/Identity/IdentityTokenClaimService.cs b/src/Infrastructure/Identity/IdentityTokenClaimService.cs index c45f355..36de7ae 100644 --- a/src/Infrastructure/Identity/IdentityTokenClaimService.cs +++ b/src/Infrastructure/Identity/IdentityTokenClaimService.cs @@ -25,6 +25,7 @@ public class IdentityTokenClaimService : ITokenClaimsService var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); var user = await _userManager.FindByNameAsync(userName); + if (user == null) throw new UserNotFoundException(userName); var roles = await _userManager.GetRolesAsync(user); var claims = new List { new Claim(ClaimTypes.Name, userName) }; diff --git a/src/Infrastructure/Identity/UserNotFoundException.cs b/src/Infrastructure/Identity/UserNotFoundException.cs new file mode 100644 index 0000000..0a98b9e --- /dev/null +++ b/src/Infrastructure/Identity/UserNotFoundException.cs @@ -0,0 +1,10 @@ +using System; + +namespace Microsoft.eShopWeb.Infrastructure.Identity; + +public class UserNotFoundException : Exception +{ + public UserNotFoundException(string userName) : base($"No user found with username: {userName}") + { + } +} diff --git a/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.ClaimValue.cs b/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.ClaimValue.cs index 5296e71..9571c3a 100644 --- a/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.ClaimValue.cs +++ b/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.ClaimValue.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints; +namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints; public class ClaimValue { @@ -17,6 +12,6 @@ public class ClaimValue Value = value; } - public string Type { get; set; } - public string Value { get; set; } + public string Type { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; } diff --git a/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.UserInfo.cs b/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.UserInfo.cs index 8c55fbb..8a4eaf8 100644 --- a/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.UserInfo.cs +++ b/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.UserInfo.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints; @@ -9,7 +6,7 @@ public class UserInfo { public static readonly UserInfo Anonymous = new UserInfo(); public bool IsAuthenticated { get; set; } - public string NameClaimType { get; set; } - public string RoleClaimType { get; set; } - public IEnumerable Claims { get; set; } + public string NameClaimType { get; set; } = string.Empty; + public string RoleClaimType { get; set; } = string.Empty; + public IEnumerable Claims { get; set; } = new List(); } diff --git a/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs b/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs index 0d5bece..c5cce65 100644 --- a/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs +++ b/src/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs @@ -33,7 +33,8 @@ public class AuthenticateEndpoint : EndpointBaseAsync OperationId = "auth.authenticate", Tags = new[] { "AuthEndpoints" }) ] - public override async Task> HandleAsync(AuthenticateRequest request, CancellationToken cancellationToken = default) + public override async Task> HandleAsync(AuthenticateRequest request, + CancellationToken cancellationToken = default) { var response = new AuthenticateResponse(request.CorrelationId()); diff --git a/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.ListPagedCatalogItemRequest.cs b/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.ListPagedCatalogItemRequest.cs index e9744d8..19691af 100644 --- a/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.ListPagedCatalogItemRequest.cs +++ b/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.ListPagedCatalogItemRequest.cs @@ -2,8 +2,8 @@ public class ListPagedCatalogItemRequest : BaseRequest { - public int? PageSize { get; init; } - public int? PageIndex { get; init; } + public int PageSize { get; init; } + public int PageIndex { get; init; } public int? CatalogBrandId { get; init; } public int? CatalogTypeId { get; init; } diff --git a/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs b/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs index 920fe4f..3e36d2f 100644 --- a/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs +++ b/src/PublicApi/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs @@ -46,8 +46,8 @@ public class CatalogItemListPagedEndpoint : IEndpoint 0) { - response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize.Value).ToString()); + response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize).ToString()); } else { diff --git a/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs b/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs index b923322..15efa68 100644 --- a/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs +++ b/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs @@ -39,6 +39,10 @@ public class UpdateCatalogItemEndpoint : IEndpoint() builder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); builder.Services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>)); builder.Services.Configure(builder.Configuration); -builder.Services.AddSingleton(new UriComposer(builder.Configuration.Get())); +var catalogSettings = builder.Configuration.Get() ?? new CatalogSettings(); +builder.Services.AddSingleton(new UriComposer(catalogSettings)); builder.Services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); builder.Services.AddScoped(); @@ -73,12 +73,12 @@ const string CORS_POLICY = "CorsPolicy"; builder.Services.AddCors(options => { options.AddPolicy(name: CORS_POLICY, - corsPolicyBuilder => - { - corsPolicyBuilder.WithOrigins(baseUrlConfig.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/')); - corsPolicyBuilder.AllowAnyMethod(); - corsPolicyBuilder.AllowAnyHeader(); - }); + corsPolicyBuilder => + { + corsPolicyBuilder.WithOrigins(baseUrlConfig!.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/')); + corsPolicyBuilder.AllowAnyMethod(); + corsPolicyBuilder.AllowAnyHeader(); + }); }); builder.Services.AddControllers(); @@ -172,12 +172,9 @@ app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }); -app.UseEndpoints(endpoints => -{ - endpoints.MapControllers(); -}); - +app.MapControllers(); app.MapEndpoints(); + app.Logger.LogInformation("LAUNCHING PublicApi"); app.Run(); diff --git a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs index fdf4b1f..fe22ef7 100644 --- a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -25,7 +25,7 @@ public class LoginModel : PageModel } [BindProperty] - public InputModel? Input { get; set; } + public required InputModel Input { get; set; } public IList? ExternalLogins { get; set; } @@ -74,7 +74,8 @@ public class LoginModel : PageModel // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, set lockoutOnFailure: true //var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true); - var result = await _signInManager.PasswordSignInAsync(Input?.Email, Input?.Password, false, true); + var result = await _signInManager.PasswordSignInAsync(Input!.Email!, Input!.Password!, + false, true); if (result.Succeeded) { diff --git a/src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs index ce66e9e..f0165fa 100644 --- a/src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -35,7 +35,7 @@ public class RegisterModel : PageModel } [BindProperty] - public InputModel? Input { get; set; } + public required InputModel Input { get; set; } public string? ReturnUrl { get; set; } @@ -69,7 +69,7 @@ public class RegisterModel : PageModel if (ModelState.IsValid) { var user = new ApplicationUser { UserName = Input?.Email, Email = Input?.Email }; - var result = await _userManager.CreateAsync(user, Input?.Password); + var result = await _userManager.CreateAsync(user, Input?.Password!); if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); @@ -82,7 +82,7 @@ public class RegisterModel : PageModel protocol: Request.Scheme); Guard.Against.Null(callbackUrl, nameof(callbackUrl)); - await _emailSender.SendEmailAsync(Input?.Email, "Confirm your email", + await _emailSender.SendEmailAsync(Input!.Email!, "Confirm your email", $"Please confirm your account by clicking here."); await _signInManager.SignInAsync(user, isPersistent: false); diff --git a/src/Web/Configuration/ConfigureCoreServices.cs b/src/Web/Configuration/ConfigureCoreServices.cs index f38657d..cf39e9a 100644 --- a/src/Web/Configuration/ConfigureCoreServices.cs +++ b/src/Web/Configuration/ConfigureCoreServices.cs @@ -4,8 +4,6 @@ using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Data.Queries; using Microsoft.eShopWeb.Infrastructure.Logging; using Microsoft.eShopWeb.Infrastructure.Services; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.eShopWeb.Web.Configuration; @@ -20,7 +18,10 @@ public static class ConfigureCoreServices services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddSingleton(new UriComposer(configuration.Get())); + + var catalogSettings = configuration.Get() ?? new CatalogSettings(); + services.AddSingleton(new UriComposer(catalogSettings)); + services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); services.AddTransient(); diff --git a/src/Web/Controllers/ManageController.cs b/src/Web/Controllers/ManageController.cs index 779fa36..d97473c 100644 --- a/src/Web/Controllers/ManageController.cs +++ b/src/Web/Controllers/ManageController.cs @@ -122,6 +122,11 @@ public class ManageController : Controller var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); Guard.Against.Null(callbackUrl, nameof(callbackUrl)); var email = user.Email; + if (email == null) + { + throw new ApplicationException($"No email associated with user {user.UserName}'."); + } + await _emailSender.SendEmailConfirmationAsync(email, callbackUrl); StatusMessage = "Verification email sent. Please check your email."; @@ -162,7 +167,8 @@ public class ManageController : Controller throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } - var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); + var changePasswordResult = await _userManager + .ChangePasswordAsync(user, model.OldPassword!, model.NewPassword!); if (!changePasswordResult.Succeeded) { AddErrors(changePasswordResult); @@ -211,7 +217,7 @@ public class ManageController : Controller throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } - var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword); + var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword!); if (!addPasswordResult.Succeeded) { AddErrors(addPasswordResult); @@ -293,6 +299,10 @@ public class ManageController : Controller { throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); } + if (!ModelState.IsValid) + { + return View(model); + } var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey); if (!result.Succeeded) @@ -407,7 +417,7 @@ public class ManageController : Controller } // Strip spaces and hypens - var verificationCode = model.Code?.Replace(" ", string.Empty).Replace("-", string.Empty); + string verificationCode = model.Code?.Replace(" ", string.Empty).Replace("-", string.Empty) ?? ""; var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); @@ -421,7 +431,7 @@ public class ManageController : Controller await _userManager.SetTwoFactorEnabledAsync(user, true); _logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id); - var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10) ?? new List(); TempData[RecoveryCodesKey] = recoveryCodes.ToArray(); return RedirectToAction(nameof(ShowRecoveryCodes)); @@ -465,7 +475,7 @@ public class ManageController : Controller throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled."); } - var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10) ?? new List(); _logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id); var model = new ShowRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() }; @@ -533,8 +543,8 @@ public class ManageController : Controller unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); } - model.SharedKey = FormatKey(unformattedKey); - model.AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey); + model.SharedKey = FormatKey(unformattedKey!); + model.AuthenticatorUri = GenerateQrCodeUri(user.Email!, unformattedKey!); } } diff --git a/src/Web/Pages/Index.cshtml.cs b/src/Web/Pages/Index.cshtml.cs index f2dd2cf..ad6a81e 100644 --- a/src/Web/Pages/Index.cshtml.cs +++ b/src/Web/Pages/Index.cshtml.cs @@ -13,7 +13,7 @@ public class IndexModel : PageModel _catalogViewModelService = catalogViewModelService; } - public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel(); + public required CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel(); public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId) { diff --git a/src/Web/Program.cs b/src/Web/Program.cs index dd9a4ad..c5dbcc1 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -82,7 +82,7 @@ var baseUrlConfig = configSection.Get(); // Blazor Admin Required Services for Prerendering builder.Services.AddScoped(s => new HttpClient { - BaseAddress = new Uri(baseUrlConfig.WebBase) + BaseAddress = new Uri(baseUrlConfig!.WebBase) }); // add blazor services @@ -171,15 +171,13 @@ app.UseCookiePolicy(); app.UseAuthentication(); app.UseAuthorization(); -app.UseEndpoints(endpoints => -{ - endpoints.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}"); - endpoints.MapRazorPages(); - endpoints.MapHealthChecks("home_page_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("homePageHealthCheck") }); - endpoints.MapHealthChecks("api_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("apiHealthCheck") }); - //endpoints.MapBlazorHub("/admin"); - endpoints.MapFallbackToFile("index.html"); -}); + +app.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}"); +app.MapRazorPages(); +app.MapHealthChecks("home_page_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("homePageHealthCheck") }); +app.MapHealthChecks("api_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("apiHealthCheck") }); +//endpoints.MapBlazorHub("/admin"); +app.MapFallbackToFile("index.html"); app.Logger.LogInformation("LAUNCHING"); app.Run(); diff --git a/src/Web/Services/CachedCatalogViewModelService.cs b/src/Web/Services/CachedCatalogViewModelService.cs index d190d43..e7a506b 100644 --- a/src/Web/Services/CachedCatalogViewModelService.cs +++ b/src/Web/Services/CachedCatalogViewModelService.cs @@ -21,30 +21,30 @@ public class CachedCatalogViewModelService : ICatalogViewModelService public async Task> GetBrands() { - return await _cache.GetOrCreateAsync(CacheHelpers.GenerateBrandsCacheKey(), async entry => + return (await _cache.GetOrCreateAsync(CacheHelpers.GenerateBrandsCacheKey(), async entry => { entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration; return await _catalogViewModelService.GetBrands(); - }); + })) ?? new List(); } public async Task GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId) { var cacheKey = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId); - return await _cache.GetOrCreateAsync(cacheKey, async entry => + return (await _cache.GetOrCreateAsync(cacheKey, async entry => { entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration; return await _catalogViewModelService.GetCatalogItems(pageIndex, itemsPage, brandId, typeId); - }); + })) ?? new CatalogIndexViewModel(); } public async Task> GetTypes() { - return await _cache.GetOrCreateAsync(CacheHelpers.GenerateTypesCacheKey(), async entry => + return (await _cache.GetOrCreateAsync(CacheHelpers.GenerateTypesCacheKey(), async entry => { entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration; return await _catalogViewModelService.GetTypes(); - }); + })) ?? new List(); } } diff --git a/src/Web/ViewModels/CatalogIndexViewModel.cs b/src/Web/ViewModels/CatalogIndexViewModel.cs index 247afe5..69e09e3 100644 --- a/src/Web/ViewModels/CatalogIndexViewModel.cs +++ b/src/Web/ViewModels/CatalogIndexViewModel.cs @@ -1,13 +1,12 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Rendering; namespace Microsoft.eShopWeb.Web.ViewModels; public class CatalogIndexViewModel { - public List? CatalogItems { get; set; } - public List? Brands { get; set; } - public List? Types { get; set; } + public List CatalogItems { get; set; } = new List(); + public List? Brands { get; set; } = new List(); + public List? Types { get; set; } = new List(); public int? BrandFilterApplied { get; set; } public int? TypesFilterApplied { get; set; } public PaginationInfoViewModel? PaginationInfo { get; set; } diff --git a/src/Web/ViewModels/Manage/RemoveLoginViewModel.cs b/src/Web/ViewModels/Manage/RemoveLoginViewModel.cs index 45a5975..78ddac1 100644 --- a/src/Web/ViewModels/Manage/RemoveLoginViewModel.cs +++ b/src/Web/ViewModels/Manage/RemoveLoginViewModel.cs @@ -1,7 +1,11 @@ -namespace Microsoft.eShopWeb.Web.ViewModels.Manage; +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.eShopWeb.Web.ViewModels.Manage; public class RemoveLoginViewModel { - public string? LoginProvider { get; set; } - public string? ProviderKey { get; set; } + [Required] + public string LoginProvider { get; set; } = string.Empty; + [Required] + public string ProviderKey { get; set; } = string.Empty; } diff --git a/src/Web/ViewModels/OrderViewModel.cs b/src/Web/ViewModels/OrderViewModel.cs index 7ecfd90..582a504 100644 --- a/src/Web/ViewModels/OrderViewModel.cs +++ b/src/Web/ViewModels/OrderViewModel.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; namespace Microsoft.eShopWeb.Web.ViewModels; @@ -13,5 +11,5 @@ public class OrderViewModel public decimal Total { get; set; } public string Status => DEFAULT_STATUS; public Address? ShippingAddress { get; set; } - public List OrderItems { get; set; } = new List(); + public List OrderItems { get; set; } = new(); } diff --git a/src/Web/Views/Manage/ShowRecoverCodes.cshtml b/src/Web/Views/Manage/ShowRecoverCodes.cshtml index ed6bc95..0d012e2 100644 --- a/src/Web/Views/Manage/ShowRecoverCodes.cshtml +++ b/src/Web/Views/Manage/ShowRecoverCodes.cshtml @@ -16,10 +16,13 @@
- @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2) + @if (Model.RecoveryCodes != null) { - @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
+ @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2) + { + @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
+ } }
-© 2021 GitHub, Inc. \ No newline at end of file +© 2023 GitHub, Inc. \ No newline at end of file diff --git a/src/Web/Views/Order/Detail.cshtml b/src/Web/Views/Order/Detail.cshtml index cb5db3f..097d17e 100644 --- a/src/Web/Views/Order/Detail.cshtml +++ b/src/Web/Views/Order/Detail.cshtml @@ -30,15 +30,15 @@
-
@Model.ShippingAddress.Street
+
@Model.ShippingAddress?.Street
-
@Model.ShippingAddress.City
+
@Model.ShippingAddress?.City
-
@Model.ShippingAddress.Country
+
@Model.ShippingAddress?.Country
diff --git a/src/Web/Views/Shared/_LoginPartial.cshtml b/src/Web/Views/Shared/_LoginPartial.cshtml index ac3f106..0e52fea 100644 --- a/src/Web/Views/Shared/_LoginPartial.cshtml +++ b/src/Web/Views/Shared/_LoginPartial.cshtml @@ -1,4 +1,4 @@ -@if (Context.User.Identity.IsAuthenticated) +@if (Context!.User!.Identity!.IsAuthenticated) {
diff --git a/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs b/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs index 72d1f23..88b37bf 100644 --- a/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs +++ b/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs @@ -23,7 +23,7 @@ public class OrderIndexOnGet : IClassFixture public async Task ReturnsRedirectGivenAnonymousUser() { var response = await Client.GetAsync("/order/my-orders"); - var redirectLocation = response.Headers.Location.OriginalString; + var redirectLocation = response!.Headers.Location!.OriginalString; Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); Assert.Contains("/Account/Login", redirectLocation); diff --git a/tests/FunctionalTests/Web/Pages/Basket/BasketPageCheckout.cs b/tests/FunctionalTests/Web/Pages/Basket/BasketPageCheckout.cs index dd19a71..be2dbb0 100644 --- a/tests/FunctionalTests/Web/Pages/Basket/BasketPageCheckout.cs +++ b/tests/FunctionalTests/Web/Pages/Basket/BasketPageCheckout.cs @@ -45,6 +45,6 @@ public class BasketPageCheckout : IClassFixture formContent = new FormUrlEncodedContent(keyValues); var postResponse2 = await Client.PostAsync("/Basket/Checkout", formContent); - Assert.Contains("/Identity/Account/Login", postResponse2.RequestMessage.RequestUri.ToString()); + Assert.Contains("/Identity/Account/Login", postResponse2!.RequestMessage!.RequestUri!.ToString()!); } } diff --git a/tests/FunctionalTests/Web/Pages/Basket/CheckoutTest.cs b/tests/FunctionalTests/Web/Pages/Basket/CheckoutTest.cs index 4657d77..8d6c0be 100644 --- a/tests/FunctionalTests/Web/Pages/Basket/CheckoutTest.cs +++ b/tests/FunctionalTests/Web/Pages/Basket/CheckoutTest.cs @@ -62,7 +62,7 @@ public class CheckoutTest : IClassFixture var checkOutResponse = await Client.PostAsync("/basket/checkout", checkOutContent); var stringCheckOutResponse = await checkOutResponse.Content.ReadAsStringAsync(); - Assert.Contains("/Basket/Success", checkOutResponse.RequestMessage.RequestUri.ToString()); + Assert.Contains("/Basket/Success", checkOutResponse.RequestMessage!.RequestUri!.ToString()); Assert.Contains("Thanks for your Order!", stringCheckOutResponse); } } diff --git a/tests/FunctionalTests/Web/Pages/Basket/IndexTest.cs b/tests/FunctionalTests/Web/Pages/Basket/IndexTest.cs index cd36458..1f68da7 100644 --- a/tests/FunctionalTests/Web/Pages/Basket/IndexTest.cs +++ b/tests/FunctionalTests/Web/Pages/Basket/IndexTest.cs @@ -52,7 +52,7 @@ public class IndexTest : IClassFixture var stringUpdateResponse = await updateResponse.Content.ReadAsStringAsync(); - Assert.Contains("/basket/update", updateResponse.RequestMessage.RequestUri.ToString()); + Assert.Contains("/basket/update", updateResponse!.RequestMessage!.RequestUri!.ToString()!); decimal expectedTotalAmount = 416.50M; Assert.Contains(expectedTotalAmount.ToString("N2"), stringUpdateResponse); } @@ -92,7 +92,7 @@ public class IndexTest : IClassFixture var stringUpdateResponse = await updateResponse.Content.ReadAsStringAsync(); - Assert.Contains("/basket/update", updateResponse.RequestMessage.RequestUri.ToString()); + Assert.Contains("/basket/update", updateResponse!.RequestMessage!.RequestUri!.ToString()!); Assert.Contains("Basket is empty", stringUpdateResponse); } } diff --git a/tests/FunctionalTests/Web/WebPageHelpers.cs b/tests/FunctionalTests/Web/WebPageHelpers.cs index d858bfb..1259c9e 100644 --- a/tests/FunctionalTests/Web/WebPageHelpers.cs +++ b/tests/FunctionalTests/Web/WebPageHelpers.cs @@ -22,6 +22,6 @@ public static class WebPageHelpers { var regex = new Regex(regexpression); var match = regex.Match(input); - return match.Groups.Values.LastOrDefault().Value; + return match!.Groups!.Values!.LastOrDefault()!.Value; } } diff --git a/tests/PublicApiIntegrationTests/AuthEndpoints/AuthenticateEndpointTest.cs b/tests/PublicApiIntegrationTests/AuthEndpoints/AuthenticateEndpointTest.cs index 03a969c..62550e6 100644 --- a/tests/PublicApiIntegrationTests/AuthEndpoints/AuthenticateEndpointTest.cs +++ b/tests/PublicApiIntegrationTests/AuthEndpoints/AuthenticateEndpointTest.cs @@ -7,29 +7,28 @@ using Microsoft.eShopWeb.ApplicationCore.Constants; using Microsoft.eShopWeb.PublicApi.AuthEndpoints; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace PublicApiIntegrationTests.AuthEndpoints -{ - [TestClass] - public class AuthenticateEndpoint - { - [TestMethod] - [DataRow("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)] - [DataRow("demouser@microsoft.com", "badpassword", false)] - [DataRow("baduser@microsoft.com", "badpassword", false)] - public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult) - { - var request = new AuthenticateRequest() - { - Username = testUsername, - Password = testPassword - }; - var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); - var response = await ProgramTest.NewClient.PostAsync("api/authenticate", jsonContent); - response.EnsureSuccessStatusCode(); - var stringResponse = await response.Content.ReadAsStringAsync(); - var model = stringResponse.FromJson(); +namespace PublicApiIntegrationTests.AuthEndpoints; - Assert.AreEqual(expectedResult, model.Result); - } +[TestClass] +public class AuthenticateEndpoint +{ + [TestMethod] + [DataRow("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)] + [DataRow("demouser@microsoft.com", "badpassword", false)] + [DataRow("baduser@microsoft.com", "badpassword", false)] + public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult) + { + var request = new AuthenticateRequest() + { + Username = testUsername, + Password = testPassword + }; + var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); + var response = await ProgramTest.NewClient.PostAsync("api/authenticate", jsonContent); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + var model = stringResponse.FromJson(); + + Assert.AreEqual(expectedResult, model!.Result); } } diff --git a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemGetByIdEndpointTest.cs b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemGetByIdEndpointTest.cs index 5882db0..9baefde 100644 --- a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemGetByIdEndpointTest.cs +++ b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemGetByIdEndpointTest.cs @@ -4,29 +4,28 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Net; using System.Threading.Tasks; -namespace PublicApiIntegrationTests.CatalogItemEndpoints +namespace PublicApiIntegrationTests.CatalogItemEndpoints; + +[TestClass] +public class CatalogItemGetByIdEndpointTest { - [TestClass] - public class CatalogItemGetByIdEndpointTest + [TestMethod] + public async Task ReturnsItemGivenValidId() { - [TestMethod] - public async Task ReturnsItemGivenValidId() - { - var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/5"); - response.EnsureSuccessStatusCode(); - var stringResponse = await response.Content.ReadAsStringAsync(); - var model = stringResponse.FromJson(); + var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/5"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + var model = stringResponse.FromJson(); - Assert.AreEqual(5, model.CatalogItem.Id); - Assert.AreEqual("Roslyn Red Sheet", model.CatalogItem.Name); - } + Assert.AreEqual(5, model!.CatalogItem.Id); + Assert.AreEqual("Roslyn Red Sheet", model.CatalogItem.Name); + } - [TestMethod] - public async Task ReturnsNotFoundGivenInvalidId() - { - var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/0"); + [TestMethod] + public async Task ReturnsNotFoundGivenInvalidId() + { + var response = await ProgramTest.NewClient.GetAsync("api/catalog-items/0"); - Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); - } + Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); } } diff --git a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs index 5eb3036..5470111 100644 --- a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs +++ b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CatalogItemListPagedEndpoint.cs @@ -8,66 +8,65 @@ using System.Net.Http; using System.Net; using System.Threading.Tasks; -namespace PublicApiIntegrationTests.CatalogItemEndpoints +namespace PublicApiIntegrationTests.CatalogItemEndpoints; + +[TestClass] +public class CatalogItemListPagedEndpoint { - [TestClass] - public class CatalogItemListPagedEndpoint + [TestMethod] + public async Task ReturnsFirst10CatalogItems() { - [TestMethod] - public async Task ReturnsFirst10CatalogItems() + var client = ProgramTest.NewClient; + var response = await client.GetAsync("/api/catalog-items?pageSize=10"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + var model = stringResponse.FromJson(); + + Assert.AreEqual(10, model!.CatalogItems.Count()); + } + + [TestMethod] + public async Task ReturnsCorrectCatalogItemsGivenPageIndex1() + { + + var pageSize = 10; + var pageIndex = 1; + + var client = ProgramTest.NewClient; + var response = await client.GetAsync($"/api/catalog-items"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + var model = stringResponse.FromJson(); + var totalItem = model!.CatalogItems.Count(); + + var response2 = await client.GetAsync($"/api/catalog-items?pageSize={pageSize}&pageIndex={pageIndex}"); + response.EnsureSuccessStatusCode(); + var stringResponse2 = await response2.Content.ReadAsStringAsync(); + var model2 = stringResponse2.FromJson(); + + var totalExpected = totalItem - (pageSize * pageIndex); + + Assert.AreEqual(totalExpected, model2!.CatalogItems.Count()); + } + + [DataTestMethod] + [DataRow("catalog-items")] + [DataRow("catalog-brands")] + [DataRow("catalog-types")] + [DataRow("catalog-items/1")] + public async Task SuccessFullMutipleParallelCall(string endpointName) + { + var client = ProgramTest.NewClient; + var tasks = new List>(); + + for (int i = 0; i < 100; i++) { - var client = ProgramTest.NewClient; - var response = await client.GetAsync("/api/catalog-items?pageSize=10"); - response.EnsureSuccessStatusCode(); - var stringResponse = await response.Content.ReadAsStringAsync(); - var model = stringResponse.FromJson(); - - Assert.AreEqual(10, model.CatalogItems.Count()); + var task = client.GetAsync($"/api/{endpointName}"); + tasks.Add(task); } + await Task.WhenAll(tasks.ToList()); + var totalKO = tasks.Count(t => t.Result.StatusCode != HttpStatusCode.OK); - [TestMethod] - public async Task ReturnsCorrectCatalogItemsGivenPageIndex1() - { - - var pageSize = 10; - var pageIndex = 1; - - var client = ProgramTest.NewClient; - var response = await client.GetAsync($"/api/catalog-items"); - response.EnsureSuccessStatusCode(); - var stringResponse = await response.Content.ReadAsStringAsync(); - var model = stringResponse.FromJson(); - var totalItem = model.CatalogItems.Count(); - - var response2 = await client.GetAsync($"/api/catalog-items?pageSize={pageSize}&pageIndex={pageIndex}"); - response.EnsureSuccessStatusCode(); - var stringResponse2 = await response2.Content.ReadAsStringAsync(); - var model2 = stringResponse2.FromJson(); - - var totalExpected = totalItem - (pageSize * pageIndex); - - Assert.AreEqual(totalExpected, model2.CatalogItems.Count()); - } - - [DataTestMethod] - [DataRow("catalog-items")] - [DataRow("catalog-brands")] - [DataRow("catalog-types")] - [DataRow("catalog-items/1")] - public async Task SuccessFullMutipleParallelCall(string endpointName) - { - var client = ProgramTest.NewClient; - var tasks = new List>(); - - for (int i = 0; i < 100; i++) - { - var task = client.GetAsync($"/api/{endpointName}"); - tasks.Add(task); - } - await Task.WhenAll(tasks.ToList()); - var totalKO = tasks.Count(t => t.Result.StatusCode != HttpStatusCode.OK); - - Assert.AreEqual(0, totalKO); - } + Assert.AreEqual(0, totalKO); } } diff --git a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CreateCatalogItemEndpointTest.cs b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CreateCatalogItemEndpointTest.cs index a85923d..6c5d79e 100644 --- a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CreateCatalogItemEndpointTest.cs +++ b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/CreateCatalogItemEndpointTest.cs @@ -8,62 +8,61 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; -namespace PublicApiIntegrationTests.AuthEndpoints +namespace PublicApiIntegrationTests.AuthEndpoints; + +[TestClass] +public class CreateCatalogItemEndpointTest { - [TestClass] - public class CreateCatalogItemEndpointTest + private int _testBrandId = 1; + private int _testTypeId = 2; + private string _testDescription = "test description"; + private string _testName = "test name"; + private decimal _testPrice = 1.23m; + + + [TestMethod] + public async Task ReturnsNotAuthorizedGivenNormalUserToken() { - private int _testBrandId = 1; - private int _testTypeId = 2; - private string _testDescription = "test description"; - private string _testName = "test name"; - private decimal _testPrice = 1.23m; + var jsonContent = GetValidNewItemJson(); + var token = ApiTokenHelper.GetNormalUserToken(); + var client = ProgramTest.NewClient; + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + var response = await client.PostAsync("api/catalog-items", jsonContent); + Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode); + } - [TestMethod] - public async Task ReturnsNotAuthorizedGivenNormalUserToken() + [TestMethod] + public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken() + { + var jsonContent = GetValidNewItemJson(); + var adminToken = ApiTokenHelper.GetAdminUserToken(); + var client = ProgramTest.NewClient; + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken); + var response = await client.PostAsync("api/catalog-items", jsonContent); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + var model = stringResponse.FromJson(); + + Assert.AreEqual(_testBrandId, model!.CatalogItem.CatalogBrandId); + Assert.AreEqual(_testTypeId, model.CatalogItem.CatalogTypeId); + Assert.AreEqual(_testDescription, model.CatalogItem.Description); + Assert.AreEqual(_testName, model.CatalogItem.Name); + Assert.AreEqual(_testPrice, model.CatalogItem.Price); + } + + private StringContent GetValidNewItemJson() + { + var request = new CreateCatalogItemRequest() { - var jsonContent = GetValidNewItemJson(); - var token = ApiTokenHelper.GetNormalUserToken(); - var client = ProgramTest.NewClient; - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - var response = await client.PostAsync("api/catalog-items", jsonContent); + CatalogBrandId = _testBrandId, + CatalogTypeId = _testTypeId, + Description = _testDescription, + Name = _testName, + Price = _testPrice + }; + var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); - Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode); - } - - [TestMethod] - public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken() - { - var jsonContent = GetValidNewItemJson(); - var adminToken = ApiTokenHelper.GetAdminUserToken(); - var client = ProgramTest.NewClient; - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken); - var response = await client.PostAsync("api/catalog-items", jsonContent); - response.EnsureSuccessStatusCode(); - var stringResponse = await response.Content.ReadAsStringAsync(); - var model = stringResponse.FromJson(); - - Assert.AreEqual(_testBrandId, model.CatalogItem.CatalogBrandId); - Assert.AreEqual(_testTypeId, model.CatalogItem.CatalogTypeId); - Assert.AreEqual(_testDescription, model.CatalogItem.Description); - Assert.AreEqual(_testName, model.CatalogItem.Name); - Assert.AreEqual(_testPrice, model.CatalogItem.Price); - } - - private StringContent GetValidNewItemJson() - { - var request = new CreateCatalogItemRequest() - { - CatalogBrandId = _testBrandId, - CatalogTypeId = _testTypeId, - Description = _testDescription, - Name = _testName, - Price = _testPrice - }; - var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); - - return jsonContent; - } + return jsonContent; } } diff --git a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/DeleteCatalogItemEndpointTest.cs b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/DeleteCatalogItemEndpointTest.cs index f41976e..98c8212 100644 --- a/tests/PublicApiIntegrationTests/CatalogItemEndpoints/DeleteCatalogItemEndpointTest.cs +++ b/tests/PublicApiIntegrationTests/CatalogItemEndpoints/DeleteCatalogItemEndpointTest.cs @@ -5,34 +5,33 @@ using System.Net; using System.Net.Http.Headers; using System.Threading.Tasks; -namespace PublicApiIntegrationTests.CatalogItemEndpoints +namespace PublicApiIntegrationTests.CatalogItemEndpoints; + +[TestClass] +public class DeleteCatalogItemEndpointTest { - [TestClass] - public class DeleteCatalogItemEndpointTest + [TestMethod] + public async Task ReturnsSuccessGivenValidIdAndAdminUserToken() { - [TestMethod] - public async Task ReturnsSuccessGivenValidIdAndAdminUserToken() - { - var adminToken = ApiTokenHelper.GetAdminUserToken(); - var client = ProgramTest.NewClient; - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken); - var response = await client.DeleteAsync("api/catalog-items/12"); - response.EnsureSuccessStatusCode(); - var stringResponse = await response.Content.ReadAsStringAsync(); - var model = stringResponse.FromJson(); + var adminToken = ApiTokenHelper.GetAdminUserToken(); + var client = ProgramTest.NewClient; + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken); + var response = await client.DeleteAsync("api/catalog-items/12"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + var model = stringResponse.FromJson(); - Assert.AreEqual("Deleted", model.Status); - } + Assert.AreEqual("Deleted", model!.Status); + } - [TestMethod] - public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken() - { - var adminToken = ApiTokenHelper.GetAdminUserToken(); - var client = ProgramTest.NewClient; - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken); - var response = await client.DeleteAsync("api/catalog-items/0"); + [TestMethod] + public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken() + { + var adminToken = ApiTokenHelper.GetAdminUserToken(); + var client = ProgramTest.NewClient; + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken); + var response = await client.DeleteAsync("api/catalog-items/0"); - Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); - } + Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); } } diff --git a/tests/PublicApiIntegrationTests/ProgramTest.cs b/tests/PublicApiIntegrationTests/ProgramTest.cs index ca92234..3f13136 100644 --- a/tests/PublicApiIntegrationTests/ProgramTest.cs +++ b/tests/PublicApiIntegrationTests/ProgramTest.cs @@ -2,26 +2,25 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Net.Http; -namespace PublicApiIntegrationTests +namespace PublicApiIntegrationTests; + +[TestClass] +public class ProgramTest { - [TestClass] - public class ProgramTest + private static WebApplicationFactory _application = new(); + + public static HttpClient NewClient { - private static WebApplicationFactory _application; - - public static HttpClient NewClient + get { - get - { - return _application.CreateClient(); - } - } - - [AssemblyInitialize] - public static void AssemblyInitialize(TestContext _) - { - _application = new WebApplicationFactory(); - + return _application.CreateClient(); } } + + [AssemblyInitialize] + public static void AssemblyInitialize(TestContext _) + { + _application = new WebApplicationFactory(); + + } }