Latest
This commit is contained in:
165
Prefab.Tests/Integration/Web/Gateway/ProductDetailShould.cs
Normal file
165
Prefab.Tests/Integration/Web/Gateway/ProductDetailShould.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using Prefab.Catalog.Domain.Exceptions;
|
||||
using Prefab.Shared.Catalog.Products;
|
||||
using Prefab.Tests.Infrastructure;
|
||||
using Prefab.Web.Client.Models.Catalog;
|
||||
using Prefab.Web.Client.Models.Shared;
|
||||
using Shouldly;
|
||||
|
||||
namespace Prefab.Tests.Integration.Web.Gateway;
|
||||
|
||||
[Collection("Prefab.BffProduct")]
|
||||
[Trait(TraitName.Category, TraitCategory.Integration)]
|
||||
public sealed class ProductDetailGatewayShould(PrefabCompositeFixture_BffProduct fixture)
|
||||
: IClassFixture<PrefabCompositeFixture_BffProduct>
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReturnProductModelWhenFound()
|
||||
{
|
||||
fixture.UseProductClient(new SuccessProductClient(CreateSampleProductResponse()));
|
||||
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var client = fixture.CreateHttpClientForWeb();
|
||||
|
||||
var response = await client.GetAsync("/bff/catalog/products/sample-widget", cancellationToken);
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<Result<ProductDisplayModel>>(cancellationToken: cancellationToken);
|
||||
payload.ShouldNotBeNull();
|
||||
payload!.IsSuccess.ShouldBeTrue(payload.Problem?.Detail);
|
||||
payload.Value.ShouldNotBeNull();
|
||||
payload.Value!.Slug.ShouldBe("sample-widget");
|
||||
payload.Value.Name.ShouldBe("Sample Widget");
|
||||
payload.Value.Sku.ShouldBe("SW-001");
|
||||
payload.Value.Categories.ShouldNotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReturnNotFoundWhenMissing()
|
||||
{
|
||||
fixture.UseProductClient(new NotFoundProductClient());
|
||||
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var client = fixture.CreateHttpClientForWeb();
|
||||
|
||||
var response = await client.GetAsync("/bff/catalog/products/missing-widget", cancellationToken);
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<Result<ProductDisplayModel>>(cancellationToken: cancellationToken);
|
||||
payload.ShouldNotBeNull();
|
||||
payload!.IsSuccess.ShouldBeFalse();
|
||||
payload.Problem.ShouldNotBeNull();
|
||||
payload.Problem!.StatusCode.ShouldBe((int)HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
private static GetProductDetail.Response CreateSampleProductResponse()
|
||||
{
|
||||
var optionValues = new[]
|
||||
{
|
||||
new GetProductDetail.OptionValueDto(Guid.NewGuid(), "red", "Red", 0m, (int)GetProductDetail.PriceDeltaKind.Absolute, null),
|
||||
new GetProductDetail.OptionValueDto(Guid.NewGuid(), "blue", "Blue", 0m, (int)GetProductDetail.PriceDeltaKind.Absolute, null)
|
||||
};
|
||||
|
||||
var optionDefinitions = new[]
|
||||
{
|
||||
new GetProductDetail.OptionDefinitionDto(
|
||||
Guid.NewGuid(),
|
||||
"color",
|
||||
"Color",
|
||||
(int)GetProductDetail.OptionDataType.Choice,
|
||||
true,
|
||||
Unit: null,
|
||||
Min: null,
|
||||
Max: null,
|
||||
Step: null,
|
||||
PricePerUnit: null,
|
||||
Tiers: Array.Empty<GetProductDetail.OptionTierDto>(),
|
||||
Values: optionValues,
|
||||
Rules: null)
|
||||
};
|
||||
|
||||
var specs = new[]
|
||||
{
|
||||
new GetProductDetail.SpecDto("Weight", "25 lb", null, null)
|
||||
};
|
||||
|
||||
var attributes = new Dictionary<string, string>
|
||||
{
|
||||
["catalog.product.docs"] = """[{ "title": "Spec Sheet", "url": "https://example.com/spec.pdf" }]"""
|
||||
};
|
||||
|
||||
var categories = new[]
|
||||
{
|
||||
new GetProductDetail.CategoryDto(Guid.NewGuid(), "Lighting", "lighting")
|
||||
};
|
||||
|
||||
var product = new GetProductDetail.ProductDetailDto(
|
||||
Guid.NewGuid(),
|
||||
"Sample Widget",
|
||||
"sample-widget",
|
||||
"Sample description.",
|
||||
42.50m,
|
||||
"SW-001",
|
||||
categories,
|
||||
optionDefinitions,
|
||||
specs,
|
||||
attributes);
|
||||
|
||||
return new GetProductDetail.Response(product);
|
||||
}
|
||||
|
||||
private sealed class SuccessProductClient(GetProductDetail.Response response) : IProductClient
|
||||
{
|
||||
public Task<GetCategoryModels.Response> GetCategoryModels(
|
||||
string slug,
|
||||
int? page,
|
||||
int? pageSize,
|
||||
string? sort,
|
||||
string? direction,
|
||||
CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Category models are not required for this test.");
|
||||
|
||||
public Task<GetCategory.Response> GetCategory(string slug, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Category client is not required for this test.");
|
||||
|
||||
public Task<GetCategoryTree.Response> GetCategoryTree(int? depth, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Category tree is not required for this test.");
|
||||
|
||||
public Task<GetProductDetail.Response> GetProductDetail(string slug, CancellationToken cancellationToken) =>
|
||||
Task.FromResult(response);
|
||||
|
||||
public Task<QuotePrice.Response> Quote(QuotePrice.Request request, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Price quotes are not required for this test.");
|
||||
|
||||
public Task<QuotePrice.Response> QuotePrice(QuotePrice.Request request, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Price quotes are not required for this test.");
|
||||
}
|
||||
|
||||
private sealed class NotFoundProductClient : IProductClient
|
||||
{
|
||||
public Task<GetCategoryModels.Response> GetCategoryModels(
|
||||
string slug,
|
||||
int? page,
|
||||
int? pageSize,
|
||||
string? sort,
|
||||
string? direction,
|
||||
CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Category models are not required for this test.");
|
||||
|
||||
public Task<GetCategory.Response> GetCategory(string slug, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Category client is not required for this test.");
|
||||
|
||||
public Task<GetCategoryTree.Response> GetCategoryTree(int? depth, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Category tree is not required for this test.");
|
||||
|
||||
public Task<GetProductDetail.Response> GetProductDetail(string slug, CancellationToken cancellationToken) =>
|
||||
throw new CatalogNotFoundException("Product", slug);
|
||||
|
||||
public Task<QuotePrice.Response> Quote(QuotePrice.Request request, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Price quotes are not required for this test.");
|
||||
|
||||
public Task<QuotePrice.Response> QuotePrice(QuotePrice.Request request, CancellationToken cancellationToken) =>
|
||||
throw new NotSupportedException("Price quotes are not required for this test.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user