using Prefab.Catalog.Domain.Entities; using Prefab.Catalog.Domain.Services; using Shouldly; namespace Prefab.Tests.Unit.Modules.Catalog.Domain.Services; [Trait(TraitName.Category, TraitCategory.Unit)] public sealed class VariantResolverShould { private readonly StubUniqueChecker _checker = new(); [Fact] public async Task ResolveVariantFromAxisSelections() { var context = await BuildModelWithVariantsAsync(); var resolver = new VariantResolver(); var selections = new[] { new OptionSelection(context.ColorOption.Id, context.RedValue.Id, null), new OptionSelection(context.DepthOption.Id, context.Depth24Value.Id, null) }; var result = resolver.Resolve(context.Model, selections); result.IsSuccess.ShouldBeTrue(); result.Variant.ShouldBe(context.Red24Variant); } [Fact] public async Task ReturnErrorWhenAxisSelectionMissing() { var context = await BuildModelWithVariantsAsync(); var resolver = new VariantResolver(); var selections = new[] { new OptionSelection(context.ColorOption.Id, context.RedValue.Id, null) }; var result = resolver.Resolve(context.Model, selections); result.IsSuccess.ShouldBeFalse(); result.Errors.ShouldContain(e => e.Contains("axis", StringComparison.OrdinalIgnoreCase)); } [Fact] public async Task ResolveBySkuWhenProvided() { var context = await BuildModelWithVariantsAsync(); var resolver = new VariantResolver(); var result = resolver.Resolve(context.Model, Array.Empty(), context.Blue34Variant.Sku); result.IsSuccess.ShouldBeTrue(); result.Variant.ShouldBe(context.Blue34Variant); } private async Task BuildModelWithVariantsAsync() { var model = await Product.CreateModel($"variant-model-{Guid.NewGuid():N}", $"variant-model-{Guid.NewGuid():N}", null, _checker); var colorOption = await OptionDefinition.CreateChoice(model, "color", "Color", _checker, isVariantAxis: true); var redValue = colorOption.AddValue("red", "Red", 0m, PriceDeltaKind.Absolute); var blueValue = colorOption.AddValue("blue", "Blue", 0m, PriceDeltaKind.Absolute); var depthOption = await OptionDefinition.CreateChoice(model, "depth", "Depth", _checker, isVariantAxis: true); var depth24Value = depthOption.AddValue("depth_24", "24\"", 0m, PriceDeltaKind.Absolute); var depth34Value = depthOption.AddValue("depth_34", "34\"", 0m, PriceDeltaKind.Absolute); var red24Variant = await Product.CreateVariant(model.Id, $"SKU-{Guid.NewGuid():N}", "Red 24", 20m, _checker); red24Variant.AxisValues.Add(new VariantAxisValue { ProductVariant = red24Variant, ProductVariantId = red24Variant.Id, OptionDefinition = colorOption, OptionDefinitionId = colorOption.Id, OptionValue = redValue, OptionValueId = redValue.Id }); red24Variant.AxisValues.Add(new VariantAxisValue { ProductVariant = red24Variant, ProductVariantId = red24Variant.Id, OptionDefinition = depthOption, OptionDefinitionId = depthOption.Id, OptionValue = depth24Value, OptionValueId = depth24Value.Id }); var blue34Variant = await Product.CreateVariant(model.Id, $"SKU-{Guid.NewGuid():N}", "Blue 34", 22m, _checker); blue34Variant.AxisValues.Add(new VariantAxisValue { ProductVariant = blue34Variant, ProductVariantId = blue34Variant.Id, OptionDefinition = colorOption, OptionDefinitionId = colorOption.Id, OptionValue = blueValue, OptionValueId = blueValue.Id }); blue34Variant.AxisValues.Add(new VariantAxisValue { ProductVariant = blue34Variant, ProductVariantId = blue34Variant.Id, OptionDefinition = depthOption, OptionDefinitionId = depthOption.Id, OptionValue = depth34Value, OptionValueId = depth34Value.Id }); model.AttachVariant(red24Variant); model.AttachVariant(blue34Variant); return new ModelContext( model, colorOption, depthOption, redValue, depth24Value, blue34Variant, red24Variant, depth34Value); } private sealed record ModelContext( Product Model, OptionDefinition ColorOption, OptionDefinition DepthOption, OptionValue RedValue, OptionValue Depth24Value, Product Blue34Variant, Product Red24Variant, OptionValue Depth34Value); private sealed class StubUniqueChecker : IUniqueChecker { public Task CategoryNameIsUnique(string name, CancellationToken cancellationToken = default) => Task.FromResult(true); public Task ProductModelNameIsUnique(string name, CancellationToken cancellationToken = default) => Task.FromResult(true); public Task ProductSlugIsUnique(string? slug, CancellationToken cancellationToken = default) => Task.FromResult(true); public Task ProductSkuIsUnique(string sku, CancellationToken cancellationToken = default) => Task.FromResult(true); public Task OptionCodeIsUniqueForProduct(Guid productId, string code, CancellationToken cancellationToken = default) => Task.FromResult(true); } }