From 28cf25756a5eaa87fac3612fb521548b7b73268d Mon Sep 17 00:00:00 2001 From: WorkBuddy Date: Sat, 19 Sep 2026 18:58:07 +0800 Subject: [PATCH] =?UTF-8?q?test(e2e-recon):=20E2E=20=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E5=AF=B9=E8=B4=A6=E8=A1=A8=EF=BC=8832=20=E9=A1=B9=EF=BC=9A31?= =?UTF-8?q?=20=E9=A1=B9=E6=9C=89=E7=9C=9F=E5=AE=9E=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E3=80=811=20=E9=A1=B9=E8=A2=AB=E8=A3=81=E5=AE=9A=E5=8F=96?= =?UTF-8?q?=E4=BB=A3=EF=BC=89+=20=E8=A1=A5=20/me/status.localEligibilitySt?= =?UTF-8?q?atus=20=E9=9B=B6=E6=96=AD=E8=A8=80=E7=BC=BA=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 对账表写入收尾计划 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) --- .../MeStatusLocalEligibilityGuardTest.java | 77 +++++++++++++++++++ ...9-18-phase23-closeout-and-uat-readiness.md | 54 +++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 backend/src/test/java/com/danyang/match/platform/auth/MeStatusLocalEligibilityGuardTest.java diff --git a/backend/src/test/java/com/danyang/match/platform/auth/MeStatusLocalEligibilityGuardTest.java b/backend/src/test/java/com/danyang/match/platform/auth/MeStatusLocalEligibilityGuardTest.java new file mode 100644 index 0000000..cae5a84 --- /dev/null +++ b/backend/src/test/java/com/danyang/match/platform/auth/MeStatusLocalEligibilityGuardTest.java @@ -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`。 + * + *

原始场景清单(Phase 23 占位文件头,「本地资格 11 项」第 6 项)在对账中发现 + * 零断言覆盖 —— 实现存在({@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 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 status = authService.getMeStatus(user.getId()); + assertEquals("declared", status.get("localEligibilityStatus"), + "/me/status 必须回读 user_local_eligibilities 的真实状态"); + assertEquals("household_registration", status.get("localEligibilityType")); + } +} diff --git a/docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md b/docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md index d22057b..d536d74 100644 --- a/docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md +++ b/docs/superpowers/plans/2026-09-18-phase23-closeout-and-uat-readiness.md @@ -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:**