166 lines
6.8 KiB
C#
166 lines
6.8 KiB
C#
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.");
|
|
}
|
|
}
|