smoke-app-performance-read.mjs 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. #!/usr/bin/env node
  2. import { randomBytes } from 'node:crypto';
  3. const APP_ID = process.env.XIAOSHU_PARSE_APP_ID || '7pIbDBJmKx_main';
  4. const MASTER_KEY = process.env.XIAOSHU_MASTER_KEY || '';
  5. const PARSE_URL = (process.env.XIAOSHU_PARSE_URL || 'https://server.xiaoshu.pro/parse').replace(/\/$/, '');
  6. const FUNCTION_URL = PARSE_URL.replace(/\/parse$/, '/api/functions/xiaoshu/app/gateway');
  7. const SAMPLE_USER_ID = Number(process.env.XIAOSHU_SMOKE_USER_ID || 1845);
  8. if (!MASTER_KEY) throw new Error('缺少 XIAOSHU_MASTER_KEY');
  9. const appHeaders = { 'X-Parse-Application-Id': APP_ID, 'Content-Type': 'application/json' };
  10. const masterHeaders = { ...appHeaders, 'X-Parse-Master-Key': MASTER_KEY };
  11. let userId = '';
  12. async function json(url, init = {}) {
  13. const response = await fetch(url, init);
  14. const payload = await response.json().catch(() => ({}));
  15. if (!response.ok || payload.error || payload.retcode === -1) throw new Error(payload.error || payload.retmsg || `HTTP ${response.status}`);
  16. return payload;
  17. }
  18. async function master(path, init = {}) {
  19. return json(`${PARSE_URL}${path}`, { ...init, headers: { ...masterHeaders, ...(init.headers || {}) } });
  20. }
  21. async function timedCloud(token, action, params) {
  22. const startedAt = performance.now();
  23. const payload = await json(FUNCTION_URL, {
  24. method: 'POST',
  25. headers: appHeaders,
  26. body: JSON.stringify({ token, params: { action, uid: SAMPLE_USER_ID, ...params } }),
  27. signal: AbortSignal.timeout(30_000),
  28. });
  29. const result = payload.result;
  30. const summary = {
  31. action,
  32. durationMs: Math.round(performance.now() - startedAt),
  33. rows: Array.isArray(result) ? result.length : Array.isArray(result?.records) ? result.records.length : Array.isArray(result?.schedules) ? result.schedules.length : undefined,
  34. };
  35. console.log(JSON.stringify(summary));
  36. return summary;
  37. }
  38. try {
  39. const company = (await master('/classes/Company?limit=1&keys=objectId')).results?.[0];
  40. if (!company?.objectId) throw new Error('生产 Parse 未找到 Company');
  41. const suffix = `${Date.now()}_${randomBytes(4).toString('hex')}`;
  42. const username = `app_read_smoke_${suffix}`;
  43. const password = `${randomBytes(24).toString('base64url')}Aa9!`;
  44. const created = await master('/users', {
  45. method: 'POST',
  46. body: JSON.stringify({
  47. username,
  48. password,
  49. isAdmin: true,
  50. role: 'admin',
  51. roles: ['admin', 'super-admin'],
  52. adminRoleKey: 'super-admin',
  53. legacyGroupId: 1,
  54. legacyUserId: 990000000 + Math.floor(Date.now() / 1000) % 9000000,
  55. company: { __type: 'Pointer', className: 'Company', objectId: company.objectId },
  56. testCreatedBy: 'smoke-app-performance-read',
  57. }),
  58. });
  59. userId = created.objectId;
  60. const login = await json(`${PARSE_URL}/login`, { method: 'POST', headers: appHeaders, body: JSON.stringify({ username, password }) });
  61. const token = login.sessionToken;
  62. const results = [];
  63. results.push(await timedCloud(token, 'app_companion_overview', { mode: 'student', rangeStart: '2026-08-01', rangeEnd: '2026-09-11', limit: 200 }));
  64. results.push(await timedCloud(token, 'app_learning_overview', { studentId: SAMPLE_USER_ID, calendarStart: '2026-08-01', calendarEnd: '2026-09-11' }));
  65. results.push(await timedCloud(token, 'e_ck_list', { nids: '40', cpage: 1, psize: 100 }));
  66. results.push(await timedCloud(token, 'app_account_overview', {}));
  67. console.log(JSON.stringify({ sampleUserId: SAMPLE_USER_ID, results }, null, 2));
  68. } finally {
  69. if (userId) {
  70. const where = encodeURIComponent(JSON.stringify({ user: { __type: 'Pointer', className: '_User', objectId: userId } }));
  71. const sessions = await master(`/classes/_Session?where=${where}&limit=100&keys=objectId`).catch(() => ({ results: [] }));
  72. for (const session of sessions.results || []) await master(`/classes/_Session/${session.objectId}`, { method: 'DELETE' }).catch(() => undefined);
  73. await master(`/users/${userId}`, { method: 'DELETE' }).catch(() => undefined);
  74. }
  75. }