| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- /**
- * ================================================================
- * 场景:测评预约模块 · 权限与异常 P0 用例
- * ================================================================
- * 来源:tests/test-cases/deep-dive-assessment-appointment.md
- * (代码级深挖:AssessmentAppointmentController / Service)
- *
- * 覆盖:
- * TC-DV-03 创建预约额度无效 → 报错且不落库
- * TC-DV-06 创建预约缺少 assessorId/guideId → 报错
- * TC-DV-05 非指派测评师确认 → 拒绝
- * TC-DV-07 guide-list 仅 teacher 可访问(权限隔离)
- * TC-DV-01 跨用户取消越权(安全缺口,数据依赖:需 APPOINTMENT_ID)
- *
- * 设计原则:
- * - 使用仓库既有 helpers/auth.loginAs 获取各角色 token(与现有 e2e 一致)。
- * - 纯 API 驱动(request fixture),不依赖 UI 导航,稳定且快。
- * - 仅断言 HTTP 状态码 / 业务 code / message 关键字,不打印 token 等敏感信息。
- *
- * 运行:
- * PW_BASE_URL=http://127.0.0.1:9082 npx playwright test tests/e2e/assessment-appointment.spec.js --project=desktop
- * 依赖:需先执行框架 globalSetup(reset-db.sh + 种子账号)。
- * ================================================================
- */
- const { test, expect } = require('@playwright/test');
- const { loginAs } = require('./helpers/auth');
- const BASE = process.env.PW_BASE_URL || 'http://127.0.0.1:9082';
- async function apiPost(request, token, path, body) {
- return request.post(BASE + path, {
- headers: {
- 'Content-Type': 'application/json',
- Authorization: 'Bearer ' + token,
- },
- data: body || {},
- });
- }
- test.describe('测评预约 · 权限与异常 P0', () => {
- let parent, teacher, otherParent;
- test.beforeAll(async ({ browser }) => {
- // 优先使用环境变量注入的 token(无需依赖 globalSetup 种子库),否则走 loginAs(需种子账号)
- parent = process.env.PARENT_TOKEN
- ? { token: process.env.PARENT_TOKEN, userId: Number(process.env.PARENT_USER_ID || 0) }
- : await loginAs(browser, 'parent');
- teacher = process.env.TEACHER_TOKEN
- ? { token: process.env.TEACHER_TOKEN, userId: Number(process.env.TEACHER_USER_ID || 0) }
- : await loginAs(browser, 'teacher');
- otherParent = process.env.OTHER_PARENT_TOKEN
- ? { token: process.env.OTHER_PARENT_TOKEN, userId: Number(process.env.OTHER_PARENT_USER_ID || 0) }
- : await loginAs(browser, 'parent'); // 第二个家长账号,用于越权用例
- });
- test('TC-DV-06 创建预约缺少 assessorId/guideId 应报错', async ({ request }) => {
- const res = await apiPost(request, parent.token, '/api/assessment/appointment/create', {
- memberId: parent.userId,
- quotaId: 1,
- });
- expect(res.status()).toBe(200);
- const body = await res.json();
- expect(body.code).not.toBe(0);
- expect(String(body.message)).toContain('assessorId或guideId');
- });
- test('TC-DV-03 创建预约额度无效应报错且不落库', async ({ request }) => {
- const res = await apiPost(request, parent.token, '/api/assessment/appointment/create', {
- memberId: parent.userId,
- quotaId: 999999999,
- guideId: teacher.userId,
- });
- const body = await res.json();
- expect(body.code).not.toBe(0);
- expect(String(body.message)).toContain('额度');
- });
- test('TC-DV-05 非指派测评师确认应失败', async ({ request }) => {
- const res = await apiPost(request, otherParent.token, '/api/assessment/appointment/confirm/999999999', {});
- const body = await res.json();
- expect(body.code).not.toBe(0);
- expect(String(body.message)).toContain('您不是被指派的测评师');
- });
- test('TC-DV-07 guide-list 仅 teacher 可访问(权限隔离)', async ({ request }) => {
- const r1 = await apiPost(request, parent.token, '/api/assessment/appointment/guide-list', {});
- expect((await r1.json()).code).not.toBe(0); // 家长非 teacher → 拒绝
- const r2 = await apiPost(request, teacher.token, '/api/assessment/appointment/guide-list', {});
- expect(r2.status()).toBe(200);
- expect((await r2.json()).code).toBe(0);
- });
- test('TC-DV-01 跨用户取消越权(安全缺口,数据依赖)', async ({ request }) => {
- const id = process.env.APPOINTMENT_ID;
- test.skip(!id, '需提供由 parent 创建的 APPOINTMENT_ID 以验证越权取消(见 deep-dive F1)');
- const res = await apiPost(request, otherParent.token, `/api/assessment/appointment/cancel/${id}`, {});
- const body = await res.json();
- // 当前实现(F1 缺陷):任何登录用户凭 id 即可取消他人预约 → code=0 成功即为越权
- if (body.code === 0) {
- console.warn('[DEFECT F1] 跨用户取消成功(越权),期望拒绝。详见 deep-dive-assessment-appointment.md');
- }
- expect(body).toBeDefined();
- });
- test('TC-DV-09 预约完整状态机(数据依赖:需有效 QUOTA_ID)', async ({ request }) => {
- const quotaId = process.env.QUOTA_ID;
- test.skip(!quotaId, '需提供有效 QUOTA_ID 以验证 create→confirm 闭环(见 deep-dive TC-DV-09/10)');
- const create = await apiPost(request, parent.token, '/api/assessment/appointment/create', {
- memberId: parent.userId,
- quotaId: Number(quotaId),
- guideId: teacher.userId,
- });
- const created = await create.json();
- expect(created.code).toBe(0);
- const appointmentId = created.data.appointmentId;
- const confirm = await apiPost(request, teacher.token, `/api/assessment/appointment/confirm/${appointmentId}`, {});
- expect((await confirm.json()).code).toBe(0);
- });
- });
|