feat: 提交校园智能助手后端工程源码(Spring Boot 2.7.18 + 平台 starter + JPA + Nacos)
build / build-push (push) Successful in 40s
build / build-push (push) Successful in 40s
- com.yunhai.agent,端口 8093,context-path /agent,制品 target/agent.jar;与 NoticeService 同构 - CAS 单点登录 + 三层角色权限(学生/教师/管理员/访客)+ 会话与工具调用审计落库 - DeepSeek 大模型编排 + 7 个业务模块工具(请假/公告/宿舍/活动/作业/资源库/教师发展) - 浏览器代执行协议(服务端挂起 → 前端用登录态代调 → toolResult 续跑)+ 调用失败深链兜底 - 登出修复:服务端真正 session.invalidate() + 前端两步登出 + logout-guard - 保留 projman 注入的 .gitea/workflows/build.yml 与 docker/Dockerfile;docker/application-prod.yml、run-app.sh 随工程入库
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package com.yunhai.agent;
|
||||
|
||||
import com.yunhai.agent.tool.FormField;
|
||||
import com.yunhai.agent.tool.ToolDef;
|
||||
import com.yunhai.agent.tool.ToolParam;
|
||||
import com.yunhai.agent.tool.defs.LeaveTools;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* 表单字段「控件形态」的回归断言(纯单元,不依赖 Spring)。
|
||||
*
|
||||
* <p>背景(2026-10-10 截图实测):请假表单里「请假类型」渲染成了普通输入框,
|
||||
* 占位文字是「请假类型编号,来自 leave.types」。根因有两层:
|
||||
* <ol>
|
||||
* <li>它在工具定义里是 {@code integer},本应落到数字控件,但前端没有 {@code number} 分支;</li>
|
||||
* <li>它的可选项来自字典工具 {@code leave.types},字典没取到时不该静默退化 ——
|
||||
* 必须把 {@link FormField#getOptionsFrom()} 一并下发,前端才能提示「可先手填编号」。</li>
|
||||
* </ol>
|
||||
*/
|
||||
class FormFieldWidgetTest {
|
||||
|
||||
/** ① 整数参数 → number 控件;字典来源必须一并下发(前端据此在字典失败时给提示) */
|
||||
@Test
|
||||
void integerParamWithDictSourceRendersNumberAndCarriesOptionsFrom() {
|
||||
ToolParam p = ToolParam.required("leaveType", "integer", "请假类型编号,来自 leave.types")
|
||||
.label("请假类型").optionsFrom("leave.types");
|
||||
|
||||
FormField f = p.toFormField();
|
||||
|
||||
assertEquals("number", f.getWidget(), "integer 参数应渲染成数字控件");
|
||||
assertEquals("leave.types", f.getOptionsFrom(), "动态选项来源必须一并下发");
|
||||
assertTrue(f.getOptions().isEmpty(), "未取到字典时 options 应为空(前端据此提示)");
|
||||
}
|
||||
|
||||
/** ② 静态枚举 → select,且不应带 optionsFrom(否则前端会误报「字典没取到」) */
|
||||
@Test
|
||||
void enumParamRendersSelectWithoutOptionsFrom() {
|
||||
ToolParam p = ToolParam.required("level", "string", "级别")
|
||||
.label("级别").options(new String[]{"1", "2"}, new String[]{"普通", "重要"});
|
||||
|
||||
FormField f = p.toFormField();
|
||||
|
||||
assertEquals("select", f.getWidget());
|
||||
assertNull(f.getOptionsFrom(), "静态枚举没有动态来源,optionsFrom 应为 null");
|
||||
assertEquals(2, f.getOptions().size());
|
||||
}
|
||||
|
||||
/** ③ 日期与长文本仍按参数名推断(防止改动上面两条时把推断逻辑带歪) */
|
||||
@Test
|
||||
void nameBasedWidgetInferenceStillWorks() {
|
||||
assertEquals("datetime",
|
||||
ToolParam.required("startDate", "string", "开始时间").toFormField().getWidget());
|
||||
assertEquals("textarea",
|
||||
ToolParam.required("remarks", "string", "请假事由").toFormField().getWidget());
|
||||
}
|
||||
|
||||
/**
|
||||
* ④ 内置兜底:声明了字典来源、而实时字典取不到时,仍应是下拉框,而不是让人填编号的数字框。
|
||||
* <p>这正是 2026-10-10 反馈「请假类型怎么变成数字框了,还是 1 这种」的修复点 ——
|
||||
* 光有选项来源不够,还得有可用的默认选项,控件才立得住。
|
||||
*/
|
||||
@Test
|
||||
void fallbackOptionsKeepSelectWidgetWhenDictUnavailable() {
|
||||
ToolParam p = ToolParam.required("leaveType", "integer", "请假类型")
|
||||
.label("请假类型")
|
||||
.fallbackOptions(new String[]{"1", "2"}, new String[]{"病假", "事假及其他"})
|
||||
.optionsFrom("leave.types");
|
||||
|
||||
FormField f = p.toFormField();
|
||||
|
||||
assertEquals("select", f.getWidget(), "有兜底选项时必须渲染成下拉框");
|
||||
assertEquals(2, f.getOptions().size());
|
||||
assertEquals("1", f.getOptions().get(0).get("value"));
|
||||
assertEquals("病假", f.getOptions().get(0).get("label"));
|
||||
assertEquals("事假及其他", f.getOptions().get(1).get("label"));
|
||||
assertTrue(f.isOptionsFallback(), "必须标记为兜底,前端才能提示「自定义项没取到」");
|
||||
assertEquals("leave.types", f.getOptionsFrom(), "实时字典来源要保留,取到后覆盖兜底");
|
||||
}
|
||||
|
||||
/** ⑤ 权威枚举优先于兜底:两者都配时兜底不生效,也不应被误标为兜底 */
|
||||
@Test
|
||||
void explicitEnumWinsOverFallback() {
|
||||
ToolParam p = ToolParam.required("status", "integer", "审批结论")
|
||||
.options(new String[]{"1"}, new String[]{"通过"})
|
||||
.fallbackOptions(new String[]{"9"}, new String[]{"兜底项"});
|
||||
|
||||
FormField f = p.toFormField();
|
||||
|
||||
assertEquals(1, f.getOptions().size());
|
||||
assertEquals("通过", f.getOptions().get(0).get("label"));
|
||||
assertFalse(f.isOptionsFallback());
|
||||
}
|
||||
|
||||
/** ⑥ 真实工具定义回归:请假类型必须是带中文选项的下拉框,不能退回数字框 */
|
||||
@Test
|
||||
void leaveSubmitTypeIsASelectInRealToolDefinition() {
|
||||
ToolDef def = LeaveTools.defs().stream()
|
||||
.filter(d -> "leave.submit".equals(d.getName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("leave.submit 工具定义丢失"));
|
||||
|
||||
ToolParam leaveType = def.getParams().stream()
|
||||
.filter(p -> "leaveType".equals(p.getName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("leaveType 参数丢失"));
|
||||
|
||||
FormField f = leaveType.toFormField();
|
||||
|
||||
assertEquals("select", f.getWidget());
|
||||
assertEquals(2, f.getOptions().size());
|
||||
assertEquals("病假", f.getOptions().get(0).get("label"));
|
||||
assertEquals("事假及其他", f.getOptions().get(1).get("label"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user