/** * 【API级】能量系统收支全流程 * ================================================================ * 对应 DOM 版:e2e/energy-system.spec.js(远程无 SPA,DOM 版不可跑) * 接口契约来源:EnergyController(/api/energy/*) * sandbox : {} 家庭能量沙盘(五维) * overview : { memberId? } 能量概览 * logs : { memberId?, dimensionCode?, page?, size? } 能量流水(Page) * grant : { memberId, taskId, amount, dimensionCode } 任务完成发放能量 * deduct-for-wish: { memberId, wishId, amount } 心愿兑换扣能量 * 五维维度代码:body(身) / mind(心) / wisdom(智) / action(行) / wealth(富) * 运行:npx playwright test -c playwright.config.api.js --project=desktop */ const { test, expect } = require('@playwright/test'); const { registerRole, apiPost, pickMemberByUserId, uniqueSuffix } = require('../helpers/api'); const DIMENSION = 'body'; // 身 test.describe('【API级】能量系统收支全流程', () => { let ctx; let selfMemberId; test.beforeAll(async ({ request }) => { ctx = await registerRole(request, 'parent', 'QA-ENERGY'); const list = await apiPost(request, '/api/family/member/list', {}, ctx.token); expect(list.code).toBe(200); const self = pickMemberByUserId(list.data, ctx.userId); expect(self, '成员列表应包含登录用户自身').toBeTruthy(); selfMemberId = self.id; }); // ===== 场景1:获取家庭能量沙盘 ===== test('[场景1] 获取家庭能量沙盘 - 期望返回五维能量数据', async ({ request }) => { const r = await apiPost(request, '/api/energy/sandbox', {}, ctx.token); expect(r.code).toBe(200); expect(r.data).toBeTruthy(); expect(typeof r.data).toBe('object'); }); test('[场景1b] 未加入家庭/越权访问 - 期望返回错误提示', async ({ request }) => { const r = await apiPost(request, '/api/family/member/list', { familyId: 999999999 }, ctx.token); expect(r.code).toBe(403); expect(String(r.message)).toContain('无权访问'); }); // ===== 场景2:任务完成能量发放 ===== /** * CFC-EN-001:/api/energy/grant 已修复(维度由调用方显式指定,能量直接发放到该维度)。 * 用例断言成功路径(200 + data + 流水记录),不再以 fixme 标记阻塞。 */ test('[场景2] 任务完成能量发放 - 期望能量增加且流水记录', async ({ request }) => { const amount = 10; const taskId = Number(uniqueSuffix()); const grant = await apiPost( request, '/api/energy/grant', { memberId: selfMemberId, taskId, amount, dimensionCode: DIMENSION }, ctx.token ); expect(grant.code, '能量发放应成功').toBe(200); expect(grant.data).toBeTruthy(); // 流水应记录本次发放 const logs = await apiPost(request, '/api/energy/logs', { memberId: selfMemberId }, ctx.token); expect(logs.code).toBe(200); const records = (logs.data && logs.data.records) || []; expect(Array.isArray(records)).toBeTruthy(); expect(records.some((r) => String(r.sourceId) === String(taskId))).toBeTruthy(); }); test('[场景2b] 能量发放缺参 - 期望返回错误提示', async ({ request }) => { const r = await apiPost(request, '/api/energy/grant', { memberId: selfMemberId }, ctx.token); expect(r.code).not.toBe(200); expect(String(r.message)).toContain('不能为空'); }); // ===== 场景3:能量兑换心愿 ===== test('[场景3] 能量兑换心愿 - 期望参数校验生效且未扣减能量', async ({ request }) => { // 3a) 缺参必须被拒 const missing = await apiPost( request, '/api/energy/deduct-for-wish', { memberId: selfMemberId }, ctx.token ); expect(missing.code).not.toBe(200); expect(String(missing.message)).toContain('不能为空'); // 3b) 心愿不存在时,接口以 code=200 + data=false 表达「未扣减」(布尔返回语义) const absent = await apiPost( request, '/api/energy/deduct-for-wish', { memberId: selfMemberId, wishId: 999999999, amount: 1 }, ctx.token ); expect(absent.code).toBe(200); expect(absent.data).toBe(false); }); // ===== 场景4:查询能量流水 ===== test('[场景4] 查询能量流水 - 期望返回分页记录', async ({ request }) => { const r = await apiPost(request, '/api/energy/logs', { memberId: selfMemberId, page: 1, size: 10 }, ctx.token); expect(r.code).toBe(200); expect(r.data).toBeTruthy(); expect(Array.isArray(r.data.records)).toBeTruthy(); expect(r.data).toHaveProperty('total'); }); // ===== 场景5:五维能量概览 ===== test('[场景5] 获取五维能量概览 - 期望返回五维汇总数据', async ({ request }) => { const r = await apiPost(request, '/api/energy/overview', { memberId: selfMemberId }, ctx.token); expect(r.code).toBe(200); expect(r.data).toBeTruthy(); // 五维:身/心/智/行/富 → body / mind / wisdom / action / wealth const dims = (r.data && r.data.dimensions) || []; expect(Array.isArray(dims)).toBeTruthy(); const codes = dims.map((d) => d.code); for (const c of ['body', 'mind', 'wisdom', 'action', 'wealth']) { expect(codes, '五维概览应包含维度 ' + c).toContain(c); } }); // ===== 场景6:完整能量收支流程 ===== test('[场景6] 完整能量收支流程 - 期望全流程成功', async ({ request }) => { const taskId = Number(uniqueSuffix()); const amount = 5; // 1) 发放 const grant = await apiPost( request, '/api/energy/grant', { memberId: selfMemberId, taskId, amount, dimensionCode: DIMENSION }, ctx.token ); expect(grant.code).toBe(200); // 2) 流水可查 const logs = await apiPost(request, '/api/energy/logs', { memberId: selfMemberId }, ctx.token); expect(logs.code).toBe(200); expect(logs.data.records.some((r) => String(r.sourceId) === String(taskId))).toBeTruthy(); // 3) 概览可查 const overview = await apiPost(request, '/api/energy/overview', { memberId: selfMemberId }, ctx.token); expect(overview.code).toBe(200); }); });