Browse Source
* migrate from classic controller to minimal api * fix all PublicApi integration test * update all nuget package add forget project * fix pay now * Adapt readme use in memory database * undo AuthenticateEndpoint to use EndpointBaseAsync * Update README.md Co-authored-by: Steve Smith <steve@kentsmiths.com> Co-authored-by: Steve Smith <steve@kentsmiths.com>main
committed by
GitHub
63 changed files with 842 additions and 630 deletions
@ -0,0 +1,40 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Data; |
||||
|
using Microsoft.eShopWeb.Infrastructure.Identity; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.Infrastructure; |
||||
|
|
||||
|
public static class Dependencies |
||||
|
{ |
||||
|
public static void ConfigureServices(IConfiguration configuration, IServiceCollection services) |
||||
|
{ |
||||
|
var useOnlyInMemoryDatabase = false; |
||||
|
if (configuration["UseOnlyInMemoryDatabase"] != null) |
||||
|
{ |
||||
|
useOnlyInMemoryDatabase = bool.Parse(configuration["UseOnlyInMemoryDatabase"]); |
||||
|
} |
||||
|
|
||||
|
if (useOnlyInMemoryDatabase) |
||||
|
{ |
||||
|
services.AddDbContext<CatalogContext>(c => |
||||
|
c.UseInMemoryDatabase("Catalog")); |
||||
|
|
||||
|
services.AddDbContext<AppIdentityDbContext>(options => |
||||
|
options.UseInMemoryDatabase("Identity")); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// 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"))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using AutoMapper; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Routing; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using MinimalApi.Endpoint; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// List Catalog Brands
|
||||
|
/// </summary>
|
||||
|
public class CatalogBrandListEndpoint : IEndpoint<IResult> |
||||
|
{ |
||||
|
private IRepository<CatalogBrand> _catalogBrandRepository; |
||||
|
private readonly IMapper _mapper; |
||||
|
|
||||
|
public CatalogBrandListEndpoint(IMapper mapper) |
||||
|
{ |
||||
|
_mapper = mapper; |
||||
|
} |
||||
|
|
||||
|
public void AddRoute(IEndpointRouteBuilder app) |
||||
|
{ |
||||
|
app.MapGet("api/catalog-brands", |
||||
|
async (IRepository<CatalogBrand> catalogBrandRepository) => |
||||
|
{ |
||||
|
_catalogBrandRepository = catalogBrandRepository; |
||||
|
return await HandleAsync(); |
||||
|
}) |
||||
|
.Produces<ListCatalogBrandsResponse>() |
||||
|
.WithTags("CatalogBrandEndpoints"); |
||||
|
} |
||||
|
|
||||
|
public async Task<IResult> HandleAsync() |
||||
|
{ |
||||
|
var response = new ListCatalogBrandsResponse(); |
||||
|
|
||||
|
var items = await _catalogBrandRepository.ListAsync(); |
||||
|
|
||||
|
response.CatalogBrands.AddRange(items.Select(_mapper.Map<CatalogBrandDto>)); |
||||
|
|
||||
|
return Results.Ok(response); |
||||
|
} |
||||
|
} |
||||
@ -1,44 +0,0 @@ |
|||||
using System.Linq; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Ardalis.ApiEndpoints; |
|
||||
using AutoMapper; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using Swashbuckle.AspNetCore.Annotations; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints; |
|
||||
|
|
||||
public class List : EndpointBaseAsync |
|
||||
.WithoutRequest |
|
||||
.WithActionResult<ListCatalogBrandsResponse> |
|
||||
{ |
|
||||
private readonly IRepository<CatalogBrand> _catalogBrandRepository; |
|
||||
private readonly IMapper _mapper; |
|
||||
|
|
||||
public List(IRepository<CatalogBrand> catalogBrandRepository, |
|
||||
IMapper mapper) |
|
||||
{ |
|
||||
_catalogBrandRepository = catalogBrandRepository; |
|
||||
_mapper = mapper; |
|
||||
} |
|
||||
|
|
||||
[HttpGet("api/catalog-brands")] |
|
||||
[SwaggerOperation( |
|
||||
Summary = "List Catalog Brands", |
|
||||
Description = "List Catalog Brands", |
|
||||
OperationId = "catalog-brands.List", |
|
||||
Tags = new[] { "CatalogBrandEndpoints" }) |
|
||||
] |
|
||||
public override async Task<ActionResult<ListCatalogBrandsResponse>> HandleAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
var response = new ListCatalogBrandsResponse(); |
|
||||
|
|
||||
var items = await _catalogBrandRepository.ListAsync(cancellationToken); |
|
||||
|
|
||||
response.CatalogBrands.AddRange(items.Select(_mapper.Map<CatalogBrandDto>)); |
|
||||
|
|
||||
return Ok(response); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
|
||||
|
public class GetByIdCatalogItemRequest : BaseRequest |
||||
|
{ |
||||
|
public int CatalogItemId { get; init; } |
||||
|
|
||||
|
public GetByIdCatalogItemRequest(int catalogItemId) |
||||
|
{ |
||||
|
CatalogItemId = catalogItemId; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Routing; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using MinimalApi.Endpoint; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get a Catalog Item by Id
|
||||
|
/// </summary>
|
||||
|
public class CatalogItemGetByIdEndpoint : IEndpoint<IResult, GetByIdCatalogItemRequest> |
||||
|
{ |
||||
|
private IRepository<CatalogItem> _itemRepository; |
||||
|
private readonly IUriComposer _uriComposer; |
||||
|
|
||||
|
public CatalogItemGetByIdEndpoint(IUriComposer uriComposer) |
||||
|
{ |
||||
|
_uriComposer = uriComposer; |
||||
|
} |
||||
|
|
||||
|
public void AddRoute(IEndpointRouteBuilder app) |
||||
|
{ |
||||
|
app.MapGet("api/catalog-items/{catalogItemId}", |
||||
|
async (int catalogItemId, IRepository<CatalogItem> itemRepository) => |
||||
|
{ |
||||
|
_itemRepository = itemRepository; |
||||
|
return await HandleAsync(new GetByIdCatalogItemRequest(catalogItemId)); |
||||
|
}) |
||||
|
.Produces<GetByIdCatalogItemResponse>() |
||||
|
.WithTags("CatalogItemEndpoints"); |
||||
|
} |
||||
|
|
||||
|
public async Task<IResult> HandleAsync(GetByIdCatalogItemRequest request) |
||||
|
{ |
||||
|
var response = new GetByIdCatalogItemResponse(request.CorrelationId()); |
||||
|
|
||||
|
var item = await _itemRepository.GetByIdAsync(request.CatalogItemId); |
||||
|
if (item is null) |
||||
|
return Results.NotFound(); |
||||
|
|
||||
|
response.CatalogItem = new CatalogItemDto |
||||
|
{ |
||||
|
Id = item.Id, |
||||
|
CatalogBrandId = item.CatalogBrandId, |
||||
|
CatalogTypeId = item.CatalogTypeId, |
||||
|
Description = item.Description, |
||||
|
Name = item.Name, |
||||
|
PictureUri = _uriComposer.ComposePicUri(item.PictureUri), |
||||
|
Price = item.Price |
||||
|
}; |
||||
|
return Results.Ok(response); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
|
||||
|
public class ListPagedCatalogItemRequest : BaseRequest |
||||
|
{ |
||||
|
public int? PageSize { get; init; } |
||||
|
public int? PageIndex { get; init; } |
||||
|
public int? CatalogBrandId { get; init; } |
||||
|
public int? CatalogTypeId { get; init; } |
||||
|
|
||||
|
public ListPagedCatalogItemRequest(int? pageSize, int? pageIndex, int? catalogBrandId, int? catalogTypeId) |
||||
|
{ |
||||
|
PageSize = pageSize ?? 0; |
||||
|
PageIndex = pageIndex ?? 0; |
||||
|
CatalogBrandId = catalogBrandId; |
||||
|
CatalogTypeId = catalogTypeId; |
||||
|
} |
||||
|
} |
||||
@ -1,9 +0,0 @@ |
|||||
using Microsoft.AspNetCore.Mvc; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
|
||||
|
|
||||
public class DeleteCatalogItemRequest : BaseRequest |
|
||||
{ |
|
||||
//[FromRoute]
|
|
||||
public int CatalogItemId { get; set; } |
|
||||
} |
|
||||
@ -1,43 +0,0 @@ |
|||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Ardalis.ApiEndpoints; |
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|
||||
using Microsoft.AspNetCore.Authorization; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using Swashbuckle.AspNetCore.Annotations; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
|
||||
|
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] |
|
||||
public class Delete : EndpointBaseAsync |
|
||||
.WithRequest<DeleteCatalogItemRequest> |
|
||||
.WithActionResult<DeleteCatalogItemResponse> |
|
||||
{ |
|
||||
private readonly IRepository<CatalogItem> _itemRepository; |
|
||||
|
|
||||
public Delete(IRepository<CatalogItem> itemRepository) |
|
||||
{ |
|
||||
_itemRepository = itemRepository; |
|
||||
} |
|
||||
|
|
||||
[HttpDelete("api/catalog-items/{CatalogItemId}")] |
|
||||
[SwaggerOperation( |
|
||||
Summary = "Deletes a Catalog Item", |
|
||||
Description = "Deletes a Catalog Item", |
|
||||
OperationId = "catalog-items.Delete", |
|
||||
Tags = new[] { "CatalogItemEndpoints" }) |
|
||||
] |
|
||||
public override async Task<ActionResult<DeleteCatalogItemResponse>> HandleAsync([FromRoute] DeleteCatalogItemRequest request, CancellationToken cancellationToken) |
|
||||
{ |
|
||||
var response = new DeleteCatalogItemResponse(request.CorrelationId()); |
|
||||
|
|
||||
var itemToDelete = await _itemRepository.GetByIdAsync(request.CatalogItemId, cancellationToken); |
|
||||
if (itemToDelete is null) return NotFound(); |
|
||||
|
|
||||
await _itemRepository.DeleteAsync(itemToDelete, cancellationToken); |
|
||||
|
|
||||
return Ok(response); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
|
||||
|
public class DeleteCatalogItemRequest : BaseRequest |
||||
|
{ |
||||
|
public int CatalogItemId { get; init; } |
||||
|
|
||||
|
public DeleteCatalogItemRequest(int catalogItemId) |
||||
|
{ |
||||
|
CatalogItemId = catalogItemId; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Routing; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using MinimalApi.Endpoint; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Deletes a Catalog Item
|
||||
|
/// </summary>
|
||||
|
public class DeleteCatalogItemEndpoint : IEndpoint<IResult, DeleteCatalogItemRequest> |
||||
|
{ |
||||
|
private IRepository<CatalogItem> _itemRepository; |
||||
|
|
||||
|
public void AddRoute(IEndpointRouteBuilder app) |
||||
|
{ |
||||
|
app.MapDelete("api/catalog-items/{catalogItemId}", |
||||
|
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async |
||||
|
(int catalogItemId, IRepository<CatalogItem> itemRepository) => |
||||
|
{ |
||||
|
_itemRepository = itemRepository; |
||||
|
return await HandleAsync(new DeleteCatalogItemRequest(catalogItemId)); |
||||
|
}) |
||||
|
.Produces<DeleteCatalogItemResponse>() |
||||
|
.WithTags("CatalogItemEndpoints"); |
||||
|
} |
||||
|
|
||||
|
public async Task<IResult> HandleAsync(DeleteCatalogItemRequest request) |
||||
|
{ |
||||
|
var response = new DeleteCatalogItemResponse(request.CorrelationId()); |
||||
|
|
||||
|
var itemToDelete = await _itemRepository.GetByIdAsync(request.CatalogItemId); |
||||
|
if (itemToDelete is null) |
||||
|
return Results.NotFound(); |
||||
|
|
||||
|
await _itemRepository.DeleteAsync(itemToDelete); |
||||
|
|
||||
|
return Results.Ok(response); |
||||
|
} |
||||
|
} |
||||
@ -1,6 +0,0 @@ |
|||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
|
||||
|
|
||||
public class GetByIdCatalogItemRequest : BaseRequest |
|
||||
{ |
|
||||
public int CatalogItemId { get; set; } |
|
||||
} |
|
||||
@ -1,50 +0,0 @@ |
|||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Ardalis.ApiEndpoints; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using Swashbuckle.AspNetCore.Annotations; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
|
||||
|
|
||||
public class GetById : EndpointBaseAsync |
|
||||
.WithRequest<GetByIdCatalogItemRequest> |
|
||||
.WithActionResult<GetByIdCatalogItemResponse> |
|
||||
{ |
|
||||
private readonly IRepository<CatalogItem> _itemRepository; |
|
||||
private readonly IUriComposer _uriComposer; |
|
||||
|
|
||||
public GetById(IRepository<CatalogItem> itemRepository, IUriComposer uriComposer) |
|
||||
{ |
|
||||
_itemRepository = itemRepository; |
|
||||
_uriComposer = uriComposer; |
|
||||
} |
|
||||
|
|
||||
[HttpGet("api/catalog-items/{CatalogItemId}")] |
|
||||
[SwaggerOperation( |
|
||||
Summary = "Get a Catalog Item by Id", |
|
||||
Description = "Gets a Catalog Item by Id", |
|
||||
OperationId = "catalog-items.GetById", |
|
||||
Tags = new[] { "CatalogItemEndpoints" }) |
|
||||
] |
|
||||
public override async Task<ActionResult<GetByIdCatalogItemResponse>> HandleAsync([FromRoute] GetByIdCatalogItemRequest request, CancellationToken cancellationToken) |
|
||||
{ |
|
||||
var response = new GetByIdCatalogItemResponse(request.CorrelationId()); |
|
||||
|
|
||||
var item = await _itemRepository.GetByIdAsync(request.CatalogItemId, cancellationToken); |
|
||||
if (item is null) return NotFound(); |
|
||||
|
|
||||
response.CatalogItem = new CatalogItemDto |
|
||||
{ |
|
||||
Id = item.Id, |
|
||||
CatalogBrandId = item.CatalogBrandId, |
|
||||
CatalogTypeId = item.CatalogTypeId, |
|
||||
Description = item.Description, |
|
||||
Name = item.Name, |
|
||||
PictureUri = _uriComposer.ComposePicUri(item.PictureUri), |
|
||||
Price = item.Price |
|
||||
}; |
|
||||
return Ok(response); |
|
||||
} |
|
||||
} |
|
||||
@ -1,9 +0,0 @@ |
|||||
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; } |
|
||||
} |
|
||||
@ -1,60 +0,0 @@ |
|||||
using System.IO; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Ardalis.ApiEndpoints; |
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|
||||
using Microsoft.AspNetCore.Authorization; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using Swashbuckle.AspNetCore.Annotations; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
|
||||
|
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] |
|
||||
public class Update : EndpointBaseAsync |
|
||||
.WithRequest<UpdateCatalogItemRequest> |
|
||||
.WithActionResult<UpdateCatalogItemResponse> |
|
||||
{ |
|
||||
private readonly IRepository<CatalogItem> _itemRepository; |
|
||||
private readonly IUriComposer _uriComposer; |
|
||||
|
|
||||
public Update(IRepository<CatalogItem> itemRepository, IUriComposer uriComposer) |
|
||||
{ |
|
||||
_itemRepository = itemRepository; |
|
||||
_uriComposer = uriComposer; |
|
||||
} |
|
||||
|
|
||||
[HttpPut("api/catalog-items")] |
|
||||
[SwaggerOperation( |
|
||||
Summary = "Updates a Catalog Item", |
|
||||
Description = "Updates a Catalog Item", |
|
||||
OperationId = "catalog-items.update", |
|
||||
Tags = new[] { "CatalogItemEndpoints" }) |
|
||||
] |
|
||||
public override async Task<ActionResult<UpdateCatalogItemResponse>> HandleAsync(UpdateCatalogItemRequest request, CancellationToken cancellationToken) |
|
||||
{ |
|
||||
var response = new UpdateCatalogItemResponse(request.CorrelationId()); |
|
||||
|
|
||||
var existingItem = await _itemRepository.GetByIdAsync(request.Id, cancellationToken); |
|
||||
|
|
||||
existingItem.UpdateDetails(request.Name, request.Description, request.Price); |
|
||||
existingItem.UpdateBrand(request.CatalogBrandId); |
|
||||
existingItem.UpdateType(request.CatalogTypeId); |
|
||||
|
|
||||
await _itemRepository.UpdateAsync(existingItem, cancellationToken); |
|
||||
|
|
||||
var dto = new CatalogItemDto |
|
||||
{ |
|
||||
Id = existingItem.Id, |
|
||||
CatalogBrandId = existingItem.CatalogBrandId, |
|
||||
CatalogTypeId = existingItem.CatalogTypeId, |
|
||||
Description = existingItem.Description, |
|
||||
Name = existingItem.Name, |
|
||||
PictureUri = _uriComposer.ComposePicUri(existingItem.PictureUri), |
|
||||
Price = existingItem.Price |
|
||||
}; |
|
||||
response.CatalogItem = dto; |
|
||||
return response; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,64 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Routing; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using MinimalApi.Endpoint; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Updates a Catalog Item
|
||||
|
/// </summary>
|
||||
|
public class UpdateCatalogItemEndpoint : IEndpoint<IResult, UpdateCatalogItemRequest> |
||||
|
{ |
||||
|
private IRepository<CatalogItem> _itemRepository; |
||||
|
private readonly IUriComposer _uriComposer; |
||||
|
|
||||
|
public UpdateCatalogItemEndpoint(IUriComposer uriComposer) |
||||
|
{ |
||||
|
_uriComposer = uriComposer; |
||||
|
} |
||||
|
|
||||
|
public void AddRoute(IEndpointRouteBuilder app) |
||||
|
{ |
||||
|
app.MapPut("api/catalog-items", |
||||
|
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] async |
||||
|
(UpdateCatalogItemRequest request, IRepository<CatalogItem> itemRepository) => |
||||
|
{ |
||||
|
_itemRepository = itemRepository; |
||||
|
return await HandleAsync(request); |
||||
|
}) |
||||
|
.Produces<UpdateCatalogItemResponse>() |
||||
|
.WithTags("CatalogItemEndpoints"); |
||||
|
} |
||||
|
|
||||
|
public async Task<IResult> HandleAsync(UpdateCatalogItemRequest request) |
||||
|
{ |
||||
|
var response = new UpdateCatalogItemResponse(request.CorrelationId()); |
||||
|
|
||||
|
var existingItem = await _itemRepository.GetByIdAsync(request.Id); |
||||
|
|
||||
|
existingItem.UpdateDetails(request.Name, request.Description, request.Price); |
||||
|
existingItem.UpdateBrand(request.CatalogBrandId); |
||||
|
existingItem.UpdateType(request.CatalogTypeId); |
||||
|
|
||||
|
await _itemRepository.UpdateAsync(existingItem); |
||||
|
|
||||
|
var dto = new CatalogItemDto |
||||
|
{ |
||||
|
Id = existingItem.Id, |
||||
|
CatalogBrandId = existingItem.CatalogBrandId, |
||||
|
CatalogTypeId = existingItem.CatalogTypeId, |
||||
|
Description = existingItem.Description, |
||||
|
Name = existingItem.Name, |
||||
|
PictureUri = _uriComposer.ComposePicUri(existingItem.PictureUri), |
||||
|
Price = existingItem.Price |
||||
|
}; |
||||
|
response.CatalogItem = dto; |
||||
|
return Results.Ok(response); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using AutoMapper; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Routing; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
||||
|
using MinimalApi.Endpoint; |
||||
|
|
||||
|
namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// List Catalog Types
|
||||
|
/// </summary>
|
||||
|
public class CatalogTypeListEndpoint : IEndpoint<IResult> |
||||
|
{ |
||||
|
private IRepository<CatalogType> _catalogTypeRepository; |
||||
|
private readonly IMapper _mapper; |
||||
|
|
||||
|
public CatalogTypeListEndpoint(IMapper mapper) |
||||
|
{ |
||||
|
_mapper = mapper; |
||||
|
} |
||||
|
|
||||
|
public void AddRoute(IEndpointRouteBuilder app) |
||||
|
{ |
||||
|
app.MapGet("api/catalog-types", |
||||
|
async (IRepository<CatalogType> catalogTypeRepository) => |
||||
|
{ |
||||
|
_catalogTypeRepository = catalogTypeRepository; |
||||
|
return await HandleAsync(); |
||||
|
}) |
||||
|
.Produces<ListCatalogTypesResponse>() |
||||
|
.WithTags("CatalogTypeEndpoints"); |
||||
|
} |
||||
|
|
||||
|
public async Task<IResult> HandleAsync() |
||||
|
{ |
||||
|
var response = new ListCatalogTypesResponse(); |
||||
|
|
||||
|
var items = await _catalogTypeRepository.ListAsync(); |
||||
|
|
||||
|
response.CatalogTypes.AddRange(items.Select(_mapper.Map<CatalogTypeDto>)); |
||||
|
|
||||
|
return Results.Ok(response); |
||||
|
} |
||||
|
} |
||||
@ -1,44 +0,0 @@ |
|||||
using System.Linq; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Ardalis.ApiEndpoints; |
|
||||
using AutoMapper; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|
||||
using Swashbuckle.AspNetCore.Annotations; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints; |
|
||||
|
|
||||
public class List : EndpointBaseAsync |
|
||||
.WithoutRequest |
|
||||
.WithActionResult<ListCatalogTypesResponse> |
|
||||
{ |
|
||||
private readonly IRepository<CatalogType> _catalogTypeRepository; |
|
||||
private readonly IMapper _mapper; |
|
||||
|
|
||||
public List(IRepository<CatalogType> catalogTypeRepository, |
|
||||
IMapper mapper) |
|
||||
{ |
|
||||
_catalogTypeRepository = catalogTypeRepository; |
|
||||
_mapper = mapper; |
|
||||
} |
|
||||
|
|
||||
[HttpGet("api/catalog-types")] |
|
||||
[SwaggerOperation( |
|
||||
Summary = "List Catalog Types", |
|
||||
Description = "List Catalog Types", |
|
||||
OperationId = "catalog-types.List", |
|
||||
Tags = new[] { "CatalogTypeEndpoints" }) |
|
||||
] |
|
||||
public override async Task<ActionResult<ListCatalogTypesResponse>> HandleAsync(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
var response = new ListCatalogTypesResponse(); |
|
||||
|
|
||||
var items = await _catalogTypeRepository.ListAsync(cancellationToken); |
|
||||
|
|
||||
response.CatalogTypes.AddRange(items.Select(_mapper.Map<CatalogTypeDto>)); |
|
||||
|
|
||||
return Ok(response); |
|
||||
} |
|
||||
} |
|
||||
@ -1,43 +1,43 @@ |
|||||
using System.Net.Http; |
//using System.Net.Http;
|
||||
using System.Text; |
//using System.Text;
|
||||
using System.Text.Json; |
//using System.Text.Json;
|
||||
using System.Threading.Tasks; |
//using System.Threading.Tasks;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Constants; |
//using Microsoft.eShopWeb.ApplicationCore.Constants;
|
||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
//using Microsoft.eShopWeb.FunctionalTests.PublicApi;
|
||||
using Microsoft.eShopWeb.PublicApi.AuthEndpoints; |
//using Microsoft.eShopWeb.PublicApi.AuthEndpoints;
|
||||
using Xunit; |
//using Xunit;
|
||||
|
|
||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; |
//namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers;
|
||||
|
|
||||
[Collection("Sequential")] |
//[Collection("Sequential")]
|
||||
public class AuthenticateEndpoint : IClassFixture<TestApiApplication> |
//public class AuthenticateEndpoint : IClassFixture<TestApiApplication>
|
||||
{ |
//{
|
||||
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; |
// JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
|
|
||||
public AuthenticateEndpoint(TestApiApplication factory) |
// public AuthenticateEndpoint(TestApiApplication factory)
|
||||
{ |
// {
|
||||
Client = factory.CreateClient(); |
// Client = factory.CreateClient();
|
||||
} |
// }
|
||||
|
|
||||
public HttpClient Client { get; } |
// public HttpClient Client { get; }
|
||||
|
|
||||
[Theory] |
// [Theory]
|
||||
[InlineData("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)] |
// [InlineData("demouser@microsoft.com", AuthorizationConstants.DEFAULT_PASSWORD, true)]
|
||||
[InlineData("demouser@microsoft.com", "badpassword", false)] |
// [InlineData("demouser@microsoft.com", "badpassword", false)]
|
||||
[InlineData("baduser@microsoft.com", "badpassword", false)] |
// [InlineData("baduser@microsoft.com", "badpassword", false)]
|
||||
public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult) |
// public async Task ReturnsExpectedResultGivenCredentials(string testUsername, string testPassword, bool expectedResult)
|
||||
{ |
// {
|
||||
var request = new AuthenticateRequest() |
// var request = new AuthenticateRequest()
|
||||
{ |
// {
|
||||
Username = testUsername, |
// Username = testUsername,
|
||||
Password = testPassword |
// Password = testPassword
|
||||
}; |
// };
|
||||
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); |
// var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
||||
var response = await Client.PostAsync("api/authenticate", jsonContent); |
// var response = await Client.PostAsync("api/authenticate", jsonContent);
|
||||
response.EnsureSuccessStatusCode(); |
// response.EnsureSuccessStatusCode();
|
||||
var stringResponse = await response.Content.ReadAsStringAsync(); |
// var stringResponse = await response.Content.ReadAsStringAsync();
|
||||
var model = stringResponse.FromJson<AuthenticateResponse>(); |
// var model = stringResponse.FromJson<AuthenticateResponse>();
|
||||
|
|
||||
Assert.Equal(expectedResult, model.Result); |
// Assert.Equal(expectedResult, model.Result);
|
||||
} |
// }
|
||||
} |
//}
|
||||
|
|||||
@ -1,41 +0,0 @@ |
|||||
using System.Linq; |
|
||||
using System.Net.Http; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
|
||||
using Microsoft.eShopWeb.Web.ViewModels; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; |
|
||||
|
|
||||
[Collection("Sequential")] |
|
||||
public class ApiCatalogControllerList : IClassFixture<TestApiApplication> |
|
||||
{ |
|
||||
public ApiCatalogControllerList(TestApiApplication factory) |
|
||||
{ |
|
||||
Client = factory.CreateClient(); |
|
||||
} |
|
||||
|
|
||||
public HttpClient Client { get; } |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task ReturnsFirst10CatalogItems() |
|
||||
{ |
|
||||
var response = await Client.GetAsync("/api/catalog-items?pageSize=10"); |
|
||||
response.EnsureSuccessStatusCode(); |
|
||||
var stringResponse = await response.Content.ReadAsStringAsync(); |
|
||||
var model = stringResponse.FromJson<CatalogIndexViewModel>(); |
|
||||
|
|
||||
Assert.Equal(10, model.CatalogItems.Count()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task ReturnsLast2CatalogItemsGivenPageIndex1() |
|
||||
{ |
|
||||
var response = await Client.GetAsync("/api/catalog-items?pageSize=10&pageIndex=1"); |
|
||||
response.EnsureSuccessStatusCode(); |
|
||||
var stringResponse = await response.Content.ReadAsStringAsync(); |
|
||||
var model = stringResponse.FromJson<CatalogIndexViewModel>(); |
|
||||
|
|
||||
Assert.Equal(2, model.CatalogItems.Count()); |
|
||||
} |
|
||||
} |
|
||||
@ -1,74 +0,0 @@ |
|||||
using System.Net; |
|
||||
using System.Net.Http; |
|
||||
using System.Net.Http.Headers; |
|
||||
using System.Text; |
|
||||
using System.Text.Json; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
|
||||
using Microsoft.eShopWeb.FunctionalTests.Web.Api; |
|
||||
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; |
|
||||
|
|
||||
[Collection("Sequential")] |
|
||||
public class CreateEndpoint : IClassFixture<TestApiApplication> |
|
||||
{ |
|
||||
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 decimal _testPrice = 1.23m; |
|
||||
|
|
||||
public CreateEndpoint(TestApiApplication 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(_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; |
|
||||
} |
|
||||
} |
|
||||
@ -1,47 +0,0 @@ |
|||||
using System.Net; |
|
||||
using System.Net.Http; |
|
||||
using System.Net.Http.Headers; |
|
||||
using System.Text.Json; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
|
||||
using Microsoft.eShopWeb.FunctionalTests.Web.Api; |
|
||||
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; |
|
||||
|
|
||||
[Collection("Sequential")] |
|
||||
public class DeleteEndpoint : IClassFixture<TestApiApplication> |
|
||||
{ |
|
||||
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; |
|
||||
|
|
||||
public DeleteEndpoint(TestApiApplication 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); |
|
||||
} |
|
||||
} |
|
||||
@ -1,42 +0,0 @@ |
|||||
using System.Net; |
|
||||
using System.Net.Http; |
|
||||
using System.Text.Json; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.eShopWeb.FunctionalTests.PublicApi; |
|
||||
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; |
|
||||
|
|
||||
[Collection("Sequential")] |
|
||||
public class GetByIdEndpoint : IClassFixture<TestApiApplication> |
|
||||
{ |
|
||||
JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; |
|
||||
|
|
||||
public GetByIdEndpoint(TestApiApplication 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); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,50 @@ |
|||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IdentityModel.Tokens.Jwt; |
||||
|
using System.Security.Claims; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace PublicApiIntegrationTests |
||||
|
{ |
||||
|
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,36 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc.Testing; |
||||
|
using Microsoft.eShopWeb; |
||||
|
using Microsoft.eShopWeb.ApplicationCore.Constants; |
||||
|
using Microsoft.eShopWeb.PublicApi.AuthEndpoints; |
||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
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<AuthenticateResponse>(); |
||||
|
|
||||
|
Assert.AreEqual(expectedResult, model.Result); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
using Microsoft.eShopWeb; |
||||
|
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting; |
||||
|
using System.Net; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace PublicApiIntegrationTests.CatalogItemEndpoints |
||||
|
{ |
||||
|
[TestClass] |
||||
|
public class CatalogItemGetByIdEndpointTest |
||||
|
{ |
||||
|
[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<GetByIdCatalogItemResponse>(); |
||||
|
|
||||
|
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"); |
||||
|
|
||||
|
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
using Microsoft.eShopWeb; |
||||
|
using Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; |
||||
|
using Microsoft.eShopWeb.Web.ViewModels; |
||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace PublicApiIntegrationTests.CatalogItemEndpoints |
||||
|
{ |
||||
|
[TestClass] |
||||
|
public class CatalogItemListPagedEndpoint |
||||
|
{ |
||||
|
[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<CatalogIndexViewModel>(); |
||||
|
|
||||
|
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<ListPagedCatalogItemResponse>(); |
||||
|
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<ListPagedCatalogItemResponse>(); |
||||
|
|
||||
|
var totalExpected = totalItem - (pageSize * pageIndex); |
||||
|
|
||||
|
Assert.AreEqual(totalExpected, model2.CatalogItems.Count()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
using BlazorShared.Models; |
||||
|
using Microsoft.eShopWeb; |
||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Net.Http.Headers; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace PublicApiIntegrationTests.AuthEndpoints |
||||
|
{ |
||||
|
[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() |
||||
|
{ |
||||
|
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 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<CreateCatalogItemResponse>(); |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using BlazorShared.Models; |
||||
|
using Microsoft.eShopWeb; |
||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting; |
||||
|
using System.Net; |
||||
|
using System.Net.Http.Headers; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace PublicApiIntegrationTests.CatalogItemEndpoints |
||||
|
{ |
||||
|
[TestClass] |
||||
|
public class DeleteCatalogItemEndpointTest |
||||
|
{ |
||||
|
[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<DeleteCatalogItemResponse>(); |
||||
|
|
||||
|
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"); |
||||
|
|
||||
|
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc.Testing; |
||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting; |
||||
|
using System.Net.Http; |
||||
|
|
||||
|
namespace PublicApiIntegrationTests |
||||
|
{ |
||||
|
[TestClass] |
||||
|
public class ProgramTest |
||||
|
{ |
||||
|
private static WebApplicationFactory<Program> _application; |
||||
|
|
||||
|
public static HttpClient NewClient |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return _application.CreateClient(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[AssemblyInitialize] |
||||
|
public static void AssemblyInitialize(TestContext _) |
||||
|
{ |
||||
|
_application = new WebApplicationFactory<Program>(); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<Nullable>enable</Nullable> |
||||
|
|
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="appsettings.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Include="appsettings.json"> |
||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile> |
||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> |
||||
|
</Content> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.1" /> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> |
||||
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" /> |
||||
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" /> |
||||
|
<PackageReference Include="coverlet.collector" Version="3.1.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\PublicApi\PublicApi.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Web\Web.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"UseOnlyInMemoryDatabase": true |
||||
|
} |
||||
Loading…
Reference in new issue