incentive-loop-sc-x05.spec.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. /**
  2. * SC-X-05 任务激励闭环全链路三类流(task.closedLoop.incentive)
  3. * ================================================================
  4. * 正常流:创建任务 → 完成 → 得积分/CF → 提心愿 → 申请兑换 → 批准 → 标记购买 → 确认收货(闭环);
  5. * 异常流:闭环中途非法状态操作被拒;重复完成不双计分;
  6. * 权限流:闭环中跨家庭干扰被拒。
  7. */
  8. const { test, expect } = require('@playwright/test');
  9. const { registerRole, apiPost, uniqueSuffix } = require('../helpers/api');
  10. test.describe('SC-X-05 任务激励闭环三类流', () => {
  11. let parent;
  12. let childA;
  13. test.beforeAll(async ({ request }) => {
  14. parent = await registerRole(request, 'parent', 'QA-LOOP');
  15. const mem = await apiPost(request, '/api/family/member/list', {}, parent.token);
  16. const self = (mem.data || []).find((m) => String(m.userId) === String(parent.userId));
  17. const addA = await apiPost(request, '/api/family/member/add', {
  18. relativeMemberId: self.id, generationLevel: 'child',
  19. nickname: 'A-Loop' + uniqueSuffix(), gender: 'female', birthday: '2016-02-01',
  20. }, parent.token);
  21. expect(addA.code, 'addA: ' + addA.message).toBe(200);
  22. childA = addA.data.id;
  23. });
  24. test('[X-05 正常] 任务→积分→心愿→兑换→批准→收货闭环', async ({ request }) => {
  25. // 1) 任务创建+完成得积分
  26. const task = await apiPost(request, '/api/tasks/create', {
  27. title: '闭环任务' + uniqueSuffix(), points: 50, memberId: childA, frequency: 'none', needReview: 0,
  28. }, parent.token);
  29. expect(task.code, 'task create: ' + task.message).toBe(200);
  30. const taskId = task.data;
  31. const done = await apiPost(request, `/api/tasks/${taskId}/complete`, { memberId: childA }, parent.token);
  32. expect(done.code, 'task complete: ' + done.message).toBe(200);
  33. expect(done.data.pointsEarned).toBeGreaterThan(0);
  34. // 2) 提心愿(CF 已由任务发放)
  35. const wish = await apiPost(request, '/api/wishes/create', {
  36. title: '闭环心愿' + uniqueSuffix(), memberId: childA, pointsRequired: 20, category: '文具',
  37. }, parent.token);
  38. expect(wish.code, 'wish create: ' + wish.message).toBe(200);
  39. const wishId = wish.data;
  40. // 3) 申请兑换 → 批准 → 标记购买 → 确认收货
  41. const req = await apiPost(request, `/api/wishes/${wishId}/exchange`, { memberId: childA }, parent.token);
  42. expect(req.code, 'exchange: ' + req.message).toBe(200);
  43. const approve = await apiPost(request, `/api/wishes/${wishId}/approve-exchange`, {
  44. memberId: childA, approved: true,
  45. }, parent.token);
  46. expect(approve.code, 'approve: ' + approve.message).toBe(200);
  47. const fulfill = await apiPost(request, `/api/wishes/${wishId}/fulfill`, {}, parent.token);
  48. expect(fulfill.code, 'fulfill: ' + fulfill.message).toBe(200);
  49. const confirm = await apiPost(request, `/api/wishes/${wishId}/confirm`, {}, parent.token);
  50. expect(confirm.code, 'confirm: ' + confirm.message).toBe(200);
  51. const detail = await apiPost(request, `/api/wishes/${wishId}`, {}, parent.token);
  52. expect(detail.data.status).toBe('fulfilled');
  53. });
  54. test('[X-05-E 异常] 重复完成已通过任务 → 不双计分', async ({ request }) => {
  55. const task = await apiPost(request, '/api/tasks/create', {
  56. title: '闭环幂等' + uniqueSuffix(), points: 10, memberId: childA, frequency: 'none', needReview: 0,
  57. }, parent.token);
  58. expect(task.code).toBe(200);
  59. const taskId = task.data;
  60. const first = await apiPost(request, `/api/tasks/${taskId}/complete`, { memberId: childA }, parent.token);
  61. expect(first.code).toBe(200);
  62. const second = await apiPost(request, `/api/tasks/${taskId}/complete`, { memberId: childA }, parent.token);
  63. // 幂等:第二次 pointsEarned=0
  64. expect(second.code).toBe(200);
  65. if (second.data && second.data.pointsEarned !== undefined) {
  66. expect(second.data.pointsEarned).toBe(0);
  67. }
  68. });
  69. test('[X-05-P 权限] 闭环中跨家庭干扰被拒', async ({ request }) => {
  70. const outsider = await registerRole(request, 'parent', 'QA-LOOP-X');
  71. const wish = await apiPost(request, '/api/wishes/create', {
  72. title: '闭环他人' + uniqueSuffix(), memberId: childA, pointsRequired: 5,
  73. }, parent.token);
  74. expect(wish.code).toBe(200);
  75. const wishId = wish.data;
  76. // 外人越权审批
  77. const xApprove = await apiPost(request, `/api/wishes/${wishId}/approve-exchange`, {
  78. memberId: childA, approved: true,
  79. }, outsider.token);
  80. expect(xApprove.code).not.toBe(200);
  81. });
  82. });