Files
pos-system/services/fnb-engine-net/tests/FnbEngine.UnitTests/Domain/RecipeTests.cs
Ho Ngoc Hai 6061164873 feat: add multi-tenant row-level security across 5 services and 96 FnB engine unit tests
Security (P0-5):
- Implement ITenantProvider + HttpContextTenantProvider per service (order, fnb, inventory, catalog, wallet)
- Add EF Core global query filters for tenant isolation (shop_id/user_id based)
- Add TenantMiddleware setting PostgreSQL session variables for RLS
- Create PostgreSQL RLS policies script (scripts/db/rls-policies.sql)
- Adapter pattern bridges API-layer to Infrastructure-layer (Clean Architecture)
- Bypass mechanisms for admin roles, service-to-service calls, and migrations

Testing (P1-12):
- Add 96 unit tests for fnb-engine (up from 3)
- 57 domain entity tests: Table(18), KitchenTicket(12), Session(8), Reservation(13), Recipe(6)
- 39 command handler tests: CRUD operations, status transitions, validation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:40:34 +07:00

125 lines
4.0 KiB
C#

using FluentAssertions;
using FnbEngine.Domain.AggregatesModel.RecipeAggregate;
using Xunit;
namespace FnbEngine.UnitTests.Domain;
/// <summary>
/// EN: Unit tests for Recipe aggregate behavior.
/// VI: Unit tests cho hanh vi aggregate Recipe.
/// </summary>
public class RecipeTests
{
private static readonly Guid ValidShopId = Guid.NewGuid();
private static readonly Guid ValidProductId = Guid.NewGuid();
[Fact]
public void Constructor_WithValidData_ShouldCreateActiveRecipe()
{
// Act
var recipe = new Recipe(ValidShopId, ValidProductId, "Pho Bo", "Boil broth for 8 hours", 480);
// Assert
recipe.Id.Should().NotBeEmpty();
recipe.ShopId.Should().Be(ValidShopId);
recipe.ProductId.Should().Be(ValidProductId);
recipe.Name.Should().Be("Pho Bo");
recipe.Instructions.Should().Be("Boil broth for 8 hours");
recipe.PrepTimeMinutes.Should().Be(480);
recipe.IsActive.Should().BeTrue();
recipe.Ingredients.Should().BeEmpty();
recipe.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
recipe.UpdatedAt.Should().BeNull();
}
[Fact]
public void Constructor_WithNullInstructions_ShouldAllowNull()
{
var recipe = new Recipe(ValidShopId, ValidProductId, "Simple Dish", null, 10);
recipe.Instructions.Should().BeNull();
}
[Fact]
public void Update_ShouldModifyAllFields()
{
// Arrange
var recipe = new Recipe(ValidShopId, ValidProductId, "Old Name", "Old instructions", 30);
var newProductId = Guid.NewGuid();
// Act
recipe.Update(newProductId, "New Name", "New instructions", 60);
// Assert
recipe.ProductId.Should().Be(newProductId);
recipe.Name.Should().Be("New Name");
recipe.Instructions.Should().Be("New instructions");
recipe.PrepTimeMinutes.Should().Be(60);
recipe.UpdatedAt.Should().NotBeNull();
}
[Fact]
public void AddIngredient_ShouldAppendToIngredientsList()
{
// Arrange
var recipe = new Recipe(ValidShopId, ValidProductId, "Pho Bo", null, 480);
// Act
recipe.AddIngredient("Beef bones", 2.0m, "kg", 150_000m);
recipe.AddIngredient("Rice noodles", 0.5m, "kg", 30_000m);
// Assert
recipe.Ingredients.Should().HaveCount(2);
recipe.Ingredients[0].IngredientName.Should().Be("Beef bones");
recipe.Ingredients[0].Quantity.Should().Be(2.0m);
recipe.Ingredients[0].Unit.Should().Be("kg");
recipe.Ingredients[0].CostPerUnit.Should().Be(150_000m);
recipe.Ingredients[1].IngredientName.Should().Be("Rice noodles");
}
[Fact]
public void AddIngredient_WithInventoryItemId_ShouldSetCOGSFields()
{
// Arrange
var recipe = new Recipe(ValidShopId, ValidProductId, "Pho Bo", null, 480);
var inventoryItemId = Guid.NewGuid();
// Act
recipe.AddIngredient("Beef", 1.0m, "kg", 200_000m, inventoryItemId, 0.3m);
// Assert
var ingredient = recipe.Ingredients.Single();
ingredient.InventoryItemId.Should().Be(inventoryItemId);
ingredient.QuantityPerServing.Should().Be(0.3m);
}
[Fact]
public void ClearIngredients_ShouldRemoveAll()
{
// Arrange
var recipe = new Recipe(ValidShopId, ValidProductId, "Pho Bo", null, 480);
recipe.AddIngredient("Beef bones", 2.0m, "kg", 150_000m);
recipe.AddIngredient("Noodles", 0.5m, "kg", 30_000m);
// Act
recipe.ClearIngredients();
// Assert
recipe.Ingredients.Should().BeEmpty();
recipe.UpdatedAt.Should().NotBeNull();
}
[Fact]
public void Deactivate_ShouldSetIsActiveToFalse()
{
// Arrange
var recipe = new Recipe(ValidShopId, ValidProductId, "Pho Bo", null, 480);
// Act
recipe.Deactivate();
// Assert
recipe.IsActive.Should().BeFalse();
recipe.UpdatedAt.Should().NotBeNull();
}
}