using BuildFlow.Platform.Application.Menus.Commands; using BuildFlow.Platform.Application.Menus.Handlers; using BuildFlow.Platform.Application.Menus.Queries; using BuildFlow.Platform.Domain.Entities; using BuildFlow.Platform.Domain.Enums; using BuildFlow.Platform.Infrastructure.Persistence; using BuildFlow.Shared.Domain.Exceptions; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace BuildFlow.Platform.Tests.Menus; /// /// 菜单管理测试。约束菜单主键使用 int64,并保证拖拽排序按 long Id 落库。 /// public class MenuSortTests { [Fact] public void MenuIdentifier_ShouldUseInt64() { typeof(Menu).GetProperty(nameof(Menu.Id))!.PropertyType.Should().Be(typeof(long)); typeof(Menu).GetProperty(nameof(Menu.ParentId))!.PropertyType.Should().Be(typeof(long?)); } [Fact] public async Task SortMenusCommand_ShouldUpdateMenusByInt64Ids() { await using var db = BuildInMemoryDb(); db.Menus.AddRange( BuildMenu(1, "root", null, 0), BuildMenu(2, "child-a", 1, 0), BuildMenu(3, "child-b", 1, 1)); await db.SaveChangesAsync(); var handler = new SortMenusCommandHandler(db); var affected = await handler.Handle( new SortMenusCommand( [ new MenuSortItem(2, null, 1), new MenuSortItem(3, null, 0), ]), CancellationToken.None); affected.Should().Be(2); var childA = await db.Menus.SingleAsync(m => m.Id == 2); var childB = await db.Menus.SingleAsync(m => m.Id == 3); childA.ParentId.Should().BeNull(); childA.Order.Should().Be(1); childB.ParentId.Should().BeNull(); childB.Order.Should().Be(0); } [Fact] public async Task SortMenusCommand_ShouldRejectMovingMenuUnderDescendant() { await using var db = BuildInMemoryDb(); db.Menus.AddRange( BuildMenu(1, "root", null, 0), BuildMenu(2, "child", 1, 0), BuildMenu(3, "grand-child", 2, 0)); await db.SaveChangesAsync(); var handler = new SortMenusCommandHandler(db); var act = () => handler.Handle( new SortMenusCommand([new MenuSortItem(1, 3, 0)]), CancellationToken.None); await act.Should().ThrowAsync() .WithMessage("不能将菜单移动到自己的子菜单下面"); } [Fact] public async Task GetAllMenusQuery_ShouldExposeBuildFlowFormRuntimeMetadata() { await using var db = BuildInMemoryDb(); var userId = Guid.NewGuid(); var roleId = Guid.NewGuid(); var formId = Guid.NewGuid(); db.Users.Add(new User { Id = userId, Username = "admin", Email = "admin@buildflow.local", PasswordHash = "hash", CreatedBy = "test", UpdatedBy = "test", OperatorIP = "127.0.0.1", }); db.Roles.Add(new Role { Id = roleId, Name = "Admin", CreatedBy = "test", UpdatedBy = "test", OperatorIP = "127.0.0.1", }); db.UserRoles.Add(new UserRole { UserId = userId, RoleId = roleId, CreatedBy = "test", UpdatedBy = "test", }); db.Menus.Add(BuildMenu( 100, "expense.request", null, 0, renderType: MenuRenderType.BuildFlowForm, formDefinitionId: formId, runtimeViewMode: MenuRuntimeViewMode.List)); await db.SaveChangesAsync(); var handler = new GetAllMenusQueryHandler(db); var routes = await handler.Handle(new GetAllMenusQuery(userId.ToString()), CancellationToken.None); var route = routes.Should().ContainSingle().Subject; route.Component.Should().Be("/buildflow/runtime/index"); route.Meta.RenderType.Should().Be("BuildFlowForm"); route.Meta.FormDefinitionId.Should().Be(formId); route.Meta.RuntimeViewMode.Should().Be("List"); } private static PlatformDbContext BuildInMemoryDb() { var services = new ServiceCollection(); services.AddDbContext(o => o.UseInMemoryDatabase($"platform-menu-{Guid.NewGuid()}")); return services.BuildServiceProvider().GetRequiredService(); } private static Menu BuildMenu( long id, string code, long? parentId, int order, MenuRenderType renderType = MenuRenderType.Route, Guid? formDefinitionId = null, MenuRuntimeViewMode runtimeViewMode = MenuRuntimeViewMode.List) => new() { Id = id, Code = code, Name = code.Replace(".", "-", StringComparison.Ordinal), Path = $"/{code}", ParentId = parentId, Title = code, Order = order, RenderType = renderType, FormDefinitionId = formDefinitionId, RuntimeViewMode = runtimeViewMode, CreatedBy = "test", CreatedAt = DateTime.UtcNow, UpdatedBy = "test", UpdatedAt = DateTime.UtcNow, OperatorIP = "127.0.0.1", }; }