Files
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

144 lines
5.2 KiB
C#

using Meezi.API.Models.Orders;
using Meezi.API.Models.Tables;
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 BranchTableTests
{
private sealed class NullKdsNotifier : IKdsNotifier
{
public Task NotifyOrderCreatedAsync(string cafeId, LiveOrderDto order, CancellationToken cancellationToken = default) =>
Task.CompletedTask;
public Task NotifyOrderStatusChangedAsync(string cafeId, string orderId, OrderStatus status, CancellationToken cancellationToken = default) =>
Task.CompletedTask;
public Task NotifyTableStatusChangedAsync(string cafeId, CancellationToken cancellationToken = default) =>
Task.CompletedTask;
}
private static (AppDbContext Db, TableService Tables, string CafeId, string BranchA, string BranchB, string ManagerId)
CreateFixture()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var db = new AppDbContext(options);
var config = new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build();
var tables = new TableService(db, config, new NullKdsNotifier(), new BranchIdentityService(db));
var cafeId = "cafe-1";
var branchA = "branch-a";
var branchB = "branch-b";
var managerId = "mgr-1";
db.Cafes.Add(new Cafe { Id = cafeId, Name = "Test", Slug = "test", PlanTier = PlanTier.Pro });
db.Branches.AddRange(
new Branch { Id = branchA, CafeId = cafeId, Name = "A", IsActive = true, UpdatedAt = DateTime.UtcNow },
new Branch { Id = branchB, CafeId = cafeId, Name = "B", IsActive = true, UpdatedAt = DateTime.UtcNow });
db.Employees.Add(new Employee
{
Id = managerId,
CafeId = cafeId,
BranchId = branchA,
Name = "Manager",
Phone = "09121111111",
Role = EmployeeRole.Manager
});
db.Tables.AddRange(
new Table { Id = "t-a", CafeId = cafeId, BranchId = branchA, Number = "1", QrCode = "qr-a" },
new Table { Id = "t-b", CafeId = cafeId, BranchId = branchB, Number = "2", QrCode = "qr-b" });
db.SaveChanges();
return (db, tables, cafeId, branchA, branchB, managerId);
}
[Fact]
public async Task GetTables_ReturnsBranchTablesOnly()
{
var (_, tables, cafeId, branchA, _, _) = CreateFixture();
var result = await tables.GetBranchTablesAsync(cafeId, branchA);
Assert.NotNull(result);
Assert.Single(result);
Assert.Equal("t-a", result[0].Id);
}
[Fact]
public async Task GetTables_DoesNotReturnOtherBranchTables()
{
var (_, tables, cafeId, branchA, _, _) = CreateFixture();
var result = await tables.GetBranchTablesAsync(cafeId, branchA);
Assert.NotNull(result);
Assert.DoesNotContain(result, t => t.Id == "t-b");
}
[Fact]
public async Task CreateTable_AssignsToBranch()
{
var (db, tables, cafeId, branchA, _, _) = CreateFixture();
var created = await tables.CreateBranchTableAsync(
cafeId,
branchA,
new CreateBranchTableRequest("9", 6));
Assert.True(created.Success);
Assert.Equal(branchA, created.Data!.BranchId);
Assert.Equal(1, await db.Tables.CountAsync(t => t.BranchId == branchA && t.Number == "9"));
}
[Fact]
public async Task DeleteTable_WithOpenOrder_ReturnsTableHasOpenOrder()
{
var (db, tables, cafeId, branchA, _, _) = CreateFixture();
db.Orders.Add(new Order
{
Id = "ord-1",
CafeId = cafeId,
BranchId = branchA,
TableId = "t-a",
Status = OrderStatus.Preparing,
OrderType = OrderType.DineIn,
Subtotal = 100,
Total = 100
});
await db.SaveChangesAsync();
var result = await tables.DeleteBranchTableAsync(cafeId, branchA, "t-a");
Assert.False(result.Success);
Assert.Equal("TABLE_HAS_OPEN_ORDER", result.ErrorCode);
}
[Fact]
public async Task DeleteSection_WithTables_ReturnsTableSectionHasTables()
{
var (db, tables, cafeId, branchA, _, _) = CreateFixture();
var section = new TableSection
{
Id = "sec-1",
CafeId = cafeId,
BranchId = branchA,
Name = "Hall"
};
db.TableSections.Add(section);
var table = await db.Tables.FirstAsync(t => t.Id == "t-a");
table.SectionId = section.Id;
await db.SaveChangesAsync();
var result = await tables.DeleteBranchSectionAsync(cafeId, branchA, section.Id);
Assert.False(result.Success);
Assert.Equal("TABLE_SECTION_HAS_TABLES", result.ErrorCode);
}
[Fact]
public async Task ManagerCannotAccessOtherBranchTables()
{
var (_, tables, cafeId, _, branchB, managerId) = CreateFixture();
var allowed = await tables.CanAccessBranchAsync(
cafeId, branchB, managerId, EmployeeRole.Manager);
Assert.False(allowed);
}
}