Browse Source
* Adding tests for GetById endpoint * Updating tests and messages * Adding paged endpoint and also AutoMapper * Authenticate endpoint works as bool with tests * Got JWT token security working with Create and Delete endpoints and Swashbuckle. * Working on getting cookie and jwt token auth working in the same app All tests are passing * Creating new project and moving APIs Build succeeds; tests need updated. * all tests passing after moving services to PublicApi project * Fix authorize attributes * Uncomment and update ApiCatalogControllerLists tests Co-authored-by: Eric Fleming <eric-fleming18@hotmail.com>main
committed by
GitHub
49 changed files with 1024 additions and 75 deletions
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces |
||||
|
{ |
||||
|
public interface ITokenClaimsService |
||||
|
{ |
||||
|
Task<string> GetTokenAsync(string userName); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IdentityModel.Tokens.Jwt; |
||||
|
using System.Security.Claims; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.Infrastructure.Identity |
||||
|
{ |
||||
|
public class IdentityTokenClaimService : ITokenClaimsService |
||||
|
{ |
||||
|
private readonly UserManager<ApplicationUser> _userManager; |
||||
|
|
||||
|
public IdentityTokenClaimService(UserManager<ApplicationUser> userManager) |
||||
|
{ |
||||
|
_userManager = userManager; |
||||
|
} |
||||
|
|
||||
|
public async Task<string> GetTokenAsync(string userName) |
||||
|
{ |
||||
|
var tokenHandler = new JwtSecurityTokenHandler(); |
||||
|
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); |
||||
|
var user = await _userManager.FindByNameAsync(userName); |
||||
|
var roles = await _userManager.GetRolesAsync(user); |
||||
|
var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) }; |
||||
|
|
||||
|
foreach(var role in roles) |
||||
|
{ |
||||
|
claims.Add(new Claim(ClaimTypes.Role, role)); |
||||
|
} |
||||
|
|
||||
|
var tokenDescriptor = new SecurityTokenDescriptor |
||||
|
{ |
||||
|
Subject = new ClaimsIdentity(claims.ToArray()), |
||||
|
Expires = DateTime.UtcNow.AddDays(7), |
||||
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) |
||||
|
}; |
||||
|
var token = tokenHandler.CreateToken(tokenDescriptor); |
||||
|
return tokenHandler.WriteToken(token); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints |
||||
|
{ |
||||
|
public class AuthenticateRequest : BaseRequest |
||||
|
{ |
||||
|
public string Username { get; set; } |
||||
|
public string Password { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints |
||||
|
{ |
||||
|
public class AuthenticateResponse : BaseResponse |
||||
|
{ |
||||
|
public AuthenticateResponse(Guid correlationId) : base(correlationId) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public AuthenticateResponse() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public bool Result { get; set; } |
||||
|
public string Token { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using Ardalis.ApiEndpoints; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Identity; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using Swashbuckle.AspNetCore.Annotations; |
||||
|
using System; |
||||
|
using System.IdentityModel.Tokens.Jwt; |
||||
|
using System.Security.Claims; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints |
||||
|
{ |
||||
|
public class Authenticate : BaseAsyncEndpoint<AuthenticateRequest, AuthenticateResponse> |
||||
|
{ |
||||
|
private readonly SignInManager<ApplicationUser> _signInManager; |
||||
|
private readonly ITokenClaimsService _tokenClaimsService; |
||||
|
|
||||
|
public Authenticate(SignInManager<ApplicationUser> signInManager, |
||||
|
ITokenClaimsService tokenClaimsService) |
||||
|
{ |
||||
|
_signInManager = signInManager; |
||||
|
_tokenClaimsService = tokenClaimsService; |
||||
|
} |
||||
|
|
||||
|
[HttpPost("api/authenticate")] |
||||
|
[SwaggerOperation( |
||||
|
Summary = "Authenticates a user", |
||||
|
Description = "Authenticates a user", |
||||
|
OperationId = "auth.authenticate", |
||||
|
Tags = new[] { "AuthEndpoints" }) |
||||
|
] |
||||
|
public override async Task<ActionResult<AuthenticateResponse>> HandleAsync(AuthenticateRequest request) |
||||
|
{ |
||||
|
var response = new AuthenticateResponse(request.CorrelationId()); |
||||
|
|
||||
|
var result = await _signInManager.PasswordSignInAsync(request.Username, request.Password, false, true); |
||||
|
|
||||
|
response.Result = result.Succeeded; |
||||
|
|
||||
|
response.Token = await _tokenClaimsService.GetTokenAsync(request.Username); |
||||
|
|
||||
|
return response; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Microsoft.eShopWeb.PublicApi |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Base class used by API requests
|
||||
|
/// </summary>
|
||||
|
public abstract class BaseRequest : BaseMessage |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,6 @@ |
|||||
using System; |
using System; |
||||
|
|
||||
namespace Microsoft.eShopWeb.Web.API |
namespace Microsoft.eShopWeb.PublicApi |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Base class used by API responses
|
/// Base class used by API responses
|
||||
@ -1,4 +1,4 @@ |
|||||
namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints |
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
{ |
{ |
||||
public class CatalogItemDto |
public class CatalogItemDto |
||||
{ |
{ |
||||
@ -1,4 +1,4 @@ |
|||||
namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints |
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
{ |
{ |
||||
public class CreateCatalogItemRequest : BaseRequest |
public class CreateCatalogItemRequest : BaseRequest |
||||
{ |
{ |
||||
@ -1,12 +1,16 @@ |
|||||
using Ardalis.ApiEndpoints; |
using Ardalis.ApiEndpoints; |
||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
using Microsoft.AspNetCore.Mvc; |
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
using Swashbuckle.AspNetCore.Annotations; |
using Swashbuckle.AspNetCore.Annotations; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
|
||||
namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints |
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
{ |
{ |
||||
|
[Authorize(Roles = AuthorizationConstants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] |
||||
public class Create : BaseAsyncEndpoint<CreateCatalogItemRequest, CreateCatalogItemResponse> |
public class Create : BaseAsyncEndpoint<CreateCatalogItemRequest, CreateCatalogItemResponse> |
||||
{ |
{ |
||||
private readonly IAsyncRepository<CatalogItem> _itemRepository; |
private readonly IAsyncRepository<CatalogItem> _itemRepository; |
||||
@ -1,6 +1,6 @@ |
|||||
using Microsoft.AspNetCore.Mvc; |
using Microsoft.AspNetCore.Mvc; |
||||
|
|
||||
namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints |
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
{ |
{ |
||||
public class DeleteCatalogItemRequest : BaseRequest |
public class DeleteCatalogItemRequest : BaseRequest |
||||
{ |
{ |
||||
@ -1,12 +1,16 @@ |
|||||
using Ardalis.ApiEndpoints; |
using Ardalis.ApiEndpoints; |
||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
using Microsoft.AspNetCore.Mvc; |
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
using Swashbuckle.AspNetCore.Annotations; |
using Swashbuckle.AspNetCore.Annotations; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
|
||||
namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints |
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
{ |
{ |
||||
|
[Authorize(Roles = AuthorizationConstants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] |
||||
public class Delete : BaseAsyncEndpoint<DeleteCatalogItemRequest, DeleteCatalogItemResponse> |
public class Delete : BaseAsyncEndpoint<DeleteCatalogItemRequest, DeleteCatalogItemResponse> |
||||
{ |
{ |
||||
private readonly IAsyncRepository<CatalogItem> _itemRepository; |
private readonly IAsyncRepository<CatalogItem> _itemRepository; |
||||
@ -1,4 +1,4 @@ |
|||||
namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints |
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
{ |
{ |
||||
public class GetByIdCatalogItemRequest : BaseRequest |
public class GetByIdCatalogItemRequest : BaseRequest |
||||
{ |
{ |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
|
{ |
||||
|
public class ListPagedCatalogItemRequest : BaseRequest |
||||
|
{ |
||||
|
public int PageSize { get; set; } |
||||
|
public int PageIndex { get; set; } |
||||
|
public int? CatalogBrandId { get; set; } |
||||
|
public int? CatalogTypeId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
|
{ |
||||
|
public class ListPagedCatalogItemResponse : BaseResponse |
||||
|
{ |
||||
|
public ListPagedCatalogItemResponse(Guid correlationId) : base(correlationId) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public ListPagedCatalogItemResponse() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public List<CatalogItemDto> CatalogItems { get; set; } = new List<CatalogItemDto>(); |
||||
|
public int PageCount { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
using Ardalis.ApiEndpoints; |
||||
|
using AutoMapper; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Specifications; |
||||
|
using Swashbuckle.AspNetCore.Annotations; |
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints |
||||
|
{ |
||||
|
public class ListPaged : BaseAsyncEndpoint<ListPagedCatalogItemRequest, ListPagedCatalogItemResponse> |
||||
|
{ |
||||
|
private readonly IAsyncRepository<CatalogItem> _itemRepository; |
||||
|
private readonly IUriComposer _uriComposer; |
||||
|
private readonly IMapper _mapper; |
||||
|
|
||||
|
public ListPaged(IAsyncRepository<CatalogItem> itemRepository, |
||||
|
IUriComposer uriComposer, |
||||
|
IMapper mapper) |
||||
|
{ |
||||
|
_itemRepository = itemRepository; |
||||
|
_uriComposer = uriComposer; |
||||
|
_mapper = mapper; |
||||
|
} |
||||
|
|
||||
|
[HttpGet("api/catalog-items")] |
||||
|
[SwaggerOperation( |
||||
|
Summary = "List Catalog Items (paged)", |
||||
|
Description = "List Catalog Items (paged)", |
||||
|
OperationId = "catalog-items.ListPaged", |
||||
|
Tags = new[] { "CatalogItemEndpoints" }) |
||||
|
] |
||||
|
public override async Task<ActionResult<ListPagedCatalogItemResponse>> HandleAsync([FromQuery]ListPagedCatalogItemRequest request) |
||||
|
{ |
||||
|
var response = new ListPagedCatalogItemResponse(request.CorrelationId()); |
||||
|
|
||||
|
var filterSpec = new CatalogFilterSpecification(request.CatalogBrandId, request.CatalogTypeId); |
||||
|
int totalItems = await _itemRepository.CountAsync(filterSpec); |
||||
|
|
||||
|
var pagedSpec = new CatalogFilterPaginatedSpecification( |
||||
|
skip: request.PageIndex * request.PageSize, |
||||
|
take: request.PageSize, |
||||
|
brandId: request.CatalogBrandId, |
||||
|
typeId: request.CatalogTypeId); |
||||
|
|
||||
|
var items = await _itemRepository.ListAsync(pagedSpec); |
||||
|
|
||||
|
response.CatalogItems.AddRange(items.Select(_mapper.Map<CatalogItemDto>)); |
||||
|
foreach (CatalogItemDto item in response.CatalogItems) |
||||
|
{ |
||||
|
item.PictureUri = _uriComposer.ComposePicUri(item.PictureUri); |
||||
|
} |
||||
|
response.PageCount = int.Parse(Math.Ceiling((decimal)totalItems / request.PageSize).ToString()); |
||||
|
|
||||
|
return Ok(response); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,7 +1,7 @@ |
|||||
using Microsoft.OpenApi.Models; |
using Microsoft.OpenApi.Models; |
||||
using Swashbuckle.AspNetCore.SwaggerGen; |
using Swashbuckle.AspNetCore.SwaggerGen; |
||||
|
|
||||
namespace Microsoft.eShopWeb.Web.API |
namespace Microsoft.eShopWeb.PublicApi |
||||
{ |
{ |
||||
public class CustomSchemaFilters : ISchemaFilter |
public class CustomSchemaFilters : ISchemaFilter |
||||
{ |
{ |
||||
@ -0,0 +1,22 @@ |
|||||
|
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. |
||||
|
|
||||
|
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base |
||||
|
WORKDIR /app |
||||
|
EXPOSE 80 |
||||
|
EXPOSE 443 |
||||
|
|
||||
|
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build |
||||
|
WORKDIR /src |
||||
|
COPY ["src/PublicApi/PublicApi.csproj", "src/PublicApi/"] |
||||
|
RUN dotnet restore "src/PublicApi/PublicApi.csproj" |
||||
|
COPY . . |
||||
|
WORKDIR "/src/src/PublicApi" |
||||
|
RUN dotnet build "PublicApi.csproj" -c Release -o /app/build |
||||
|
|
||||
|
FROM build AS publish |
||||
|
RUN dotnet publish "PublicApi.csproj" -c Release -o /app/publish |
||||
|
|
||||
|
FROM base AS final |
||||
|
WORKDIR /app |
||||
|
COPY --from=publish /app/publish . |
||||
|
ENTRYPOINT ["dotnet", "PublicApi.dll"] |
||||
@ -0,0 +1,14 @@ |
|||||
|
using AutoMapper; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi |
||||
|
{ |
||||
|
public class MappingProfile : Profile |
||||
|
{ |
||||
|
public MappingProfile() |
||||
|
{ |
||||
|
CreateMap<CatalogItem, CatalogItemDto>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Data; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Identity; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi |
||||
|
{ |
||||
|
public class Program |
||||
|
{ |
||||
|
public async static Task Main(string[] args) |
||||
|
{ |
||||
|
var host = CreateHostBuilder(args) |
||||
|
.Build(); |
||||
|
|
||||
|
using (var scope = host.Services.CreateScope()) |
||||
|
{ |
||||
|
var services = scope.ServiceProvider; |
||||
|
var loggerFactory = services.GetRequiredService<ILoggerFactory>(); |
||||
|
try |
||||
|
{ |
||||
|
var catalogContext = services.GetRequiredService<CatalogContext>(); |
||||
|
await CatalogContextSeed.SeedAsync(catalogContext, loggerFactory); |
||||
|
|
||||
|
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>(); |
||||
|
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>(); |
||||
|
await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
var logger = loggerFactory.CreateLogger<Program>(); |
||||
|
logger.LogError(ex, "An error occurred seeding the DB."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
host.Run(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static IHostBuilder CreateHostBuilder(string[] args) => |
||||
|
Host.CreateDefaultBuilder(args) |
||||
|
.ConfigureWebHostDefaults(webBuilder => |
||||
|
{ |
||||
|
webBuilder.UseStartup<Startup>(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
{ |
||||
|
"iisSettings": { |
||||
|
"windowsAuthentication": false, |
||||
|
"anonymousAuthentication": true, |
||||
|
"iisExpress": { |
||||
|
"applicationUrl": "http://localhost:52023", |
||||
|
"sslPort": 44339 |
||||
|
} |
||||
|
}, |
||||
|
"$schema": "http://json.schemastore.org/launchsettings.json", |
||||
|
"profiles": { |
||||
|
"IIS Express": { |
||||
|
"commandName": "IISExpress", |
||||
|
"launchBrowser": true, |
||||
|
"launchUrl": "swagger", |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
}, |
||||
|
"PublicApi": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"launchUrl": "swagger", |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
}, |
||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
||||
|
}, |
||||
|
"Docker": { |
||||
|
"commandName": "Docker", |
||||
|
"launchBrowser": true, |
||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", |
||||
|
"publishAllPorts": true, |
||||
|
"useSSL": true |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Web"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<RootNamespace>Microsoft.eShopWeb.PublicApi</RootNamespace> |
||||
|
<UserSecretsId>5b662463-1efd-4bae-bde4-befe0be3e8ff</UserSecretsId> |
||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> |
||||
|
<DockerfileContext>..\..</DockerfileContext> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Ardalis.ApiEndpoints" Version="1.0.0" /> |
||||
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" /> |
||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.0" /> |
||||
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="5.5.0" /> |
||||
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.5.0" /> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.0" /> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="3.1.5" /> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.5" /> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="3.1.5" /> |
||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.5" /> |
||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.5" /> |
||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.5"> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
</PackageReference> |
||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" /> |
||||
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" /> |
||||
|
|
||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" /> |
||||
|
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,193 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using AutoMapper; |
||||
|
using Microsoft.AspNetCore.Authentication.Cookies; |
||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Services; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Data; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Identity; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Logging; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using Microsoft.OpenApi.Models; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi |
||||
|
{ |
||||
|
public class Startup |
||||
|
{ |
||||
|
public Startup(IConfiguration configuration) |
||||
|
{ |
||||
|
Configuration = configuration; |
||||
|
} |
||||
|
|
||||
|
public IConfiguration Configuration { get; } |
||||
|
|
||||
|
public void ConfigureDevelopmentServices(IServiceCollection services) |
||||
|
{ |
||||
|
// use in-memory database
|
||||
|
ConfigureInMemoryDatabases(services); |
||||
|
|
||||
|
// use real database
|
||||
|
//ConfigureProductionServices(services);
|
||||
|
} |
||||
|
|
||||
|
private void ConfigureInMemoryDatabases(IServiceCollection services) |
||||
|
{ |
||||
|
// use in-memory database
|
||||
|
services.AddDbContext<CatalogContext>(c => |
||||
|
c.UseInMemoryDatabase("Catalog")); |
||||
|
|
||||
|
// Add Identity DbContext
|
||||
|
services.AddDbContext<AppIdentityDbContext>(options => |
||||
|
options.UseInMemoryDatabase("Identity")); |
||||
|
|
||||
|
ConfigureServices(services); |
||||
|
} |
||||
|
|
||||
|
public void ConfigureProductionServices(IServiceCollection services) |
||||
|
{ |
||||
|
// use real database
|
||||
|
// Requires LocalDB which can be installed with SQL Server Express 2016
|
||||
|
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
|
||||
|
services.AddDbContext<CatalogContext>(c => |
||||
|
c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection"))); |
||||
|
|
||||
|
// Add Identity DbContext
|
||||
|
services.AddDbContext<AppIdentityDbContext>(options => |
||||
|
options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection"))); |
||||
|
|
||||
|
ConfigureServices(services); |
||||
|
} |
||||
|
|
||||
|
public void ConfigureTestingServices(IServiceCollection services) |
||||
|
{ |
||||
|
ConfigureInMemoryDatabases(services); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
|
public void ConfigureServices(IServiceCollection services) |
||||
|
{ |
||||
|
services.AddIdentity<ApplicationUser, IdentityRole>() |
||||
|
.AddEntityFrameworkStores<AppIdentityDbContext>() |
||||
|
.AddDefaultTokenProviders(); |
||||
|
|
||||
|
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>)); |
||||
|
services.Configure<CatalogSettings>(Configuration); |
||||
|
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>())); |
||||
|
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); |
||||
|
services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>(); |
||||
|
|
||||
|
// Add memory cache services
|
||||
|
services.AddMemoryCache(); |
||||
|
|
||||
|
// https://stackoverflow.com/questions/46938248/asp-net-core-2-0-combining-cookies-and-bearer-authorization-for-the-same-endpoin
|
||||
|
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); |
||||
|
services.AddAuthentication(config => |
||||
|
{ |
||||
|
//config.DefaultScheme = "smart";
|
||||
|
//config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
|
//config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
|
|
||||
|
config.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme; |
||||
|
config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; |
||||
|
config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; |
||||
|
config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; |
||||
|
}) |
||||
|
.AddJwtBearer(x => |
||||
|
{ |
||||
|
x.RequireHttpsMetadata = false; |
||||
|
x.SaveToken = true; |
||||
|
x.TokenValidationParameters = new TokenValidationParameters |
||||
|
{ |
||||
|
ValidateIssuerSigningKey = true, |
||||
|
IssuerSigningKey = new SymmetricSecurityKey(key), |
||||
|
ValidateIssuer = false, |
||||
|
ValidateAudience = false |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
services.AddControllers(); |
||||
|
|
||||
|
services.AddAutoMapper(typeof(Startup).Assembly); |
||||
|
|
||||
|
services.AddSwaggerGen(c => |
||||
|
{ |
||||
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); |
||||
|
c.EnableAnnotations(); |
||||
|
c.SchemaFilter<CustomSchemaFilters>(); |
||||
|
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme |
||||
|
{ |
||||
|
Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n
|
||||
|
Enter 'Bearer' [space] and then your token in the text input below. |
||||
|
\r\n\r\nExample: 'Bearer 12345abcdef'",
|
||||
|
Name = "Authorization", |
||||
|
In = ParameterLocation.Header, |
||||
|
Type = SecuritySchemeType.ApiKey, |
||||
|
Scheme = "Bearer" |
||||
|
}); |
||||
|
|
||||
|
c.AddSecurityRequirement(new OpenApiSecurityRequirement() |
||||
|
{ |
||||
|
{ |
||||
|
new OpenApiSecurityScheme |
||||
|
{ |
||||
|
Reference = new OpenApiReference |
||||
|
{ |
||||
|
Type = ReferenceType.SecurityScheme, |
||||
|
Id = "Bearer" |
||||
|
}, |
||||
|
Scheme = "oauth2", |
||||
|
Name = "Bearer", |
||||
|
In = ParameterLocation.Header, |
||||
|
|
||||
|
}, |
||||
|
new List<string>() |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) |
||||
|
{ |
||||
|
if (env.IsDevelopment()) |
||||
|
{ |
||||
|
app.UseDeveloperExceptionPage(); |
||||
|
} |
||||
|
|
||||
|
app.UseHttpsRedirection(); |
||||
|
|
||||
|
app.UseRouting(); |
||||
|
|
||||
|
app.UseAuthorization(); |
||||
|
|
||||
|
// Enable middleware to serve generated Swagger as a JSON endpoint.
|
||||
|
app.UseSwagger(); |
||||
|
|
||||
|
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
|
||||
|
// specifying the Swagger JSON endpoint.
|
||||
|
app.UseSwaggerUI(c => |
||||
|
{ |
||||
|
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); |
||||
|
}); |
||||
|
|
||||
|
app.UseEndpoints(endpoints => |
||||
|
{ |
||||
|
endpoints.MapControllers(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
{ |
||||
|
"Logging": { |
||||
|
"LogLevel": { |
||||
|
"Default": "Information", |
||||
|
"Microsoft": "Warning", |
||||
|
"Microsoft.Hosting.Lifetime": "Information" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
{ |
||||
|
"ConnectionStrings": { |
||||
|
"CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", |
||||
|
"IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" |
||||
|
}, |
||||
|
"CatalogBaseUrl": "", |
||||
|
"Logging": { |
||||
|
"IncludeScopes": false, |
||||
|
"LogLevel": { |
||||
|
"Default": "Warning", |
||||
|
"Microsoft": "Warning", |
||||
|
"System": "Warning" |
||||
|
}, |
||||
|
"AllowedHosts": "*" |
||||
|
} |
||||
|
} |
||||
@ -1,21 +0,0 @@ |
|||||
using Microsoft.eShopWeb.Web.Services; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.Web.Controllers.Api |
|
||||
{ |
|
||||
public class CatalogController : BaseApiController |
|
||||
{ |
|
||||
private readonly ICatalogViewModelService _catalogViewModelService; |
|
||||
|
|
||||
public CatalogController(ICatalogViewModelService catalogViewModelService) => _catalogViewModelService = catalogViewModelService; |
|
||||
|
|
||||
[HttpGet] |
|
||||
public async Task<IActionResult> List(int? brandFilterApplied, int? typesFilterApplied, int? page) |
|
||||
{ |
|
||||
var itemsPage = 10; |
|
||||
var catalogModel = await _catalogViewModelService.GetCatalogItems(page ?? 0, itemsPage, brandFilterApplied, typesFilterApplied); |
|
||||
return Ok(catalogModel); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,79 @@ |
|||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc.Testing; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Data; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Identity; |
||||
|
using Microsoft.eShopWeb.PublicApi; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.FunctionalTests.PublicApi |
||||
|
{ |
||||
|
public class ApiTestFixture : WebApplicationFactory<Startup> |
||||
|
{ |
||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder) |
||||
|
{ |
||||
|
builder.UseEnvironment("Testing"); |
||||
|
|
||||
|
builder.ConfigureServices(services => |
||||
|
{ |
||||
|
services.AddEntityFrameworkInMemoryDatabase(); |
||||
|
|
||||
|
// Create a new service provider.
|
||||
|
var provider = services |
||||
|
.AddEntityFrameworkInMemoryDatabase() |
||||
|
.BuildServiceProvider(); |
||||
|
|
||||
|
// Add a database context (ApplicationDbContext) using an in-memory
|
||||
|
// database for testing.
|
||||
|
services.AddDbContext<CatalogContext>(options => |
||||
|
{ |
||||
|
options.UseInMemoryDatabase("InMemoryDbForTesting"); |
||||
|
options.UseInternalServiceProvider(provider); |
||||
|
}); |
||||
|
|
||||
|
services.AddDbContext<AppIdentityDbContext>(options => |
||||
|
{ |
||||
|
options.UseInMemoryDatabase("Identity"); |
||||
|
options.UseInternalServiceProvider(provider); |
||||
|
}); |
||||
|
|
||||
|
// Build the service provider.
|
||||
|
var sp = services.BuildServiceProvider(); |
||||
|
|
||||
|
// Create a scope to obtain a reference to the database
|
||||
|
// context (ApplicationDbContext).
|
||||
|
using (var scope = sp.CreateScope()) |
||||
|
{ |
||||
|
var scopedServices = scope.ServiceProvider; |
||||
|
var db = scopedServices.GetRequiredService<CatalogContext>(); |
||||
|
var loggerFactory = scopedServices.GetRequiredService<ILoggerFactory>(); |
||||
|
|
||||
|
var logger = scopedServices |
||||
|
.GetRequiredService<ILogger<ApiTestFixture>>(); |
||||
|
|
||||
|
// Ensure the database is created.
|
||||
|
db.Database.EnsureCreated(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
// Seed the database with test data.
|
||||
|
CatalogContextSeed.SeedAsync(db, loggerFactory).Wait(); |
||||
|
|
||||
|
// seed sample user data
|
||||
|
var userManager = scopedServices.GetRequiredService<UserManager<ApplicationUser>>(); |
||||
|
var roleManager = scopedServices.GetRequiredService<RoleManager<IdentityRole>>(); |
||||
|
AppIdentityDbContextSeed.SeedAsync(userManager, roleManager).Wait(); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
logger.LogError(ex, $"An error occurred seeding the " + |
||||
|
"database with test messages. Error: {ex.Message}"); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Identity; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IdentityModel.Tokens.Jwt; |
||||
|
using System.Security.Claims; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.FunctionalTests.Web.Api |
||||
|
{ |
||||
|
public class ApiTokenHelper |
||||
|
{ |
||||
|
public static string GetAdminUserToken() |
||||
|
{ |
||||
|
string userName = "admin@microsoft.com"; |
||||
|
string[] roles = { "Administrators" }; |
||||
|
|
||||
|
return CreateToken(userName, roles); |
||||
|
} |
||||
|
|
||||
|
public static string GetNormalUserToken() |
||||
|
{ |
||||
|
string userName = "demouser@microsoft.com"; |
||||
|
string[] roles = { }; |
||||
|
|
||||
|
return CreateToken(userName, roles); |
||||
|
} |
||||
|
|
||||
|
private static string CreateToken(string userName, string[] roles) |
||||
|
{ |
||||
|
var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) }; |
||||
|
|
||||
|
foreach (var role in roles) |
||||
|
{ |
||||
|
claims.Add(new Claim(ClaimTypes.Role, role)); |
||||
|
} |
||||
|
|
||||
|
var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); |
||||
|
var tokenDescriptor = new SecurityTokenDescriptor |
||||
|
{ |
||||
|
Subject = new ClaimsIdentity(claims.ToArray()), |
||||
|
Expires = DateTime.UtcNow.AddHours(1), |
||||
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) |
||||
|
}; |
||||
|
var tokenHandler = new JwtSecurityTokenHandler(); |
||||
|
var token = tokenHandler.CreateToken(tokenDescriptor); |
||||
|
return tokenHandler.WriteToken(token); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
|
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
||||
|
using Microsoft.eShopWeb.PublicApi.AuthEndpoints; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers |
||||
|
{ |
||||
|
[Collection("Sequential")] |
||||
|
public class AuthenticateEndpoint : IClassFixture<ApiTestFixture> |
||||
|
{ |
||||
|
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; |
||||
|
|
||||
|
public AuthenticateEndpoint(ApiTestFixture factory) |
||||
|
{ |
||||
|
Client = factory.CreateClient(); |
||||
|
} |
||||
|
|
||||
|
public HttpClient Client { get; } |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)] |
||||
|
[InlineData("demouser@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 Client.PostAsync("api/authenticate", jsonContent); |
||||
|
response.EnsureSuccessStatusCode(); |
||||
|
var stringResponse = await response.Content.ReadAsStringAsync(); |
||||
|
var model = stringResponse.FromJson<AuthenticateResponse>(); |
||||
|
|
||||
|
Assert.Equal(expectedResult, model.Result); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
||||
|
using Microsoft.eShopWeb.FunctionalTests.Web.Api; |
||||
|
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Net.Http.Headers; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers |
||||
|
{ |
||||
|
[Collection("Sequential")] |
||||
|
public class CreateEndpoint : IClassFixture<ApiTestFixture> |
||||
|
{ |
||||
|
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; |
||||
|
private int _testBrandId = 1; |
||||
|
private int _testTypeId = 2; |
||||
|
private string _testDescription = "test description"; |
||||
|
private string _testName = "test name"; |
||||
|
private string _testUri = "test uri"; |
||||
|
private decimal _testPrice = 1.23m; |
||||
|
|
||||
|
public CreateEndpoint(ApiTestFixture factory) |
||||
|
{ |
||||
|
Client = factory.CreateClient(); |
||||
|
} |
||||
|
|
||||
|
public HttpClient Client { get; } |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task ReturnsNotAuthorizedGivenNormalUserToken() |
||||
|
{ |
||||
|
var jsonContent = GetValidNewItemJson(); |
||||
|
var token = ApiTokenHelper.GetNormalUserToken(); |
||||
|
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); |
||||
|
var response = await Client.PostAsync("api/catalog-items", jsonContent); |
||||
|
|
||||
|
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken() |
||||
|
{ |
||||
|
var jsonContent = GetValidNewItemJson(); |
||||
|
var adminToken = ApiTokenHelper.GetAdminUserToken(); |
||||
|
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<CreateCatalogItemResponse>(); |
||||
|
|
||||
|
Assert.Equal(_testBrandId, model.CatalogItem.CatalogBrandId); |
||||
|
Assert.Equal(_testTypeId, model.CatalogItem.CatalogTypeId); |
||||
|
Assert.Equal(_testDescription, model.CatalogItem.Description); |
||||
|
Assert.Equal(_testName, model.CatalogItem.Name); |
||||
|
Assert.Equal(_testUri, model.CatalogItem.PictureUri); |
||||
|
Assert.Equal(_testPrice, model.CatalogItem.Price); |
||||
|
} |
||||
|
|
||||
|
private StringContent GetValidNewItemJson() |
||||
|
{ |
||||
|
var request = new CreateCatalogItemRequest() |
||||
|
{ |
||||
|
CatalogBrandId = _testBrandId, |
||||
|
CatalogTypeId = _testTypeId, |
||||
|
Description = _testDescription, |
||||
|
Name = _testName, |
||||
|
PictureUri = _testUri, |
||||
|
Price = _testPrice |
||||
|
}; |
||||
|
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); |
||||
|
|
||||
|
return jsonContent; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
||||
|
using Microsoft.eShopWeb.FunctionalTests.Web.Api; |
||||
|
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Net.Http.Headers; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers |
||||
|
{ |
||||
|
[Collection("Sequential")] |
||||
|
public class DeleteEndpoint : IClassFixture<ApiTestFixture> |
||||
|
{ |
||||
|
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; |
||||
|
|
||||
|
public DeleteEndpoint(ApiTestFixture factory) |
||||
|
{ |
||||
|
Client = factory.CreateClient(); |
||||
|
} |
||||
|
|
||||
|
public HttpClient Client { get; } |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task ReturnsSuccessGivenValidIdAndAdminUserToken() |
||||
|
{ |
||||
|
var adminToken = ApiTokenHelper.GetAdminUserToken(); |
||||
|
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<DeleteCatalogItemResponse>(); |
||||
|
|
||||
|
Assert.Equal("Deleted", model.Status); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken() |
||||
|
{ |
||||
|
var adminToken = ApiTokenHelper.GetAdminUserToken(); |
||||
|
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", adminToken); |
||||
|
var response = await Client.DeleteAsync("api/catalog-items/0"); |
||||
|
|
||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
||||
|
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers |
||||
|
{ |
||||
|
[Collection("Sequential")] |
||||
|
public class GetByIdEndpoint : IClassFixture<ApiTestFixture> |
||||
|
{ |
||||
|
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; |
||||
|
|
||||
|
public GetByIdEndpoint(ApiTestFixture factory) |
||||
|
{ |
||||
|
Client = factory.CreateClient(); |
||||
|
} |
||||
|
|
||||
|
public HttpClient Client { get; } |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task ReturnsItemGivenValidId() |
||||
|
{ |
||||
|
var response = await Client.GetAsync("api/catalog-items/5"); |
||||
|
response.EnsureSuccessStatusCode(); |
||||
|
var stringResponse = await response.Content.ReadAsStringAsync(); |
||||
|
var model = stringResponse.FromJson<GetByIdCatalogItemResponse>(); |
||||
|
|
||||
|
Assert.Equal(5, model.CatalogItem.Id); |
||||
|
Assert.Equal("Roslyn Red Sheet", model.CatalogItem.Name); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task ReturnsNotFoundGivenInvalidId() |
||||
|
{ |
||||
|
var response = await Client.GetAsync("api/catalog-items/0"); |
||||
|
|
||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue