test(aihr): cover organization display field boundary

This commit is contained in:
2026-07-14 12:08:25 +08:00
parent ed998753be
commit 2d371e0447
2 changed files with 21 additions and 0 deletions
@@ -14,6 +14,7 @@ import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -56,6 +57,25 @@ public class AihrOrgSyncServiceTest {
assertFalse(AihrOrgSyncService.hasUnusablePhoneCandidate(mapper.readTree("{\"name\":\"张三\"}")));
}
@Test
public void displayFieldsAreOmittedOutsideDevelopmentMode() throws Exception {
ObjectMapper mapper = new ObjectMapper();
AihrOrgSyncService service = new AihrOrgSyncService(new ObjectMapper(), mock(JdbcTemplate.class), mock(TransactionTemplate.class));
var employee = mapper.readTree("""
{"id":"EMP-1","name":"测试员工","department_name":"项目管理","project_name":"翡翠湾","position_name":"主管","phone":"13900001111"}
""");
ReflectionTestUtils.setField(service, "storeDisplayFields", false);
Object masked = ReflectionTestUtils.invokeMethod(service, "orgRow", employee, Map.of(), Map.of());
assertEquals("", ReflectionTestUtils.getField(masked, "personName"));
assertEquals("", ReflectionTestUtils.getField(masked, "deptName"));
ReflectionTestUtils.setField(service, "storeDisplayFields", true);
Object demo = ReflectionTestUtils.invokeMethod(service, "orgRow", employee, Map.of(), Map.of());
assertEquals("测试员工", ReflectionTestUtils.getField(demo, "personName"));
assertEquals("项目管理", ReflectionTestUtils.getField(demo, "deptName"));
}
@Test
public void snapshotSuspectTextQuerySearchesLiteralQuestionMarks() {
SnapshotJdbcTemplate jdbcTemplate = new SnapshotJdbcTemplate();
+1
View File
@@ -27,6 +27,7 @@
| 缺口 | BRD依据 | 当前事实 | 下一步 |
|---|---|---|---|
| 正式主管身份、项目范围和数据权限 | 5.4、L1-B、G3 | 代码已有 `aihr_org_snapshot`、岗位/项目过滤和主管接口;生产快照当前为 22 条 active、2 个项目,但手机号关联为 0/22;生产环境当前未配置 `AIHR_ORG_SYNC_*` 组织同步凭据;移动端角色查询失败已改为安全降级员工端,员工登录/路由现在通过认证的 `/api/aihr/mobile/me` 刷新官方岗位,只有组织未映射时才保留本地演示岗位回退;线上视觉测试不能替代真实主管身份验证;组织覆盖写入现在会拒绝被跳过、手机号不完整、脱敏手机号或疑似乱码的快照,除非显式传 `allowPartialReplace=true`;重复 `ext_party_id` 无论是否允许部分覆盖都直接拒绝,避免唯一键折叠不同员工身份 | 由业务/运维提供正式组织接口配置后先做 dry-run,核对项目、岗位、`ext_party_id`、手机号;补齐手机号映射并确认主管身份后,才执行正式同步和主管数据权限验收 |
| 组织快照姓名/部门存储边界 | BRD 5.4、TechSpec 3.1 | BRD 要求本地只存外部 `party_id`,当前快照历史表仍有姓名/部门字段;已增加生产默认关闭的 `store-display-fields`,新同步和快照 API 可不再写/返姓名部门,但旧行尚未清理,且 `person_phone` 仍用于身份映射 | 由业务/法务/安全确定最小字段清单和保留期;完成历史数据清理、生产迁移和管理端只读展示方案后,才能关闭该 P0 |
| G5 情绪疏导树洞隐私边界 | BRD 12.1、附录 A 第三层 | 当前仓库没有树洞入口、独立存储、留痕策略、训练集排除机制或删除/审计合同;历史一期方案明确将“情绪疏导树洞”列为一期不做。现有陪练中的情绪识别、纠偏和反馈属于训练证据,不能冒充“不留痕树洞” | 在进入正式生产前由业务、法务和安全共同确认是否纳入当前阶段;若纳入,必须单独定义匿名/身份边界、零落库与日志脱敏、模型供应商留存、删除证明和误伤升级路径;在决策和合同未明确前保持未实现,不用陪练情绪数据承接树洞诉求 |
| 外部 AI 的 PII 出境与留存 | 4.3.2、4.3.3、G3、G6 | 对练、面试、SOP、Embedding、Rerank、TTS 的文本请求已统一做最小脱敏;ASR 仍发送音频本体,视觉 OCR/视频关键帧仍发送图片本体,当前没有供应商不留存、删除证明或本地语音/视觉模型证据;视频失败日志已遮蔽文件名中的已知 PII | 正式试点前由业务、法务和安全确认供应商留存/训练/跨境条款,或切换本地模型;补音频、图片的审计与删除证据后,才能把 G3/G6 判为通过 |
| LLM/ASR/TTS 调用成本计量与自动降级 | 6.3、12.1、G6、TechSpec 3.8 | 已有 `AIHR_AI_RUNTIME_ENABLED`、`AIHR_AI_CHAT_ENABLED`、`AIHR_AI_SPEECH_ENABLED` 手动断路器;当前没有 `llm_call_log`,也没有按模块、模型、token、延迟和费用的持久化月度计量,不能把手动开关宣称为成本闸门已完成 | 先由供应商/财务确认 token 计费字段、单价、月度上限和告警阈值,再新增租户范围的调用日志与自动降级;在计费契约明确前保持手动断路器,不硬编码价格或用日志估算正式成本 |