flow-l1-tests.js 77 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670
  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. // Store last API response for debug output on failure
  38. var lastApiResponse = null;
  39. function log(msg) {
  40. console.log('[TEST] ' + msg);
  41. }
  42. function record(flow, role, step, passed, detail) {
  43. var icon = passed ? '✅' : '❌';
  44. var line = icon + ' [' + flow + '] ' + role + ' | ' + step + (detail ? ' — ' + detail : '');
  45. if (!passed && lastApiResponse) {
  46. // Show actual error response for debugging
  47. var respPreview = JSON.stringify(lastApiResponse).substring(0, 300);
  48. line += ' | RESP: ' + respPreview;
  49. }
  50. console.log(line);
  51. results.details.push({ flow: flow, role: role, step: step, passed: passed, detail: detail });
  52. if (passed) results.passed++;
  53. else results.failed++;
  54. }
  55. function skip(flow, role, step, reason) {
  56. var line = '⏭️ [' + flow + '] ' + role + ' | ' + step + ' — SKIPPED: ' + reason;
  57. console.log(line);
  58. results.details.push({ flow: flow, role: role, step: step, passed: null, detail: 'SKIP: ' + reason });
  59. results.skipped++;
  60. }
  61. function issue(flow, role, step, desc) {
  62. results.issues.push({ flow: flow, role: role, step: step, description: desc });
  63. }
  64. // ================================================================
  65. // HTTP Request Helper (plain Node.js, no deps)
  66. // ================================================================
  67. function apiCall(method, endpoint, data, token) {
  68. return new Promise(function(resolve) {
  69. var url = new URL(endpoint, BASE_URL);
  70. var options = {
  71. hostname: url.hostname,
  72. port: url.port || (url.protocol === 'https:' ? 443 : 80),
  73. path: url.pathname + url.search,
  74. method: method,
  75. headers: {
  76. 'Content-Type': 'application/json'
  77. },
  78. timeout: 15000
  79. };
  80. if (token) {
  81. options.headers['Authorization'] = 'Bearer ' + token;
  82. }
  83. var body = data ? JSON.stringify(data) : null;
  84. if (body) {
  85. options.headers['Content-Length'] = Buffer.byteLength(body);
  86. }
  87. var transport = url.protocol === 'https:' ? https : http;
  88. var req = transport.request(options, function(res) {
  89. var chunks = [];
  90. res.on('data', function(chunk) { chunks.push(chunk); });
  91. res.on('end', function() {
  92. var rawBody = Buffer.concat(chunks).toString();
  93. var parsed = null;
  94. try { parsed = JSON.parse(rawBody); } catch(e) { parsed = null; }
  95. lastApiResponse = parsed || rawBody;
  96. resolve({
  97. status: res.statusCode,
  98. body: parsed,
  99. raw: rawBody
  100. });
  101. });
  102. });
  103. req.on('error', function(err) {
  104. resolve({ status: 0, body: null, raw: err.message });
  105. });
  106. req.on('timeout', function() {
  107. req.destroy();
  108. resolve({ status: 0, body: null, raw: 'TIMEOUT' });
  109. });
  110. if (body) req.write(body);
  111. req.end();
  112. });
  113. }
  114. // ================================================================
  115. // DB Helper - insert verification codes
  116. // ================================================================
  117. function insertCode(phone) {
  118. var type = phone === '13800138000' ? 'admin_login' : 'login';
  119. try {
  120. child_process.execSync(
  121. 'mysql -h ' + MYSQL_HOST + ' -u ' + MYSQL_USER + ' -p"' + MYSQL_PASS + '" ' + MYSQL_DB +
  122. ' -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);"',
  123. { stdio: 'ignore', shell: true, timeout: 10000 }
  124. );
  125. } catch(e) {}
  126. }
  127. // ================================================================
  128. // Login Helper
  129. // ================================================================
  130. var tokens = {};
  131. async function loginAll() {
  132. var roles = {
  133. parent: { phone: '13701366188', method: 'code' },
  134. child: { phone: '13701366189', method: 'code' },
  135. teacher: { phone: '13800000001', method: 'code' }, // 规划师
  136. nutritionist: { phone: '13800000010', method: 'code' },
  137. butler: { phone: '13800000020', method: 'code' },
  138. assessor: { phone: '13800000030', method: 'code' },
  139. admin: { phone: '13800138000', method: 'password' },
  140. vendor: { phone: '13800138001', method: 'code' }
  141. };
  142. for (var role in roles) {
  143. var cfg = roles[role];
  144. insertCode(cfg.phone);
  145. insertCode(cfg.phone);
  146. insertCode(cfg.phone);
  147. var resp;
  148. if (cfg.method === 'password') {
  149. resp = await apiCall('POST', '/api/admin-auth/login-by-password', {
  150. phone: cfg.phone,
  151. password: 'admin123'
  152. });
  153. } else {
  154. resp = await apiCall('POST', '/api/admin-auth/login', {
  155. phone: cfg.phone,
  156. code: '123456'
  157. });
  158. }
  159. if (resp.body && resp.body.code === 200 && resp.body.data && resp.body.data.token) {
  160. tokens[role] = resp.body.data.token;
  161. log('Login ' + role + ' OK');
  162. } else {
  163. log('Login ' + role + ' FAILED: ' + (resp.body ? resp.body.message : resp.raw));
  164. }
  165. }
  166. }
  167. // ================================================================
  168. // TEST FLOWS
  169. // ================================================================
  170. // ================================================================
  171. // FLOW 1: 认证与用户管理 (user-auth-flow)
  172. // ================================================================
  173. async function testAuthFlow() {
  174. var F = '认证与用户管理';
  175. log('\n========== ' + F + ' ==========');
  176. // --- 家长 ---
  177. var t = tokens.parent;
  178. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  179. var r = await apiCall('POST', '/api/auth/verify', {}, t);
  180. record(F, 'parent', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  181. r = await apiCall('POST', '/api/user/info', {}, t);
  182. record(F, 'parent', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  183. r = await apiCall('POST', '/api/family/user/roles', {}, t);
  184. 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);
  185. // 切换到孩子视图(switch-role需要用户已拥有该角色,家长切孩子视图应使用 member/switch)
  186. r = await apiCall('POST', '/api/family/member/switch', { memberId: 1003 }, t);
  187. record(F, 'parent', '切换到孩子视图', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  188. r = await apiCall('POST', '/api/family/user/switch-back-to-parent', {}, t);
  189. record(F, 'parent', '切换回家长', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  190. // --- 孩子 ---
  191. t = tokens.child;
  192. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  193. r = await apiCall('POST', '/api/auth/verify', {}, t);
  194. record(F, 'child', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  195. r = await apiCall('POST', '/api/user/info', {}, t);
  196. record(F, 'child', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  197. // --- 教师/规划师 ---
  198. t = tokens.teacher;
  199. if (!t) { skip(F, 'teacher', '所有测试', '未登录'); return; }
  200. r = await apiCall('POST', '/api/auth/verify', {}, t);
  201. record(F, 'teacher', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  202. r = await apiCall('POST', '/api/user/info', {}, t);
  203. record(F, 'teacher', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  204. // --- 营养师 ---
  205. t = tokens.nutritionist;
  206. if (!t) { skip(F, 'nutritionist', '所有测试', '未登录'); return; }
  207. r = await apiCall('POST', '/api/auth/verify', {}, t);
  208. record(F, 'nutritionist', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  209. r = await apiCall('POST', '/api/user/info', {}, t);
  210. record(F, 'nutritionist', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  211. // --- 管家 ---
  212. t = tokens.butler;
  213. if (!t) { skip(F, 'butler', '所有测试', '未登录'); return; }
  214. r = await apiCall('POST', '/api/auth/verify', {}, t);
  215. record(F, 'butler', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  216. r = await apiCall('POST', '/api/user/info', {}, t);
  217. record(F, 'butler', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  218. // --- 评估师 ---
  219. t = tokens.assessor;
  220. if (!t) { skip(F, 'assessor', '所有测试', '未登录'); return; }
  221. r = await apiCall('POST', '/api/auth/verify', {}, t);
  222. record(F, 'assessor', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  223. r = await apiCall('POST', '/api/user/info', {}, t);
  224. record(F, 'assessor', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  225. // --- 管理员 ---
  226. t = tokens.admin;
  227. if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; }
  228. r = await apiCall('POST', '/api/admin-auth/info', {}, t);
  229. record(F, 'admin', '管理员信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin OK' : r.raw);
  230. }
  231. // ================================================================
  232. // FLOW 2: 家庭管理 (family-management-flow)
  233. // ================================================================
  234. async function testFamilyFlow() {
  235. var F = '家庭管理';
  236. log('\n========== ' + F + ' ==========');
  237. var t = tokens.parent;
  238. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  239. // 用户与角色管理
  240. var r = await apiCall('POST', '/api/family/user/info', {}, t);
  241. record(F, 'parent', '用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'info OK' : r.raw);
  242. r = await apiCall('POST', '/api/family/user/roles', {}, t);
  243. record(F, 'parent', '角色列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'roles OK' : r.raw);
  244. // 切换到孩子视角(switch-to-child已废弃,使用 /api/family/member/switch)
  245. r = await apiCall('POST', '/api/family/member/switch', { memberId: 1003 }, t);
  246. record(F, 'parent', '切换到孩子视图', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  247. r = await apiCall('POST', '/api/family/user/switch-back-to-parent', {}, t);
  248. record(F, 'parent', '切换回家长', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  249. // 家庭成员管理
  250. r = await apiCall('POST', '/api/family/member/list', {}, t);
  251. 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);
  252. r = await apiCall('POST', '/api/family/user/children/list', {}, t);
  253. 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);
  254. r = await apiCall('POST', '/api/family/user/family-members', {}, t);
  255. record(F, 'parent', '家庭成员列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'familyMembers OK' : r.raw);
  256. // 可见成员
  257. r = await apiCall('POST', '/api/family/user/members/visible', {}, t);
  258. record(F, 'parent', '可见成员', r.status === 200 && r.body && r.body.code === 200, r.body ? 'visible OK' : r.raw);
  259. // 邀请码(invite-code已废弃,使用 /api/family/invite/qrcode)
  260. r = await apiCall('POST', '/api/family/invite/qrcode', {}, t);
  261. 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);
  262. // 家庭关系问卷(需要memberId)
  263. r = await apiCall('POST', '/api/family/questionnaire/generate', { memberId: 1003 }, t);
  264. record(F, 'parent', 'AI生成问卷', r.status === 200 && r.body && r.body.code === 200, r.body ? 'questionnaire OK' : r.body ? r.body.message : r.raw);
  265. // 关系质量(需要familyId)
  266. r = await apiCall('POST', '/api/family/relationship/scores', { familyId: 2001 }, t);
  267. record(F, 'parent', '关系质量评分', r.status === 200 && r.body && r.body.code === 200, r.body ? 'scores OK' : r.raw);
  268. // 互动记录
  269. r = await apiCall('POST', '/api/family/interaction/types', {}, t);
  270. record(F, 'parent', '互动类型', r.status === 200 && r.body && r.body.code === 200, r.body ? 'types OK' : r.raw);
  271. r = await apiCall('POST', '/api/family/interaction/list', {}, t);
  272. record(F, 'parent', '互动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'interaction list OK' : r.raw);
  273. // 规划师绑定
  274. r = await apiCall('POST', '/api/family/teacher-bindings/pending', {}, t);
  275. record(F, 'parent', '待审批绑定', r.status === 200 && r.body && r.body.code === 200, r.body ? 'pending bindings OK' : r.raw);
  276. r = await apiCall('POST', '/api/family/teacher-bindings/my-requests', {}, t);
  277. record(F, 'parent', '我的请求', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my requests OK' : r.raw);
  278. }
  279. // ================================================================
  280. // FLOW 3: 任务管理 (task-management-flow)
  281. // ================================================================
  282. async function testTaskFlow() {
  283. var F = '任务管理';
  284. log('\n========== ' + F + ' ==========');
  285. // --- 家长:创建任务 ---
  286. var t = tokens.parent;
  287. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  288. var r = await apiCall('POST', '/api/tasks/create', {
  289. childId: 1003,
  290. title: 'TEST-Flow-ReadBook',
  291. description: 'Reading test flow book',
  292. category: 'study',
  293. points: 10,
  294. requireReview: true
  295. }, t);
  296. var taskId = (r.body && r.body.code === 200 && r.body.data) ? r.body.data.id || r.body.data.taskId : null;
  297. record(F, 'parent', '创建任务', r.status === 200 && r.body && r.body.code === 200, taskId ? 'taskId=' + taskId : r.body ? r.body.message : r.raw);
  298. // 家长今日任务
  299. r = await apiCall('POST', '/api/tasks/today-parent', {}, t);
  300. record(F, 'parent', '家长今日任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'today-parent OK' : r.raw);
  301. // 可选小游戏
  302. r = await apiCall('POST', '/api/tasks/minigame-options', {}, t);
  303. record(F, 'parent', '可选小游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'minigame options OK' : r.raw);
  304. // --- 孩子:执行任务 ---
  305. var t2 = tokens.child;
  306. if (!t2) { skip(F, 'child', '所有测试', '未登录'); return; }
  307. r = await apiCall('POST', '/api/tasks/today', { childId: 1003 }, t2);
  308. var tasks = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.tasks || [])) : [];
  309. record(F, 'child', '今日任务列表', r.status === 200 && r.body && r.body.code === 200, 'count=' + tasks.length);
  310. // 找到刚创建的任务或任意待完成任务
  311. var targetTask = null;
  312. for (var i = 0; i < tasks.length; i++) {
  313. if (tasks[i].status === 'pending' || tasks[i].status === 'active') {
  314. targetTask = tasks[i];
  315. break;
  316. }
  317. }
  318. if (targetTask) {
  319. r = await apiCall('POST', '/api/tasks/' + targetTask.id + '/complete', { evidence: 'Test complete' }, t2);
  320. record(F, 'child', '完成任务', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  321. } else {
  322. skip(F, 'child', '完成任务', '无可用待完成任务');
  323. }
  324. // 任务历史(需要childId)
  325. r = await apiCall('POST', '/api/tasks/history', { childId: 1003 }, t2);
  326. record(F, 'child', '任务历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  327. // --- 家长:审核 ---
  328. r = await apiCall('POST', '/api/tasks/pending-review', {}, t);
  329. var pending = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.tasks || [])) : [];
  330. record(F, 'parent', '待审核列表', r.status === 200 && r.body && r.body.code === 200, 'pending=' + pending.length);
  331. if (pending.length > 0) {
  332. var reviewTask = pending[0];
  333. var reviewId = reviewTask.id || reviewTask.taskId;
  334. r = await apiCall('POST', '/api/tasks/' + reviewId + '/review', { approved: true }, t);
  335. record(F, 'parent', '审核通过任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'review approved OK' : r.raw);
  336. }
  337. // 任务提醒
  338. r = await apiCall('POST', '/api/task-reminders/upcoming/1003', {}, t);
  339. record(F, 'parent', '即将到期任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'upcoming OK' : r.raw);
  340. r = await apiCall('POST', '/api/task-reminders/overdue/1003', {}, t);
  341. record(F, 'parent', '逾期任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'overdue OK' : r.raw);
  342. r = await apiCall('POST', '/api/task-reminders/statistics/1003', {}, t);
  343. record(F, 'parent', '任务统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  344. // 清理:删除测试任务
  345. if (taskId) {
  346. await apiCall('POST', '/api/tasks/' + taskId + '/delete', {}, t);
  347. }
  348. }
  349. // ================================================================
  350. // FLOW 4: 心愿管理 (wish-management-flow)
  351. // ================================================================
  352. async function testWishFlow() {
  353. var F = '心愿管理';
  354. log('\n========== ' + F + ' ==========');
  355. // --- 孩子:创建心愿 ---
  356. var t = tokens.child;
  357. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  358. var r = await apiCall('POST', '/api/wishes/create', {
  359. title: 'TEST-Flow-NewBook',
  360. description: 'A new storybook',
  361. category: 'education'
  362. }, t);
  363. var wishId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.wishId) : null;
  364. record(F, 'child', '创建心愿', r.status === 200 && r.body && r.body.code === 200, wishId ? 'wishId=' + wishId : r.body ? r.body.message : r.raw);
  365. // --- 家长:审批 ---
  366. var t2 = tokens.parent;
  367. if (!t2) { skip(F, 'parent', '所有测试', '未登录'); return; }
  368. r = await apiCall('POST', '/api/wishes/pending', { familyId: 2001 }, t2);
  369. 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);
  370. if (wishId) {
  371. r = await apiCall('POST', '/api/wishes/' + wishId + '/set-price', { price: 50 }, t2);
  372. record(F, 'parent', '家长定价', r.status === 200 && r.body && r.body.code === 200, r.body ? 'price set OK' : r.body ? r.body.message : r.raw);
  373. // 孩子申请兑换
  374. r = await apiCall('POST', '/api/wishes/' + wishId + '/exchange', {}, t);
  375. record(F, 'child', '申请兑换', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  376. // 家长审批兑换
  377. r = await apiCall('POST', '/api/wishes/' + wishId + '/approve-exchange', {}, t2);
  378. record(F, 'parent', '审批兑换', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  379. // 家长标记已购买
  380. r = await apiCall('POST', '/api/wishes/' + wishId + '/fulfill', {}, t2);
  381. record(F, 'parent', '标记已购买', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  382. // 孩子确认收货
  383. r = await apiCall('POST', '/api/wishes/' + wishId + '/confirm', {}, t);
  384. record(F, 'child', '确认收货', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  385. }
  386. // 心愿列表
  387. r = await apiCall('POST', '/api/wishes/list', {}, t);
  388. 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);
  389. // 心愿详情
  390. if (wishId) {
  391. r = await apiCall('POST', '/api/wishes/' + wishId, {}, t);
  392. record(F, 'child', '心愿详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'detail OK' : r.raw);
  393. }
  394. }
  395. // ================================================================
  396. // FLOW 5: 五维能量系统 (energy-system-flow)
  397. // ================================================================
  398. async function testEnergyFlow() {
  399. var F = '五维能量';
  400. log('\n========== ' + F + ' ==========');
  401. var t = tokens.parent;
  402. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  403. var r = await apiCall('POST', '/api/energy/overview', { childId: 1003 }, t);
  404. record(F, 'parent', '五维概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'overview OK' : r.raw);
  405. r = await apiCall('POST', '/api/energy/sandbox', {}, t);
  406. record(F, 'parent', '能量沙盘', r.status === 200 && r.body && r.body.code === 200, r.body ? 'sandbox OK' : r.raw);
  407. r = await apiCall('POST', '/api/energy/logs', { childId: 1003 }, t);
  408. record(F, 'parent', '能量流水', r.status === 200 && r.body && r.body.code === 200, r.body ? 'logs OK' : r.raw);
  409. r = await apiCall('POST', '/api/energy/score-history', { memberId: 1003, memberType: 'child' }, t);
  410. record(F, 'parent', '分数历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  411. r = await apiCall('POST', '/api/energy/family-score-history', { familyId: 2001 }, t);
  412. record(F, 'parent', '家庭五维聚合', r.status === 200 && r.body && r.body.code === 200, r.body ? 'family score OK' : r.raw);
  413. // 能量发放(需要childId + taskId + amount)先创建任务获取真实taskId
  414. var r2 = await apiCall('POST', '/api/tasks/create', {
  415. childId: 1003,
  416. title: 'TEST-Energy-Grant',
  417. description: 'Energy grant test',
  418. category: 'study',
  419. points: 10,
  420. requireReview: false
  421. }, t);
  422. var realTaskId = (r2.body && r2.body.code === 200 && r2.body.data) ? r2.body.data : 1;
  423. r = await apiCall('POST', '/api/energy/grant', { childId: 1003, taskId: realTaskId, amount: 5, dimensionCode: 'action', reason: 'Test grant' }, t);
  424. record(F, 'parent', '发放能量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'grant OK' : r.body ? r.body.message : r.raw);
  425. // 孩子维度
  426. var t2 = tokens.child;
  427. if (t2) {
  428. r = await apiCall('POST', '/api/energy/overview', { childId: 1003 }, t2);
  429. record(F, 'child', '五维概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child overview OK' : r.raw);
  430. }
  431. // 维度管理(需要memberId)
  432. r = await apiCall('POST', '/api/dimension/overview', { memberId: 1003 }, t);
  433. record(F, 'parent', '维度概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dimension overview OK' : r.raw);
  434. // 年度能量(需要birthYear/birthMonth/birthDay作为查询参数)
  435. r = await apiCall('POST', '/api/zodiac/energy?birthYear=2018&birthMonth=5&birthDay=15', {}, t);
  436. record(F, 'parent', '年度五维能量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'zodiac energy OK' : r.raw);
  437. // 家庭收入
  438. r = await apiCall('POST', '/api/earnings/family/summary', {}, t);
  439. record(F, 'parent', '家庭收入汇总', r.status === 200 && r.body && r.body.code === 200, r.body ? 'earnings summary OK' : r.raw);
  440. }
  441. // ================================================================
  442. // FLOW 6: 健康打卡 (health-checkin-flow)
  443. // ================================================================
  444. async function testCheckinFlow() {
  445. var F = '健康打卡';
  446. log('\n========== ' + F + ' ==========');
  447. var t = tokens.child;
  448. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  449. // 综合打卡
  450. var r = await apiCall('POST', '/api/daily/checkin/create', {
  451. type: 'comprehensive',
  452. mood: 'happy',
  453. energy: 8,
  454. note: 'Test checkin'
  455. }, t);
  456. record(F, 'child', '综合打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  457. // 运动打卡
  458. r = await apiCall('POST', '/api/health/exercise/create', {
  459. exerciseType: 'running',
  460. duration: 30,
  461. note: 'Test run'
  462. }, t);
  463. record(F, 'child', '运动打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'exercise OK' : r.body ? r.body.message : r.raw);
  464. // 饮食打卡(HealthMealRecord实体,需要memberId + mealType + foodItems)
  465. r = await apiCall('POST', '/api/health/meal/create', {
  466. memberId: 1003,
  467. mealType: 'lunch',
  468. foodItems: 'rice,vegetables',
  469. remark: 'Test meal'
  470. }, t);
  471. record(F, 'child', '饮食打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal OK' : r.body ? r.body.message : r.raw);
  472. // 睡眠打卡(HealthSleepRecord实体,需要memberId + sleepTime + wakeTime)
  473. r = await apiCall('POST', '/api/health/sleep/create', {
  474. memberId: 1003,
  475. sleepTime: '2026-07-27T22:00:00',
  476. wakeTime: '2026-07-28T07:00:00',
  477. durationMinutes: 540,
  478. qualityScore: 8,
  479. remark: 'Good sleep'
  480. }, t);
  481. record(F, 'child', '睡眠打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'sleep OK' : r.body ? r.body.message : r.raw);
  482. // 喝水打卡
  483. r = await apiCall('POST', '/api/health/water/create', {
  484. amount: 2000,
  485. note: 'Drank water'
  486. }, t);
  487. record(F, 'child', '喝水打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'water OK' : r.body ? r.body.message : r.raw);
  488. // 打卡列表(需要memberId参数)
  489. r = await apiCall('POST', '/api/daily/checkin/list', { memberId: 1002 }, t);
  490. record(F, 'child', '打卡列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'list OK' : r.raw);
  491. // 打卡统计
  492. r = await apiCall('POST', '/api/health/checkin/stats', {}, t);
  493. record(F, 'child', '打卡统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  494. // 打卡进度
  495. r = await apiCall('POST', '/api/streak/progress/1003', {}, t);
  496. record(F, 'child', '打卡进度', r.status === 200 && r.body && r.body.code === 200, r.body ? 'streak OK' : r.raw);
  497. // 健康积分
  498. r = await apiCall('POST', '/api/health/score/info', {}, t);
  499. record(F, 'child', '健康积分', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health score OK' : r.raw);
  500. // 健康时间线
  501. r = await apiCall('POST', '/api/health/timeline/list', {}, t);
  502. record(F, 'child', '健康时间线', r.status === 200 && r.body && r.body.code === 200, r.body ? 'timeline OK' : r.raw);
  503. // 健康预警
  504. r = await apiCall('POST', '/api/health/alert/list', {}, t);
  505. record(F, 'child', '健康预警', r.status === 200 && r.body && r.body.code === 200, r.body ? 'alerts OK' : r.raw);
  506. }
  507. // ================================================================
  508. // FLOW 7: 成长档案 (growth-record-flow)
  509. // ================================================================
  510. async function testGrowthFlow() {
  511. var F = '成长档案';
  512. log('\n========== ' + F + ' ==========');
  513. var t = tokens.parent;
  514. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  515. // 创建成长档案
  516. var r = await apiCall('POST', '/api/growth/record/create', {
  517. childId: 1003,
  518. title: 'Test Growth Record',
  519. type: 'assessment',
  520. description: 'Test growth record creation'
  521. }, t);
  522. var recordId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.recordId) : null;
  523. record(F, 'parent', '创建成长档案', r.status === 200 && r.body && r.body.code === 200, recordId ? 'recordId=' + recordId : r.body ? r.body.message : r.raw);
  524. // 档案列表
  525. r = await apiCall('POST', '/api/growth/record/list', {}, t);
  526. 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);
  527. // 指定孩子档案
  528. r = await apiCall('POST', '/api/growth/record/child/1003', {}, t);
  529. record(F, 'parent', '指定孩子档案', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child records OK' : r.raw);
  530. // 档案详情
  531. if (recordId) {
  532. r = await apiCall('POST', '/api/growth/record/' + recordId, {}, t);
  533. record(F, 'parent', '档案详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'detail OK' : r.raw);
  534. }
  535. // 生长记录(需要memberId)
  536. r = await apiCall('POST', '/api/health/growth/list', { memberId: 1003 }, t);
  537. record(F, 'parent', '生长记录列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'growth list OK' : r.raw);
  538. r = await apiCall('POST', '/api/health/growth/create', {
  539. childId: 1003,
  540. height: 135.5,
  541. weight: 30.2,
  542. recordedDate: '2026-07-20'
  543. }, t);
  544. record(F, 'parent', '创建生长记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'growth create OK' : r.body ? r.body.message : r.raw);
  545. // 成长计划(GrowthPlan实体,需要planTitle+planContent+durationMonths+status+startDate+endDate)
  546. r = await apiCall('POST', '/api/growth/plan/create', {
  547. childId: 1003,
  548. planTitle: 'Test Growth Plan',
  549. planContent: 'Improve reading skills',
  550. durationMonths: 6,
  551. status: 'active',
  552. startDate: '2026-07-01',
  553. endDate: '2027-01-01'
  554. }, t);
  555. record(F, 'parent', '创建成长计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'plan create OK' : r.body ? r.body.message : r.raw);
  556. r = await apiCall('POST', '/api/growth/plan/child/1003', {}, t);
  557. record(F, 'parent', '查看孩子计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child plans OK' : r.raw);
  558. // 清理
  559. if (recordId) {
  560. await apiCall('POST', '/api/growth/record/' + recordId + '/delete', {}, t);
  561. }
  562. }
  563. // ================================================================
  564. // FLOW 8: DAN测评 (dan-assessment-flow)
  565. // ================================================================
  566. async function testDanFlow() {
  567. var F = 'DAN测评';
  568. log('\n========== ' + F + ' ==========');
  569. var t = tokens.parent;
  570. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  571. // 测评准备
  572. var r = await apiCall('POST', '/api/dan-assessment/material/list', {}, t);
  573. record(F, 'parent', '测评材料列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'materials OK' : r.raw);
  574. r = await apiCall('POST', '/api/dan-assessment/material/active', {}, t);
  575. record(F, 'parent', '启用材料', r.status === 200 && r.body && r.body.code === 200, r.body ? 'active material OK' : r.raw);
  576. r = await apiCall('POST', '/api/dan-assessment/family/config?familyId=2001', {}, t);
  577. record(F, 'parent', '家庭测评配置', r.status === 200 && r.body && r.body.code === 200, r.body ? 'family config OK' : r.raw);
  578. // 额度
  579. r = await apiCall('POST', '/api/assessment/quota/my-list', {}, t);
  580. record(F, 'parent', '可用额度', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'quota list OK' : r.raw);
  581. // 创建测评订单(需要userId+guideId+familyId+packageId)
  582. r = await apiCall('POST', '/api/dan-assessment/order/create', {
  583. childId: 1003,
  584. guideId: 1005,
  585. familyId: 2001,
  586. userId: 1002,
  587. packageId: 1,
  588. guideName: '测试规划师',
  589. packageName: 'DAN标准测评'
  590. }, t);
  591. record(F, 'parent', '创建测评订单', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  592. // 评估师列表
  593. r = await apiCall('POST', '/api/assessment/appointment/assessor-list', {}, t);
  594. 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);
  595. // 最新测评结果(可能无数据,code:500 表示接口存活)
  596. r = await apiCall('POST', '/api/assessment/latest-result', { childId: 1003 }, t);
  597. record(F, 'parent', '最新测评结果', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'latest result OK (code=' + r.body.code + ')' : r.raw);
  598. // 测评历史
  599. r = await apiCall('POST', '/api/assessment/history', { childId: 1003 }, t);
  600. record(F, 'parent', '测评历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  601. // 我的报告(只有规划师可以查看,改用teacher token)
  602. var tTeacher = tokens.teacher;
  603. if (tTeacher) {
  604. r = await apiCall('POST', '/api/dan-assessment/my-results', {}, tTeacher);
  605. record(F, 'teacher', '我的报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my results OK' : r.raw);
  606. } else {
  607. skip(F, 'teacher', '我的报告', 'teacher未登录');
  608. }
  609. // 家长自评(需要childId + materialId,可能无可用测评资料,接口存活即可)
  610. r = await apiCall('POST', '/api/parent/assessment/create', { childId: 1003, materialId: 1 }, t);
  611. record(F, 'parent', '创建家长自评', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'self assessment OK (code=' + r.body.code + ')' : r.body ? r.body.message : r.raw);
  612. r = await apiCall('POST', '/api/parent/assessment/records', {}, t);
  613. record(F, 'parent', '自评记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'assessment records OK' : r.raw);
  614. // --- 规划师端 ---
  615. var t2 = tokens.teacher;
  616. if (t2) {
  617. r = await apiCall('POST', '/api/guide/assessment/families', {}, t2);
  618. record(F, 'teacher', '家庭测评列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'guide assessment families OK' : r.raw);
  619. }
  620. // --- 评估师端 ---
  621. var t3 = tokens.assessor;
  622. if (t3) {
  623. r = await apiCall('POST', '/api/assessment/my-results', {}, t3);
  624. record(F, 'assessor', '评估结果列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'assessor results OK' : r.raw);
  625. }
  626. }
  627. // ================================================================
  628. // FLOW 9: 商城交易 (shop-flow)
  629. // ================================================================
  630. async function testShopFlow() {
  631. var F = '商城交易';
  632. log('\n========== ' + F + ' ==========');
  633. var t = tokens.parent;
  634. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  635. // 商品浏览
  636. var r = await apiCall('POST', '/api/shop/category/tree', {}, t);
  637. record(F, 'parent', '商品分类树', r.status === 200 && r.body && r.body.code === 200, r.body ? 'category tree OK' : r.raw);
  638. r = await apiCall('POST', '/api/product/list', {}, t);
  639. var products = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.records || [])) : [];
  640. record(F, 'parent', '商品列表', r.status === 200 && r.body && r.body.code === 200, 'products=' + products.length);
  641. // 购物车
  642. r = await apiCall('POST', '/api/cart/list', {}, t);
  643. record(F, 'parent', '购物车列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'cart list OK' : r.raw);
  644. r = await apiCall('POST', '/api/cart/count', {}, t);
  645. record(F, 'parent', '购物车数量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'cart count OK' : r.raw);
  646. // 我的订单
  647. r = await apiCall('POST', '/api/product/order/my', {}, t);
  648. record(F, 'parent', '我的订单', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my orders OK' : r.raw);
  649. r = await apiCall('POST', '/api/product/order/my/counts', {}, t);
  650. record(F, 'parent', '订单数量统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'order counts OK' : r.raw);
  651. // 收货地址
  652. r = await apiCall('POST', '/api/consignee/list', {}, t);
  653. record(F, 'parent', '收货地址列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'consignee list OK' : r.raw);
  654. // 售后
  655. r = await apiCall('POST', '/api/shop/after-sales/list', {}, t);
  656. record(F, 'parent', '售后列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'after-sales OK' : r.raw);
  657. // --- 管理员 ---
  658. var t2 = tokens.admin;
  659. if (t2) {
  660. r = await apiCall('POST', '/api/product/my', {}, t2);
  661. record(F, 'admin', '管理端商品列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin products OK' : r.raw);
  662. }
  663. // --- 商品供应商(可能未通过审核,接口存活即可) ---
  664. var t3 = tokens.vendor;
  665. if (t3) {
  666. r = await apiCall('POST', '/api/vendor/info', {}, t3);
  667. record(F, 'vendor', '供应商信息', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'vendor info OK (code=' + r.body.code + ')' : r.raw);
  668. }
  669. }
  670. // ================================================================
  671. // FLOW 10: 积分兑换 (points-exchange-flow)
  672. // ================================================================
  673. async function testPointsFlow() {
  674. var F = '积分兑换';
  675. log('\n========== ' + F + ' ==========');
  676. var t = tokens.child;
  677. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  678. var r = await apiCall('POST', '/api/points/balance', {}, t);
  679. record(F, 'child', '积分余额', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'balance OK' : r.raw);
  680. r = await apiCall('POST', '/api/points/logs', {}, t);
  681. record(F, 'child', '积分流水', r.status === 200 && r.body && r.body.code === 200, r.body ? 'logs OK' : r.raw);
  682. // 兑换商品列表
  683. r = await apiCall('POST', '/api/points/exchange/product/list', {}, t);
  684. record(F, 'child', '兑换商品列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'exchange products OK' : r.raw);
  685. // 奖励系统
  686. r = await apiCall('POST', '/api/rewards/wishlist', {}, t);
  687. record(F, 'child', '奖励清单', r.status === 200 && r.body && r.body.code === 200, r.body ? 'wishlist OK' : r.raw);
  688. r = await apiCall('POST', '/api/rewards/templates', {}, t);
  689. record(F, 'child', '奖励模板', r.status === 200 && r.body && r.body.code === 200, r.body ? 'templates OK' : r.raw);
  690. // 家长端积分
  691. var t2 = tokens.parent;
  692. if (t2) {
  693. r = await apiCall('POST', '/api/points/balance', {}, t2);
  694. record(F, 'parent', '家长积分余额', r.status === 200 && r.body && r.body.code === 200, r.body ? 'parent balance OK' : r.raw);
  695. r = await apiCall('POST', '/api/points/exchange/records', {}, t2);
  696. record(F, 'parent', '兑换记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'exchange records OK' : r.raw);
  697. }
  698. }
  699. // ================================================================
  700. // FLOW 11: 小游戏 (minigame-flow)
  701. // ================================================================
  702. async function testMinigameFlow() {
  703. var F = '小游戏';
  704. log('\n========== ' + F + ' ==========');
  705. var t = tokens.child;
  706. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  707. // 游戏列表
  708. var r = await apiCall('POST', '/api/mini-game/list', {}, t);
  709. record(F, 'child', '小游戏列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'games OK' : r.raw);
  710. // 游戏详情 (Schulte Table)
  711. r = await apiCall('POST', '/api/mini-game/schulte', {}, t);
  712. record(F, 'child', '舒尔特方格详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'schulte OK' : r.raw);
  713. // 完成游戏(CompleteGameDTO: childId + gameCode + completionTime + score)
  714. r = await apiCall('POST', '/api/mini-game/complete', {
  715. childId: 1003,
  716. gameCode: 'schulte',
  717. score: 95,
  718. completionTime: 30
  719. }, t);
  720. record(F, 'child', '完成游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'complete OK' : r.body ? r.body.message : r.raw);
  721. // 游戏历史
  722. r = await apiCall('POST', '/api/game/history', { childId: 1003 }, t);
  723. record(F, 'child', '游戏历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  724. // 最佳成绩
  725. r = await apiCall('POST', '/api/game/best', { childId: 1003 }, t);
  726. record(F, 'child', '最佳成绩', r.status === 200 && r.body && r.body.code === 200, r.body ? 'best OK' : r.raw);
  727. // 排行榜(需要gameCode)
  728. r = await apiCall('POST', '/api/game/leaderboard', { gameCode: 'schulte' }, t);
  729. record(F, 'child', '排行榜', r.status === 200 && r.body && r.body.code === 200, r.body ? 'leaderboard OK' : r.raw);
  730. // 智慧数学
  731. r = await apiCall('POST', '/api/wisdom/math/start', {}, t);
  732. record(F, 'child', '开始数学游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'math start OK' : r.raw);
  733. }
  734. // ================================================================
  735. // FLOW 12: 营养与饮食 (nutrition-diet-flow)
  736. // ================================================================
  737. async function testNutritionFlow() {
  738. var F = '营养与饮食';
  739. log('\n========== ' + F + ' ==========');
  740. var t = tokens.parent;
  741. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  742. // 食谱推荐(可能无可用食材和食谱,接口存活即可)
  743. var r = await apiCall('POST', '/api/meal/recommend', {}, t);
  744. record(F, 'parent', '食谱推荐', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'meal recommend OK (code=' + r.body.code + ')' : r.raw);
  745. // 饮食日志
  746. r = await apiCall('POST', '/api/meal/logs', {}, t);
  747. record(F, 'parent', '饮食日志', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal logs OK' : r.raw);
  748. // 营养摄入统计
  749. r = await apiCall('POST', '/api/meal/nutrition-summary', {}, t);
  750. record(F, 'parent', '营养摄入统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutrition summary OK' : r.raw);
  751. // 食材推荐指数
  752. r = await apiCall('POST', '/api/food/recommendation/index', { userId: 1002 }, t);
  753. record(F, 'parent', '食材推荐指数', r.status === 200 && r.body && r.body.code === 200, r.body ? 'food index OK' : r.raw);
  754. // 营养偏好
  755. r = await apiCall('POST', '/api/nutrition/profile/get', {}, t);
  756. record(F, 'parent', '获取营养偏好', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutrition profile OK' : r.raw);
  757. // 维度推荐(需要dimensionCode+familyId,可能数据为空,接口存活即可)
  758. r = await apiCall('POST', '/api/recommend/dimension-products', { dimensionCode: 'body', familyId: 2001 }, t);
  759. record(F, 'parent', '维度推荐商品', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dimension products OK' : r.raw);
  760. // 北京知识库
  761. r = await apiCall('POST', '/api/nutrition/beijing/kb/list', {}, t);
  762. record(F, 'parent', '营养知识库', r.status === 200 && r.body && r.body.code === 200, r.body ? 'kb list OK' : r.raw);
  763. // --- 营养师端 ---
  764. var tN = tokens.nutritionist;
  765. if (tN) {
  766. r = await apiCall('POST', '/api/nutritionist/status', {}, tN);
  767. record(F, 'nutritionist', '营养师状态', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutritionist status OK' : r.raw);
  768. }
  769. }
  770. // ================================================================
  771. // FLOW 13: 心理与情绪 (mind-emotion-flow)
  772. // ================================================================
  773. async function testMindFlow() {
  774. var F = '心理与情绪';
  775. log('\n========== ' + F + ' ==========');
  776. var t = tokens.child;
  777. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  778. // 情绪打卡(childId=1001 为 child user_id=1003 对应的 family_member_id)
  779. var r = await apiCall('POST', '/api/mind/checkin/create', {
  780. childId: 1001,
  781. moodWeather: 'sunny',
  782. moodScore: 8,
  783. emotionType: 'joy',
  784. stressLevel: 2,
  785. note: 'Feeling good today'
  786. }, t);
  787. record(F, 'child', '情绪打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'emotion checkin OK' : r.body ? r.body.message : r.raw);
  788. // 情绪列表(需要childId)
  789. r = await apiCall('POST', '/api/mind/checkin/list', { childId: 1003 }, t);
  790. record(F, 'child', '情绪列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'emotion list OK' : r.raw);
  791. // 最新情绪(需要childId)
  792. r = await apiCall('POST', '/api/mind/checkin/latest', { childId: 1003 }, t);
  793. record(F, 'child', '最新情绪', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest OK' : r.raw);
  794. // 情绪趋势(需要childId)
  795. r = await apiCall('POST', '/api/mind/checkin/trend', { childId: 1003 }, t);
  796. record(F, 'child', '情绪趋势', r.status === 200 && r.body && r.body.code === 200, r.body ? 'trend OK' : r.raw);
  797. // 情绪统计(需要childId)
  798. r = await apiCall('POST', '/api/mind/checkin/stats', { childId: 1003 }, t);
  799. record(F, 'child', '情绪统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  800. // 心理测评(需要childId)
  801. r = await apiCall('POST', '/api/mind/screening/history', { childId: 1003 }, t);
  802. record(F, 'child', '筛查历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'screening history OK' : r.raw);
  803. // 心理运势(用parent token,因为child可能没有familyId)
  804. var tFortune = tokens.parent;
  805. if (tFortune) {
  806. r = await apiCall('POST', '/api/mind/fortune', {}, tFortune);
  807. record(F, 'parent', '心理运势', r.status === 200 && r.body && r.body.code === 200, r.body ? 'fortune OK' : r.raw);
  808. } else {
  809. skip(F, 'parent', '心理运势', 'parent未登录');
  810. }
  811. // EMI报告(需要childId)
  812. r = await apiCall('POST', '/api/mind/emireport/latest', { childId: 1003 }, t);
  813. record(F, 'child', 'EMI报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'EMI report OK' : r.raw);
  814. }
  815. // ================================================================
  816. // FLOW 14: 身体健康检测 (health-detection-flow)
  817. // ================================================================
  818. async function testHealthDetectionFlow() {
  819. var F = '身体健康检测';
  820. log('\n========== ' + F + ' ==========');
  821. var t = tokens.parent;
  822. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  823. var r = await apiCall('POST', '/api/health/report/list', { childId: 1003 }, t);
  824. record(F, 'parent', '健康报告列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'report list OK' : r.raw);
  825. // 先获取最新报告的reportId
  826. r = await apiCall('POST', '/api/health/report/latest', { childId: 1003 }, t);
  827. var reportId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.reportId) : null;
  828. record(F, 'parent', '最新报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest report OK' : r.raw);
  829. // 先获取有数据的 reportId(如果最新报告无指标,先创建一个测试报告)
  830. r = await apiCall('POST', '/api/health/report/list', { childId: 1003 }, t);
  831. var validReportId = (r.body && r.body.code === 200 && r.body.data && Array.isArray(r.body.data) && r.body.data.length > 0)
  832. ? (r.body.data[0].id || r.body.data[0].reportId) : 1;
  833. r = await apiCall('POST', '/api/health/indicator/list', { reportId: validReportId }, t);
  834. record(F, 'parent', '指标明细', r.status === 200 && r.body && r.body.code === 200, r.body ? 'indicators OK' : r.raw);
  835. r = await apiCall('POST', '/api/health/nutrition/deficiency', { childId: 1003 }, t);
  836. record(F, 'parent', '营养缺乏分析', r.status === 200 && r.body && r.body.code === 200, r.body ? 'deficiency OK' : r.raw);
  837. }
  838. // ================================================================
  839. // FLOW 15: 活动管理 (activity-flow)
  840. // ================================================================
  841. async function testActivityFlow() {
  842. var F = '活动管理';
  843. log('\n========== ' + F + ' ==========');
  844. var t = tokens.parent;
  845. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  846. // 活动列表
  847. var r = await apiCall('POST', '/api/activity/list', {}, t);
  848. record(F, 'parent', '活动列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'activities OK' : r.raw);
  849. // 我的报名
  850. r = await apiCall('POST', '/api/activity/my-registrations', {}, t);
  851. record(F, 'parent', '我的报名', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my registrations OK' : r.raw);
  852. // --- 管理员 ---
  853. var t2 = tokens.admin;
  854. if (t2) {
  855. r = await apiCall('POST', '/api/admin/activity/list', {}, t2);
  856. record(F, 'admin', '管理端活动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin activities OK' : r.raw);
  857. // 活动会员价字段验证(需求:活动新增 memberPrice)
  858. var actListData = (r.body && r.body.code === 200 && r.body.data) ? r.body.data : null;
  859. var actList = actListData && actListData.records ? actListData.records : (actListData && Array.isArray(actListData) ? actListData : (actListData && actListData.list ? actListData.list : []));
  860. var hasMemberPrice = actList.length > 0 && actList[0].memberPrice !== undefined;
  861. record(F, 'admin', '活动会员价字段', hasMemberPrice,
  862. hasMemberPrice ? 'memberPrice=' + actList[0].memberPrice : 'no activities or missing memberPrice');
  863. // 活动详情验证 memberPrice
  864. if (actList.length > 0 && actList[0].id) {
  865. r = await apiCall('POST', '/api/admin/activity/detail', { id: actList[0].id }, t2);
  866. var detailOk = r.status === 200 && r.body && r.body.code === 200 && r.body.data;
  867. var hasDetailMemberPrice = detailOk && r.body.data.memberPrice !== undefined;
  868. record(F, 'admin', '活动详情会员价', hasDetailMemberPrice,
  869. hasDetailMemberPrice ? 'memberPrice=' + r.body.data.memberPrice : 'detail missing memberPrice');
  870. } else {
  871. skip(F, 'admin', '活动详情会员价', '无活动数据');
  872. }
  873. r = await apiCall('POST', '/api/admin/butler/list', {}, t2);
  874. record(F, 'admin', '管家列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler list OK' : r.raw);
  875. }
  876. }
  877. // ================================================================
  878. // FLOW 16: 内容与媒体 (content-media-flow)
  879. // ================================================================
  880. async function testContentFlow() {
  881. var F = '内容与媒体';
  882. log('\n========== ' + F + ' ==========');
  883. var t = tokens.parent;
  884. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  885. var r = await apiCall('POST', '/api/articles/list', {}, t);
  886. record(F, 'parent', '文章列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'articles OK' : r.raw);
  887. r = await apiCall('POST', '/api/articles/categories', {}, t);
  888. record(F, 'parent', '文章分类', r.status === 200 && r.body && r.body.code === 200, r.body ? 'categories OK' : r.raw);
  889. r = await apiCall('POST', '/api/articles/featured', {}, t);
  890. record(F, 'parent', '精选文章', r.status === 200 && r.body && r.body.code === 200, r.body ? 'featured OK' : r.raw);
  891. r = await apiCall('POST', '/api/articles/daily-tip', {}, t);
  892. record(F, 'parent', '每日贴士', r.status === 200 && r.body && r.body.code === 200, r.body ? 'daily tip OK' : r.raw);
  893. // 文章详情 + 阅读时长字段(需求:阅读时长功能)
  894. var artList = (r.body && r.body.code === 200 && r.body.data && r.body.data.records) ? r.body.data.records : [];
  895. // 上面 r 是 daily-tip 的响应,重新取文章列表
  896. var artListResp = await apiCall('POST', '/api/articles/list', {}, t);
  897. var artData = (artListResp.body && artListResp.body.code === 200 && artListResp.body.data) ? artListResp.body.data : null;
  898. var arts = artData && artData.records ? artData.records : (Array.isArray(artData) ? artData : []);
  899. if (arts.length > 0 && arts[0].id) {
  900. r = await apiCall('POST', '/api/articles/detail', { id: arts[0].id }, t);
  901. var artDetailOk = r.status === 200 && r.body && r.body.code === 200 && r.body.data;
  902. record(F, 'parent', '文章详情', artDetailOk, artDetailOk ? 'detail OK' : r.raw);
  903. // readTime 字段(需求:阅读时长)
  904. var hasReadTime = artDetailOk && r.body.data.readTime !== undefined;
  905. record(F, 'parent', '文章阅读时长字段', hasReadTime,
  906. hasReadTime ? 'readTime=' + r.body.data.readTime : 'missing readTime');
  907. // 上报阅读时长(需求:阅读时长统计)
  908. r = await apiCall('POST', '/api/articles/record-read', { id: arts[0].id, childId: 1003, durationSeconds: 30 }, t);
  909. record(F, 'parent', '上报阅读时长', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recorded' : r.raw);
  910. } else {
  911. skip(F, 'parent', '文章详情', '无文章数据');
  912. skip(F, 'parent', '文章阅读时长字段', '无文章数据');
  913. skip(F, 'parent', '上报阅读时长', '无文章数据');
  914. }
  915. // 分享
  916. r = await apiCall('POST', '/api/share/my', {}, t);
  917. record(F, 'parent', '我的分享', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my shares OK' : r.raw);
  918. // 分享海报 — 生成小程序码(需求 855a8df5)
  919. r = await apiCall('POST', '/api/share/qrcode', { page: 'pages/activity/activity-detail/activity-detail', scene: 'id=1' }, t);
  920. record(F, 'parent', '分享小程序码', r.status === 200 && r.body && r.body.code === 200,
  921. (r.body && r.body.data) ? 'qrcode OK' : r.raw);
  922. // 媒体(使用真实存在的taskId)
  923. r = await apiCall('POST', '/api/media/upload-text', { taskId: 192, creatorId: 1002, content: 'Test media text' }, t);
  924. record(F, 'parent', '保存文字记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'text upload OK' : r.body ? r.body.message : r.raw);
  925. // --- 文章管理员 ---
  926. var tA = tokens.admin;
  927. if (tA) {
  928. r = await apiCall('POST', '/api/admin/articles/list', {}, tA);
  929. record(F, 'admin', '文章管理列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin articles OK' : r.raw);
  930. // 文章审核状态过滤(需求 ab9f4a44:admin/articles/list 支持 auditStatus 参数)
  931. r = await apiCall('POST', '/api/admin/articles/list', { auditStatus: 'approved' }, tA);
  932. record(F, 'admin', '文章按审核状态过滤', r.status === 200 && r.body && r.body.code === 200,
  933. r.body ? 'auditStatus filter OK' : r.raw);
  934. // 图片上传端点存在性验证(需求 598e7601 + c697fb7b:图片上传+5M限制)
  935. // 不发文件只验证端点可达(无文件应返回错误而非 404)
  936. r = await apiCall('POST', '/api/admin/articles/upload/image', {}, tA);
  937. var uploadEndpointExists = r.status === 200 && r.body && (r.body.code === 500 || r.body.code === 200);
  938. record(F, 'admin', '图片上传端点', uploadEndpointExists,
  939. r.body ? 'upload endpoint reachable: ' + (r.body.message || '') : r.raw);
  940. }
  941. }
  942. // ================================================================
  943. // FLOW 17: AI助手 (ai-assistant-flow)
  944. // ================================================================
  945. async function testAIFlow() {
  946. var F = 'AI助手';
  947. log('\n========== ' + F + ' ==========');
  948. var t = tokens.parent;
  949. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  950. // 会话列表
  951. var r = await apiCall('POST', '/api/ai/chat/conversations', {}, t);
  952. record(F, 'parent', 'AI会话列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'conversations OK' : r.raw);
  953. // AI管家会话
  954. r = await apiCall('POST', '/api/ai/butler/sessions/list', {}, t);
  955. record(F, 'parent', 'AI管家会话列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler sessions OK' : r.raw);
  956. // AI上下文(user_id 必须是字符串类型,后端用 (String) 强转)
  957. r = await apiCall('POST', '/api/ai/context', { user_id: "1002", intent_type: "general" }, t);
  958. record(F, 'parent', 'AI上下文', r.status === 200 && r.body && r.body.code === 200, r.body ? 'context OK' : r.raw);
  959. // 推荐搜索
  960. r = await apiCall('POST', '/api/recommend/search', { keyword: 'nutrition' }, t);
  961. record(F, 'parent', '推荐搜索', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recommend search OK' : r.raw);
  962. }
  963. // ================================================================
  964. // FLOW 18: 会员与订阅 (membership-flow)
  965. // ================================================================
  966. async function testMembershipFlow() {
  967. var F = '会员与订阅';
  968. log('\n========== ' + F + ' ==========');
  969. var t = tokens.parent;
  970. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  971. var r = await apiCall('POST', '/api/membership/levels', {}, t);
  972. record(F, 'parent', '会员等级列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'levels OK' : r.raw);
  973. r = await apiCall('POST', '/api/membership/my', {}, t);
  974. record(F, 'parent', '我的会员信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my membership OK' : r.raw);
  975. r = await apiCall('POST', '/api/membership/level', {}, t);
  976. record(F, 'parent', '当前等级', r.status === 200 && r.body && r.body.code === 200, r.body ? 'current level OK' : r.raw);
  977. // 功能权限(需要 feature 参数)
  978. r = await apiCall('POST', '/api/membership/can-use', { feature: 'advance_analysis' }, t);
  979. record(F, 'parent', '功能权限', r.status === 200 && r.body && r.body.code === 200, r.body ? 'can-use OK' : r.raw);
  980. // 会员折扣(需要 amount 参数)
  981. r = await apiCall('POST', '/api/membership/discount', { amount: 1000 }, t);
  982. record(F, 'parent', '会员折扣', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discount OK' : r.raw);
  983. // 订阅状态(需要familyId)
  984. r = await apiCall('POST', '/api/subscription/status?familyId=2001', {}, t);
  985. record(F, 'parent', '订阅状态', r.status === 200 && r.body && r.body.code === 200, r.body ? 'subscription status OK' : r.raw);
  986. r = await apiCall('POST', '/api/subscription/plans', {}, t);
  987. record(F, 'parent', '订阅计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'subscription plans OK' : r.raw);
  988. }
  989. // ================================================================
  990. // FLOW 19: 管理后台 (admin-overview-flow)
  991. // ================================================================
  992. async function testAdminFlow() {
  993. var F = '管理后台';
  994. log('\n========== ' + F + ' ==========');
  995. var t = tokens.admin;
  996. if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; }
  997. // 用户管理
  998. var r = await apiCall('POST', '/api/admin/users', {}, t);
  999. record(F, 'admin', '用户列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'users OK' : r.raw);
  1000. // 孩子管理
  1001. r = await apiCall('POST', '/api/admin/children', {}, t);
  1002. record(F, 'admin', '孩子列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'children OK' : r.raw);
  1003. // 家庭管理
  1004. r = await apiCall('POST', '/api/admin/families', {}, t);
  1005. record(F, 'admin', '家庭列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'families OK' : r.raw);
  1006. // 任务管理
  1007. r = await apiCall('POST', '/api/admin/tasks', {}, t);
  1008. record(F, 'admin', '任务列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'tasks OK' : r.raw);
  1009. // 积分管理
  1010. r = await apiCall('POST', '/api/admin/points/logs', {}, t);
  1011. record(F, 'admin', '积分日志', r.status === 200 && r.body && r.body.code === 200, r.body ? 'points logs OK' : r.raw);
  1012. // 规划师审核
  1013. r = await apiCall('POST', '/api/admin/guide/applications/pending', {}, t);
  1014. record(F, 'admin', '规划师待审批', r.status === 200 && r.body && r.body.code === 200, r.body ? 'guide pending OK' : r.raw);
  1015. // 商品管理
  1016. r = await apiCall('POST', '/api/admin/product/list', {}, t);
  1017. record(F, 'admin', '商品列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'product list OK' : r.raw);
  1018. // 订单管理
  1019. r = await apiCall('POST', '/api/admin/product/order/list', {}, t);
  1020. record(F, 'admin', '订单列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'order list OK' : r.raw);
  1021. // 小游戏管理
  1022. r = await apiCall('POST', '/api/mini-game/all', {}, t);
  1023. record(F, 'admin', '所有游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'all games OK' : r.raw);
  1024. // 健康检查
  1025. r = await apiCall('POST', '/api/system/health', {}, t);
  1026. record(F, 'admin', '系统健康检查', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health OK' : r.raw);
  1027. // 供应商体系管理
  1028. r = await apiCall('POST', '/api/admin/supplier/list', {}, t);
  1029. record(F, 'admin', '供应商列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'supplier list OK' : r.raw);
  1030. // 管家后台管理
  1031. r = await apiCall('POST', '/api/admin/butler/list', {}, t);
  1032. record(F, 'admin', '管家列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler list OK' : r.raw);
  1033. }
  1034. // ================================================================
  1035. // FLOW 20: 规划师体系 (guide-system-flow)
  1036. // ================================================================
  1037. async function testGuideFlow() {
  1038. var F = '规划师体系';
  1039. log('\n========== ' + F + ' ==========');
  1040. var t = tokens.teacher;
  1041. if (!t) { skip(F, 'teacher', '所有测试', '未登录'); return; }
  1042. // 个人信息
  1043. var r = await apiCall('POST', '/api/guide/my', {}, t);
  1044. record(F, 'teacher', '我的信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my info OK' : r.raw);
  1045. r = await apiCall('POST', '/api/guide/my/packages', {}, t);
  1046. record(F, 'teacher', '我的套餐', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my packages OK' : r.raw);
  1047. // 团队管理
  1048. r = await apiCall('POST', '/api/guide/team/my-team', {}, t);
  1049. record(F, 'teacher', '我的团队', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my team OK' : r.raw);
  1050. r = await apiCall('POST', '/api/guide/team/members', {}, t);
  1051. record(F, 'teacher', '团队成员', r.status === 200 && r.body && r.body.code === 200, r.body ? 'team members OK' : r.raw);
  1052. // 绑定家庭
  1053. r = await apiCall('POST', '/api/guide/families/bound-families', {}, t);
  1054. record(F, 'teacher', '绑定家庭', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'bound families OK' : r.raw);
  1055. // 任务模板
  1056. r = await apiCall('POST', '/api/guide/packages/list', {}, t);
  1057. record(F, 'teacher', '模板列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'package list OK' : r.raw);
  1058. // 训练方案
  1059. r = await apiCall('POST', '/api/guide/training-plans/list', {}, t);
  1060. record(F, 'teacher', '方案列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'training plans OK' : r.raw);
  1061. // 活动管理
  1062. r = await apiCall('POST', '/api/guide/activities/list', {}, t);
  1063. record(F, 'teacher', '活动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'activities OK' : r.raw);
  1064. // 成长记录
  1065. r = await apiCall('POST', '/api/guide/record/my-records', {}, t);
  1066. record(F, 'teacher', '我的记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'records OK' : r.raw);
  1067. // 咨询消息
  1068. r = await apiCall('POST', '/api/guide/messages/list', {}, t);
  1069. record(F, 'teacher', '咨询列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'messages OK' : r.raw);
  1070. // 订单佣金
  1071. r = await apiCall('POST', '/api/guide/orders/list', {}, t);
  1072. record(F, 'teacher', '订单列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'orders OK' : r.raw);
  1073. // 仪表盘
  1074. r = await apiCall('POST', '/api/guide/families/dashboard-stats', {}, t);
  1075. record(F, 'teacher', '仪表盘统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dashboard stats OK' : r.raw);
  1076. }
  1077. // ================================================================
  1078. // FLOW 21: 圈子与联系人 (circle-contact-flow)
  1079. // ================================================================
  1080. async function testCircleFlow() {
  1081. var F = '圈子与联系人';
  1082. log('\n========== ' + F + ' ==========');
  1083. var t = tokens.parent;
  1084. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  1085. var r = await apiCall('POST', '/api/circle/my-circles', { memberId: 1003 }, t);
  1086. record(F, 'parent', '我的圈子', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my circles OK' : r.raw);
  1087. r = await apiCall('POST', '/api/circle/discover', { childId: 1003 }, t);
  1088. record(F, 'parent', '发现圈子', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discover OK' : r.raw);
  1089. // 健康圈子
  1090. r = await apiCall('POST', '/api/health/circle/list', {}, t);
  1091. record(F, 'parent', '健康圈子列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health circle OK' : r.raw);
  1092. // 联系人
  1093. r = await apiCall('POST', '/api/contact/list', {}, t);
  1094. record(F, 'parent', '联系人列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'contacts OK' : r.raw);
  1095. // 家庭挑战
  1096. // 家庭挑战 — 双目标模式字段验证
  1097. // 先从 /api/family/user/info 获取 familyId
  1098. var familyId = null;
  1099. var infoResp = await apiCall('POST', '/api/family/user/info', {}, t);
  1100. if (infoResp.body && infoResp.body.code === 200 && infoResp.body.data) {
  1101. familyId = infoResp.body.data.familyId || infoResp.body.data.familyId;
  1102. }
  1103. // 如果上面没拿到,用固定测试值 2001(与文件其他地方一致,如 line 333/462/520)
  1104. if (!familyId) familyId = 2001;
  1105. r = await apiCall('POST', '/api/health/challenge/list', { familyId: familyId }, t);
  1106. var challengeOk = r.status === 200 && r.body && r.body.code === 200;
  1107. record(F, 'parent', '家庭挑战列表', challengeOk, challengeOk ? 'list OK' : r.raw);
  1108. // 验证双目标字段
  1109. var challenges = (r.body && r.body.data) ? r.body.data : [];
  1110. var hasTargetFields = challenges.length > 0 && challenges[0].targetMode && challenges[0].targetValue !== undefined;
  1111. record(F, 'parent', '挑战目标模式字段', challengeOk && hasTargetFields,
  1112. hasTargetFields ? 'targetMode=' + challenges[0].targetMode + ' targetValue=' + challenges[0].targetValue : 'no active challenges or missing fields');
  1113. // 验证进度字段
  1114. var hasProgressFields = challenges.length > 0 && challenges[0].totalProgress !== undefined && Array.isArray(challenges[0].memberProgress);
  1115. record(F, 'parent', '挑战进度字段', challengeOk && hasProgressFields,
  1116. hasProgressFields ? 'totalProgress=' + challenges[0].totalProgress + ' memberProgress=' + challenges[0].memberProgress.length + '人' : 'no progress fields');
  1117. // 历史挑战
  1118. r = await apiCall('POST', '/api/health/challenge/history', { familyId: familyId }, t);
  1119. record(F, 'parent', '挑战历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  1120. // 进度上报(如存在 active 挑战,上报 delta=1)
  1121. if (challenges.length > 0) {
  1122. var ch = challenges[0];
  1123. r = await apiCall('POST', '/api/health/challenge/progress', { challengeId: ch.id, childId: 1003, delta: 1 }, t);
  1124. record(F, 'parent', '挑战进度上报', r.status === 200 && r.body && r.body.code === 200, r.body ? 'progress reported' : r.raw);
  1125. } else {
  1126. skip(F, 'parent', '挑战进度上报', '无活跃挑战');
  1127. }
  1128. }
  1129. // ================================================================
  1130. // FLOW 22: 知识库与邀请 (knowledge-invite-flow)
  1131. // ================================================================
  1132. async function testKnowledgeFlow() {
  1133. var F = '知识库与邀请';
  1134. log('\n========== ' + F + ' ==========');
  1135. // 管理员:知识库
  1136. var t = tokens.admin;
  1137. if (t) {
  1138. var r = await apiCall('POST', '/api/health/knowledge/list', {}, t);
  1139. record(F, 'admin', '知识库列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'kb list OK' : r.raw);
  1140. r = await apiCall('POST', '/api/health/knowledge/v3/bacteria/query', { name: 'lactobacillus' }, t);
  1141. record(F, 'admin', '菌属查询', r.status === 200 && r.body && r.body.code === 200, r.body ? 'bacteria query OK' : r.raw);
  1142. r = await apiCall('POST', '/api/health/knowledge/v3/food/query', { name: 'apple' }, t);
  1143. record(F, 'admin', '食物营养查询', r.status === 200 && r.body && r.body.code === 200, r.body ? 'food query OK' : r.raw);
  1144. }
  1145. // 用户:邀请码
  1146. var t2 = tokens.parent;
  1147. if (t2) {
  1148. var r = await apiCall('POST', '/api/invite/code', {}, t2);
  1149. record(F, 'parent', '生成邀请码', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite code OK' : r.raw);
  1150. r = await apiCall('POST', '/api/invite/list', {}, t2);
  1151. record(F, 'parent', '邀请列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite list OK' : r.raw);
  1152. r = await apiCall('POST', '/api/invite/summary', {}, t2);
  1153. record(F, 'parent', '邀请汇总', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite summary OK' : r.raw);
  1154. // 里程碑
  1155. r = await apiCall('POST', '/api/invite/milestone/list', {}, t2);
  1156. record(F, 'parent', '里程碑列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'milestones OK' : r.raw);
  1157. }
  1158. }
  1159. // ================================================================
  1160. // FLOW 23: 其他功能 (extra-features-flow)
  1161. // ================================================================
  1162. async function testExtraFlow() {
  1163. var F = '其他功能';
  1164. log('\n========== ' + F + ' ==========');
  1165. var t = tokens.parent;
  1166. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  1167. // 吉祥物
  1168. var r = await apiCall('POST', '/api/user/mascot/get', {}, t);
  1169. record(F, 'parent', '获取吉祥物', r.status === 200 && r.body && r.body.code === 200, r.body ? 'mascot OK' : r.raw);
  1170. // 每日反馈
  1171. r = await apiCall('POST', '/api/feedback/today', {}, t);
  1172. record(F, 'parent', '今日反馈', r.status === 200 && r.body && r.body.code === 200, r.body ? 'today feedback OK' : r.raw);
  1173. r = await apiCall('POST', '/api/feedback/recent', {}, t);
  1174. record(F, 'parent', '最近反馈', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recent feedback OK' : r.raw);
  1175. // 生成小程序码(仅支持 type=register 且需要 referralCode)
  1176. r = await apiCall('POST', '/api/common/qrcode', { type: 'register', referralCode: 'TEST2001' }, t);
  1177. record(F, 'parent', '生成小程序码', r.status === 200 && r.body && r.body.code === 200, r.body ? 'qrcode OK' : r.raw);
  1178. // 引导进度
  1179. r = await apiCall('POST', '/api/onboarding/progress', {}, t);
  1180. record(F, 'parent', '引导进度', r.status === 200 && r.body && r.body.code === 200, r.body ? 'onboarding OK' : r.raw);
  1181. }
  1182. // ================================================================
  1183. // FLOW 24: 管家服务 (butler-service-flow)
  1184. // ================================================================
  1185. async function testButlerFlow() {
  1186. var F = '管家服务';
  1187. log('\n========== ' + F + ' ==========');
  1188. var t = tokens.butler;
  1189. if (!t) { skip(F, 'butler', '所有测试', '未登录'); return; }
  1190. var r = await apiCall('POST', '/api/butler/profile', {}, t);
  1191. record(F, 'butler', '管家档案', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'butler profile OK (code=' + r.body.code + ')' : r.raw);
  1192. r = await apiCall('POST', '/api/butler/members', {}, t);
  1193. record(F, 'butler', '家庭成员列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler members OK' : r.raw);
  1194. }
  1195. // ================================================================
  1196. // FLOW 25: 营养师管理 (nutritionist-management-flow)
  1197. // ================================================================
  1198. async function testNutritionistFlow() {
  1199. var F = '营养师管理';
  1200. log('\n========== ' + F + ' ==========');
  1201. var t = tokens.nutritionist;
  1202. if (!t) { skip(F, 'nutritionist', '所有测试', '未登录'); return; }
  1203. var r = await apiCall('POST', '/api/nutritionist/status', {}, t);
  1204. record(F, 'nutritionist', '营养师状态', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutritionist status OK' : r.raw);
  1205. }
  1206. // ================================================================
  1207. // FLOW 26: 供应商体系 (supply-system-flow)
  1208. // ================================================================
  1209. async function testSupplyFlow() {
  1210. var F = '供应商体系';
  1211. log('\n========== ' + F + ' ==========');
  1212. var t = tokens.admin;
  1213. if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; }
  1214. var r = await apiCall('POST', '/api/admin/supplier/list', {}, t);
  1215. record(F, 'admin', '供应商列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'supplier list OK' : r.raw);
  1216. r = await apiCall('POST', '/api/admin/supplier/product/list', {}, t);
  1217. record(F, 'admin', '供应商商品列表', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'supplier products OK (code=' + r.body.code + ')' : r.raw);
  1218. }
  1219. // ================================================================
  1220. // FLOW 27: 评估师管理 (assessor-management-flow)
  1221. // ================================================================
  1222. async function testAssessorFlow() {
  1223. var F = '评估师管理';
  1224. log('\n========== ' + F + ' ==========');
  1225. var t = tokens.assessor;
  1226. if (!t) { skip(F, 'assessor', '所有测试', '未登录'); return; }
  1227. var r = await apiCall('POST', '/api/assessment/my-results', {}, t);
  1228. record(F, 'assessor', '评估结果列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'assessor results OK' : r.raw);
  1229. }
  1230. // ================================================================
  1231. // MAIN RUNNER
  1232. // ================================================================
  1233. async function main() {
  1234. console.log('============================================================');
  1235. console.log(' 流程级一级测试用例 — FLOW L1 TEST SUITE');
  1236. console.log(' Target: ' + BASE_URL);
  1237. console.log(' Date: ' + new Date().toISOString());
  1238. console.log('============================================================\n');
  1239. // Step 1: Login all roles
  1240. console.log('--- LOGIN ALL ROLES ---');
  1241. await loginAll();
  1242. console.log('');
  1243. // Step 2: Run all flow tests
  1244. await testAuthFlow();
  1245. await testFamilyFlow();
  1246. await testTaskFlow();
  1247. await testWishFlow();
  1248. await testEnergyFlow();
  1249. await testCheckinFlow();
  1250. await testGrowthFlow();
  1251. await testDanFlow();
  1252. await testShopFlow();
  1253. await testPointsFlow();
  1254. await testMinigameFlow();
  1255. await testNutritionFlow();
  1256. await testMindFlow();
  1257. await testHealthDetectionFlow();
  1258. await testActivityFlow();
  1259. await testContentFlow();
  1260. await testAIFlow();
  1261. await testMembershipFlow();
  1262. await testAdminFlow();
  1263. await testGuideFlow();
  1264. await testCircleFlow();
  1265. await testKnowledgeFlow();
  1266. await testExtraFlow();
  1267. await testButlerFlow();
  1268. await testNutritionistFlow();
  1269. await testSupplyFlow();
  1270. await testAssessorFlow();
  1271. // Step 3: Summary
  1272. console.log('\n============================================================');
  1273. console.log(' TEST RESULTS SUMMARY');
  1274. console.log('============================================================');
  1275. console.log(' ✅ Passed: ' + results.passed);
  1276. console.log(' ❌ Failed: ' + results.failed);
  1277. console.log(' ⏭️ Skipped: ' + results.skipped);
  1278. console.log(' Total: ' + (results.passed + results.failed + results.skipped));
  1279. console.log('============================================================');
  1280. // Group failures by flow
  1281. var failures = results.details.filter(function(d) { return d.passed === false; });
  1282. if (failures.length > 0) {
  1283. console.log('\n--- FAILURES ---');
  1284. var byFlow = {};
  1285. failures.forEach(function(f) {
  1286. if (!byFlow[f.flow]) byFlow[f.flow] = [];
  1287. byFlow[f.flow].push(f);
  1288. });
  1289. for (var flow in byFlow) {
  1290. console.log('\n[' + flow + ']');
  1291. byFlow[flow].forEach(function(f) {
  1292. console.log(' ❌ ' + f.role + ' | ' + f.step + ' — ' + (f.detail || ''));
  1293. });
  1294. }
  1295. }
  1296. // Group issues
  1297. if (results.issues.length > 0) {
  1298. console.log('\n--- ISSUES ---');
  1299. results.issues.forEach(function(iss) {
  1300. console.log('⚠️ [' + iss.flow + '] ' + iss.role + ' | ' + iss.step + ': ' + iss.description);
  1301. });
  1302. }
  1303. // Flows with all passes
  1304. var allFlows = {};
  1305. results.details.forEach(function(d) {
  1306. if (!allFlows[d.flow]) allFlows[d.flow] = { pass: 0, fail: 0, skip: 0 };
  1307. if (d.passed === true) allFlows[d.flow].pass++;
  1308. else if (d.passed === false) allFlows[d.flow].fail++;
  1309. else allFlows[d.flow].skip++;
  1310. });
  1311. console.log('\n--- FLOW STATUS ---');
  1312. for (var f in allFlows) {
  1313. var s = allFlows[f];
  1314. var status = s.fail === 0 ? '✅' : '❌';
  1315. console.log(status + ' ' + f + ': ' + s.pass + '/' + (s.pass + s.fail) + ' passed, ' + s.skip + ' skipped');
  1316. }
  1317. console.log('\n============================================================');
  1318. console.log(' DONE');
  1319. console.log('============================================================');
  1320. }
  1321. main().catch(function(err) {
  1322. console.error('FATAL:', err);
  1323. process.exit(1);
  1324. });