using FluentValidation; using FluentValidation.Results; using Moq; using Prefab.Shared.Catalog; using Prefab.Shared.Catalog.Products; using Prefab.Web.Gateway.Shared; using Shouldly; namespace Prefab.Tests.Unit.Web.Gateway; [Trait(TraitName.Category, TraitCategory.Unit)] public sealed class NavMenuServiceShould { [Fact] public async Task ReturnOnlyFeaturedChildrenWhenAvailable() { var moduleClient = new Mock(); var productClient = new Mock(); moduleClient.SetupGet(c => c.Product).Returns(productClient.Object); var featuredChild = CreateNode( name: "Featured", slug: "featured", displayOrder: 2, isFeatured: true, isLeaf: true); var secondaryChild = CreateNode( name: "Secondary", slug: "secondary", displayOrder: 1, isFeatured: false, isLeaf: true); var root = CreateNode( name: "Root", slug: "root", displayOrder: 0, isFeatured: false, isLeaf: false, children: new[] { secondaryChild, featuredChild }); productClient .Setup(p => p.GetCategoryTree(It.IsAny(), It.IsAny())) .ReturnsAsync(new GetCategoryTree.Response(new[] { root })); var sut = new NavMenu.Service(moduleClient.Object); var result = await sut.Get(2, CancellationToken.None); result.IsSuccess.ShouldBeTrue(); var model = result.Value.ShouldNotBeNull(); model.Depth.ShouldBe(2); model.Columns.Count.ShouldBe(1); var column = model.Columns.Single(); column.Root.Name.ShouldBe("Root"); column.Root.Url.ShouldBe("/catalog/category?category-slug=root"); column.Items.Count.ShouldBe(1); var item = column.Items.Single(); item.Name.ShouldBe("Featured"); item.Url.ShouldBe("/catalog/products?category-slug=featured"); column.HasMore.ShouldBeFalse(); column.SeeAllUrl.ShouldBe("/catalog/category?category-slug=root"); } [Fact] public async Task FallBackToAllChildrenWhenNoFeatured() { var moduleClient = new Mock(); var productClient = new Mock(); moduleClient.SetupGet(c => c.Product).Returns(productClient.Object); var firstChild = CreateNode( name: "Alpha", slug: "alpha", displayOrder: 3, isFeatured: false, isLeaf: true); var secondChild = CreateNode( name: "Beta", slug: "beta", displayOrder: 1, isFeatured: false, isLeaf: false); var thirdChild = CreateNode( name: "Gamma", slug: "gamma", displayOrder: 2, isFeatured: false, isLeaf: true); var root = CreateNode( name: "Root", slug: "root", displayOrder: 0, isFeatured: false, isLeaf: false, children: new[] { firstChild, secondChild, thirdChild }); productClient .Setup(p => p.GetCategoryTree(It.IsAny(), It.IsAny())) .ReturnsAsync(new GetCategoryTree.Response(new[] { root })); var sut = new NavMenu.Service(moduleClient.Object); var result = await sut.Get(1, CancellationToken.None); result.IsSuccess.ShouldBeTrue(); var model = result.Value.ShouldNotBeNull(); model.Depth.ShouldBe(1); var names = model.Columns.Single().Items.Select(i => i.Name).ToList(); names.ShouldBe(["Beta", "Gamma", "Alpha"]); } [Fact] public async Task ClampDepthBeforeCallingCatalog() { var moduleClient = new Mock(); var productClient = new Mock(); moduleClient.SetupGet(c => c.Product).Returns(productClient.Object); var root = CreateNode("Root", "root", 0, false, false); productClient .Setup(p => p.GetCategoryTree(2, It.IsAny())) .ReturnsAsync(new GetCategoryTree.Response(new[] { root })); var sut = new NavMenu.Service(moduleClient.Object); await sut.Get(10, CancellationToken.None); productClient.Verify(p => p.GetCategoryTree(2, It.IsAny()), Times.Once); } [Fact] public async Task ReturnProblemWhenCatalogValidationFails() { var moduleClient = new Mock(); var productClient = new Mock(); moduleClient.SetupGet(c => c.Product).Returns(productClient.Object); productClient .Setup(p => p.GetCategoryTree(It.IsAny(), It.IsAny())) .ThrowsAsync(new ValidationException([new ValidationFailure("Depth", "invalid")])); var sut = new NavMenu.Service(moduleClient.Object); var result = await sut.Get(-1, CancellationToken.None); result.IsSuccess.ShouldBeFalse(); result.Value.ShouldBeNull(); var problem = result.Problem.ShouldNotBeNull(); problem.StatusCode.ShouldBe(400); problem.Detail.ShouldBe("Request did not satisfy validation rules."); problem.Errors.ShouldNotBeNull(); problem.Errors!.ShouldContainKey("Depth"); problem.Errors["Depth"].ShouldContain("invalid"); } private static GetCategoryModels.CategoryNodeDto CreateNode( string name, string slug, int displayOrder, bool isFeatured, bool isLeaf, IReadOnlyList? children = null) { return new GetCategoryModels.CategoryNodeDto( Guid.NewGuid(), name, slug, null, null, null, displayOrder, isFeatured, isLeaf, children ?? []); } }