energy-api.spec.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /**
  2. * 【API级】能量系统收支全流程
  3. * ================================================================
  4. * 对应 DOM 版:e2e/energy-system.spec.js(远程无 SPA,DOM 版不可跑)
  5. * 接口契约来源:EnergyController(/api/energy/*)
  6. * sandbox : {} 家庭能量沙盘(五维)
  7. * overview : { memberId? } 能量概览
  8. * logs : { memberId?, dimensionCode?, page?, size? } 能量流水(Page)
  9. * grant : { memberId, taskId, amount, dimensionCode } 任务完成发放能量
  10. * deduct-for-wish: { memberId, wishId, amount } 心愿兑换扣能量
  11. * 五维维度代码:body(身) / mind(心) / wisdom(智) / action(行) / wealth(富)
  12. * 运行:npx playwright test -c playwright.config.api.js --project=desktop
  13. */
  14. const { test, expect } = require('@playwright/test');
  15. const { registerRole, apiPost, pickMemberByUserId, uniqueSuffix } = require('../helpers/api');
  16. const DIMENSION = 'body'; // 身
  17. test.describe('【API级】能量系统收支全流程', () => {
  18. let ctx;
  19. let selfMemberId;
  20. test.beforeAll(async ({ request }) => {
  21. ctx = await registerRole(request, 'parent', 'QA-ENERGY');
  22. const list = await apiPost(request, '/api/family/member/list', {}, ctx.token);
  23. expect(list.code).toBe(200);
  24. const self = pickMemberByUserId(list.data, ctx.userId);
  25. expect(self, '成员列表应包含登录用户自身').toBeTruthy();
  26. selfMemberId = self.id;
  27. });
  28. // ===== 场景1:获取家庭能量沙盘 =====
  29. test('[场景1] 获取家庭能量沙盘 - 期望返回五维能量数据', async ({ request }) => {
  30. const r = await apiPost(request, '/api/energy/sandbox', {}, ctx.token);
  31. expect(r.code).toBe(200);
  32. expect(r.data).toBeTruthy();
  33. expect(typeof r.data).toBe('object');
  34. });
  35. test('[场景1b] 未加入家庭/越权访问 - 期望返回错误提示', async ({ request }) => {
  36. const r = await apiPost(request, '/api/family/member/list', { familyId: 999999999 }, ctx.token);
  37. expect(r.code).toBe(403);
  38. expect(String(r.message)).toContain('无权访问');
  39. });
  40. // ===== 场景2:任务完成能量发放 =====
  41. /**
  42. * CFC-EN-001:/api/energy/grant 已修复(维度由调用方显式指定,能量直接发放到该维度)。
  43. * 用例断言成功路径(200 + data + 流水记录),不再以 fixme 标记阻塞。
  44. */
  45. test('[场景2] 任务完成能量发放 - 期望能量增加且流水记录', async ({ request }) => {
  46. const amount = 10;
  47. const taskId = Number(uniqueSuffix());
  48. const grant = await apiPost(
  49. request,
  50. '/api/energy/grant',
  51. { memberId: selfMemberId, taskId, amount, dimensionCode: DIMENSION },
  52. ctx.token
  53. );
  54. expect(grant.code, '能量发放应成功').toBe(200);
  55. expect(grant.data).toBeTruthy();
  56. // 流水应记录本次发放
  57. const logs = await apiPost(request, '/api/energy/logs', { memberId: selfMemberId }, ctx.token);
  58. expect(logs.code).toBe(200);
  59. const records = (logs.data && logs.data.records) || [];
  60. expect(Array.isArray(records)).toBeTruthy();
  61. expect(records.some((r) => String(r.sourceId) === String(taskId))).toBeTruthy();
  62. });
  63. test('[场景2b] 能量发放缺参 - 期望返回错误提示', async ({ request }) => {
  64. const r = await apiPost(request, '/api/energy/grant', { memberId: selfMemberId }, ctx.token);
  65. expect(r.code).not.toBe(200);
  66. expect(String(r.message)).toContain('不能为空');
  67. });
  68. // ===== 场景3:能量兑换心愿 =====
  69. test('[场景3] 能量兑换心愿 - 期望参数校验生效且未扣减能量', async ({ request }) => {
  70. // 3a) 缺参必须被拒
  71. const missing = await apiPost(
  72. request,
  73. '/api/energy/deduct-for-wish',
  74. { memberId: selfMemberId },
  75. ctx.token
  76. );
  77. expect(missing.code).not.toBe(200);
  78. expect(String(missing.message)).toContain('不能为空');
  79. // 3b) 心愿不存在时,接口以 code=200 + data=false 表达「未扣减」(布尔返回语义)
  80. const absent = await apiPost(
  81. request,
  82. '/api/energy/deduct-for-wish',
  83. { memberId: selfMemberId, wishId: 999999999, amount: 1 },
  84. ctx.token
  85. );
  86. expect(absent.code).toBe(200);
  87. expect(absent.data).toBe(false);
  88. });
  89. // ===== 场景4:查询能量流水 =====
  90. test('[场景4] 查询能量流水 - 期望返回分页记录', async ({ request }) => {
  91. const r = await apiPost(request, '/api/energy/logs', { memberId: selfMemberId, page: 1, size: 10 }, ctx.token);
  92. expect(r.code).toBe(200);
  93. expect(r.data).toBeTruthy();
  94. expect(Array.isArray(r.data.records)).toBeTruthy();
  95. expect(r.data).toHaveProperty('total');
  96. });
  97. // ===== 场景5:五维能量概览 =====
  98. test('[场景5] 获取五维能量概览 - 期望返回五维汇总数据', async ({ request }) => {
  99. const r = await apiPost(request, '/api/energy/overview', { memberId: selfMemberId }, ctx.token);
  100. expect(r.code).toBe(200);
  101. expect(r.data).toBeTruthy();
  102. // 五维:身/心/智/行/富 → body / mind / wisdom / action / wealth
  103. const dims = (r.data && r.data.dimensions) || [];
  104. expect(Array.isArray(dims)).toBeTruthy();
  105. const codes = dims.map((d) => d.code);
  106. for (const c of ['body', 'mind', 'wisdom', 'action', 'wealth']) {
  107. expect(codes, '五维概览应包含维度 ' + c).toContain(c);
  108. }
  109. });
  110. // ===== 场景6:完整能量收支流程 =====
  111. test('[场景6] 完整能量收支流程 - 期望全流程成功', async ({ request }) => {
  112. const taskId = Number(uniqueSuffix());
  113. const amount = 5;
  114. // 1) 发放
  115. const grant = await apiPost(
  116. request,
  117. '/api/energy/grant',
  118. { memberId: selfMemberId, taskId, amount, dimensionCode: DIMENSION },
  119. ctx.token
  120. );
  121. expect(grant.code).toBe(200);
  122. // 2) 流水可查
  123. const logs = await apiPost(request, '/api/energy/logs', { memberId: selfMemberId }, ctx.token);
  124. expect(logs.code).toBe(200);
  125. expect(logs.data.records.some((r) => String(r.sourceId) === String(taskId))).toBeTruthy();
  126. // 3) 概览可查
  127. const overview = await apiPost(request, '/api/energy/overview', { memberId: selfMemberId }, ctx.token);
  128. expect(overview.code).toBe(200);
  129. });
  130. });