Files
yunhai-agent-service/src/test/java/com/yunhai/agent/RolePermissionTest.java
T
lijing 8303b857e9
build / build-push (push) Successful in 40s
feat: 提交校园智能助手后端工程源码(Spring Boot 2.7.18 + 平台 starter + JPA + Nacos)
- 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 随工程入库
2026-10-12 11:18:29 +08:00

219 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.agent;
import com.yunhai.agent.auth.AgentRole;
import com.yunhai.agent.auth.RoleResolver;
import com.yunhai.agent.config.AgentSettings;
import com.yunhai.agent.tool.CallerContext;
import com.yunhai.agent.tool.ToolDef;
import com.yunhai.agent.tool.ToolExecutor;
import com.yunhai.agent.tool.ToolRegistry;
import com.yunhai.agent.tool.ToolResult;
import com.yunhai.platform.utils.model.BoaRoleInfo;
import com.yunhai.platform.utils.model.BoaUser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 「分学生、教师权限」的核心断言(纯单元,不依赖 Spring / SSO / 数据库)。
*
* <p>验证两件事:① 模型侧过滤(哪些工具可见);② 服务端强校验(越权直接拒绝执行)。
*/
class RolePermissionTest {
private AgentSettings settings;
private ToolRegistry registry;
private RoleResolver resolver;
@BeforeEach
void setUp() {
settings = new AgentSettings();
registry = new ToolRegistry(settings);
registry.init();
resolver = new RoleResolver(settings);
}
/** 伪造一个指定角色号的平台用户(type=null 表示非超管) */
private BoaUser user(Integer id, Integer... roleIds) {
BoaUser u = new BoaUser();
u.setId(id);
u.setName("测试用户" + id);
u.setAccount("test" + id);
u.setRoleIds(Arrays.asList(roleIds));
return u;
}
/** 伪造一个带「当前身份」的平台用户 */
private BoaUser userWithCurrent(Integer id, Integer currentRoleId, String currentRoleName, Integer... allRoleIds) {
BoaUser u = user(id, allRoleIds);
if (currentRoleId != null) {
BoaRoleInfo cur = new BoaRoleInfo();
cur.setRoleId(currentRoleId);
cur.setRoleName(currentRoleName);
cur.setSchId(3);
cur.setSchName("测试学校");
u.setCurrentRole(cur);
}
return u;
}
private boolean visible(List<ToolDef> tools, String name) {
return tools.stream().anyMatch(t -> t.getName().equals(name));
}
@Test
void multiIdentityAccountUsesCurrentRoleOnly() {
// 实测账号形态:10008 同时挂着 学生(1) + 教师(2) + 管理员(4),当前身份 = 学生
BoaUser u = userWithCurrent(10008, 1, "学生", 1, 2, 4);
Set<AgentRole> roles = resolver.resolve(u);
assertTrue(roles.contains(AgentRole.STUDENT), "当前身份是学生,应识别为学生");
assertFalse(roles.contains(AgentRole.TEACHER), "不应因账号另挂教师身份而获得教师能力");
assertFalse(roles.contains(AgentRole.ADMIN), "不应因账号另挂管理员身份而获得管理员能力");
List<ToolDef> visible = registry.visibleTools(null, roles);
assertTrue(visible(visible, "homework.submit"), "学生当前身份应可用 homework.submit");
assertFalse(visible(visible, "notice.publish"), "学生当前身份不应看到 notice.publish");
assertFalse(visible(visible, "profession.myPoints"), "学生当前身份不应看到 profession.*");
}
@Test
void switchingCurrentRoleSwitchesCapabilities() {
BoaUser u = userWithCurrent(10008, 1, "学生", 1, 2, 4);
assertTrue(resolver.resolve(u).contains(AgentRole.STUDENT));
// 门户把当前身份切成教师 → 同一账号能力随之变化
u.getCurrentRole().setRoleId(2);
u.getCurrentRole().setRoleName("教师");
Set<AgentRole> asTeacher = resolver.resolve(u);
assertTrue(asTeacher.contains(AgentRole.TEACHER));
assertFalse(asTeacher.contains(AgentRole.STUDENT));
List<ToolDef> teacherTools = registry.visibleTools(null, asTeacher);
assertTrue(visible(teacherTools, "notice.publish"), "教师当前身份应可用 notice.publish");
assertFalse(visible(teacherTools, "homework.submit"), "教师当前身份不应看到 homework.submit");
// 切成管理员 → 管理员 + 教师(admin-also-teacher 默认 true)
u.getCurrentRole().setRoleId(4);
u.getCurrentRole().setRoleName("管理员");
Set<AgentRole> asAdmin = resolver.resolve(u);
assertTrue(asAdmin.contains(AgentRole.ADMIN));
assertTrue(asAdmin.contains(AgentRole.TEACHER));
assertFalse(asAdmin.contains(AgentRole.STUDENT), "管理员当前身份不应含学生专属能力");
}
@Test
void unknownCurrentRoleFallsBackToAccountRoles() {
// 当前身份号未在 agent.roles.* 里声明 → 回退按账号全部身份判定(避免误判成访客)
BoaUser u = userWithCurrent(200, 999, "自定义角色", 1, 2);
Set<AgentRole> roles = resolver.resolve(u);
assertTrue(roles.contains(AgentRole.STUDENT), "回退后应保留账号的学生身份");
assertTrue(roles.contains(AgentRole.TEACHER), "回退后应保留账号的教师身份");
}
@Test
void accountWithoutCurrentRoleStillResolvesByRoleIds() {
// 老口径:平台没下发 currentRole 时,仍按 roleIds 判定(不能变成访客)
Set<AgentRole> roles = resolver.resolve(user(300, 2));
assertTrue(roles.contains(AgentRole.TEACHER));
}
@Test
void studentSeesOnlyStudentSideTools() {
Set<AgentRole> roles = resolver.resolve(user(101, 1));
assertTrue(roles.contains(AgentRole.STUDENT), "roleId=1 应识别为学生");
List<ToolDef> visible = registry.visibleTools(null, roles);
// 学生能做:交作业、报名活动、请假申请
assertTrue(visible(visible, "homework.submit"), "学生应可用 homework.submit");
assertTrue(visible(visible, "activity.signup"), "学生应可用 activity.signup");
assertTrue(visible(visible, "leave.submit"), "学生应可用 leave.submit");
// 学生不能做:审批、发布、专业发展、宿管管理
assertFalse(visible(visible, "leave.audit"), "学生不应看到 leave.audit");
assertFalse(visible(visible, "leave.batchPass"), "学生不应看到 leave.batchPass");
assertFalse(visible(visible, "notice.publish"), "学生不应看到 notice.publish");
assertFalse(visible(visible, "profession.myPoints"), "学生不应看到 profession.*");
assertFalse(visible(visible, "dorm.buildings"), "学生不应看到宿舍管理侧");
}
@Test
void teacherSeesStaffToolsButNotStudentHomework() {
Set<AgentRole> roles = resolver.resolve(user(102, 2));
assertTrue(roles.contains(AgentRole.TEACHER), "roleId=2 应识别为教师");
List<ToolDef> visible = registry.visibleTools(null, roles);
// 教师能做:审批、发布、专业发展
assertTrue(visible(visible, "leave.audit"), "教师应可用 leave.audit");
assertTrue(visible(visible, "notice.publish"), "教师应可用 notice.publish");
assertTrue(visible(visible, "profession.myPoints"), "教师应可用 profession.*");
assertTrue(visible(visible, "leave.stats"), "教师应可用 leave.stats");
// 教师不做:学生交作业、报名活动
assertFalse(visible(visible, "homework.submit"), "教师不应看到 homework.submit");
assertFalse(visible(visible, "activity.signup"), "教师不应看到 activity.signup");
}
@Test
void adminByTypeGetsStaffCapabilities() {
BoaUser u = user(1);
u.setType(1); // 平台超管口径
Set<AgentRole> roles = resolver.resolve(u);
assertTrue(roles.contains(AgentRole.ADMIN), "type=1 应识别为管理员");
assertTrue(roles.contains(AgentRole.TEACHER), "管理员默认兼有教师能力");
List<ToolDef> visible = registry.visibleTools(null, roles);
assertTrue(visible(visible, "leave.audit"));
assertTrue(visible(visible, "notice.publish"));
}
@Test
void guestSeesOnlyUnrestrictedTools() {
Set<AgentRole> roles = resolver.resolve(null);
assertTrue(roles.contains(AgentRole.GUEST));
List<ToolDef> visible = registry.visibleTools(null, roles);
assertTrue(visible(visible, "notice.received"), "公共只读能力应对访客可见");
assertFalse(visible(visible, "leave.submit"), "访客不应看到请假申请");
assertFalse(visible(visible, "leave.audit"), "访客不应看到审批");
}
@Test
void executorRejectsUnauthorizedToolAtServerSide() {
// 即便模型绕过过滤硬调用,服务端也必须拒绝
ToolExecutor executor = new ToolExecutor(settings, new RestTemplate());
ToolDef audit = registry.get("leave.audit");
assertTrue(audit != null && !audit.allowsAny(Collections.singleton(AgentRole.STUDENT)),
"leave.audit 应限制学生使用");
CallerContext student = new CallerContext();
student.setRoles(new LinkedHashSet<>(Collections.singletonList(AgentRole.STUDENT)));
ToolResult tr = executor.execute(audit, new LinkedHashMap<>(), student);
assertFalse(tr.isSuccess(), "学生调用审批工具必须失败");
assertTrue(tr.getError() != null && tr.getError().contains("无权"),
"拒绝理由应说明无权,实际:" + tr.getError());
}
@Test
void executorAllowsAuthorizedTool() {
// 教师调用审批工具不会被角色闸门拦截(因未配置模块地址,会在网关层报"未配置地址",不是"无权")
ToolExecutor executor = new ToolExecutor(settings, new RestTemplate());
ToolDef audit = registry.get("leave.audit");
CallerContext teacher = new CallerContext();
teacher.setRoles(new LinkedHashSet<>(Collections.singletonList(AgentRole.TEACHER)));
ToolResult tr = executor.execute(audit, new LinkedHashMap<>(), teacher);
assertTrue(tr.getError() == null || !tr.getError().contains("无权"),
"教师调用不应被角色闸门拒绝,实际:" + tr.getError());
}
}