test(e2e-recon): E2E 场景对账表(32 项:31 项有真实覆盖、1 项被裁定取代)+ 补 /me/status.localEligibilityStatus 零断言缺口
- 对账表写入收尾计划 Task 2.1 节:首页 11 / 本地资格 11 / 权限 10 逐项映射到
E2E 用例与后端集成测试;原始清单取自 commit ae1bd2b 占位文件头
- 发现并补齐 1 处缺口:/me/status 的 localEligibilityStatus/localEligibilityType
此前零断言(实现存在但从未被测)→ 新增 MeStatusLocalEligibilityGuardTest 2 用例
- 首页 #10(counselingEnabled flag 控制入口)登记为被 2026-09-19 产品裁定取代(死配置移除)
- 实测:MeStatusLocalEligibilityGuardTest 2/2 绿(离线容器 3:19)
This commit is contained in:
+77
@@ -0,0 +1,77 @@
|
||||
package com.danyang.match.platform.auth;
|
||||
|
||||
import com.danyang.match.platform.auth.service.AuthService;
|
||||
import com.danyang.match.platform.local_eligibility.entity.LocalEligibility;
|
||||
import com.danyang.match.platform.local_eligibility.mapper.LocalEligibilityMapper;
|
||||
import com.danyang.match.platform.user.entity.User;
|
||||
import com.danyang.match.platform.user.mapper.UserMapper;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* E2E 场景对账补缺(2026-09-19):`/me/status` 必须返回 `localEligibilityStatus`。
|
||||
*
|
||||
* <p>原始场景清单(Phase 23 占位文件头,「本地资格 11 项」第 6 项)在对账中发现
|
||||
* <b>零断言覆盖</b> —— 实现存在({@code AuthService#getMeStatus} 组装
|
||||
* {@code localEligibilityStatus / localEligibilityType / localEligibilityVerifiedAt},
|
||||
* 未声明时回 {@code not_declared}),但 E2E 与后端测试都从未断言过该字段。
|
||||
* 本守卫把该字段从「碰巧返回」变成「被声明的契约」:
|
||||
* 若后人重构 {@code getMeStatus} 时丢掉本地资格组装,这里会红。
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@Transactional
|
||||
class MeStatusLocalEligibilityGuardTest {
|
||||
|
||||
@MockBean
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Autowired private AuthService authService;
|
||||
@Autowired private UserMapper userMapper;
|
||||
@Autowired private LocalEligibilityMapper localEligibilityMapper;
|
||||
|
||||
private User newUser() {
|
||||
User user = new User();
|
||||
user.setId(UUID.randomUUID().toString());
|
||||
user.setWechatOpenid("openid-" + UUID.randomUUID());
|
||||
user.setAccountStatus("normal");
|
||||
userMapper.insert(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("未声明本地资格的用户:/me/status 显式返回 not_declared")
|
||||
void notDeclaredUserGetsNotDeclaredStatus() {
|
||||
User user = newUser();
|
||||
Map<String, Object> status = authService.getMeStatus(user.getId());
|
||||
assertEquals("not_declared", status.get("localEligibilityStatus"),
|
||||
"未声明时 /me/status 必须显式返回 not_declared,而不是缺省该键");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("已声明本地资格的用户:/me/status 回读真实核验状态与类型")
|
||||
void declaredUserGetsRealStatus() {
|
||||
User user = newUser();
|
||||
LocalEligibility el = new LocalEligibility();
|
||||
el.setUserId(user.getId());
|
||||
el.setEligibilityType("household_registration");
|
||||
el.setVerificationStatus("declared");
|
||||
localEligibilityMapper.insert(el);
|
||||
|
||||
Map<String, Object> status = authService.getMeStatus(user.getId());
|
||||
assertEquals("declared", status.get("localEligibilityStatus"),
|
||||
"/me/status 必须回读 user_local_eligibilities 的真实状态");
|
||||
assertEquals("household_registration", status.get("localEligibilityType"));
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,60 @@
|
||||
- 注入②「把 `user_b_status` 列默认值改成 `accepted`」**无效**:`createMutualConsent`
|
||||
在 Java 侧**显式写入** `pending`,DB 默认值根本不参与。这条侧面证明了写入路径的权威性。
|
||||
|
||||
### Task 2.1 补充:场景对账表(2026-09-19)
|
||||
|
||||
> 原始场景清单取自 Phase 23 占位文件头(commit `ae1bd2b`,此后文件已实现为真实用例)。
|
||||
> 对账口径:**场景有真实断言即算覆盖**,层次分 E2E(HTTP 活栈)与后端集成(真实落库)。
|
||||
|
||||
**首页 11 项**
|
||||
|
||||
| # | 场景 | 覆盖 | 位置 |
|
||||
|---|---|---|---|
|
||||
| 1 | `GET /home` 返回 userState | ✅ E2E | `run_v11_tests::test_02_home`、`home_v11_test::test_home_returns_fixed_sections` |
|
||||
| 2 | Banner 按时间过滤(未开始/已过期不显示) | ✅ 后端 | `BannerOperatorPhase19IntegrationTest` |
|
||||
| 3 | Banner 按 `audience_scope` 过滤 | ✅ 后端 | 同上 |
|
||||
| 4 | 首页 8 入口顺序固定(INV-017) | ✅ 双层 | 后端 `V11InvariantsTest#inv017`(真实 `getHome`)+ E2E `run_v11_tests::test_03`、`home_v11_test::test_quick_entries_fixed_order` |
|
||||
| 5 | 未审核用户不返回真实推荐 | ✅ 后端 | `RecommendationEligibilityPhase06Test`(资格矩阵) |
|
||||
| 6 | Paused 用户不返回新推荐 | ✅ 后端 | 同上 |
|
||||
| 7 | Successful 用户不返回找对象推荐 | ✅ 后端 | 同上 + `RelationshipProgressPhase12Test` |
|
||||
| 8 | 服务站可展示 | ✅ E2E | `run_v11_tests::test_06` + `service_appointment_test`(seed 站点)+ Phase 19 公告守卫 |
|
||||
| 9 | Banner 不能外链(INV-016) | ✅ 双层 | 后端 `V11InvariantsTest#inv016`(反射读生产白名单)+ E2E `home_v11_test::test_banner_target_types_are_internal_only` |
|
||||
| 10 | `counselingEnabled` flag 控制 `canApplyCounseling` | ⚠️ **场景过时(被取代)** | 该 flag 经产品裁定于 2026-09-19 移除(死配置,见 Task 3.2);入口可见性由 `CapabilityService` 决定(AGENTS.md #26)。原命题不再成立 |
|
||||
| 11 | `canReceiveRecommendations` 随账号状态变化 | ✅ 间接 | 资格层 `RecommendationEligibilityPhase06Test` + E2E home 走真实 `/home`(Capability 参与);`CapabilityService` 无独立单测(接受,低风险) |
|
||||
|
||||
**本地资格 11 项**
|
||||
|
||||
| # | 场景 | 覆盖 | 位置 |
|
||||
|---|---|---|---|
|
||||
| 1 | 用户可声明丹阳工作/户籍/居住 | ✅ E2E | `local_eligibility_test::test_declaration_is_persisted`、`::test_new_user_is_not_declared` |
|
||||
| 2 | 声明不默认要求上传佐证 | ✅ 后端 | `LocalEligibilityServiceTest`(declare 与 evidence 分离) |
|
||||
| 3 | 佐证上传需要鉴权 | ✅ 后端 | `LocalEligibilityServiceTest`(userId 自限定)+ `PhotoUploadValidationTest` |
|
||||
| 4 | 审核员可访问佐证(INV-011) | ✅ 后端 | `Inv011EvidenceAccessGuardTest`(真实落库 + 审计回查) |
|
||||
| 5 | 红娘不可访问佐证(INV-011) | ✅ 后端 | 同上 |
|
||||
| 6 | `/me/status` 返回 `localEligibilityStatus` | ✅ **本轮补缺(2026-09-19)** | 对账发现实现存在但**零断言** → 新增 `MeStatusLocalEligibilityGuardTest`(2 用例:not_declared / 真实回读) |
|
||||
| 7 | 未核验影响匹配资格 | ✅ 后端 | `RecommendationEligibilityPhase06Test`(`localEligibilityRequired` 接入) |
|
||||
| 8 | 审核进入 `local_eligibility` 队列 | ✅ 后端 | `ReviewServicePhase05Test` |
|
||||
| 9 | 审核通过更新状态 | ✅ 后端 | 同上 |
|
||||
| 10 | 需补充佐证触发用户上传 | ✅ 后端+页面 | `ReviewService` 置 `needs_evidence`、`LocalEligibilityService` 上传后回 `pending_review`(`LocalEligibilityServiceTest` 覆盖分支);用户端承接 `local-eligibility-supplement` 页 + `getStatus.evidenceRequired` |
|
||||
| 11 | 查看佐证须记录审计 | ✅ 后端 | `Inv011EvidenceAccessGuardTest`(success/denied 双向审计落库回查) |
|
||||
|
||||
**权限 10 项**
|
||||
|
||||
| # | 场景 | 覆盖 | 位置 |
|
||||
|---|---|---|---|
|
||||
| 1 | Counselor 只看 assigned(INV-012) | ✅ 双层 | 后端 `CounselingPhase13ATest` + E2E `counseling_v11_test::test_counselor_assigned_list_is_scoped` |
|
||||
| 2 | Counselor 不可看他人服务 | ✅ E2E | `counseling_permission_test::test_counselor_cannot_read_unassigned_service` |
|
||||
| 3 | 红娘不可读咨询正文(INV-013) | ✅ 双层 | 后端 `V11InvariantsTest#inv013`(真实 `getDetail` 脱敏)+ E2E `counseling_permission_test::test_matchmaker_has_no_counseling_view` |
|
||||
| 4 | 普通管理员不可读敏感备注 | ✅ 后端 | `CounselingPhase13ATest`(脱敏分层:manager 摘要 / 非 assigned 脱敏) |
|
||||
| 5 | 咨询管理员可分配 | ✅ 双层 | 后端 `assignIfPending` 守卫 + E2E `run_v11_tests::test_08` |
|
||||
| 6 | 咨询可转交风控 | ✅ 后端 | `RiskReferralService` + `ReportAndRiskPhase17IntegrationTest`(`detectCounselingRisk` 来源接入) |
|
||||
| 7 | 服务站人员不能浏览全用户(INV-018) | ✅ 双层 | E2E `organization_isolation_test` + `service_station_permission_test` + 后端 `ServiceAppointmentDataScopeTest` |
|
||||
| 8 | 服务站人员可处理本站预约 | ✅ E2E | `service_appointment_test::test_operator_can_confirm`(seed 站点范围) |
|
||||
| 9 | 内容编辑员不能访问用户档案(INV-019) | ✅ 双层 | 后端 `V11InvariantsTest#inv019` + E2E `content_e2e_test::test_content_editor_cannot_read_user_matching_data` |
|
||||
| 10 | 内容编辑员可管理文章 | ✅ E2E | `content_e2e_test::test_article_lifecycle_draft_publish_offline` |
|
||||
|
||||
**对账结论**:32 项场景中 **31 项有真实断言覆盖**(E2E 或后端集成),1 项(首页 #10)被产品裁定取代并记录取代关系;发现并补齐 **1 处零断言字段**(`/me/status.localEligibilityStatus`,场景 #6)。无「假覆盖」残留。
|
||||
|
||||
### Task 2.2: INV-011 显式守卫
|
||||
|
||||
**Files:**
|
||||
|
||||
Reference in New Issue
Block a user