diff --git a/services/merchant-service-net/tests/MerchantService.UnitTests/Domain/ShopAggregateTests.cs b/services/merchant-service-net/tests/MerchantService.UnitTests/Domain/ShopAggregateTests.cs
new file mode 100644
index 00000000..df2a7790
--- /dev/null
+++ b/services/merchant-service-net/tests/MerchantService.UnitTests/Domain/ShopAggregateTests.cs
@@ -0,0 +1,354 @@
+using FluentAssertions;
+using MerchantService.Domain.AggregatesModel.ShopAggregate;
+using MerchantService.Domain.Events;
+using MerchantService.Domain.Exceptions;
+using Xunit;
+
+namespace MerchantService.UnitTests.Domain;
+
+///
+/// EN: Unit tests for Shop aggregate root — creation, updates, status lifecycle, and branches.
+/// VI: Unit tests cho aggregate root Shop — tạo mới, cập nhật, vòng đời trạng thái, và chi nhánh.
+///
+public class ShopAggregateTests
+{
+ // ──────── HELPERS ────────
+
+ private static Shop CreateValidShop(
+ ShopType? type = null,
+ BusinessCategory? category = null)
+ {
+ return new Shop(
+ Guid.NewGuid(),
+ "Test Coffee Shop",
+ "test-coffee-shop",
+ type ?? ShopType.Hybrid,
+ category ?? BusinessCategory.Cafe);
+ }
+
+ private static Address CreateTestAddress() => new()
+ {
+ Street = "123 Nguyễn Huệ",
+ District = "Quận 1",
+ City = "Hồ Chí Minh",
+ PostalCode = "700000",
+ CountryCode = "VN"
+ };
+
+ // ──────── CREATION TESTS ────────
+
+ [Fact]
+ public void CreateShop_WithValidData_ShouldStartInDraftStatus()
+ {
+ // Act
+ var shop = CreateValidShop();
+
+ // Assert
+ shop.Id.Should().NotBe(Guid.Empty);
+ shop.Name.Should().Be("Test Coffee Shop");
+ shop.Slug.Should().Be("test-coffee-shop");
+ shop.Status.Should().Be(ShopStatus.Draft);
+ shop.IsDeleted.Should().BeFalse();
+ shop.Branches.Should().BeEmpty();
+ }
+
+ [Fact]
+ public void CreateShop_ShouldRaiseShopCreatedDomainEvent()
+ {
+ // Act
+ var shop = CreateValidShop();
+
+ // Assert
+ shop.DomainEvents.Should().ContainSingle()
+ .Which.Should().BeOfType();
+ }
+
+ [Fact]
+ public void CreateShop_WithCafeCategory_ShouldAssignFnBFeatures()
+ {
+ // Act
+ var shop = CreateValidShop(category: BusinessCategory.Cafe);
+
+ // Assert
+ shop.Features.HasInventory.Should().BeTrue();
+ shop.Features.HasTables.Should().BeTrue();
+ shop.Features.HasKitchen.Should().BeTrue();
+ shop.Features.HasDelivery.Should().BeTrue();
+ shop.Features.HasBooking.Should().BeFalse();
+ }
+
+ [Fact]
+ public void CreateShop_WithEmptyMerchantId_ShouldThrowDomainException()
+ {
+ // Act
+ var act = () => new Shop(Guid.Empty, "Shop", "shop", ShopType.Hybrid, BusinessCategory.FoodBeverage);
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Merchant ID cannot be empty");
+ }
+
+ [Fact]
+ public void CreateShop_WithEmptyName_ShouldThrowDomainException()
+ {
+ // Act
+ var act = () => new Shop(Guid.NewGuid(), "", "shop", ShopType.Hybrid, BusinessCategory.FoodBeverage);
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Shop name cannot be empty");
+ }
+
+ [Fact]
+ public void CreateShop_WithInvalidSlug_ShouldThrowDomainException()
+ {
+ // Act
+ var act = () => new Shop(
+ Guid.NewGuid(), "Shop", "INVALID SLUG!", ShopType.Hybrid, BusinessCategory.FoodBeverage);
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Slug must contain only lowercase letters, numbers, and hyphens");
+ }
+
+ // ──────── UPDATE TESTS ────────
+
+ [Fact]
+ public void UpdateInfo_ShouldUpdateNameAndDescription()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+
+ // Act
+ shop.UpdateInfo("New Name", "New desc");
+
+ // Assert
+ shop.Name.Should().Be("New Name");
+ shop.Description.Should().Be("New desc");
+ shop.UpdatedAt.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void UpdateSlug_WithValidSlug_ShouldUpdateSuccessfully()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+
+ // Act
+ shop.UpdateSlug("new-slug-123");
+
+ // Assert
+ shop.Slug.Should().Be("new-slug-123");
+ }
+
+ [Fact]
+ public void UpdateSlug_WithInvalidSlug_ShouldThrowDomainException()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+
+ // Act
+ var act = () => shop.UpdateSlug("Invalid Slug!");
+
+ // Assert
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void UpdateFeatures_ShouldOverrideFeatures()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+ var newFeatures = new ShopFeatures { HasBooking = true, HasShipping = true };
+
+ // Act
+ shop.UpdateFeatures(newFeatures);
+
+ // Assert
+ shop.Features.HasBooking.Should().BeTrue();
+ shop.Features.HasShipping.Should().BeTrue();
+ shop.Features.HasTables.Should().BeFalse(); // Overridden from Cafe defaults
+ }
+
+ [Fact]
+ public void UpdateImages_ShouldSetLogoAndCover()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+
+ // Act
+ shop.UpdateImages("https://cdn/logo.png", "https://cdn/cover.png");
+
+ // Assert
+ shop.LogoUrl.Should().Be("https://cdn/logo.png");
+ shop.CoverImageUrl.Should().Be("https://cdn/cover.png");
+ }
+
+ // ──────── STATUS LIFECYCLE TESTS ────────
+
+ [Fact]
+ public void Publish_DraftShop_ShouldSetStatusToActive()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+
+ // Act
+ shop.Publish();
+
+ // Assert
+ shop.Status.Should().Be(ShopStatus.Active);
+ shop.DomainEvents.Should().Contain(e => e is ShopPublishedDomainEvent);
+ }
+
+ [Fact]
+ public void Publish_ActiveShop_ShouldThrowDomainException()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+ shop.Publish(); // Already active
+
+ // Act
+ var act = () => shop.Publish();
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Cannot publish shop with status Active");
+ }
+
+ [Fact]
+ public void SetInactive_ActiveShop_ShouldSetStatusInactive()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+ shop.Publish();
+
+ // Act
+ shop.SetInactive();
+
+ // Assert
+ shop.Status.Should().Be(ShopStatus.Inactive);
+ }
+
+ [Fact]
+ public void SetInactive_ClosedShop_ShouldThrowDomainException()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+ shop.Close();
+
+ // Act
+ var act = () => shop.SetInactive();
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Cannot change status of closed shop");
+ }
+
+ [Fact]
+ public void Close_ShouldSetStatusClosed()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+ shop.Publish();
+
+ // Act
+ shop.Close();
+
+ // Assert
+ shop.Status.Should().Be(ShopStatus.Closed);
+ shop.DomainEvents.Should().Contain(e => e is ShopClosedDomainEvent);
+ }
+
+ // ──────── BRANCH TESTS ────────
+
+ [Fact]
+ public void AddBranch_HybridShop_ShouldAddSuccessfully()
+ {
+ // Arrange
+ var shop = CreateValidShop(type: ShopType.Hybrid);
+ var address = CreateTestAddress();
+
+ // Act
+ var branch = shop.AddBranch("Chi nhánh Quận 1", address);
+
+ // Assert
+ shop.Branches.Should().HaveCount(1);
+ branch.Name.Should().Be("Chi nhánh Quận 1");
+ branch.IsActive.Should().BeTrue();
+ shop.DomainEvents.Should().Contain(e => e is ShopBranchAddedDomainEvent);
+ }
+
+ [Fact]
+ public void AddBranch_OnlineOnlyShop_ShouldThrowDomainException()
+ {
+ // Arrange
+ var shop = CreateValidShop(type: ShopType.OnlineOnly);
+ var address = CreateTestAddress();
+
+ // Act
+ var act = () => shop.AddBranch("Branch 1", address);
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Online-only shops cannot have physical branches");
+ }
+
+ [Fact]
+ public void RemoveBranch_ExistingBranch_ShouldRemoveSuccessfully()
+ {
+ // Arrange
+ var shop = CreateValidShop(type: ShopType.PhysicalOnly);
+ var branch = shop.AddBranch("Branch 1", CreateTestAddress());
+ var branchId = branch.Id;
+
+ // Act
+ shop.RemoveBranch(branchId);
+
+ // Assert
+ shop.Branches.Should().BeEmpty();
+ }
+
+ [Fact]
+ public void RemoveBranch_NonExistingBranch_ShouldThrowDomainException()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+
+ // Act
+ var act = () => shop.RemoveBranch(Guid.NewGuid());
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Branch not found");
+ }
+
+ // ──────── SOFT DELETE TESTS ────────
+
+ [Fact]
+ public void Delete_ShouldSoftDeleteShop()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+
+ // Act
+ shop.Delete();
+
+ // Assert
+ shop.IsDeleted.Should().BeTrue();
+ }
+
+ [Fact]
+ public void Delete_AlreadyDeletedShop_ShouldThrowDomainException()
+ {
+ // Arrange
+ var shop = CreateValidShop();
+ shop.Delete();
+
+ // Act
+ var act = () => shop.Delete();
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Shop is already deleted");
+ }
+}
diff --git a/services/merchant-service-net/tests/MerchantService.UnitTests/Domain/ShopFeaturesTests.cs b/services/merchant-service-net/tests/MerchantService.UnitTests/Domain/ShopFeaturesTests.cs
new file mode 100644
index 00000000..d49ccc2f
--- /dev/null
+++ b/services/merchant-service-net/tests/MerchantService.UnitTests/Domain/ShopFeaturesTests.cs
@@ -0,0 +1,148 @@
+using FluentAssertions;
+using MerchantService.Domain.AggregatesModel.ShopAggregate;
+using MerchantService.Domain.SeedWork;
+using Xunit;
+
+namespace MerchantService.UnitTests.Domain;
+
+///
+/// EN: Unit tests for ShopFeatures value object — validates ForCategory() mapping for all verticals.
+/// VI: Unit tests cho value object ShopFeatures — kiểm tra mapping ForCategory() cho tất cả ngành hàng.
+///
+public class ShopFeaturesTests
+{
+ // ──────── HELPER ────────
+
+ private static BusinessCategory Cat(int id) => Enumeration.FromValue(id);
+
+ // ──────── F&B VERTICALS ────────
+
+ [Theory]
+ [InlineData(1)] // FoodBeverage
+ [InlineData(11)] // Cafe
+ [InlineData(12)] // Restaurant
+ public void ForCategory_FnBVerticals_ShouldHaveTablesKitchenInventoryDelivery(int categoryId)
+ {
+ // Act
+ var features = ShopFeatures.ForCategory(Cat(categoryId));
+
+ // Assert
+ features.HasInventory.Should().BeTrue($"Category {categoryId} should have Inventory");
+ features.HasTables.Should().BeTrue($"Category {categoryId} should have Tables");
+ features.HasKitchen.Should().BeTrue($"Category {categoryId} should have Kitchen");
+ features.HasDelivery.Should().BeTrue($"Category {categoryId} should have Delivery");
+ features.HasBooking.Should().BeFalse($"Category {categoryId} should not have Booking");
+ features.HasShipping.Should().BeFalse($"Category {categoryId} should not have Shipping");
+ }
+
+ // ──────── RETAIL VERTICALS ────────
+
+ [Theory]
+ [InlineData(2)] // Fashion
+ [InlineData(3)] // Electronics
+ [InlineData(9)] // Grocery
+ [InlineData(10)] // HomeFurniture
+ public void ForCategory_RetailVerticals_ShouldHaveInventoryAndShipping(int categoryId)
+ {
+ // Act
+ var features = ShopFeatures.ForCategory(Cat(categoryId));
+
+ // Assert
+ features.HasInventory.Should().BeTrue();
+ features.HasShipping.Should().BeTrue();
+ features.HasTables.Should().BeFalse();
+ features.HasKitchen.Should().BeFalse();
+ features.HasBooking.Should().BeFalse();
+ }
+
+ // ──────── SERVICE VERTICALS ────────
+
+ [Theory]
+ [InlineData(4)] // Healthcare
+ [InlineData(6)] // Education
+ [InlineData(8)] // Services
+ public void ForCategory_ServiceVerticals_ShouldHaveBookingOnly(int categoryId)
+ {
+ // Act
+ var features = ShopFeatures.ForCategory(Cat(categoryId));
+
+ // Assert
+ features.HasBooking.Should().BeTrue();
+ features.HasInventory.Should().BeFalse();
+ features.HasTables.Should().BeFalse();
+ }
+
+ // ──────── BEAUTY (SPECIAL CASE) ────────
+
+ [Fact]
+ public void ForCategory_Beauty_ShouldHaveBookingAndInventory()
+ {
+ // Act
+ var features = ShopFeatures.ForCategory(BusinessCategory.Beauty);
+
+ // Assert
+ features.HasBooking.Should().BeTrue();
+ features.HasInventory.Should().BeTrue("Beauty clinics sell products");
+ features.HasTables.Should().BeFalse();
+ features.HasKitchen.Should().BeFalse();
+ }
+
+ // ──────── ENTERTAINMENT VERTICALS ────────
+
+ [Theory]
+ [InlineData(7)] // Entertainment
+ [InlineData(13)] // Karaoke
+ public void ForCategory_EntertainmentVerticals_ShouldHaveBookingAndInventory(int categoryId)
+ {
+ // Act
+ var features = ShopFeatures.ForCategory(Cat(categoryId));
+
+ // Assert
+ features.HasBooking.Should().BeTrue();
+ features.HasInventory.Should().BeTrue();
+ }
+
+ // ──────── SPA ────────
+
+ [Fact]
+ public void ForCategory_Spa_ShouldHaveBookingOnly()
+ {
+ // Act
+ var features = ShopFeatures.ForCategory(BusinessCategory.Spa);
+
+ // Assert
+ features.HasBooking.Should().BeTrue();
+ features.HasInventory.Should().BeFalse("Spa focuses on services, not products");
+ features.HasTables.Should().BeFalse();
+ }
+
+ // ──────── DEFAULT (OTHER) ────────
+
+ [Fact]
+ public void ForCategory_OtherCategory_ShouldHaveInventoryAndBooking()
+ {
+ // Act
+ var features = ShopFeatures.ForCategory(BusinessCategory.Other);
+
+ // Assert
+ features.HasInventory.Should().BeTrue();
+ features.HasBooking.Should().BeTrue();
+ }
+
+ // ──────── VALUE OBJECT TESTS ────────
+
+ [Fact]
+ public void Empty_ShouldReturnAllFlagsDisabled()
+ {
+ // Act
+ var features = ShopFeatures.Empty;
+
+ // Assert
+ features.HasInventory.Should().BeFalse();
+ features.HasBooking.Should().BeFalse();
+ features.HasTables.Should().BeFalse();
+ features.HasKitchen.Should().BeFalse();
+ features.HasShipping.Should().BeFalse();
+ features.HasDelivery.Should().BeFalse();
+ }
+}