| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- /**
- * SC-P-10 创建任务+下发(task.parent.createTask / S-cfc-12)— API 级 e2e
- * ================================================================
- * 场景(tests/user-scenarios/应用场景总览.md 业务域B / 02-parent.md P-4):
- * 建任务(成员/积分/频率)→ 下发 → B 列表待执行;边界:重复提交不重复发分、频率/截止边界。
- * 关联用例:test-cases/parent.md TC-P-05(正常)/ TC-P-05-E(异常)/ TC-P-05-P(权限)。
- * 设计锚点(API_REFERENCE.md 4.3 / family-management-flow.md):
- * POST /api/tasks/create(TaskController→TaskService.createTask)、POST /api/tasks/today。
- *
- * 运行:
- * 本 feature 分支实现(本地/自建实例):npx playwright test api/task-create-scp10.spec.js \
- * -c playwright.config.api.local.js --project=desktop
- * 远端已部署基线:npx playwright test api/task-create-scp10.spec.js \
- * -c playwright.config.api.js --project=desktop
- * (异常流/越权注入类用例仅在本 feature 实现上通过——远端基线无参数校验/幂等/跨家庭校验)
- */
- const { test, expect } = require('@playwright/test');
- const { registerRole, apiPost, uniqueSuffix } = require('../helpers/api');
- test.describe('SC-P-10 创建任务+下发(TC-P-05/05-E/05-P)', () => {
- let parent; // 家庭1 家长(下发方)
- let childB; // 家庭1 孩子成员(执行人 B)
- let outsider; // 家庭2 家长(越权方)
- let outsiderChild; // 家庭2 孩子成员
- test.beforeAll(async ({ request }) => {
- parent = await registerRole(request, 'parent', 'QA-P10-P');
- const memList = await apiPost(request, '/api/family/member/list', {}, parent.token);
- expect(memList.code, memList.message).toBe(200);
- const self = (memList.data || []).find((m) => String(m.userId) === String(parent.userId));
- expect(self, '家长本人 member 记录').toBeTruthy();
- const addChild = await apiPost(request, '/api/family/member/add', {
- relativeMemberId: self.id,
- generationLevel: 'child',
- nickname: 'B-小王' + uniqueSuffix(),
- gender: 'male',
- birthday: '2015-06-01',
- }, parent.token);
- expect(addChild.code, '添加孩子成员: ' + addChild.message).toBe(200);
- childB = addChild.data.id;
- outsider = await registerRole(request, 'parent', 'QA-P10-X');
- const xList = await apiPost(request, '/api/family/member/list', {}, outsider.token);
- const xSelf = (xList.data || []).find((m) => String(m.userId) === String(outsider.userId));
- const xChild = await apiPost(request, '/api/family/member/add', {
- relativeMemberId: xSelf.id,
- generationLevel: 'child',
- nickname: 'Y-小明' + uniqueSuffix(),
- gender: 'male',
- }, outsider.token);
- expect(xChild.code, '家庭2添加孩子: ' + xChild.message).toBe(200);
- outsiderChild = xChild.data.id;
- });
- test.describe('正常流(TC-P-05)', () => {
- test('家长创建任务(标题/成员B/积分10/频率每日) → 任务出现在B列表,状态待执行', async ({ request }) => {
- const title = '睡前阅读' + uniqueSuffix();
- const created = await apiPost(request, '/api/tasks/create', {
- title,
- points: 10,
- memberId: childB,
- frequency: 'daily',
- needReview: 0,
- }, parent.token);
- expect(created.code, '创建任务: ' + created.message).toBe(200);
- const taskId = created.data;
- expect(taskId).toBeTruthy();
- const today = await apiPost(request, '/api/tasks/today', { memberId: childB }, parent.token);
- expect(today.code, today.message).toBe(200);
- const hit = (today.data || []).find((t) => String(t.id) === String(taskId));
- expect(hit, '任务应出现在 B 的今日列表').toBeTruthy();
- expect(hit.status, '下发后状态应为待执行').toBe('pending');
- expect(String(hit.familyMemberId)).toBe(String(childB));
- expect(hit.points).toBe(10);
- expect(hit.frequency).toBe('daily');
- });
- });
- test.describe('异常流(TC-P-05-E)', () => {
- test('频率非法(0/负数/非枚举) → 400', async ({ request }) => {
- for (const freq of ['0', '-1', '999']) {
- const r = await apiPost(request, '/api/tasks/create', {
- title: '频率非法' + freq, points: 5, memberId: childB, frequency: freq,
- }, parent.token);
- expect(r.code, '频率=' + freq + ' 应 400: ' + r.message).toBe(400);
- expect(String(r.message)).toContain('频率');
- }
- });
- test('负积分 → 400', async ({ request }) => {
- const r = await apiPost(request, '/api/tasks/create', {
- title: '负积分', points: -5, memberId: childB, frequency: 'daily',
- }, parent.token);
- expect(r.code, r.message).toBe(400);
- expect(String(r.message)).toContain('积分');
- });
- test('空标题 → 400', async ({ request }) => {
- const r = await apiPost(request, '/api/tasks/create', {
- title: '', points: 5, memberId: childB, frequency: 'daily',
- }, parent.token);
- expect(r.code, r.message).toBe(400);
- expect(String(r.message)).toContain('标题');
- });
- test('截止时间在过去 → 400(超期任务拒绝提交)', async ({ request }) => {
- const r = await apiPost(request, '/api/tasks/create', {
- title: '超期任务', points: 5, memberId: childB, frequency: 'daily',
- deadline: '2020-01-01T00:00:00',
- }, parent.token);
- expect(r.code, r.message).toBe(400);
- expect(String(r.message)).toContain('截止');
- });
- test('重复提交同一创建请求 → 仅生成1条任务(幂等)', async ({ request }) => {
- const body = {
- title: '幂等任务' + uniqueSuffix(), points: 7, memberId: childB, frequency: 'daily',
- };
- const r1 = await apiPost(request, '/api/tasks/create', body, parent.token);
- const r2 = await apiPost(request, '/api/tasks/create', body, parent.token);
- expect(r1.code, r1.message).toBe(200);
- expect(r2.code, r2.message).toBe(200);
- expect(String(r1.data), '重复提交应复用同一任务 id').toBe(String(r2.data));
- const today = await apiPost(request, '/api/tasks/today', { memberId: childB }, parent.token);
- const hits = (today.data || []).filter((t) => String(t.id) === String(r1.data));
- expect(hits.length, 'B 列表中该任务应只有 1 条').toBe(1);
- });
- test('并发双发同一创建请求 → 仅生成1条任务', async ({ request }) => {
- const body = {
- title: '并发任务' + uniqueSuffix(), points: 3, memberId: childB, frequency: 'daily',
- };
- const [a, b] = await Promise.all([
- apiPost(request, '/api/tasks/create', body, parent.token),
- apiPost(request, '/api/tasks/create', body, parent.token),
- ]);
- expect(a.code, a.message).toBe(200);
- expect(b.code, b.message).toBe(200);
- expect(String(a.data), '并发双发应复用同一任务 id').toBe(String(b.data));
- });
- });
- test.describe('权限流(TC-P-05-P)', () => {
- test('他人家庭家长为孩子 B 创建任务 → 400 拒绝(横向越权注入)', async ({ request }) => {
- const r = await apiPost(request, '/api/tasks/create', {
- title: 'X越权任务', points: 5, memberId: childB, frequency: 'daily',
- }, outsider.token);
- expect(r.code, r.message).toBe(400);
- expect(String(r.message)).toContain('不属于本家庭');
- });
- test('member 类型给其他家庭孩子下发 → 400 拒绝', async ({ request }) => {
- const r = await apiPost(request, '/api/tasks/create', {
- title: '跨家庭member下发', points: 5, executorType: 'member', memberIds: [outsiderChild], frequency: 'daily',
- }, parent.token);
- expect(r.code, r.message).toBe(400);
- expect(String(r.message)).toContain('不属于本家庭');
- });
- test('越权查看他人家庭成员列表 → 403', async ({ request }) => {
- const r = await apiPost(request, '/api/family/member/list', { familyId: outsider.familyId }, parent.token);
- expect(r.code, r.message).toBe(403);
- expect(String(r.message)).toContain('无权访问');
- });
- });
- });
|