- 在菜单系统中新增 RenderType、FormDefinitionId 和 RuntimeViewMode 字段 - 实现自定义表单菜单类型的组件映射和验证逻辑 - 为分片上传相关端点添加 file:upload 权限控制 - 为桶和文件夹操作添加 bucket:write 和 file:write 权限控制 - 为文件共享功能添加 file-share:write 权限控制 - 为文件读取操作添加 file:read 权限控制 - 扩展 AI 模型提供商枚举,支持更多模型供应商 - 在 AI 模型创建和更新时添加枚举值验证 - 重构工作流按钮权限检查逻辑,使用统一的权限码查询方式 - 为 JSON 格式文档添加提取器支持 - 增强表单数据派生服务,支持自定义表单关联解析 - 为表单数据添加按表单定义 ID 的索引优化 - 添加聊天消息搜索和上下文获取的集成测试 - 修正删除菜单端点的 ID 类型从 Guid 改为 long
164 lines
5.3 KiB
C#
164 lines
5.3 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 菜单管理测试。约束菜单主键使用 int64,并保证拖拽排序按 long Id 落库。
|
||
/// </summary>
|
||
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<BusinessException>()
|
||
.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<PlatformDbContext>(o => o.UseInMemoryDatabase($"platform-menu-{Guid.NewGuid()}"));
|
||
return services.BuildServiceProvider().GetRequiredService<PlatformDbContext>();
|
||
}
|
||
|
||
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",
|
||
};
|
||
}
|