| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- /**
- * SC-G-02 微信授权登录 · API 级 e2e(auth.guest.wechatLogin / S-cfc-9)
- * ================================================================
- * 覆盖:
- * - 正常流(TC-G-02):POST /api/auth/wechat-login → Result 含 token + 用户信息,
- * 角色默认 parent;新微信自动建 User + 家庭 + family_members 成员(自己能查到)。
- * - 权限流(TC-G-02-P):微信登录是公开路径(游客无 token 可触达);受保护接口
- * /api/family/member/list 无 token → 401;跨家庭 familyId → 403(越权被拒)。
- * - 边界(TC-G-02-E):登录响应不泄露内部错误堆栈(Result 统一 code/message/data 信封)。
- *
- * 目标端由 PW_BASE_URL 指定(默认 http://cfc.iwintrue.com,本地验证用
- * http://127.0.0.1:9082,后端开 wechat.test-mode=true)。
- *
- * 运行:npx playwright test wechat-login -c playwright.config.api.js --project=desktop
- */
- const { test, expect } = require('@playwright/test');
- const BASE = process.env.PW_BASE_URL || 'http://cfc.iwintrue.com';
- const API_TIMEOUT = 60000;
- /**
- * 统一 POST(全项目接口一律 POST),返回 { http, code, message, data }
- */
- async function api(request, path, body, token) {
- const headers = { 'Content-Type': 'application/json' };
- if (token) headers.Authorization = 'Bearer ' + token;
- const res = await request.post(BASE + path, { data: body || {}, headers, timeout: API_TIMEOUT });
- const raw = await res.text();
- let json = null;
- try {
- json = raw ? JSON.parse(raw) : null;
- } catch (e) {
- json = null;
- }
- return {
- http: res.status(),
- code: json ? json.code : null,
- message: json ? json.message : null,
- data: json ? json.data : null,
- };
- }
- test.describe('SC-G-02 微信授权登录(API 级)', () => {
- let ctx = {};
- test.beforeAll(async ({ request }) => {
- const login = await api(request, '/api/auth/wechat-login', {
- code: 'scg02-' + Date.now() + '-' + String(Math.random()).slice(2, 8),
- nickname: 'SC-G-02游客',
- });
- expect(login.http).toBe(200);
- expect(login.code).toBe(200);
- expect(login.data && login.data.token).toBeTruthy();
- ctx = login.data;
- });
- test('正常流:微信一键登录返回 token+用户信息,角色默认 parent', async () => {
- expect(ctx.role).toBe('parent');
- expect(ctx.userId).toBeTruthy();
- expect(ctx.openid).toBeTruthy();
- expect(typeof ctx.token).toBe('string');
- expect(ctx.token.length).toBeGreaterThan(20); // JWT 三段式
- });
- test('正常流:首次登录自动建档,家庭列表能看到自己(family_members 成员)', async ({ request }) => {
- const members = await api(request, '/api/family/member/list', { includeFamily: true }, ctx.token);
- expect(members.http).toBe(200);
- expect(members.code).toBe(200);
- expect(Array.isArray(members.data)).toBeTruthy();
- const self = (members.data || []).find((m) => String(m.userId) === String(ctx.userId));
- expect(self).toBeTruthy();
- });
- test('权限流:微信登录是公开路径,无 token 也可调用(游客可触达)', async ({ request }) => {
- const login = await api(request, '/api/auth/wechat-login', {
- code: 'scg02-anon-' + Date.now(),
- });
- expect(login.http).toBe(200);
- expect(login.code).toBe(200);
- expect(login.data && login.data.token).toBeTruthy();
- });
- test('权限流:受保护接口无 token → 统一 401,不泄露内部堆栈', async ({ request }) => {
- const r = await api(request, '/api/family/member/list', {});
- expect(r.http).toBe(401);
- expect(r.code).toBe(401);
- expect(String(r.message)).toContain('请先登录');
- expect(JSON.stringify(r)).not.toContain('Exception');
- expect(JSON.stringify(r)).not.toContain('at com.etotem');
- });
- test('权限流:登录后携带 token 可访问;跨家庭 familyId → 403 越权被拒', async ({ request }) => {
- const own = await api(request, '/api/family/member/list', { familyId: ctx.familyId }, ctx.token);
- expect(own.code).toBe(200);
- const cross = await api(request, '/api/family/member/list', { familyId: 999999999 }, ctx.token);
- expect(cross.http).toBe(403);
- expect(cross.code).toBe(403);
- expect(String(cross.message)).toContain('无权访问');
- });
- });
|