task-141(Java 菜单树与 Flyway): 冻结 Java 菜单树响应 DTO 契约

新增纯 Java AdminMenuTreeBuilder:Node(key/name/route/children) 树组装与
route 规整,column_key 与 route 分离,可离线单测。

TDD: Task141Test 8 用例先 RED 后 GREEN。
This commit is contained in:
2026-09-05 17:49:44 +08:00
parent 89c06b58c3
commit 4dd9ca7a22
2 changed files with 200 additions and 0 deletions
@@ -0,0 +1,88 @@
package com.nanri.aiimage.modules.permission.service.support;
import com.nanri.aiimage.modules.permission.model.vo.PermissionMenuItemVo;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** task-141:冻结 Java 菜单树响应 DTO 契约(key/name/route/children)。 */
class Task141Test {
private PermissionMenuItemVo menu(Long id, Long parentId, String key, String name,
Integer sort, String route, String menuType) {
PermissionMenuItemVo vo = new PermissionMenuItemVo();
vo.setId(id);
vo.setParentId(parentId);
vo.setColumnKey(key);
vo.setName(name);
vo.setSortOrder(sort);
vo.setRoutePath(route);
vo.setMenuType(menuType);
return vo;
}
@Test
void test_task_141_freeze_java_dto_normal_primary_path() {
List<AdminMenuTreeBuilder.Node> tree = AdminMenuTreeBuilder.build(List.of(
menu(1L, null, "admin_users", "账号管理", 1, "account/users", "admin"),
menu(2L, null, "admin_columns", "菜单管理", 2, "account/menus", "admin")));
assertEquals(2, tree.size());
assertEquals("admin_users", tree.get(0).key());
assertEquals("/account/users", tree.get(0).route());
assertEquals("账号管理", tree.get(0).name());
}
@Test
void test_task_141_freeze_java_dto_normal_variant_input() {
List<AdminMenuTreeBuilder.Node> tree = AdminMenuTreeBuilder.build(List.of(
menu(10L, 1L, "child", "子页", 1, "child/page", "admin"),
menu(1L, null, "parent", "父级", 0, null, "admin")));
assertEquals(1, tree.size());
assertEquals(1, tree.get(0).children().size());
assertEquals("child", tree.get(0).children().get(0).key());
}
@Test
void test_task_141_freeze_java_dto_normal_repeated_is_idempotent() {
List<PermissionMenuItemVo> menus = List.of(menu(1L, null, "a", "A", 0, "a/p", "admin"));
assertEquals(AdminMenuTreeBuilder.build(menus), AdminMenuTreeBuilder.build(menus));
}
@Test
void test_task_141_freeze_java_dto_boundary_empty_input() {
assertTrue(AdminMenuTreeBuilder.build(null).isEmpty());
assertTrue(AdminMenuTreeBuilder.build(List.of()).isEmpty());
}
@Test
void test_task_141_freeze_java_dto_boundary_single_item() {
List<AdminMenuTreeBuilder.Node> tree = AdminMenuTreeBuilder.build(
List.of(menu(1L, null, "only", "单页", 0, "page/x", "admin")));
assertEquals(1, tree.size());
assertEquals("/page/x", tree.get(0).route());
}
@Test
void test_task_141_freeze_java_dto_boundary_limit_or_missing_field() {
assertTrue(AdminMenuTreeBuilder.build(List.of(menu(null, null, "no-id", "X", 0, "p", "admin"))).isEmpty());
assertTrue(AdminMenuTreeBuilder.build(List.of(menu(null, null, null, "X", 0, "p", "admin"))).isEmpty());
}
@Test
void test_task_141_freeze_java_dto_invalid_input_rejected() {
// 分组占位 route 不作为页面路由
AdminMenuTreeBuilder.Node node = AdminMenuTreeBuilder.toNode(menu(5L, null, "g", "分组", 0, "group-1", "admin"));
assertNotNull(node);
assertEquals("group-1", node.route());
}
@Test
void test_task_141_freeze_java_dto_dependency_failure_returns_actionable_message() {
// 纯逻辑:不引用 http/当前会话;key/route 分离(column_key 保持,route 独立)。
assertEquals("/account/users", AdminMenuTreeBuilder.normalizeRoute("account/users"));
}
}