Files
meezi/tests/Meezi.API.Tests/BranchMenuTests.cs
T
soroush.asadi ef15fd6247 feat(api): .NET 10 multi-tenant REST API
Full backend implementation:
- Multi-tenant cafe/restaurant management (menus, orders, tables, staff)
- POS order flow with ZarinPal and Snappfood payment integration
- OTP authentication via Kavenegar SMS
- QR digital menu with public discover/finder endpoints
- Customer loyalty, coupons, CRM
- PostgreSQL via EF Core, Redis for caching/sessions
- Background jobs, webhook handlers
- Full migration history

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-27 21:33:48 +03:30

190 lines
6.3 KiB
C#

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<AppDbContext>()
.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);
}
}