| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- #!/usr/bin/env node
- import { randomBytes } from 'node:crypto';
- const APP_ID = process.env.XIAOSHU_PARSE_APP_ID || '7pIbDBJmKx_main';
- const MASTER_KEY = process.env.XIAOSHU_MASTER_KEY || '';
- const PARSE_URL = (process.env.XIAOSHU_PARSE_URL || 'https://server.xiaoshu.pro/parse').replace(/\/$/, '');
- const FUNCTION_URL = (process.env.XIAOSHU_FUNCTION_URL || `${new URL(PARSE_URL).origin}/api/functions`).replace(/\/$/, '');
- const COMPANY_ID = '7pIbDBJmKx';
- if (!MASTER_KEY) throw new Error('缺少 XIAOSHU_MASTER_KEY');
- const appHeaders = { 'X-Parse-Application-Id': APP_ID, 'Content-Type': 'application/json' };
- const masterHeaders = { ...appHeaders, 'X-Parse-Master-Key': MASTER_KEY };
- const company = { __type: 'Pointer', className: 'Company', objectId: COMPANY_ID };
- const userIds = [];
- async function request(url, init = {}, expected = 200) {
- const response = await fetch(url, init);
- const payload = await response.json().catch(() => ({}));
- if (response.status !== expected) throw new Error(`${url} 期望 ${expected},实际 ${response.status}: ${payload.retmsg || payload.error || ''}`);
- return payload;
- }
- async function master(path, init = {}, expected = 200) {
- return request(`${PARSE_URL}${path}`, { ...init, headers: { ...masterHeaders, ...(init.headers || {}) } }, expected);
- }
- async function cloud(token, action, params = {}, expected = 200) {
- return request(`${FUNCTION_URL}/xiaoshu/app/gateway`, {
- method: 'POST', headers: appHeaders, body: JSON.stringify({ ...(token ? { token } : {}), params: { action, ...params } }),
- }, expected);
- }
- async function first(className, where, keys) {
- const query = new URLSearchParams({ where: JSON.stringify(where), limit: '1', keys });
- return (await master(`/classes/${className}?${query}`)).results?.[0] || null;
- }
- try {
- const memory = await first('MemoryPracticeRecord', { company, yhid: { $gt: 0 }, plid: { $gt: 0 } }, 'yhid,plid');
- const study = await first('DailyStudyRecord', { company, userId: { $gt: 0 } }, 'userId');
- const studyCommon = await first('CommonModel', { company, modelId: 56 }, 'generalId');
- const orderCommon = await first('CommonModel', { company, modelId: 54 }, 'generalId');
- const practice = await first('PracticeRecord', { company, yhid: { $gt: 0 } }, 'yhid');
- if (!memory?.yhid || !study?.userId || !studyCommon?.generalId || !orderCommon?.generalId || !practice?.yhid) throw new Error('生产学习数据样本不完整');
- const username = `auth_learning_smoke_${Date.now()}`;
- const mobile = `139${String(Date.now()).slice(-8)}`;
- const password = `${randomBytes(24).toString('base64url')}Aa9!`;
- const created = await master('/users', {
- method: 'POST',
- body: JSON.stringify({ username, password, mobile, company, isAdmin: true, role: 'admin', roles: ['admin'], legacyUserId: 990000001, legacyGroupId: 1, testCreatedBy: 'smoke-app-auth-learning' }),
- }, 201);
- userIds.push(created.objectId);
- const login = await cloud('', 'user_login_passwd', { name: mobile, passwd: password });
- const sessionToken = String(login.result?.sessionToken || '');
- if (!sessionToken.startsWith('r:')) throw new Error('手机号映射登录没有返回有效 Parse 会话');
- await cloud('', 'e_get_21list', { uid: memory.yhid, cpage: 1, psize: 1 }, 401);
- const memoryList = await cloud(sessionToken, 'e_get_21list', { uid: memory.yhid, cpage: 1, psize: 20 });
- if (!Array.isArray(memoryList.result) || memoryList.result.length < 1 || Number(memoryList.page?.itemCount) < 1) throw new Error('抗遗忘记录读取为空');
- const studyList = await cloud(sessionToken, 'content_list', { modelId: 56, myfield2: `UserId=${study.userId}`, cpage: 1, psize: 20 });
- if (!Array.isArray(studyList.result) || studyList.result.length < 1 || Number(studyList.page?.itemCount) < 1) throw new Error('学习记录列表读取为空');
- const studyDetail = await cloud(sessionToken, 'e_record_detail', { id: studyCommon.generalId });
- if (!Array.isArray(studyDetail.result)) throw new Error('学习记录详情响应格式错误');
- const orderDetail = await cloud(sessionToken, 'e_order_detail', { id: orderCommon.generalId });
- if (!Array.isArray(orderDetail.result) || !orderDetail.result.length) throw new Error('预约详情读取为空');
- const newWords = await cloud(sessionToken, 'e_words_list', { uid: practice.yhid, cpage: 1, psize: 20 });
- if (!Array.isArray(newWords.result)) throw new Error('生词本响应格式错误');
- const overviewStartedAt = performance.now();
- const learningOverview = await cloud(sessionToken, 'app_learning_overview', { uid: study.userId, studentId: study.userId, date: '' });
- const learningOverviewMs = Math.round(performance.now() - overviewStartedAt);
- if (!Array.isArray(learningOverview.result?.records) || !learningOverview.result.records.length) throw new Error('学习记录轻量总览为空');
- if (!learningOverview.result?.recordDateCounts || !Array.isArray(learningOverview.result?.courses) || !Array.isArray(learningOverview.result?.schedules)) throw new Error('学习记录轻量总览结构错误');
- const companionStartedAt = performance.now();
- const companionOverview = await cloud(sessionToken, 'app_companion_overview', { uid: study.userId, mode: 'student', history: false, limit: 500 });
- const companionOverviewMs = Math.round(performance.now() - companionStartedAt);
- if (!Array.isArray(companionOverview.result?.courses) || !Array.isArray(companionOverview.result?.schedules)) throw new Error('伴学轻量总览结构错误');
- const accountStartedAt = performance.now();
- const accountOverview = await cloud(sessionToken, 'app_account_overview', { uid: memory.yhid });
- const accountOverviewMs = Math.round(performance.now() - accountStartedAt);
- if (!accountOverview.result?.profile || !accountOverview.result?.antiForgetSummary || !accountOverview.result?.companionSummary) throw new Error('我的页面轻量总览结构错误');
- const coachUsername = `coach_learning_smoke_${Date.now()}`;
- const coachPassword = `${randomBytes(24).toString('base64url')}Bb8!`;
- const coach = await master('/users', {
- method: 'POST',
- body: JSON.stringify({ username: coachUsername, password: coachPassword, company, legacyUserId: Number(memory.plid), legacyGroupId: 3, testCreatedBy: 'smoke-app-auth-learning' }),
- }, 201);
- userIds.push(coach.objectId);
- const coachLogin = await cloud('', 'user_login_passwd', { name: coachUsername, passwd: coachPassword });
- const coachToken = String(coachLogin.result?.sessionToken || '');
- const coachMemory = await cloud(coachToken, 'content_list', { modelId: 60, myfield2: `plid=${memory.plid}`, cpage: 1, psize: 20 });
- if (!Array.isArray(coachMemory.result) || coachMemory.result.length < 1) throw new Error('陪练本人抗遗忘任务读取为空');
- const coachCompanionStartedAt = performance.now();
- const coachCompanionOverview = await cloud(coachToken, 'app_companion_overview', { uid: memory.plid, mode: 'coach', history: false, limit: 500 });
- const coachCompanionOverviewMs = Math.round(performance.now() - coachCompanionStartedAt);
- if (!Array.isArray(coachCompanionOverview.result?.schedules)) throw new Error('陪练排课轻量总览结构错误');
- const coachAccountStartedAt = performance.now();
- const coachAccountOverview = await cloud(coachToken, 'app_account_overview', { uid: memory.plid });
- const coachAccountOverviewMs = Math.round(performance.now() - coachAccountStartedAt);
- if (!coachAccountOverview.result?.profile || !coachAccountOverview.result?.coachPanel) throw new Error('陪练我的页面轻量总览结构错误');
- console.log(JSON.stringify({ passed: true, parseSession: true, privateSessionRequired: true, coachOwnership: true, memoryRecords: memoryList.page?.itemCount, studyList: studyList.page?.itemCount, studyDetail: true, orderDetail: true, newWords: newWords.page?.itemCount, overviewLatencyMs: { learning: learningOverviewMs, companion: companionOverviewMs, account: accountOverviewMs, coachCompanion: coachCompanionOverviewMs, coachAccount: coachAccountOverviewMs } }, null, 2));
- } finally {
- for (const userId of userIds) {
- const pointer = { __type: 'Pointer', className: '_User', objectId: userId };
- const sessions = await master(`/classes/_Session?where=${encodeURIComponent(JSON.stringify({ user: pointer }))}&limit=1000&keys=objectId`).catch(() => ({ results: [] }));
- for (const session of sessions.results || []) await master(`/classes/_Session/${session.objectId}`, { method: 'DELETE' }).catch(() => undefined);
- await master(`/users/${userId}`, { method: 'DELETE' }).catch(() => undefined);
- }
- }
|