diff --git a/src/backend/YesChef.Api.IntegrationTests/Builders/RecipeBuilder.cs b/src/backend/YesChef.Api.IntegrationTests/Builders/RecipeBuilder.cs index fcb8e58..6c5a12b 100644 --- a/src/backend/YesChef.Api.IntegrationTests/Builders/RecipeBuilder.cs +++ b/src/backend/YesChef.Api.IntegrationTests/Builders/RecipeBuilder.cs @@ -24,7 +24,14 @@ public sealed class RecipeBuilder public RecipeBuilder ForFamily(int familyId) { _familyId = familyId; return this; } public RecipeBuilder ForFamily(Family family) { _familyId = family.Id; return this; } - public RecipeBuilder WithIngredient(string name, int sortOrder = 0, decimal? quantity = null, int? unitOfMeasureId = null, int? familyUnitOfMeasureId = null) + public RecipeBuilder WithIngredient( + string name, + int sortOrder = 0, + decimal? quantity = null, + int? unitOfMeasureId = null, + int? familyUnitOfMeasureId = null, + int? productId = null, + int? familyProductId = null) { _ingredients.Add(new RecipeIngredient { @@ -33,6 +40,8 @@ public sealed class RecipeBuilder Quantity = quantity, UnitOfMeasureId = unitOfMeasureId, FamilyUnitOfMeasureId = familyUnitOfMeasureId, + ProductId = productId, + FamilyProductId = familyProductId, }); return this; } diff --git a/src/backend/YesChef.Api.IntegrationTests/Features/RecipeEndpointsTests.cs b/src/backend/YesChef.Api.IntegrationTests/Features/RecipeEndpointsTests.cs index 0227365..c8a946b 100644 --- a/src/backend/YesChef.Api.IntegrationTests/Features/RecipeEndpointsTests.cs +++ b/src/backend/YesChef.Api.IntegrationTests/Features/RecipeEndpointsTests.cs @@ -336,6 +336,100 @@ public class RecipeEndpointsTests : AuthenticatedIntegrationTest await Assert.That(stored.AllowedUnitCategories & UnitCategoryFlags.Volume).IsEqualTo(UnitCategoryFlags.Volume); } + [Test] + public async Task Get_returns_allowed_unit_categories_from_global_product_when_no_override() + { + var familyId = await UseDbAsync(db => + db.FamilyMemberships.Where(m => m.UserId == User.Id).Select(m => m.FamilyId).SingleAsync()); + var productId = await UseDbAsync(async db => + { + var p = new Product { Name = "Flour-base", AllowedUnitCategories = UnitCategoryFlags.Weight | UnitCategoryFlags.Volume }; + db.Products.Add(p); + await db.SaveChangesAsync(); + return p.Id; + }); + var recipe = await CreateRecipeAsync(b => b + .Titled("Bread") + .WithIngredient("flour", sortOrder: 1, quantity: 2m, productId: productId)); + + var body = await Client.GetFromJsonAsync($"/api/recipes/{recipe.Id}"); + + var ingredient = body.GetProperty("ingredients").EnumerateArray().Single(); + var cats = (UnitCategoryFlags)ingredient.GetProperty("allowedUnitCategories").GetInt32(); + await Assert.That(cats).IsEqualTo(UnitCategoryFlags.Weight | UnitCategoryFlags.Volume); + } + + [Test] + public async Task Get_returns_override_allowed_unit_categories_when_family_overrides_product() + { + var familyId = await UseDbAsync(db => + db.FamilyMemberships.Where(m => m.UserId == User.Id).Select(m => m.FamilyId).SingleAsync()); + var productId = await UseDbAsync(async db => + { + var p = new Product { Name = "Flour-ovr", AllowedUnitCategories = UnitCategoryFlags.Weight }; + db.Products.Add(p); + await db.SaveChangesAsync(); + db.Set().Add(new FamilyProductOverride + { + FamilyId = familyId, + ProductId = p.Id, + AllowedUnitCategories = UnitCategoryFlags.Volume | UnitCategoryFlags.Count, + }); + await db.SaveChangesAsync(); + return p.Id; + }); + var recipe = await CreateRecipeAsync(b => b + .Titled("Pancakes-ovr") + .WithIngredient("flour", sortOrder: 1, quantity: 1m, productId: productId)); + + var body = await Client.GetFromJsonAsync($"/api/recipes/{recipe.Id}"); + + var ingredient = body.GetProperty("ingredients").EnumerateArray().Single(); + var cats = (UnitCategoryFlags)ingredient.GetProperty("allowedUnitCategories").GetInt32(); + await Assert.That(cats).IsEqualTo(UnitCategoryFlags.Volume | UnitCategoryFlags.Count); + } + + [Test] + public async Task Get_returns_allowed_unit_categories_from_family_product() + { + var familyId = await UseDbAsync(db => + db.FamilyMemberships.Where(m => m.UserId == User.Id).Select(m => m.FamilyId).SingleAsync()); + var familyProductId = await UseDbAsync(async db => + { + var fp = new FamilyProduct + { + FamilyId = familyId, + Name = "House Flour-fp", + AllowedUnitCategories = UnitCategoryFlags.Weight | UnitCategoryFlags.Packaging, + }; + db.FamilyProducts.Add(fp); + await db.SaveChangesAsync(); + return fp.Id; + }); + var recipe = await CreateRecipeAsync(b => b + .Titled("Loaf") + .WithIngredient("house flour", sortOrder: 1, quantity: 1m, familyProductId: familyProductId)); + + var body = await Client.GetFromJsonAsync($"/api/recipes/{recipe.Id}"); + + var ingredient = body.GetProperty("ingredients").EnumerateArray().Single(); + var cats = (UnitCategoryFlags)ingredient.GetProperty("allowedUnitCategories").GetInt32(); + await Assert.That(cats).IsEqualTo(UnitCategoryFlags.Weight | UnitCategoryFlags.Packaging); + } + + [Test] + public async Task Get_returns_zero_allowed_unit_categories_for_unlinked_ingredient() + { + var recipe = await CreateRecipeAsync(b => b + .Titled("Mystery") + .WithIngredient("salt", sortOrder: 1)); + + var body = await Client.GetFromJsonAsync($"/api/recipes/{recipe.Id}"); + + var ingredient = body.GetProperty("ingredients").EnumerateArray().Single(); + await Assert.That(ingredient.GetProperty("allowedUnitCategories").GetInt32()).IsEqualTo(0); + } + [Test] public async Task Create_accumulates_distinct_categories_across_ingredients() { diff --git a/src/frontend/src/routes/recipes/[id]/+page.svelte b/src/frontend/src/routes/recipes/[id]/+page.svelte index 49a088d..6de2d5e 100644 --- a/src/frontend/src/routes/recipes/[id]/+page.svelte +++ b/src/frontend/src/routes/recipes/[id]/+page.svelte @@ -107,12 +107,6 @@ > Edit - {#if showAddToList} @@ -162,5 +156,14 @@ {/if} + +
+ +
{/if} diff --git a/src/frontend/src/routes/recipes/[id]/edit/+page.svelte b/src/frontend/src/routes/recipes/[id]/edit/+page.svelte index b8a5ebd..a6a9794 100644 --- a/src/frontend/src/routes/recipes/[id]/edit/+page.svelte +++ b/src/frontend/src/routes/recipes/[id]/edit/+page.svelte @@ -1,7 +1,7 @@