Files
pos-system/services/fnb-engine-net/tests/FnbEngine.UnitTests/Domain/KitchenTicketTests.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

170 lines
4.9 KiB
C#

using FluentAssertions;
using FnbEngine.Domain.AggregatesModel.KitchenAggregate;
using FnbEngine.Domain.Events;
using Xunit;
namespace FnbEngine.UnitTests.Domain;
/// <summary>
/// EN: Unit tests for KitchenTicket aggregate behavior.
/// VI: Unit tests cho hanh vi aggregate KitchenTicket.
/// </summary>
public class KitchenTicketTests
{
private static readonly Guid ValidSessionId = Guid.NewGuid();
private static readonly Guid ValidOrderItemId = Guid.NewGuid();
private static readonly Guid ValidProductId = Guid.NewGuid();
[Fact]
public void Constructor_WithBasicParams_ShouldCreatePendingTicket()
{
// Act
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, "Pho Bo", "Kitchen", 1);
// Assert
ticket.Id.Should().NotBeEmpty();
ticket.SessionId.Should().Be(ValidSessionId);
ticket.OrderItemId.Should().Be(ValidOrderItemId);
ticket.ProductId.Should().Be(ValidOrderItemId); // defaults to orderItemId
ticket.ItemName.Should().Be("Pho Bo");
ticket.Station.Should().Be("Kitchen");
ticket.Priority.Should().Be(1);
ticket.Quantity.Should().Be(1);
ticket.Status.Should().Be("Pending");
ticket.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
ticket.CompletedAt.Should().BeNull();
}
[Fact]
public void Constructor_WithProductIdAndQuantity_ShouldSetCorrectValues()
{
// Act
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, ValidProductId, "Bun Cha", 3, "Kitchen", 2);
// Assert
ticket.ProductId.Should().Be(ValidProductId);
ticket.Quantity.Should().Be(3);
ticket.ItemName.Should().Be("Bun Cha");
ticket.Priority.Should().Be(2);
}
[Fact]
public void Constructor_WithZeroQuantity_ShouldDefaultToOne()
{
// Act
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, ValidProductId, "Item", 0);
// Assert
ticket.Quantity.Should().Be(1);
}
[Fact]
public void Constructor_WithNegativeQuantity_ShouldDefaultToOne()
{
// Act
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, ValidProductId, "Item", -5);
// Assert
ticket.Quantity.Should().Be(1);
}
[Fact]
public void Constructor_WithNullStation_ShouldAllowNull()
{
// Act
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, "Drink");
// Assert
ticket.Station.Should().BeNull();
}
[Fact]
public void MarkAsInProgress_ShouldChangeStatusToInProgress()
{
// Arrange
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, "Pho Bo");
// Act
ticket.MarkAsInProgress();
// Assert
ticket.Status.Should().Be("InProgress");
ticket.CompletedAt.Should().BeNull();
}
[Fact]
public void MarkAsReady_ShouldChangeStatusAndSetCompletedAt()
{
// Arrange
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, "Pho Bo");
// Act
ticket.MarkAsReady();
// Assert
ticket.Status.Should().Be("Ready");
ticket.CompletedAt.Should().NotBeNull();
ticket.CompletedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
}
[Fact]
public void MarkAsServed_ShouldChangeStatusToServed()
{
// Arrange
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, "Pho Bo");
// Act
ticket.MarkAsServed();
// Assert
ticket.Status.Should().Be("Served");
}
[Fact]
public void MarkAsServed_ShouldRaiseKitchenTicketServedDomainEvent()
{
// Arrange
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, "Pho Bo");
// Act
ticket.MarkAsServed();
// Assert
ticket.DomainEvents.Should().ContainSingle();
ticket.DomainEvents.Should().ContainSingle(e => e is KitchenTicketServedDomainEvent);
var domainEvent = ticket.DomainEvents.OfType<KitchenTicketServedDomainEvent>().Single();
domainEvent.Ticket.Should().BeSameAs(ticket);
}
[Fact]
public void MarkAsServed_CalledTwice_ShouldRaiseTwoDomainEvents()
{
// Arrange
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, "Pho Bo");
// Act
ticket.MarkAsServed();
ticket.MarkAsServed();
// Assert
ticket.DomainEvents.Should().HaveCount(2);
}
[Fact]
public void StatusTransition_PendingToInProgressToReadyToServed_ShouldSucceed()
{
// Arrange
var ticket = new KitchenTicket(ValidSessionId, ValidOrderItemId, "Pho Bo");
// Act & Assert
ticket.MarkAsInProgress();
ticket.Status.Should().Be("InProgress");
ticket.MarkAsReady();
ticket.Status.Should().Be("Ready");
ticket.MarkAsServed();
ticket.Status.Should().Be("Served");
}
}