using Meezi.API.Models.Menu; using Meezi.API.Services; using Meezi.Core.Entities; using Meezi.Core.Enums; using Meezi.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Xunit; namespace Meezi.API.Tests; public class BranchMenuTests { private static (AppDbContext Db, BranchMenuService Menu, string CafeId, string BranchId, string ItemA, string ItemB) CreateFixture(PlanTier tier = PlanTier.Pro) { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var db = new AppDbContext(options); var cafeId = "cafe-1"; var branchId = "branch-1"; var catId = "cat-1"; var itemA = "item-a"; var itemB = "item-b"; db.Cafes.Add(new Cafe { Id = cafeId, Name = "Test", Slug = "test", PlanTier = tier }); db.Branches.Add(new Branch { Id = branchId, CafeId = cafeId, Name = "Main", IsActive = true, UpdatedAt = DateTime.UtcNow }); db.MenuCategories.Add(new MenuCategory { Id = catId, CafeId = cafeId, Name = "Drinks", NameEn = "Drinks", SortOrder = 0, IsActive = true }); db.MenuItems.AddRange( new MenuItem { Id = itemA, CafeId = cafeId, CategoryId = catId, Name = "Espresso", Price = 100_000m, IsAvailable = true }, new MenuItem { Id = itemB, CafeId = cafeId, CategoryId = catId, Name = "Latte", Price = 150_000m, IsAvailable = true }); db.SaveChanges(); return (db, new BranchMenuService(db), cafeId, branchId, itemA, itemB); } [Fact] public async Task GetBranchMenu_ExcludesUnavailableItems() { var (_, menu, cafeId, branchId, itemA, itemB) = CreateFixture(); await menu.UpsertOverrideAsync( cafeId, branchId, itemB, new UpsertBranchMenuOverrideRequest(false, null), PlanTier.Pro, EmployeeRole.Manager, "user-1"); var result = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: false); Assert.NotNull(result); Assert.Single(result!); Assert.Equal(itemA, result![0].Id); } [Fact] public async Task GetBranchMenu_AppliesPriceOverride_WhenSet() { var (_, menu, cafeId, branchId, itemA, _) = CreateFixture(); await menu.UpsertOverrideAsync( cafeId, branchId, itemA, new UpsertBranchMenuOverrideRequest(true, 88_000m), PlanTier.Pro, EmployeeRole.Manager, "user-1"); var result = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: false); Assert.NotNull(result); var row = Assert.Single(result!, r => r.Id == itemA); Assert.Equal(100_000m, row.MasterPrice); Assert.Equal(88_000m, row.EffectivePrice); Assert.True(row.HasPriceOverride); } [Fact] public async Task GetBranchMenu_UsesMasterPrice_WhenNoOverride() { var (_, menu, cafeId, branchId, itemA, _) = CreateFixture(); var result = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: false); var row = Assert.Single(result!, r => r.Id == itemA); Assert.Equal(100_000m, row.MasterPrice); Assert.Equal(100_000m, row.EffectivePrice); Assert.False(row.IsOverridden); } [Fact] public async Task UpsertOverride_FreePlan_PriceOverride_ReturnsPlanLimitReached() { var (_, menu, cafeId, branchId, itemA, _) = CreateFixture(PlanTier.Free); var result = await menu.UpsertOverrideAsync( cafeId, branchId, itemA, new UpsertBranchMenuOverrideRequest(true, 90_000m), PlanTier.Free, EmployeeRole.Manager, "user-1"); Assert.False(result.Success); Assert.Equal("PLAN_LIMIT_REACHED", result.ErrorCode); } [Fact] public async Task UpsertOverride_ProPlan_PriceOverride_Succeeds() { var (_, menu, cafeId, branchId, itemA, _) = CreateFixture(PlanTier.Pro); var result = await menu.UpsertOverrideAsync( cafeId, branchId, itemA, new UpsertBranchMenuOverrideRequest(true, 90_000m), PlanTier.Pro, EmployeeRole.Manager, "user-1"); Assert.True(result.Success); Assert.Equal(90_000m, result.Data!.PriceOverride); } [Fact] public async Task DeleteOverride_ResetsToMasterPrice() { var (_, menu, cafeId, branchId, itemA, _) = CreateFixture(); await menu.UpsertOverrideAsync( cafeId, branchId, itemA, new UpsertBranchMenuOverrideRequest(true, 90_000m), PlanTier.Pro, EmployeeRole.Owner, "owner-1"); var deleted = await menu.DeleteOverrideAsync(cafeId, branchId, itemA); Assert.True(deleted); var menuRows = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: false); var row = Assert.Single(menuRows!, r => r.Id == itemA); Assert.Equal(100_000m, row.EffectivePrice); Assert.False(row.IsOverridden); } [Fact] public async Task Override_UniqueConstraint_UpsertNotDuplicate() { var (db, menu, cafeId, branchId, itemA, _) = CreateFixture(); await menu.UpsertOverrideAsync( cafeId, branchId, itemA, new UpsertBranchMenuOverrideRequest(true, 80_000m), PlanTier.Pro, EmployeeRole.Manager, "user-1"); await menu.UpsertOverrideAsync( cafeId, branchId, itemA, new UpsertBranchMenuOverrideRequest(false, 85_000m), PlanTier.Pro, EmployeeRole.Manager, "user-1"); var count = await db.BranchMenuItemOverrides .CountAsync(o => o.BranchId == branchId && o.MenuItemId == itemA); Assert.Equal(1, count); var rows = await menu.GetBranchMenuAsync(cafeId, branchId, includeUnavailable: true); Assert.Equal(2, rows!.Count); var hidden = Assert.Single(rows, r => r.Id == itemA); Assert.False(hidden.IsAvailable); Assert.Equal(85_000m, hidden.EffectivePrice); } }