flow-l1-tests.js 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379
  1. /**
  2. * ================================================================
  3. * FLOW-LEVEL L1 TEST SUITE — 流程级一级测试用例
  4. * ================================================================
  5. * Based on docs/flows/*.md flow diagrams
  6. * Tests each flow's key endpoints per role
  7. *
  8. * Roles: parent, child, teacher, admin, vendor
  9. *
  10. * Usage:
  11. * node tests/e2e/flow-l1-tests.js
  12. * ================================================================
  13. */
  14. var http = require('http');
  15. var https = require('https');
  16. var querystring = require('querystring');
  17. var path = require('path');
  18. var child_process = require('child_process');
  19. // ================================================================
  20. // Configuration
  21. // ================================================================
  22. var BASE_URL = process.env.PW_BASE_URL || 'http://cfc.iwintrue.com';
  23. var MYSQL_HOST = process.env.MYSQL_HOST || '192.168.16.251';
  24. var MYSQL_USER = process.env.MYSQL_USER || 'zxyj';
  25. var MYSQL_PASS = process.env.MYSQL_PASS || 'zxyj@123';
  26. var MYSQL_DB = process.env.MYSQL_DB || 'zxyj';
  27. // ================================================================
  28. // Test Results Collector
  29. // ================================================================
  30. var results = {
  31. passed: 0,
  32. failed: 0,
  33. skipped: 0,
  34. issues: [],
  35. details: []
  36. };
  37. function log(msg) {
  38. console.log('[TEST] ' + msg);
  39. }
  40. function record(flow, role, step, passed, detail) {
  41. var icon = passed ? '✅' : '❌';
  42. var line = icon + ' [' + flow + '] ' + role + ' | ' + step + (detail ? ' — ' + detail : '');
  43. console.log(line);
  44. results.details.push({ flow: flow, role: role, step: step, passed: passed, detail: detail });
  45. if (passed) results.passed++;
  46. else results.failed++;
  47. }
  48. function skip(flow, role, step, reason) {
  49. var line = '⏭️ [' + flow + '] ' + role + ' | ' + step + ' — SKIPPED: ' + reason;
  50. console.log(line);
  51. results.details.push({ flow: flow, role: role, step: step, passed: null, detail: 'SKIP: ' + reason });
  52. results.skipped++;
  53. }
  54. function issue(flow, role, step, desc) {
  55. results.issues.push({ flow: flow, role: role, step: step, description: desc });
  56. }
  57. // ================================================================
  58. // HTTP Request Helper (plain Node.js, no deps)
  59. // ================================================================
  60. function apiCall(method, endpoint, data, token) {
  61. return new Promise(function(resolve) {
  62. var url = new URL(endpoint, BASE_URL);
  63. var options = {
  64. hostname: url.hostname,
  65. port: url.port || (url.protocol === 'https:' ? 443 : 80),
  66. path: url.pathname + url.search,
  67. method: method,
  68. headers: {
  69. 'Content-Type': 'application/json'
  70. },
  71. timeout: 15000
  72. };
  73. if (token) {
  74. options.headers['Authorization'] = 'Bearer ' + token;
  75. }
  76. var body = data ? JSON.stringify(data) : null;
  77. if (body) {
  78. options.headers['Content-Length'] = Buffer.byteLength(body);
  79. }
  80. var transport = url.protocol === 'https:' ? https : http;
  81. var req = transport.request(options, function(res) {
  82. var chunks = [];
  83. res.on('data', function(chunk) { chunks.push(chunk); });
  84. res.on('end', function() {
  85. var rawBody = Buffer.concat(chunks).toString();
  86. var parsed = null;
  87. try { parsed = JSON.parse(rawBody); } catch(e) { parsed = null; }
  88. resolve({
  89. status: res.statusCode,
  90. body: parsed,
  91. raw: rawBody
  92. });
  93. });
  94. });
  95. req.on('error', function(err) {
  96. resolve({ status: 0, body: null, raw: err.message });
  97. });
  98. req.on('timeout', function() {
  99. req.destroy();
  100. resolve({ status: 0, body: null, raw: 'TIMEOUT' });
  101. });
  102. if (body) req.write(body);
  103. req.end();
  104. });
  105. }
  106. // ================================================================
  107. // DB Helper - insert verification codes
  108. // ================================================================
  109. function insertCode(phone) {
  110. var type = phone === '13800138000' ? 'admin_login' : 'login';
  111. try {
  112. child_process.execSync(
  113. 'mysql -h ' + MYSQL_HOST + ' -u ' + MYSQL_USER + ' -p"' + MYSQL_PASS + '" ' + MYSQL_DB +
  114. ' -e "INSERT INTO verification_codes (phone, code, type, expires_in, created_at, expires_at, used) VALUES (\'' + phone + '\', \'123456\', \'' + type + '\', 300, NOW(), \'2026-12-31 23:59:59\', 0);"',
  115. { stdio: 'ignore', shell: true, timeout: 10000 }
  116. );
  117. } catch(e) {}
  118. }
  119. // ================================================================
  120. // Login Helper
  121. // ================================================================
  122. var tokens = {};
  123. async function loginAll() {
  124. var roles = {
  125. parent: { phone: '13701366188', method: 'code' },
  126. child: { phone: '13701366189', method: 'code' },
  127. teacher: { phone: '13800000001', method: 'code' },
  128. admin: { phone: '13800138000', method: 'password' },
  129. vendor: { phone: '13800138001', method: 'code' }
  130. };
  131. for (var role in roles) {
  132. var cfg = roles[role];
  133. insertCode(cfg.phone);
  134. insertCode(cfg.phone);
  135. insertCode(cfg.phone);
  136. var resp;
  137. if (cfg.method === 'password') {
  138. resp = await apiCall('POST', '/api/admin-auth/login-by-password', {
  139. phone: cfg.phone,
  140. password: 'admin123'
  141. });
  142. } else {
  143. resp = await apiCall('POST', '/api/admin-auth/login', {
  144. phone: cfg.phone,
  145. code: '123456'
  146. });
  147. }
  148. if (resp.body && resp.body.code === 200 && resp.body.data && resp.body.data.token) {
  149. tokens[role] = resp.body.data.token;
  150. log('Login ' + role + ' OK');
  151. } else {
  152. log('Login ' + role + ' FAILED: ' + (resp.body ? resp.body.message : resp.raw));
  153. }
  154. }
  155. }
  156. // ================================================================
  157. // TEST FLOWS
  158. // ================================================================
  159. // ================================================================
  160. // FLOW 1: 认证与用户管理 (user-auth-flow)
  161. // ================================================================
  162. async function testAuthFlow() {
  163. var F = '认证与用户管理';
  164. log('\n========== ' + F + ' ==========');
  165. // --- 家长 ---
  166. var t = tokens.parent;
  167. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  168. var r = await apiCall('POST', '/api/auth/verify', {}, t);
  169. record(F, 'parent', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  170. r = await apiCall('POST', '/api/user/info', {}, t);
  171. record(F, 'parent', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  172. r = await apiCall('POST', '/api/family/user/roles', {}, t);
  173. record(F, 'parent', '获取角色列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'roles=' + JSON.stringify(r.body.data) : r.raw);
  174. // 切换到孩子视图(switch-role需要用户已拥有该角色,家长切孩子视图应使用 member/switch)
  175. r = await apiCall('POST', '/api/family/member/switch', { memberId: 1003 }, t);
  176. record(F, 'parent', '切换到孩子视图', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  177. r = await apiCall('POST', '/api/family/user/switch-back-to-parent', {}, t);
  178. record(F, 'parent', '切换回家长', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  179. // --- 孩子 ---
  180. t = tokens.child;
  181. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  182. r = await apiCall('POST', '/api/auth/verify', {}, t);
  183. record(F, 'child', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  184. r = await apiCall('POST', '/api/user/info', {}, t);
  185. record(F, 'child', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  186. // --- 教师/规划师 ---
  187. t = tokens.teacher;
  188. if (!t) { skip(F, 'teacher', '所有测试', '未登录'); return; }
  189. r = await apiCall('POST', '/api/auth/verify', {}, t);
  190. record(F, 'teacher', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  191. r = await apiCall('POST', '/api/user/info', {}, t);
  192. record(F, 'teacher', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  193. // --- 管理员 ---
  194. t = tokens.admin;
  195. if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; }
  196. r = await apiCall('POST', '/api/admin-auth/info', {}, t);
  197. record(F, 'admin', '管理员信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin OK' : r.raw);
  198. }
  199. // ================================================================
  200. // FLOW 2: 家庭管理 (family-management-flow)
  201. // ================================================================
  202. async function testFamilyFlow() {
  203. var F = '家庭管理';
  204. log('\n========== ' + F + ' ==========');
  205. var t = tokens.parent;
  206. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  207. // 用户与角色管理
  208. var r = await apiCall('POST', '/api/family/user/info', {}, t);
  209. record(F, 'parent', '用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'info OK' : r.raw);
  210. r = await apiCall('POST', '/api/family/user/roles', {}, t);
  211. record(F, 'parent', '角色列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'roles OK' : r.raw);
  212. // 切换到孩子视角(switch-to-child已废弃,使用 /api/family/member/switch)
  213. r = await apiCall('POST', '/api/family/member/switch', { memberId: 1003 }, t);
  214. record(F, 'parent', '切换到孩子视图', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  215. r = await apiCall('POST', '/api/family/user/switch-back-to-parent', {}, t);
  216. record(F, 'parent', '切换回家长', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  217. // 家庭成员管理
  218. r = await apiCall('POST', '/api/family/member/list', {}, t);
  219. record(F, 'parent', '成员列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'members=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw);
  220. r = await apiCall('POST', '/api/family/user/children/list', {}, t);
  221. record(F, 'parent', '孩子列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'children=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw);
  222. r = await apiCall('POST', '/api/family/user/family-members', {}, t);
  223. record(F, 'parent', '家庭成员列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'familyMembers OK' : r.raw);
  224. // 可见成员
  225. r = await apiCall('POST', '/api/family/user/members/visible', {}, t);
  226. record(F, 'parent', '可见成员', r.status === 200 && r.body && r.body.code === 200, r.body ? 'visible OK' : r.raw);
  227. // 邀请码(invite-code已废弃,使用 /api/family/invite/qrcode)
  228. r = await apiCall('POST', '/api/family/invite/qrcode', {}, t);
  229. record(F, 'parent', '查看邀请码', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data && r.body.data.inviteCode ? 'inviteCode=' + r.body.data.inviteCode : r.raw);
  230. // 家庭关系问卷
  231. r = await apiCall('POST', '/api/family/questionnaire/generate', {}, t);
  232. record(F, 'parent', 'AI生成问卷', r.status === 200 && r.body && r.body.code === 200, r.body ? 'questionnaire OK' : r.body ? r.body.message : r.raw);
  233. // 关系质量
  234. r = await apiCall('POST', '/api/family/relationship/scores', {}, t);
  235. record(F, 'parent', '关系质量评分', r.status === 200 && r.body && r.body.code === 200, r.body ? 'scores OK' : r.raw);
  236. // 互动记录
  237. r = await apiCall('POST', '/api/family/interaction/types', {}, t);
  238. record(F, 'parent', '互动类型', r.status === 200 && r.body && r.body.code === 200, r.body ? 'types OK' : r.raw);
  239. r = await apiCall('POST', '/api/family/interaction/list', {}, t);
  240. record(F, 'parent', '互动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'interaction list OK' : r.raw);
  241. // 规划师绑定
  242. r = await apiCall('POST', '/api/family/teacher-bindings/pending', {}, t);
  243. record(F, 'parent', '待审批绑定', r.status === 200 && r.body && r.body.code === 200, r.body ? 'pending bindings OK' : r.raw);
  244. r = await apiCall('POST', '/api/family/teacher-bindings/my-requests', {}, t);
  245. record(F, 'parent', '我的请求', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my requests OK' : r.raw);
  246. }
  247. // ================================================================
  248. // FLOW 3: 任务管理 (task-management-flow)
  249. // ================================================================
  250. async function testTaskFlow() {
  251. var F = '任务管理';
  252. log('\n========== ' + F + ' ==========');
  253. // --- 家长:创建任务 ---
  254. var t = tokens.parent;
  255. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  256. var r = await apiCall('POST', '/api/tasks/create', {
  257. childId: 1003,
  258. title: 'TEST-Flow-ReadBook',
  259. description: 'Reading test flow book',
  260. category: 'study',
  261. points: 10,
  262. requireReview: true
  263. }, t);
  264. var taskId = (r.body && r.body.code === 200 && r.body.data) ? r.body.data.id || r.body.data.taskId : null;
  265. record(F, 'parent', '创建任务', r.status === 200 && r.body && r.body.code === 200, taskId ? 'taskId=' + taskId : r.body ? r.body.message : r.raw);
  266. // 家长今日任务
  267. r = await apiCall('POST', '/api/tasks/today-parent', {}, t);
  268. record(F, 'parent', '家长今日任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'today-parent OK' : r.raw);
  269. // 可选小游戏
  270. r = await apiCall('POST', '/api/tasks/minigame-options', {}, t);
  271. record(F, 'parent', '可选小游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'minigame options OK' : r.raw);
  272. // --- 孩子:执行任务 ---
  273. var t2 = tokens.child;
  274. if (!t2) { skip(F, 'child', '所有测试', '未登录'); return; }
  275. r = await apiCall('POST', '/api/tasks/today', {}, t2);
  276. var tasks = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.tasks || [])) : [];
  277. record(F, 'child', '今日任务列表', r.status === 200 && r.body && r.body.code === 200, 'count=' + tasks.length);
  278. // 找到刚创建的任务或任意待完成任务
  279. var targetTask = null;
  280. for (var i = 0; i < tasks.length; i++) {
  281. if (tasks[i].status === 'pending' || tasks[i].status === 'active') {
  282. targetTask = tasks[i];
  283. break;
  284. }
  285. }
  286. if (targetTask) {
  287. r = await apiCall('POST', '/api/tasks/' + targetTask.id + '/complete', { evidence: 'Test complete' }, t2);
  288. record(F, 'child', '完成任务', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  289. } else {
  290. skip(F, 'child', '完成任务', '无可用待完成任务');
  291. }
  292. // 任务历史
  293. r = await apiCall('POST', '/api/tasks/history', {}, t2);
  294. record(F, 'child', '任务历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  295. // --- 家长:审核 ---
  296. r = await apiCall('POST', '/api/tasks/pending-review', {}, t);
  297. var pending = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.tasks || [])) : [];
  298. record(F, 'parent', '待审核列表', r.status === 200 && r.body && r.body.code === 200, 'pending=' + pending.length);
  299. if (pending.length > 0) {
  300. var reviewTask = pending[0];
  301. var reviewId = reviewTask.id || reviewTask.taskId;
  302. r = await apiCall('POST', '/api/tasks/' + reviewId + '/review', { approved: true }, t);
  303. record(F, 'parent', '审核通过任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'review approved OK' : r.raw);
  304. }
  305. // 任务提醒
  306. r = await apiCall('POST', '/api/task-reminders/upcoming/1003', {}, t);
  307. record(F, 'parent', '即将到期任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'upcoming OK' : r.raw);
  308. r = await apiCall('POST', '/api/task-reminders/overdue/1003', {}, t);
  309. record(F, 'parent', '逾期任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'overdue OK' : r.raw);
  310. r = await apiCall('POST', '/api/task-reminders/statistics/1003', {}, t);
  311. record(F, 'parent', '任务统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  312. // 清理:删除测试任务
  313. if (taskId) {
  314. await apiCall('POST', '/api/tasks/' + taskId + '/delete', {}, t);
  315. }
  316. }
  317. // ================================================================
  318. // FLOW 4: 心愿管理 (wish-management-flow)
  319. // ================================================================
  320. async function testWishFlow() {
  321. var F = '心愿管理';
  322. log('\n========== ' + F + ' ==========');
  323. // --- 孩子:创建心愿 ---
  324. var t = tokens.child;
  325. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  326. var r = await apiCall('POST', '/api/wishes/create', {
  327. title: 'TEST-Flow-NewBook',
  328. description: 'A new storybook',
  329. category: 'education'
  330. }, t);
  331. var wishId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.wishId) : null;
  332. record(F, 'child', '创建心愿', r.status === 200 && r.body && r.body.code === 200, wishId ? 'wishId=' + wishId : r.body ? r.body.message : r.raw);
  333. // --- 家长:审批 ---
  334. var t2 = tokens.parent;
  335. if (!t2) { skip(F, 'parent', '所有测试', '未登录'); return; }
  336. r = await apiCall('POST', '/api/wishes/pending', { familyId: 2001 }, t2);
  337. record(F, 'parent', '待处理心愿', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'pending=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw);
  338. if (wishId) {
  339. r = await apiCall('POST', '/api/wishes/' + wishId + '/set-price', { price: 50 }, t2);
  340. record(F, 'parent', '家长定价', r.status === 200 && r.body && r.body.code === 200, r.body ? 'price set OK' : r.body ? r.body.message : r.raw);
  341. // 孩子申请兑换
  342. r = await apiCall('POST', '/api/wishes/' + wishId + '/exchange', {}, t);
  343. record(F, 'child', '申请兑换', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  344. // 家长审批兑换
  345. r = await apiCall('POST', '/api/wishes/' + wishId + '/approve-exchange', {}, t2);
  346. record(F, 'parent', '审批兑换', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  347. // 家长标记已购买
  348. r = await apiCall('POST', '/api/wishes/' + wishId + '/fulfill', {}, t2);
  349. record(F, 'parent', '标记已购买', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  350. // 孩子确认收货
  351. r = await apiCall('POST', '/api/wishes/' + wishId + '/confirm', {}, t);
  352. record(F, 'child', '确认收货', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  353. }
  354. // 心愿列表
  355. r = await apiCall('POST', '/api/wishes/list', {}, t);
  356. record(F, 'child', '心愿列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'wishes=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw);
  357. // 心愿详情
  358. if (wishId) {
  359. r = await apiCall('POST', '/api/wishes/' + wishId, {}, t);
  360. record(F, 'child', '心愿详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'detail OK' : r.raw);
  361. }
  362. }
  363. // ================================================================
  364. // FLOW 5: 五维能量系统 (energy-system-flow)
  365. // ================================================================
  366. async function testEnergyFlow() {
  367. var F = '五维能量';
  368. log('\n========== ' + F + ' ==========');
  369. var t = tokens.parent;
  370. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  371. var r = await apiCall('POST', '/api/energy/overview', {}, t);
  372. record(F, 'parent', '五维概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'overview OK' : r.raw);
  373. r = await apiCall('POST', '/api/energy/sandbox', {}, t);
  374. record(F, 'parent', '能量沙盘', r.status === 200 && r.body && r.body.code === 200, r.body ? 'sandbox OK' : r.raw);
  375. r = await apiCall('POST', '/api/energy/logs', {}, t);
  376. record(F, 'parent', '能量流水', r.status === 200 && r.body && r.body.code === 200, r.body ? 'logs OK' : r.raw);
  377. r = await apiCall('POST', '/api/energy/score-history', {}, t);
  378. record(F, 'parent', '分数历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  379. r = await apiCall('POST', '/api/energy/family-score-history', {}, t);
  380. record(F, 'parent', '家庭五维聚合', r.status === 200 && r.body && r.body.code === 200, r.body ? 'family score OK' : r.raw);
  381. // 能量发放
  382. r = await apiCall('POST', '/api/energy/grant', { childId: 1003, amount: 5, reason: 'Test grant' }, t);
  383. record(F, 'parent', '发放能量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'grant OK' : r.body ? r.body.message : r.raw);
  384. // 孩子维度
  385. var t2 = tokens.child;
  386. if (t2) {
  387. r = await apiCall('POST', '/api/energy/overview', {}, t2);
  388. record(F, 'child', '五维概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child overview OK' : r.raw);
  389. }
  390. // 维度管理
  391. r = await apiCall('POST', '/api/dimension/overview', {}, t);
  392. record(F, 'parent', '维度概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dimension overview OK' : r.raw);
  393. // 年度能量
  394. r = await apiCall('POST', '/api/zodiac/energy', {}, t);
  395. record(F, 'parent', '年度五维能量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'zodiac energy OK' : r.raw);
  396. // 家庭收入
  397. r = await apiCall('POST', '/api/earnings/family/summary', {}, t);
  398. record(F, 'parent', '家庭收入汇总', r.status === 200 && r.body && r.body.code === 200, r.body ? 'earnings summary OK' : r.raw);
  399. }
  400. // ================================================================
  401. // FLOW 6: 健康打卡 (health-checkin-flow)
  402. // ================================================================
  403. async function testCheckinFlow() {
  404. var F = '健康打卡';
  405. log('\n========== ' + F + ' ==========');
  406. var t = tokens.child;
  407. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  408. // 综合打卡
  409. var r = await apiCall('POST', '/api/daily/checkin/create', {
  410. type: 'comprehensive',
  411. mood: 'happy',
  412. energy: 8,
  413. note: 'Test checkin'
  414. }, t);
  415. record(F, 'child', '综合打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  416. // 运动打卡
  417. r = await apiCall('POST', '/api/health/exercise/create', {
  418. exerciseType: 'running',
  419. duration: 30,
  420. note: 'Test run'
  421. }, t);
  422. record(F, 'child', '运动打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'exercise OK' : r.body ? r.body.message : r.raw);
  423. // 饮食打卡
  424. r = await apiCall('POST', '/api/health/meal/create', {
  425. mealType: 'lunch',
  426. foods: 'rice,vegetables',
  427. note: 'Test meal'
  428. }, t);
  429. record(F, 'child', '饮食打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal OK' : r.body ? r.body.message : r.raw);
  430. // 睡眠打卡
  431. r = await apiCall('POST', '/api/health/sleep/create', {
  432. sleepTime: '22:00',
  433. wakeTime: '07:00',
  434. quality: 'good'
  435. }, t);
  436. record(F, 'child', '睡眠打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'sleep OK' : r.body ? r.body.message : r.raw);
  437. // 喝水打卡
  438. r = await apiCall('POST', '/api/health/water/create', {
  439. amount: 2000,
  440. note: 'Drank water'
  441. }, t);
  442. record(F, 'child', '喝水打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'water OK' : r.body ? r.body.message : r.raw);
  443. // 打卡列表(需要memberId参数)
  444. r = await apiCall('POST', '/api/daily/checkin/list', { memberId: 1002 }, t);
  445. record(F, 'child', '打卡列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'list OK' : r.raw);
  446. // 打卡统计
  447. r = await apiCall('POST', '/api/health/checkin/stats', {}, t);
  448. record(F, 'child', '打卡统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  449. // 打卡进度
  450. r = await apiCall('POST', '/api/streak/progress/1003', {}, t);
  451. record(F, 'child', '打卡进度', r.status === 200 && r.body && r.body.code === 200, r.body ? 'streak OK' : r.raw);
  452. // 健康积分
  453. r = await apiCall('POST', '/api/health/score/info', {}, t);
  454. record(F, 'child', '健康积分', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health score OK' : r.raw);
  455. // 健康时间线
  456. r = await apiCall('POST', '/api/health/timeline/list', {}, t);
  457. record(F, 'child', '健康时间线', r.status === 200 && r.body && r.body.code === 200, r.body ? 'timeline OK' : r.raw);
  458. // 健康预警
  459. r = await apiCall('POST', '/api/health/alert/list', {}, t);
  460. record(F, 'child', '健康预警', r.status === 200 && r.body && r.body.code === 200, r.body ? 'alerts OK' : r.raw);
  461. }
  462. // ================================================================
  463. // FLOW 7: 成长档案 (growth-record-flow)
  464. // ================================================================
  465. async function testGrowthFlow() {
  466. var F = '成长档案';
  467. log('\n========== ' + F + ' ==========');
  468. var t = tokens.parent;
  469. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  470. // 创建成长档案
  471. var r = await apiCall('POST', '/api/growth/record/create', {
  472. childId: 1003,
  473. title: 'Test Growth Record',
  474. type: 'assessment',
  475. description: 'Test growth record creation'
  476. }, t);
  477. var recordId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.recordId) : null;
  478. record(F, 'parent', '创建成长档案', r.status === 200 && r.body && r.body.code === 200, recordId ? 'recordId=' + recordId : r.body ? r.body.message : r.raw);
  479. // 档案列表
  480. r = await apiCall('POST', '/api/growth/record/list', {}, t);
  481. record(F, 'parent', '档案列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'records=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw);
  482. // 指定孩子档案
  483. r = await apiCall('POST', '/api/growth/record/child/1003', {}, t);
  484. record(F, 'parent', '指定孩子档案', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child records OK' : r.raw);
  485. // 档案详情
  486. if (recordId) {
  487. r = await apiCall('POST', '/api/growth/record/' + recordId, {}, t);
  488. record(F, 'parent', '档案详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'detail OK' : r.raw);
  489. }
  490. // 生长记录
  491. r = await apiCall('POST', '/api/health/growth/list', {}, t);
  492. record(F, 'parent', '生长记录列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'growth list OK' : r.raw);
  493. r = await apiCall('POST', '/api/health/growth/create', {
  494. childId: 1003,
  495. height: 135.5,
  496. weight: 30.2,
  497. recordedDate: '2026-07-20'
  498. }, t);
  499. record(F, 'parent', '创建生长记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'growth create OK' : r.body ? r.body.message : r.raw);
  500. // 成长计划
  501. r = await apiCall('POST', '/api/growth/plan/create', {
  502. childId: 1003,
  503. title: 'Test Growth Plan',
  504. goals: 'Improve reading skills'
  505. }, t);
  506. record(F, 'parent', '创建成长计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'plan create OK' : r.body ? r.body.message : r.raw);
  507. r = await apiCall('POST', '/api/growth/plan/child/1003', {}, t);
  508. record(F, 'parent', '查看孩子计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child plans OK' : r.raw);
  509. // 清理
  510. if (recordId) {
  511. await apiCall('POST', '/api/growth/record/' + recordId + '/delete', {}, t);
  512. }
  513. }
  514. // ================================================================
  515. // FLOW 8: DAN测评 (dan-assessment-flow)
  516. // ================================================================
  517. async function testDanFlow() {
  518. var F = 'DAN测评';
  519. log('\n========== ' + F + ' ==========');
  520. var t = tokens.parent;
  521. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  522. // 测评准备
  523. var r = await apiCall('POST', '/api/dan-assessment/material/list', {}, t);
  524. record(F, 'parent', '测评材料列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'materials OK' : r.raw);
  525. r = await apiCall('POST', '/api/dan-assessment/material/active', {}, t);
  526. record(F, 'parent', '启用材料', r.status === 200 && r.body && r.body.code === 200, r.body ? 'active material OK' : r.raw);
  527. r = await apiCall('POST', '/api/dan-assessment/family/config', {}, t);
  528. record(F, 'parent', '家庭测评配置', r.status === 200 && r.body && r.body.code === 200, r.body ? 'family config OK' : r.raw);
  529. // 额度
  530. r = await apiCall('POST', '/api/assessment/quota/my-list', {}, t);
  531. record(F, 'parent', '可用额度', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'quota list OK' : r.raw);
  532. // 创建测评订单
  533. r = await apiCall('POST', '/api/dan-assessment/order/create', {
  534. childId: 1003,
  535. materialId: 1
  536. }, t);
  537. record(F, 'parent', '创建测评订单', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  538. // 评估师列表
  539. r = await apiCall('POST', '/api/assessment/appointment/assessor-list', {}, t);
  540. record(F, 'parent', '评估师列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'assessors=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw);
  541. // 最新测评结果
  542. r = await apiCall('POST', '/api/assessment/latest-result', {}, t);
  543. record(F, 'parent', '最新测评结果', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest result OK' : r.raw);
  544. // 测评历史
  545. r = await apiCall('POST', '/api/assessment/history', {}, t);
  546. record(F, 'parent', '测评历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  547. // 我的报告
  548. r = await apiCall('POST', '/api/assessment/my-results', {}, t);
  549. record(F, 'parent', '我的报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my results OK' : r.raw);
  550. // 家长自评
  551. r = await apiCall('POST', '/api/parent/assessment/create', {}, t);
  552. record(F, 'parent', '创建家长自评', r.status === 200 && r.body && r.body.code === 200, r.body ? 'self assessment OK' : r.body ? r.body.message : r.raw);
  553. r = await apiCall('POST', '/api/parent/assessment/records', {}, t);
  554. record(F, 'parent', '自评记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'assessment records OK' : r.raw);
  555. // --- 规划师端 ---
  556. var t2 = tokens.teacher;
  557. if (t2) {
  558. r = await apiCall('POST', '/api/guide/assessment/families', {}, t2);
  559. record(F, 'teacher', '家庭测评列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'guide assessment families OK' : r.raw);
  560. }
  561. }
  562. // ================================================================
  563. // FLOW 9: 商城交易 (shop-flow)
  564. // ================================================================
  565. async function testShopFlow() {
  566. var F = '商城交易';
  567. log('\n========== ' + F + ' ==========');
  568. var t = tokens.parent;
  569. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  570. // 商品浏览
  571. var r = await apiCall('POST', '/api/shop/category/tree', {}, t);
  572. record(F, 'parent', '商品分类树', r.status === 200 && r.body && r.body.code === 200, r.body ? 'category tree OK' : r.raw);
  573. r = await apiCall('POST', '/api/product/list', {}, t);
  574. var products = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.records || [])) : [];
  575. record(F, 'parent', '商品列表', r.status === 200 && r.body && r.body.code === 200, 'products=' + products.length);
  576. // 购物车
  577. r = await apiCall('POST', '/api/cart/list', {}, t);
  578. record(F, 'parent', '购物车列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'cart list OK' : r.raw);
  579. r = await apiCall('POST', '/api/cart/count', {}, t);
  580. record(F, 'parent', '购物车数量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'cart count OK' : r.raw);
  581. // 我的订单
  582. r = await apiCall('POST', '/api/product/order/my', {}, t);
  583. record(F, 'parent', '我的订单', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my orders OK' : r.raw);
  584. r = await apiCall('POST', '/api/product/order/my/counts', {}, t);
  585. record(F, 'parent', '订单数量统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'order counts OK' : r.raw);
  586. // 收货地址
  587. r = await apiCall('POST', '/api/consignee/list', {}, t);
  588. record(F, 'parent', '收货地址列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'consignee list OK' : r.raw);
  589. // 售后
  590. r = await apiCall('POST', '/api/shop/after-sales/list', {}, t);
  591. record(F, 'parent', '售后列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'after-sales OK' : r.raw);
  592. // --- 管理员 ---
  593. var t2 = tokens.admin;
  594. if (t2) {
  595. r = await apiCall('POST', '/api/product/my', {}, t2);
  596. record(F, 'admin', '管理端商品列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin products OK' : r.raw);
  597. }
  598. }
  599. // ================================================================
  600. // FLOW 10: 积分兑换 (points-exchange-flow)
  601. // ================================================================
  602. async function testPointsFlow() {
  603. var F = '积分兑换';
  604. log('\n========== ' + F + ' ==========');
  605. var t = tokens.child;
  606. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  607. var r = await apiCall('POST', '/api/points/balance', {}, t);
  608. record(F, 'child', '积分余额', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'balance OK' : r.raw);
  609. r = await apiCall('POST', '/api/points/logs', {}, t);
  610. record(F, 'child', '积分流水', r.status === 200 && r.body && r.body.code === 200, r.body ? 'logs OK' : r.raw);
  611. // 兑换商品列表
  612. r = await apiCall('POST', '/api/points/exchange/product/list', {}, t);
  613. record(F, 'child', '兑换商品列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'exchange products OK' : r.raw);
  614. // 奖励系统
  615. r = await apiCall('POST', '/api/rewards/wishlist', {}, t);
  616. record(F, 'child', '奖励清单', r.status === 200 && r.body && r.body.code === 200, r.body ? 'wishlist OK' : r.raw);
  617. r = await apiCall('POST', '/api/rewards/templates', {}, t);
  618. record(F, 'child', '奖励模板', r.status === 200 && r.body && r.body.code === 200, r.body ? 'templates OK' : r.raw);
  619. // 家长端积分
  620. var t2 = tokens.parent;
  621. if (t2) {
  622. r = await apiCall('POST', '/api/points/balance', {}, t2);
  623. record(F, 'parent', '家长积分余额', r.status === 200 && r.body && r.body.code === 200, r.body ? 'parent balance OK' : r.raw);
  624. r = await apiCall('POST', '/api/points/exchange/records', {}, t2);
  625. record(F, 'parent', '兑换记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'exchange records OK' : r.raw);
  626. }
  627. }
  628. // ================================================================
  629. // FLOW 11: 小游戏 (minigame-flow)
  630. // ================================================================
  631. async function testMinigameFlow() {
  632. var F = '小游戏';
  633. log('\n========== ' + F + ' ==========');
  634. var t = tokens.child;
  635. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  636. // 游戏列表
  637. var r = await apiCall('POST', '/api/mini-game/list', {}, t);
  638. record(F, 'child', '小游戏列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'games OK' : r.raw);
  639. // 游戏详情 (Schulte Table)
  640. r = await apiCall('POST', '/api/mini-game/schulte', {}, t);
  641. record(F, 'child', '舒尔特方格详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'schulte OK' : r.raw);
  642. // 完成游戏
  643. r = await apiCall('POST', '/api/mini-game/complete', {
  644. gameCode: 'schulte',
  645. score: 95,
  646. timeSpent: 30
  647. }, t);
  648. record(F, 'child', '完成游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'complete OK' : r.body ? r.body.message : r.raw);
  649. // 游戏历史
  650. r = await apiCall('POST', '/api/game/history', {}, t);
  651. record(F, 'child', '游戏历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  652. // 最佳成绩
  653. r = await apiCall('POST', '/api/game/best', {}, t);
  654. record(F, 'child', '最佳成绩', r.status === 200 && r.body && r.body.code === 200, r.body ? 'best OK' : r.raw);
  655. // 排行榜
  656. r = await apiCall('POST', '/api/game/leaderboard', {}, t);
  657. record(F, 'child', '排行榜', r.status === 200 && r.body && r.body.code === 200, r.body ? 'leaderboard OK' : r.raw);
  658. // 智慧数学
  659. r = await apiCall('POST', '/api/wisdom/math/start', {}, t);
  660. record(F, 'child', '开始数学游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'math start OK' : r.raw);
  661. }
  662. // ================================================================
  663. // FLOW 12: 营养与饮食 (nutrition-diet-flow)
  664. // ================================================================
  665. async function testNutritionFlow() {
  666. var F = '营养与饮食';
  667. log('\n========== ' + F + ' ==========');
  668. var t = tokens.parent;
  669. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  670. // 食谱推荐
  671. var r = await apiCall('POST', '/api/meal/recommend', {}, t);
  672. record(F, 'parent', '食谱推荐', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal recommend OK' : r.raw);
  673. // 饮食日志
  674. r = await apiCall('POST', '/api/meal/logs', {}, t);
  675. record(F, 'parent', '饮食日志', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal logs OK' : r.raw);
  676. // 营养摄入统计
  677. r = await apiCall('POST', '/api/meal/nutrition-summary', {}, t);
  678. record(F, 'parent', '营养摄入统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutrition summary OK' : r.raw);
  679. // 食材推荐指数
  680. r = await apiCall('POST', '/api/food/recommendation/index', {}, t);
  681. record(F, 'parent', '食材推荐指数', r.status === 200 && r.body && r.body.code === 200, r.body ? 'food index OK' : r.raw);
  682. // 营养偏好
  683. r = await apiCall('POST', '/api/nutrition/profile/get', {}, t);
  684. record(F, 'parent', '获取营养偏好', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutrition profile OK' : r.raw);
  685. // 维度推荐
  686. r = await apiCall('POST', '/api/recommend/dimension-products', {}, t);
  687. record(F, 'parent', '维度推荐商品', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dimension products OK' : r.raw);
  688. // 北京知识库
  689. r = await apiCall('POST', '/api/nutrition/beijing/kb/list', {}, t);
  690. record(F, 'parent', '营养知识库', r.status === 200 && r.body && r.body.code === 200, r.body ? 'kb list OK' : r.raw);
  691. }
  692. // ================================================================
  693. // FLOW 13: 心理与情绪 (mind-emotion-flow)
  694. // ================================================================
  695. async function testMindFlow() {
  696. var F = '心理与情绪';
  697. log('\n========== ' + F + ' ==========');
  698. var t = tokens.child;
  699. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  700. // 情绪打卡
  701. var r = await apiCall('POST', '/api/mind/checkin/create', {
  702. emotion: 'happy',
  703. intensity: 8,
  704. note: 'Feeling good today'
  705. }, t);
  706. record(F, 'child', '情绪打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'emotion checkin OK' : r.body ? r.body.message : r.raw);
  707. r = await apiCall('POST', '/api/mind/checkin/list', {}, t);
  708. record(F, 'child', '情绪列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'emotion list OK' : r.raw);
  709. r = await apiCall('POST', '/api/mind/checkin/latest', {}, t);
  710. record(F, 'child', '最新情绪', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest OK' : r.raw);
  711. r = await apiCall('POST', '/api/mind/checkin/trend', {}, t);
  712. record(F, 'child', '情绪趋势', r.status === 200 && r.body && r.body.code === 200, r.body ? 'trend OK' : r.raw);
  713. r = await apiCall('POST', '/api/mind/checkin/stats', {}, t);
  714. record(F, 'child', '情绪统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  715. // 心理测评
  716. r = await apiCall('POST', '/api/mind/screening/history', {}, t);
  717. record(F, 'child', '筛查历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'screening history OK' : r.raw);
  718. // 心理运势
  719. r = await apiCall('POST', '/api/mind/fortune', {}, t);
  720. record(F, 'child', '心理运势', r.status === 200 && r.body && r.body.code === 200, r.body ? 'fortune OK' : r.raw);
  721. // EMI报告
  722. r = await apiCall('POST', '/api/mind/emireport/latest', {}, t);
  723. record(F, 'child', 'EMI报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'EMI report OK' : r.raw);
  724. }
  725. // ================================================================
  726. // FLOW 14: 身体健康检测 (health-detection-flow)
  727. // ================================================================
  728. async function testHealthDetectionFlow() {
  729. var F = '身体健康检测';
  730. log('\n========== ' + F + ' ==========');
  731. var t = tokens.parent;
  732. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  733. var r = await apiCall('POST', '/api/health/report/list', {}, t);
  734. record(F, 'parent', '健康报告列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'report list OK' : r.raw);
  735. r = await apiCall('POST', '/api/health/report/latest', {}, t);
  736. record(F, 'parent', '最新报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest report OK' : r.raw);
  737. r = await apiCall('POST', '/api/health/indicator/list', {}, t);
  738. record(F, 'parent', '指标明细', r.status === 200 && r.body && r.body.code === 200, r.body ? 'indicators OK' : r.raw);
  739. r = await apiCall('POST', '/api/health/nutrition/deficiency', {}, t);
  740. record(F, 'parent', '营养缺乏分析', r.status === 200 && r.body && r.body.code === 200, r.body ? 'deficiency OK' : r.raw);
  741. }
  742. // ================================================================
  743. // FLOW 15: 活动管理 (activity-flow)
  744. // ================================================================
  745. async function testActivityFlow() {
  746. var F = '活动管理';
  747. log('\n========== ' + F + ' ==========');
  748. var t = tokens.parent;
  749. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  750. // 活动列表
  751. var r = await apiCall('POST', '/api/activity/list', {}, t);
  752. record(F, 'parent', '活动列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'activities OK' : r.raw);
  753. // 我的报名
  754. r = await apiCall('POST', '/api/activity/my-registrations', {}, t);
  755. record(F, 'parent', '我的报名', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my registrations OK' : r.raw);
  756. // --- 管理员 ---
  757. var t2 = tokens.admin;
  758. if (t2) {
  759. r = await apiCall('POST', '/api/admin/activity/list', {}, t2);
  760. record(F, 'admin', '管理端活动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin activities OK' : r.raw);
  761. }
  762. }
  763. // ================================================================
  764. // FLOW 16: 内容与媒体 (content-media-flow)
  765. // ================================================================
  766. async function testContentFlow() {
  767. var F = '内容与媒体';
  768. log('\n========== ' + F + ' ==========');
  769. var t = tokens.parent;
  770. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  771. var r = await apiCall('POST', '/api/articles/list', {}, t);
  772. record(F, 'parent', '文章列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'articles OK' : r.raw);
  773. r = await apiCall('POST', '/api/articles/categories', {}, t);
  774. record(F, 'parent', '文章分类', r.status === 200 && r.body && r.body.code === 200, r.body ? 'categories OK' : r.raw);
  775. r = await apiCall('POST', '/api/articles/featured', {}, t);
  776. record(F, 'parent', '精选文章', r.status === 200 && r.body && r.body.code === 200, r.body ? 'featured OK' : r.raw);
  777. r = await apiCall('POST', '/api/articles/daily-tip', {}, t);
  778. record(F, 'parent', '每日贴士', r.status === 200 && r.body && r.body.code === 200, r.body ? 'daily tip OK' : r.raw);
  779. // 分享
  780. r = await apiCall('POST', '/api/share/my', {}, t);
  781. record(F, 'parent', '我的分享', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my shares OK' : r.raw);
  782. // 媒体
  783. r = await apiCall('POST', '/api/media/upload-text', { text: 'Test media text' }, t);
  784. record(F, 'parent', '保存文字记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'text upload OK' : r.body ? r.body.message : r.raw);
  785. }
  786. // ================================================================
  787. // FLOW 17: AI助手 (ai-assistant-flow)
  788. // ================================================================
  789. async function testAIFlow() {
  790. var F = 'AI助手';
  791. log('\n========== ' + F + ' ==========');
  792. var t = tokens.parent;
  793. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  794. // 会话列表
  795. var r = await apiCall('POST', '/api/ai/chat/conversations', {}, t);
  796. record(F, 'parent', 'AI会话列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'conversations OK' : r.raw);
  797. // AI管家会话
  798. r = await apiCall('POST', '/api/ai/butler/sessions/list', {}, t);
  799. record(F, 'parent', 'AI管家会话列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler sessions OK' : r.raw);
  800. // AI上下文
  801. r = await apiCall('POST', '/api/ai/context', {}, t);
  802. record(F, 'parent', 'AI上下文', r.status === 200 && r.body && r.body.code === 200, r.body ? 'context OK' : r.raw);
  803. // 推荐搜索
  804. r = await apiCall('POST', '/api/recommend/search', { keyword: 'nutrition' }, t);
  805. record(F, 'parent', '推荐搜索', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recommend search OK' : r.raw);
  806. }
  807. // ================================================================
  808. // FLOW 18: 会员与订阅 (membership-flow)
  809. // ================================================================
  810. async function testMembershipFlow() {
  811. var F = '会员与订阅';
  812. log('\n========== ' + F + ' ==========');
  813. var t = tokens.parent;
  814. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  815. var r = await apiCall('POST', '/api/membership/levels', {}, t);
  816. record(F, 'parent', '会员等级列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'levels OK' : r.raw);
  817. r = await apiCall('POST', '/api/membership/my', {}, t);
  818. record(F, 'parent', '我的会员信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my membership OK' : r.raw);
  819. r = await apiCall('POST', '/api/membership/level', {}, t);
  820. record(F, 'parent', '当前等级', r.status === 200 && r.body && r.body.code === 200, r.body ? 'current level OK' : r.raw);
  821. r = await apiCall('POST', '/api/membership/can-use', {}, t);
  822. record(F, 'parent', '功能权限', r.status === 200 && r.body && r.body.code === 200, r.body ? 'can-use OK' : r.raw);
  823. r = await apiCall('POST', '/api/membership/discount', {}, t);
  824. record(F, 'parent', '会员折扣', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discount OK' : r.raw);
  825. // 订阅
  826. r = await apiCall('POST', '/api/subscription/status', {}, t);
  827. record(F, 'parent', '订阅状态', r.status === 200 && r.body && r.body.code === 200, r.body ? 'subscription status OK' : r.raw);
  828. r = await apiCall('POST', '/api/subscription/plans', {}, t);
  829. record(F, 'parent', '订阅计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'subscription plans OK' : r.raw);
  830. }
  831. // ================================================================
  832. // FLOW 19: 管理后台 (admin-overview-flow)
  833. // ================================================================
  834. async function testAdminFlow() {
  835. var F = '管理后台';
  836. log('\n========== ' + F + ' ==========');
  837. var t = tokens.admin;
  838. if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; }
  839. // 用户管理
  840. var r = await apiCall('POST', '/api/admin/users', {}, t);
  841. record(F, 'admin', '用户列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'users OK' : r.raw);
  842. // 孩子管理
  843. r = await apiCall('POST', '/api/admin/children', {}, t);
  844. record(F, 'admin', '孩子列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'children OK' : r.raw);
  845. // 家庭管理
  846. r = await apiCall('POST', '/api/admin/families', {}, t);
  847. record(F, 'admin', '家庭列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'families OK' : r.raw);
  848. // 任务管理
  849. r = await apiCall('POST', '/api/admin/tasks', {}, t);
  850. record(F, 'admin', '任务列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'tasks OK' : r.raw);
  851. // 积分管理
  852. r = await apiCall('POST', '/api/admin/points/logs', {}, t);
  853. record(F, 'admin', '积分日志', r.status === 200 && r.body && r.body.code === 200, r.body ? 'points logs OK' : r.raw);
  854. // 规划师审核
  855. r = await apiCall('POST', '/api/admin/guide/applications/pending', {}, t);
  856. record(F, 'admin', '规划师待审批', r.status === 200 && r.body && r.body.code === 200, r.body ? 'guide pending OK' : r.raw);
  857. // 商品管理
  858. r = await apiCall('POST', '/api/admin/product/list', {}, t);
  859. record(F, 'admin', '商品列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'product list OK' : r.raw);
  860. // 订单管理
  861. r = await apiCall('POST', '/api/admin/product/order/list', {}, t);
  862. record(F, 'admin', '订单列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'order list OK' : r.raw);
  863. // 小游戏管理
  864. r = await apiCall('POST', '/api/mini-game/all', {}, t);
  865. record(F, 'admin', '所有游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'all games OK' : r.raw);
  866. // 健康检查
  867. r = await apiCall('POST', '/api/system/health', {}, t);
  868. record(F, 'admin', '系统健康检查', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health OK' : r.raw);
  869. }
  870. // ================================================================
  871. // FLOW 20: 规划师体系 (guide-system-flow)
  872. // ================================================================
  873. async function testGuideFlow() {
  874. var F = '规划师体系';
  875. log('\n========== ' + F + ' ==========');
  876. var t = tokens.teacher;
  877. if (!t) { skip(F, 'teacher', '所有测试', '未登录'); return; }
  878. // 个人信息
  879. var r = await apiCall('POST', '/api/guide/my', {}, t);
  880. record(F, 'teacher', '我的信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my info OK' : r.raw);
  881. r = await apiCall('POST', '/api/guide/my/packages', {}, t);
  882. record(F, 'teacher', '我的套餐', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my packages OK' : r.raw);
  883. // 团队管理
  884. r = await apiCall('POST', '/api/guide/team/my-team', {}, t);
  885. record(F, 'teacher', '我的团队', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my team OK' : r.raw);
  886. r = await apiCall('POST', '/api/guide/team/members', {}, t);
  887. record(F, 'teacher', '团队成员', r.status === 200 && r.body && r.body.code === 200, r.body ? 'team members OK' : r.raw);
  888. // 绑定家庭
  889. r = await apiCall('POST', '/api/guide/families/bound-families', {}, t);
  890. record(F, 'teacher', '绑定家庭', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'bound families OK' : r.raw);
  891. // 任务模板
  892. r = await apiCall('POST', '/api/guide/packages/list', {}, t);
  893. record(F, 'teacher', '模板列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'package list OK' : r.raw);
  894. // 训练方案
  895. r = await apiCall('POST', '/api/guide/training-plans/list', {}, t);
  896. record(F, 'teacher', '方案列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'training plans OK' : r.raw);
  897. // 活动管理
  898. r = await apiCall('POST', '/api/guide/activities/list', {}, t);
  899. record(F, 'teacher', '活动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'activities OK' : r.raw);
  900. // 成长记录
  901. r = await apiCall('POST', '/api/guide/record/my-records', {}, t);
  902. record(F, 'teacher', '我的记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'records OK' : r.raw);
  903. // 咨询消息
  904. r = await apiCall('POST', '/api/guide/messages/list', {}, t);
  905. record(F, 'teacher', '咨询列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'messages OK' : r.raw);
  906. // 订单佣金
  907. r = await apiCall('POST', '/api/guide/orders/list', {}, t);
  908. record(F, 'teacher', '订单列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'orders OK' : r.raw);
  909. // 仪表盘
  910. r = await apiCall('POST', '/api/guide/families/dashboard-stats', {}, t);
  911. record(F, 'teacher', '仪表盘统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dashboard stats OK' : r.raw);
  912. }
  913. // ================================================================
  914. // FLOW 21: 圈子与联系人 (circle-contact-flow)
  915. // ================================================================
  916. async function testCircleFlow() {
  917. var F = '圈子与联系人';
  918. log('\n========== ' + F + ' ==========');
  919. var t = tokens.parent;
  920. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  921. var r = await apiCall('POST', '/api/circle/my-circles', {}, t);
  922. record(F, 'parent', '我的圈子', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my circles OK' : r.raw);
  923. r = await apiCall('POST', '/api/circle/discover', {}, t);
  924. record(F, 'parent', '发现圈子', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discover OK' : r.raw);
  925. // 健康圈子
  926. r = await apiCall('POST', '/api/health/circle/list', {}, t);
  927. record(F, 'parent', '健康圈子列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health circle OK' : r.raw);
  928. // 联系人
  929. r = await apiCall('POST', '/api/contact/list', {}, t);
  930. record(F, 'parent', '联系人列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'contacts OK' : r.raw);
  931. // 家庭挑战
  932. r = await apiCall('POST', '/api/health/challenge/list', {}, t);
  933. record(F, 'parent', '家庭挑战列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'challenges OK' : r.raw);
  934. }
  935. // ================================================================
  936. // FLOW 22: 知识库与邀请 (knowledge-invite-flow)
  937. // ================================================================
  938. async function testKnowledgeFlow() {
  939. var F = '知识库与邀请';
  940. log('\n========== ' + F + ' ==========');
  941. // 管理员:知识库
  942. var t = tokens.admin;
  943. if (t) {
  944. var r = await apiCall('POST', '/api/health/knowledge/list', {}, t);
  945. record(F, 'admin', '知识库列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'kb list OK' : r.raw);
  946. r = await apiCall('POST', '/api/health/knowledge/v3/bacteria/query', { keyword: 'lactobacillus' }, t);
  947. record(F, 'admin', '菌属查询', r.status === 200 && r.body && r.body.code === 200, r.body ? 'bacteria query OK' : r.raw);
  948. r = await apiCall('POST', '/api/health/knowledge/v3/food/query', { keyword: 'apple' }, t);
  949. record(F, 'admin', '食物营养查询', r.status === 200 && r.body && r.body.code === 200, r.body ? 'food query OK' : r.raw);
  950. }
  951. // 用户:邀请码
  952. var t2 = tokens.parent;
  953. if (t2) {
  954. var r = await apiCall('POST', '/api/invite/code', {}, t2);
  955. record(F, 'parent', '生成邀请码', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite code OK' : r.raw);
  956. r = await apiCall('POST', '/api/invite/list', {}, t2);
  957. record(F, 'parent', '邀请列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite list OK' : r.raw);
  958. r = await apiCall('POST', '/api/invite/summary', {}, t2);
  959. record(F, 'parent', '邀请汇总', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite summary OK' : r.raw);
  960. // 里程碑
  961. r = await apiCall('POST', '/api/invite/milestone/list', {}, t2);
  962. record(F, 'parent', '里程碑列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'milestones OK' : r.raw);
  963. }
  964. }
  965. // ================================================================
  966. // FLOW 23: 其他功能 (extra-features-flow)
  967. // ================================================================
  968. async function testExtraFlow() {
  969. var F = '其他功能';
  970. log('\n========== ' + F + ' ==========');
  971. var t = tokens.parent;
  972. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  973. // 吉祥物
  974. var r = await apiCall('POST', '/api/user/mascot/get', {}, t);
  975. record(F, 'parent', '获取吉祥物', r.status === 200 && r.body && r.body.code === 200, r.body ? 'mascot OK' : r.raw);
  976. // 每日反馈
  977. r = await apiCall('POST', '/api/feedback/today', {}, t);
  978. record(F, 'parent', '今日反馈', r.status === 200 && r.body && r.body.code === 200, r.body ? 'today feedback OK' : r.raw);
  979. r = await apiCall('POST', '/api/feedback/recent', {}, t);
  980. record(F, 'parent', '最近反馈', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recent feedback OK' : r.raw);
  981. // 生成小程序码
  982. r = await apiCall('POST', '/api/common/qrcode', {}, t);
  983. record(F, 'parent', '生成小程序码', r.status === 200 && r.body && r.body.code === 200, r.body ? 'qrcode OK' : r.raw);
  984. // 引导进度
  985. r = await apiCall('POST', '/api/onboarding/progress', {}, t);
  986. record(F, 'parent', '引导进度', r.status === 200 && r.body && r.body.code === 200, r.body ? 'onboarding OK' : r.raw);
  987. }
  988. // ================================================================
  989. // MAIN RUNNER
  990. // ================================================================
  991. async function main() {
  992. console.log('============================================================');
  993. console.log(' 流程级一级测试用例 — FLOW L1 TEST SUITE');
  994. console.log(' Target: ' + BASE_URL);
  995. console.log(' Date: ' + new Date().toISOString());
  996. console.log('============================================================\n');
  997. // Step 1: Login all roles
  998. console.log('--- LOGIN ALL ROLES ---');
  999. await loginAll();
  1000. console.log('');
  1001. // Step 2: Run all flow tests
  1002. await testAuthFlow();
  1003. await testFamilyFlow();
  1004. await testTaskFlow();
  1005. await testWishFlow();
  1006. await testEnergyFlow();
  1007. await testCheckinFlow();
  1008. await testGrowthFlow();
  1009. await testDanFlow();
  1010. await testShopFlow();
  1011. await testPointsFlow();
  1012. await testMinigameFlow();
  1013. await testNutritionFlow();
  1014. await testMindFlow();
  1015. await testHealthDetectionFlow();
  1016. await testActivityFlow();
  1017. await testContentFlow();
  1018. await testAIFlow();
  1019. await testMembershipFlow();
  1020. await testAdminFlow();
  1021. await testGuideFlow();
  1022. await testCircleFlow();
  1023. await testKnowledgeFlow();
  1024. await testExtraFlow();
  1025. // Step 3: Summary
  1026. console.log('\n============================================================');
  1027. console.log(' TEST RESULTS SUMMARY');
  1028. console.log('============================================================');
  1029. console.log(' ✅ Passed: ' + results.passed);
  1030. console.log(' ❌ Failed: ' + results.failed);
  1031. console.log(' ⏭️ Skipped: ' + results.skipped);
  1032. console.log(' Total: ' + (results.passed + results.failed + results.skipped));
  1033. console.log('============================================================');
  1034. // Group failures by flow
  1035. var failures = results.details.filter(function(d) { return d.passed === false; });
  1036. if (failures.length > 0) {
  1037. console.log('\n--- FAILURES ---');
  1038. var byFlow = {};
  1039. failures.forEach(function(f) {
  1040. if (!byFlow[f.flow]) byFlow[f.flow] = [];
  1041. byFlow[f.flow].push(f);
  1042. });
  1043. for (var flow in byFlow) {
  1044. console.log('\n[' + flow + ']');
  1045. byFlow[flow].forEach(function(f) {
  1046. console.log(' ❌ ' + f.role + ' | ' + f.step + ' — ' + (f.detail || ''));
  1047. });
  1048. }
  1049. }
  1050. // Group issues
  1051. if (results.issues.length > 0) {
  1052. console.log('\n--- ISSUES ---');
  1053. results.issues.forEach(function(iss) {
  1054. console.log('⚠️ [' + iss.flow + '] ' + iss.role + ' | ' + iss.step + ': ' + iss.description);
  1055. });
  1056. }
  1057. // Flows with all passes
  1058. var allFlows = {};
  1059. results.details.forEach(function(d) {
  1060. if (!allFlows[d.flow]) allFlows[d.flow] = { pass: 0, fail: 0, skip: 0 };
  1061. if (d.passed === true) allFlows[d.flow].pass++;
  1062. else if (d.passed === false) allFlows[d.flow].fail++;
  1063. else allFlows[d.flow].skip++;
  1064. });
  1065. console.log('\n--- FLOW STATUS ---');
  1066. for (var f in allFlows) {
  1067. var s = allFlows[f];
  1068. var status = s.fail === 0 ? '✅' : '❌';
  1069. console.log(status + ' ' + f + ': ' + s.pass + '/' + (s.pass + s.fail) + ' passed, ' + s.skip + ' skipped');
  1070. }
  1071. console.log('\n============================================================');
  1072. console.log(' DONE');
  1073. console.log('============================================================');
  1074. }
  1075. main().catch(function(err) {
  1076. console.error('FATAL:', err);
  1077. process.exit(1);
  1078. });