Init
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Prefab.Tests.Infrastructure;
|
||||
using Shouldly;
|
||||
|
||||
namespace Prefab.Tests.Integration.Modules.Catalog.App.Categories;
|
||||
|
||||
[Trait(TraitName.Category, TraitCategory.Integration)]
|
||||
public sealed class CategoriesCreateShould(PrefabCompositeFixture_Ephemeral fixture)
|
||||
: IClassFixture<PrefabCompositeFixture_Ephemeral>
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReturnDomainProblemDetailsWhenNameAlreadyExists()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var client = fixture.CreateHttpClientForWeb();
|
||||
client.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-Proto", "https");
|
||||
|
||||
var suffix = Guid.NewGuid().ToString("N")[..6];
|
||||
var name = $"Duplicate Category {suffix}";
|
||||
var description = $"Duplicate description {suffix}";
|
||||
|
||||
var payload = new { name, description };
|
||||
|
||||
var firstResponse = await client.PostAsJsonAsync("/api/catalog/categories", payload, cancellationToken);
|
||||
firstResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var secondResponse = await client.PostAsJsonAsync("/api/catalog/categories", payload, cancellationToken);
|
||||
|
||||
var responseBody = await secondResponse.Content.ReadAsStringAsync(cancellationToken);
|
||||
secondResponse.StatusCode.ShouldBe(HttpStatusCode.BadRequest, responseBody);
|
||||
|
||||
var problem = JsonSerializer.Deserialize<ProblemDetails?>(responseBody, new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
||||
problem.ShouldNotBeNull();
|
||||
problem!.Type.ShouldBe("https://prefab.dev/problems/domain-error");
|
||||
problem.Title.ShouldBe("Request could not be processed.");
|
||||
problem.Detail.ShouldNotBeNull();
|
||||
problem.Detail!.ShouldContain(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Prefab.Tests.Infrastructure;
|
||||
using Shouldly;
|
||||
|
||||
namespace Prefab.Tests.Integration.Modules.Catalog.App.Products;
|
||||
|
||||
[Trait(TraitName.Category, TraitCategory.Integration)]
|
||||
public sealed class CatalogProductDetailShould(PrefabCompositeFixture_Ephemeral fixture)
|
||||
: IClassFixture<PrefabCompositeFixture_Ephemeral>
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReturnNotFoundProblemDetailsForUnknownSlug()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var client = fixture.CreateHttpClientForWeb();
|
||||
client.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-Proto", "https");
|
||||
|
||||
var response = await client.GetAsync("/api/catalog/products/does-not-exist", cancellationToken);
|
||||
|
||||
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
|
||||
|
||||
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails?>(cancellationToken: cancellationToken);
|
||||
problem.ShouldNotBeNull();
|
||||
problem!.Title.ShouldBe("Resource not found.");
|
||||
problem.Type.ShouldBe("https://prefab.dev/problems/not-found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using Prefab.Catalog.Domain.Entities;
|
||||
using Prefab.Catalog.Domain.Services;
|
||||
using Prefab.Shared.Catalog.Products;
|
||||
using Prefab.Tests.Infrastructure;
|
||||
using Shouldly;
|
||||
|
||||
namespace Prefab.Tests.Integration.Modules.Catalog.App.Products;
|
||||
|
||||
[Trait(TraitName.Category, TraitCategory.Integration)]
|
||||
public sealed class CategoryModelsOverHttpShould(PrefabCompositeFixture_Ephemeral fixture)
|
||||
: IClassFixture<PrefabCompositeFixture_Ephemeral>
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReturnFromPriceForVariantsAndStandaloneModel()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var suffix = Guid.NewGuid().ToString("N")[..8];
|
||||
var categoryName = $"Browse Category {suffix}";
|
||||
var categoryDescription = $"Category description {suffix}";
|
||||
var categorySlug = Slugify(categoryName);
|
||||
|
||||
await fixture.SeedAsync(async (db, token) =>
|
||||
{
|
||||
var checker = new SeedUniqueChecker();
|
||||
|
||||
var category = await Category.Create(categoryName, categoryDescription, checker, token);
|
||||
db.Categories.Add(category);
|
||||
|
||||
var modelWithVariants = await Product.CreateModel(
|
||||
$"Model With Variants {suffix}",
|
||||
$"model-with-variants-{suffix}",
|
||||
"Variants model",
|
||||
checker,
|
||||
token);
|
||||
modelWithVariants.SetBasePrice(15.75m);
|
||||
|
||||
var variantSkuPrefix = $"SKU-{suffix}";
|
||||
var variantA = await Product.CreateVariant(modelWithVariants.Id, $"{variantSkuPrefix}-A", $"Variant A {suffix}", 12.99m, checker, token);
|
||||
var variantB = await Product.CreateVariant(modelWithVariants.Id, $"{variantSkuPrefix}-B", $"Variant B {suffix}", 14.49m, checker, token);
|
||||
|
||||
modelWithVariants.AttachVariant(variantA);
|
||||
modelWithVariants.AttachVariant(variantB);
|
||||
modelWithVariants.AssignToCategory(category.Id, true);
|
||||
|
||||
var modelWithoutVariants = await Product.CreateModel(
|
||||
$"Standalone Model {suffix}",
|
||||
$"standalone-model-{suffix}",
|
||||
"Standalone model",
|
||||
checker,
|
||||
token);
|
||||
modelWithoutVariants.SetBasePrice(9.75m);
|
||||
modelWithoutVariants.AssignToCategory(category.Id, true);
|
||||
|
||||
db.Products.AddRange(modelWithVariants, variantA, variantB, modelWithoutVariants);
|
||||
|
||||
await db.SaveChangesAsync(token);
|
||||
}, cancellationToken);
|
||||
|
||||
var client = fixture.CreateHttpClientForWeb();
|
||||
var httpResponse = await client.GetAsync($"/api/catalog/categories/{categorySlug}/models", cancellationToken);
|
||||
httpResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var payload = await httpResponse.Content.ReadFromJsonAsync<GetCategoryModels.Response>(cancellationToken: cancellationToken);
|
||||
payload.ShouldNotBeNull();
|
||||
payload.Result.ShouldNotBeNull();
|
||||
payload.Result.Products.ShouldNotBeNull();
|
||||
|
||||
var variantCard = payload.Result.Products.Single(card => card.Slug == $"model-with-variants-{suffix}");
|
||||
variantCard.FromPrice.ShouldBe(15.75m);
|
||||
|
||||
var standaloneCard = payload.Result.Products.Single(card => card.Slug == $"standalone-model-{suffix}");
|
||||
standaloneCard.FromPrice.ShouldBe(9.75m);
|
||||
}
|
||||
|
||||
private static string Slugify(string value)
|
||||
{
|
||||
var normalized = Regex.Replace(value.Trim().ToLowerInvariant(), "[^a-z0-9]+", "-");
|
||||
return normalized.Trim('-');
|
||||
}
|
||||
|
||||
private sealed class SeedUniqueChecker : IUniqueChecker
|
||||
{
|
||||
public Task<bool> CategoryNameIsUnique(string name, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(true);
|
||||
|
||||
public Task<bool> ProductModelNameIsUnique(string name, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(true);
|
||||
|
||||
public Task<bool> ProductSlugIsUnique(string? slug, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(true);
|
||||
|
||||
public Task<bool> ProductSkuIsUnique(string sku, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(true);
|
||||
|
||||
public Task<bool> OptionCodeIsUniqueForProduct(Guid productId, string code, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user