Files
danyang-match/tests/e2e/sql/seed_e2e_activity.sql
T
WorkBuddy e666ee6f8a feat(e2e): Phase 23 Task 2.1 —— 活动互选 8 场景 + 服务预约 8 场景真实实现(16 个真实用例)
背景:`tests/e2e/activity_mutual_consent_test.py` 与 `service_appointment_test.py` 此前是
**诚实占位**(仅 docstring、0 用例,指针指向 run_v11_tests.py),计划 Task 2.1 要求把
文件头保留的场景清单逐条实现为真实 HTTP 用例。本提交完成该任务。

═══ 新增真实用例 16 个(本机活栈 pytest tests/e2e 49 → 65 passed)═══

activity_mutual_consent_test.py(8,覆盖 INV-014/015/006 + AGENTS.md #19/#20)
  1 建立互选记录不建 Match(INV-014)
  2 单方确认不建 Match(INV-015)
  3 双方确认建 Match Episode 且双方可见
  4 已有 active Match 时再次双确认必须复用(#19)
  5 曾 Unmatch 后重新双确认开启新 Episode:episode +1(#20)
  6 一方拒绝不建 Match,对方随后确认也不建
  7 超时:已过期记录不得被推动成双确认(含「未过期必须生效」对照,防假绿)
  8 单向意向不泄露(INV-006,含「B 确认后必须看到 accepted」反证)

service_appointment_test.py(8)
  1 提交预约;2 幂等键重放不得产生第二条;3 运营确认 submitted→confirmed(用户侧同态可见)
  4 并发「用户取消 × 运营确认」不得 5xx、最终状态必须收敛;5 我的列表只含本人
  6 待确认可取消;7 归属隔离(他人不可读详情/不可取消)
  8 运营侧状态闸门:已完成预约不得再被取消

═══ 两个幂等 seed(E2E 前置数据,本机与 CI 通用)═══

- seed_e2e_activity.sql:organization + 两个 activity。
  必须建的原因是硬约束而非选择:`activity_mutual_connection_consents.activity_id`
  有 **FK 到 activities(id)**(实测约束名 …_activity_id_fkey),而 activities.organizer_org_id
  又 FK 到 organizations(id) 且 NOT NULL —— 全新库里三张表全空,互选无活动可挂。
- seed_e2e_service_station.sql:service_location + **站点-运营关联**。
  关联必需:运营确认受数据范围约束(`assertOperatorScope` → `canAccessByOperator`),
  super_admin **不豁免**,实测 e2e_admin 确认他站预约返回 403。
  E2E 因此改用 seed 中该站点关联的 e2e_station。

两个 seed 由测试首用时幂等执行(docker exec psql),无需改 CI 步骤。

═══ 负向注入(Task 2.1 Step 3)═══

③ 撤销 service_station_operator 的 service_appointment.confirm →
   恰 2 红(test_operator_can_confirm / test_operator_cannot_cancel_completed,403),其余 6 绿;
④ 触发器强制新建互选记录的对方侧 accepted →
   3 红(both_confirm_creates_match_episode / timeout_handled / single_interest_not_leaked),其余 5 绿。
两处均已还原并复跑全绿(65 passed),确认无残留(无 e2e* 触发器、权限计数回到 1)。

**两次失败的注入(记录以免后人重复踩)**:
- 删除站点→运营关联:**无效** —— 测试的 seed fixture 在用例前幂等重放 seed 会自愈该前置条件;
- 改 user_b_status 列默认值:**无效** —— `createMutualConsent` 在 Java 侧显式写 pending,
  DB 默认值根本不参与(侧面证明写入路径的权威性)。

═══ 计划文档校正(审阅产物)═══

按实测推翻 4 处失实断言,登记 7 项新发现(详见计划文档「附 A / 附 B」):
- 推翻:matchmaker_services「无表/需扩权」(实为 V009 已建表且 mapper 齐备);
  门禁 14/14(实为 18/18);「tests/e2e 现为占位」(实为仅 2 个文件);
  E2E 基线 49(实为 65)。
- 新发现:① `POST /operator/organizations` 必然 500(createOrganization 不赋 id +
  IdType.INPUT + 无 DB 默认值;且未校验 chk_org_type)→ 连带「无法新建活动」;
  ② `createMutualConsent` 从不写 expires_at → 互选永不过期(即批 3 P16-T13,配置与
  V041 部分索引空转);③ 用户侧取消预约不校验状态(已确认/已完成也能自取消),
  与运营侧明确拒绝 completed 口径不一致 → 需产品裁定;
  ④ E2E 环境 organizations/activities/service_locations 均为空表(无迁移种子);
  ⑤ 运营数据范围 super_admin 不豁免(预期行为,勿误判);
  ⑥ 本机复跑会遇到 Redis 登录锁定键(auth:operator_login_lock:*);
  ⑦ e2e_* 账号因 ON CONFLICT DO NOTHING 重跑 seed 不覆盖旧口令。

═══ 验证 ═══

- `pytest tests/e2e` → **65 passed**(含新增 16);`node scripts/run-ui-contracts.mjs` → **18/18 PASS**,退出 0;
- 本机环境处置(不属代码改动):把 6 个 e2e_* 账号口令对齐到 CI 口令、清除 Redis 登录锁定键。
- **未验证**:微信开发者工具、CI 上的首次 E2E 实测绿(Task 2.3 Step 3,本机无 Docker Hub 出口)。
2026-09-19 12:28:16 +08:00

46 lines
2.5 KiB
SQL
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.
-- ============================================================
-- E2E 活动互选前置数据 seed(幂等,本机与 CI 通用)
--
-- 为什么需要:
-- `activity_mutual_connection_consents.activity_id` 有 **FK 到 activities(id)**
-- (实测:约束名 activity_mutual_connection_consents_activity_id_fkey),
-- 而 `activities.organizer_org_id` 又有 FK 到 organizations(id) 且 NOT NULL。
-- 全新库里 organizations / activities 都是**空表** —— 互选 E2E 无活动可挂。
--
-- 为什么不走运营 API 建:
-- 实测 `POST /operator/organizations` 当前**必然 500**
-- (`OrganizationService.createOrganization` 从不给 id 赋值,
-- 而 BaseEntity 用 `@TableId(type = IdType.INPUT)`、organizations.id 无 DB 默认值
-- → `invalid input syntax for type uuid: ""`)。
-- 该缺陷已单独记录,不属本 seed 的职责范围;此处按 E2E 基线数据的既有约定
-- (同目录 seed_e2e_operators.sql)直接落库。
--
-- 幂等:全部 ON CONFLICT DO NOTHING,可重复执行。
-- 固定 UUID:测试按常量引用,不依赖「查库拿 id」。
-- ============================================================
-- type 必须落在生产 chk_org_type 白名单内:
-- women_federation / trade_union / charity_center / volunteer_team / civil_affairs_partner / other
INSERT INTO organizations (id, name, type, status)
VALUES ('e2e00000-0000-0000-0000-0000000000a1', 'E2E 测试机构', 'charity_center', 'active')
ON CONFLICT (id) DO NOTHING;
-- 两个活动:互选用例需要「同一对用户在两个不同活动里各走一次」
-- (唯一约束是 (activity_id, user_a_id, user_b_id),换活动即换记录)。
INSERT INTO activities (
id, title, organizer_org_id, start_at, end_at,
location_name, location_address, total_limit,
need_review, status, created_by, activity_type
) VALUES
('e2e00000-0000-0000-0000-0000000000a2', 'E2E 互选测试活动 A',
'e2e00000-0000-0000-0000-0000000000a1',
NOW() - INTERVAL '1 day', NOW() + INTERVAL '30 day',
'E2E 测试地点', 'E2E 测试地址', 30,
false, 'ongoing', 'e2e00000-0000-0000-0000-000000000001', 'matchmaking'),
('e2e00000-0000-0000-0000-0000000000a3', 'E2E 互选测试活动 B',
'e2e00000-0000-0000-0000-0000000000a1',
NOW() - INTERVAL '1 day', NOW() + INTERVAL '30 day',
'E2E 测试地点', 'E2E 测试地址', 30,
false, 'ongoing', 'e2e00000-0000-0000-0000-000000000001', 'matchmaking')
ON CONFLICT (id) DO NOTHING;