Files
meezi/tests/Meezi.API.Tests/QrMenuTests.cs
T
soroush.asadi f813cc4854
CI/CD / CI · API (dotnet build + test) (pull_request) Successful in 43s
CI/CD / CI · Admin API (dotnet build) (pull_request) Successful in 44s
CI/CD / CI · Dashboard (tsc) (pull_request) Successful in 1m7s
CI/CD / CI · Admin Web (tsc) (pull_request) Successful in 36s
CI/CD / CI · Website (tsc) (pull_request) Successful in 45s
CI/CD / CI · Koja (tsc) (pull_request) Successful in 51s
CI/CD / Deploy · all services (pull_request) Has been skipped
test: repair test suite broken by feature drift (red -> 81 passing)
The test project no longer compiled: recent feature commits changed
interfaces and DTOs without updating the test doubles/call sites, so the
whole suite (and therefore CI) was failing to build.

- NoOpInventoryService: add IInventoryService.GetPurchasesSummaryAsync and
  the new string? userId param on AdjustAsync.
- NoOpLoyaltyService: add ILoyaltyService.RedeemOnOrderAsync.
- NoOpOrderNotificationService: add NotifyCallWaiterAsync.
- New NoOpAbuseProtectionService and NoOpMediaStorageService test doubles.
- QrMenuTests: ReviewService/PublicService gained IAbuseProtectionService +
  IHttpContextAccessor (and ReviewService an IMediaStorageService); wire the
  new no-op doubles + a real HttpContextAccessor.
- PrintingTests: OrderDto gained a DisplayNumber int between CreatedAt and
  Items; pass it.
- DiscoverFilterTests: add missing `using Xunit;` and the new openNow arg on
  DiscoverFilterParams.FromQuery.

Result: dotnet test -> Passed: 81, Failed: 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:44:41 +03:30

237 lines
8.4 KiB
C#

