fix(matchmaker,referral): close Phase 13 KEEP-VERIFY/EXTEND — 7 real defects

KEEP-VERIFY/EXTEND 复验(历史 ✅ 不可信;测试覆盖函数 ≠ 覆盖运行时路径)。
逐条核对运行时路径与生产约束后修复 7 处缺陷,其中 3 处「只在真实 PostgreSQL 上炸」。

D1 (Critical, 生产故障)
  matchmaker_recommendations.status 代码写 liked/passed/confirmed_mutual,
  而 V009 chk_rec_status 只允许 pending_view/viewed/responded 且从未扩展
  → 真实 PG 上用户接受/跳过推荐必崩。测试库未复刻该 CHECK,故长期静默。

D2 (Critical, 生产故障)
  控制器缺省 serviceType="recommendation" 不在 chk_matchmaker_service_type
  白名单 → 提交红娘服务必崩。
  D1+D2 同修于新增 V038__align_matchmaker_status_constraints.sql
  (沿用 V018/V020/V025 的 DROP CONSTRAINT IF EXISTS + 重加白名单惯例)。

D3 (High, 越权写)
  MatchmakerServiceService.recommendPartner 无 assignedOperatorId 归属校验,
  且可把用户推荐给自己 → 补校验 + 禁止自我推荐。

D4 (High, 越权读)
  getServiceDetail 无归属校验且返回完整用户资料(userId/description/
  userProfile/followups)→ 签名加 requesterId,按调用者返回完整/脱敏视图。

D5 (High, 越权写)
  ReferralService 的 userId 取自请求体且零授权校验 → 新增
  assertMatchmakerServesUser,在 referToServiceStation/referToActivity 入口
  强制校验存在进行中的服务关系。

D6 (High, 生产故障)
  MatchmakerFollowupMapper.findByServiceId 只有声明无 @Select
  → Invalid bound statement (not found),getServiceDetail 必崩。
  该缺陷由本轮新增测试首次暴露(历史测试从未调用该路径)→ 补 @Select。

D7 (Medium, 契约缺口)
  admin-web 5 处 API 缺口(4 条 404 + C 端端点被运营端误用)+ 小程序缺转介服务层
  → 后端补 GET /operator/matchmaker/recommendations 与
  GET /referrals/operator/history 两个运营端端点;前端改路径、
  assign 改 POST + body operatorId;新增 miniapp/services/referralService.js。

测试保真度
  - 测试库补 matchmaker_services/recommendations/followups 三表(此前该模块零真实覆盖),
    约束与 V038 对齐;EntityMigrationParityTest 三表移入 REQUIRED_IN_TEST_SCHEMA;
    MigrationValidationTest LATEST_MIGRATION 37 → 38。
  - 新增 MatchmakerReferralPhase13Test(17 用例,@SpringBootTest 走真实方法路径 + 真实落库)。
  - 重写 MatchmakerServiceTest(6 用例):直接解析 db/migration/V*.sql,按文件名顺序应用
    DROP CONSTRAINT IF EXISTS + ADD CONSTRAINT 取"最终有效"白名单,并内置解析器自检。
    【关键】首版守卫硬编码白名单副本,注入 D1 后仍全绿(对漂移零反应)→ 必须解析真实 DDL。
  - 删除 3 处假测试占位(MatchmakerServiceTest.testMatchmakerCannotLike 的 assertTrue(true);
    CoreInvariantsTest INV-007 断言的是状态常量、INV-010 亦为 assertTrue(true)),
    已如实改名并注明行为验证位置。

负向验证(两轮)
  注入 D1(回退 chk_rec_status 白名单)→ 行为守卫 DataIntegrityViolation
  + 常量守卫 Multiple Failures (3 failures) → Tests run: 23, Failures: 1, Errors: 2
  / BUILD FAILURE,守卫有效性得证。缺陷已还原。

全量回归(离线容器,实测)
  [WARNING] Tests run: 391, Failures: 0, Errors: 0, Skipped: 3
  [INFO] BUILD SUCCESS / Total time: 04:59 min
  Phase 12 基线 371 → 391(净 +20)。

报告:docs/Phase-13-V11-Recovery-Completion-Report-2026-09-16.md
Phase 13A 未自动进入,等待用户确认。
This commit is contained in:
WorkBuddy
2026-09-17 00:01:53 +08:00
parent c607e87b45
commit 5b3bad326e
21 changed files with 1336 additions and 71 deletions
+53
View File
@@ -0,0 +1,53 @@
/**
* 红娘转介服务(Phase 13)
*
* 封装「用户确认转介」的两阶段流程与「我的转介历史」。
*
* 关键不变量(AGENTS.md):
* - 第 5 条:红娘不可替用户操作。转介由红娘发起(运营端),
* **必须由用户本人在小程序确认** 才算生效 —— 本层只提供用户侧的
* 确认与查询,不提供任何"代用户确认"的入口。
* - 转介形成可追踪记录(source=matchmaker_referral + createdByOperatorId + userConfirmedAt)。
*
* 后端端点:
* GET /api/v1/referrals/my-history — 我的转介历史(UserPrincipal)
* POST /api/v1/referrals/{id}/confirm — 用户确认转介
*/
const api = require('../utils/api');
/**
* 我的转介历史(含是否已确认)
* @returns {Promise<Array<{
* type: 'service_station'|'activity',
* id: string,
* status: string,
* createdByOperatorId: string,
* userConfirmedAt: string|null,
* createdAt: string
* }>>}
*/
function getMyReferralHistory() {
return api.get('/referrals/my-history');
}
/**
* 用户确认转介(两阶段流程的第二阶段)
*
* ⚠️ 只有转介的目标用户本人可以确认;他人调用后端会拒绝。
* @param {string} targetId 转介记录 ID(appointmentId 或 registrationId)
* @param {'service_station'|'activity'} targetType
* @returns {Promise<{ id: string, status: string, confirmedAt: string }>}
*/
function confirmReferral(targetId, targetType) {
return api.post(
`/referrals/${encodeURIComponent(targetId)}/confirm`,
{ targetType },
undefined,
`referral-confirm:${targetId}:${targetType}`
);
}
module.exports = {
getMyReferralHistory,
confirmReferral,
};