ef15fd6247
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>
144 lines
5.2 KiB
C#
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);
|
|
}
|
|
}
|