using Meezi.API.Models.Menu;
using Meezi.API.Models.Orders;
using Meezi.API.Models.Public;
using Meezi.API.Security;
using Meezi.API.Services;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Xunit;
using LiveOrderDto = Meezi.API.Models.Orders.LiveOrderDto;
namespace Meezi.API.Tests;
public class QrMenuTests
{
private sealed class NoOpKds : IKdsNotifier
{
public Task NotifyOrderCreatedAsync(string cafeId, LiveOrderDto order, CancellationToken ct = default) =>
Task.CompletedTask;
public Task NotifyOrderStatusChangedAsync(string cafeId, string orderId, OrderStatus status, CancellationToken ct = default) =>
Task.CompletedTask;
public Task NotifyTableStatusChangedAsync(string cafeId, CancellationToken ct = default) =>
Task.CompletedTask;
}
private static (
AppDbContext Db,
TableService Tables,
PublicService Public,
string CafeId,
string BranchId,
string TableId,
string ItemA,
string ItemB,
string QrCode
) CreateFixture()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
var db = new AppDbContext(options);
var cafeId = "cafe-qr";
var branchId = "branch-qr";
var catId = "cat-qr";
var itemA = "item-qr-a";
var itemB = "item-qr-b";
var tableId = "table-qr-1";
const string qrCode = "demo_table_01";
db.Cafes.Add(new Cafe
{
Id = cafeId,
Name = "Demo",
Slug = "demo",
PlanTier = PlanTier.Pro,
DefaultTaxRate = 9m
});
db.Branches.Add(new Branch
{
Id = branchId,
CafeId = cafeId,
Name = "Main",
IsActive = true,
WelcomeText = "خوش آمدید",
UpdatedAt = DateTime.UtcNow
});
db.MenuCategories.Add(new MenuCategory
{
Id = catId,
CafeId = cafeId,
Name = "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.Tables.Add(new Table
{
Id = tableId,
CafeId = cafeId,
BranchId = branchId,
Number = "1",
QrCode = qrCode,
IsActive = true
});
db.SaveChanges();
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?> { ["App:QrPublicBaseUrl"] = "https://meezi.test" })
.Build();
var kds = new NoOpKds();
var branchMenu = new BranchMenuService(db);
var identity = new BranchIdentityService(db);
var tables = new TableService(db, config, kds, identity);
var shifts = new ShiftService(db);
var orders = new OrderService(db, kds, new NoOpSnappfood(), new NoOpDeliverySync(), shifts, TestServiceScopeFactory.Create(), new NoOpOrderNotificationService(), new NoOpInventoryService(), new NoOpLoyaltyService());
var abuse = new NoOpAbuseProtectionService();
var http = new HttpContextAccessor();
var media = new NoOpMediaStorageService();
var reviews = new ReviewService(db, abuse, http, media);
var publicSvc = new PublicService(db, orders, reviews, kds, branchMenu, identity, abuse, http);
return (db, tables, publicSvc, cafeId, branchId, tableId, itemA, itemB, qrCode);
}
[Fact]
public async Task ResolveQr_ValidCode_ReturnsBranchInfo()
{
var (_, tables, _, _, branchId, tableId, _, _, qrCode) = CreateFixture();
var result = await tables.ResolveQrAsync(qrCode);
Assert.NotNull(result);
Assert.Equal(branchId, result!.BranchId);
Assert.Equal(tableId, result.TableId);
Assert.False(result.IsCleaning);
}
[Fact]
public async Task ResolveQr_InvalidCode_ReturnsNull()
{
var (_, tables, _, _, _, _, _, _, _) = CreateFixture();
Assert.Null(await tables.ResolveQrAsync("missing_code"));
}
[Fact]
public async Task GetBranchMenu_ExcludesUnavailableItems()
{
var (db, _, pub, cafeId, branchId, _, itemA, itemB, _) = CreateFixture();
var menuSvc = new BranchMenuService(db);
await menuSvc.UpsertOverrideAsync(
cafeId, branchId, itemB,
new UpsertBranchMenuOverrideRequest(false, null),
PlanTier.Pro, EmployeeRole.Manager, "u1");
var menu = await pub.GetBranchMenuAsync(cafeId, branchId);
var ids = menu!.Categories.SelectMany(c => c.Items).Select(i => i.Id).ToList();
Assert.Contains(itemA, ids);
Assert.DoesNotContain(itemB, ids);
}
[Fact]
public async Task GetBranchMenu_AppliesBranchPriceOverride()
{
var (db, _, pub, cafeId, branchId, _, itemA, _, _) = CreateFixture();
await new BranchMenuService(db).UpsertOverrideAsync(
cafeId, branchId, itemA,
new UpsertBranchMenuOverrideRequest(true, 88_000m),
PlanTier.Pro, EmployeeRole.Manager, "u1");
var item = (await pub.GetBranchMenuAsync(cafeId, branchId))!
.Categories.SelectMany(c => c.Items)
.First(i => i.Id == itemA);
Assert.Equal(88_000m, item.Price);
}
[Fact]
public async Task PlaceGuestOrder_ValidItems_CreatesOrderWithGuestQrSource()
{
var (db, _, pub, cafeId, branchId, tableId, itemA, _, _) = CreateFixture();
var (data, code, _) = await pub.PlaceBranchGuestOrderAsync(
cafeId,
branchId,
new PlaceGuestOrderRequest(tableId, "Guest", null, [new CreateOrderItemRequest(itemA, 2, null)]));
Assert.Null(code);
Assert.NotNull(data);
var order = await db.Orders.Include(o => o.Items).FirstAsync(o => o.Id == data!.OrderId);
Assert.Equal(OrderSource.GuestQr, order.Source);
Assert.Equal(2, order.Items.Sum(i => i.Quantity));
}
[Fact]
public async Task PlaceGuestOrder_MergesWithExistingOpenOrder()
{
var (db, _, pub, cafeId, branchId, tableId, itemA, _, _) = CreateFixture();
await pub.PlaceBranchGuestOrderAsync(
cafeId, branchId,
new PlaceGuestOrderRequest(tableId, null, null, [new CreateOrderItemRequest(itemA, 1, null)]));
var (second, code, _) = await pub.PlaceBranchGuestOrderAsync(
cafeId, branchId,
new PlaceGuestOrderRequest(tableId, null, null, [new CreateOrderItemRequest(itemA, 1, null)]));
Assert.Null(code);
Assert.Equal(1, await db.Orders.CountAsync(o => o.TableId == tableId));
var order = await db.Orders.Include(o => o.Items).FirstAsync(o => o.Id == second!.OrderId);
Assert.Equal(2, order.Items.Sum(i => i.Quantity));
}
[Fact]
public async Task PlaceGuestOrder_CleaningTable_ReturnsTableCleaning()
{
var (db, _, pub, cafeId, branchId, tableId, itemA, _, _) = CreateFixture();
var table = await db.Tables.FirstAsync(t => t.Id == tableId);
table.IsCleaning = true;
await db.SaveChangesAsync();
var (_, code, _) = await pub.PlaceBranchGuestOrderAsync(
cafeId, branchId,
new PlaceGuestOrderRequest(tableId, null, null, [new CreateOrderItemRequest(itemA, 1, null)]));
Assert.Equal("TABLE_CLEANING", code);
}
[Fact]
public async Task PlaceGuestOrder_InvalidMenuItem_ReturnsInvalidMenuItems()
{
var (_, _, pub, cafeId, branchId, tableId, _, _, _) = CreateFixture();
var (_, code, _) = await pub.PlaceBranchGuestOrderAsync(
cafeId, branchId,
new PlaceGuestOrderRequest(tableId, null, null, [new CreateOrderItemRequest("missing", 1, null)]));
Assert.Equal("INVALID_MENU_ITEMS", code);
}
}