v11_test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. const http = require('http');
  2. const fs = require('fs');
  3. const path = require('path');
  4. const BASE = 'cfc.iwintrue.com';
  5. const PORT = 80;
  6. const RESULT_FILE = path.join(__dirname, 'v11_result.txt');
  7. let out = '';
  8. let adminToken = null;
  9. function post(path, body, headers) {
  10. return new Promise(resolve => {
  11. const data = JSON.stringify(body);
  12. const req = http.request({ hostname: BASE, port: PORT, path, method: 'POST', headers: { 'Content-Type': 'application/json', ...headers, 'Content-Length': Buffer.byteLength(data) } }, res => {
  13. let d = '';
  14. res.on('data', c => d += c);
  15. res.on('end', () => {
  16. try { resolve({ status: res.statusCode, data: JSON.parse(d) }); }
  17. catch { resolve({ status: res.statusCode, raw: d.slice(0, 300) }); }
  18. });
  19. });
  20. req.on('error', e => resolve({ error: e.message }));
  21. req.write(data); req.end();
  22. });
  23. }
  24. async function main() {
  25. out += '=== CFC API v11 Results ===\n';
  26. out += 'Time: ' + new Date().toISOString() + '\n\n';
  27. // ===== Admin Login =====
  28. await post('/api/admin-auth/send-code', { phone: '13800138000' });
  29. const lr = await post('/api/admin-auth/login', { phone: '13800138000', code: '123456' });
  30. if (lr.data && lr.data.data && lr.data.data.token) {
  31. adminToken = lr.data.data.token;
  32. out += '[PASS] Admin login userId=' + (lr.data.data.adminId || 7) + '\n';
  33. } else {
  34. out += '[FAIL] Admin login: status=' + lr.status + ' raw=' + (lr.raw || JSON.stringify(lr.data)) + '\n';
  35. fs.writeFileSync(RESULT_FILE, out);
  36. return;
  37. }
  38. function check(name, resp) {
  39. if (resp.status === 200 && resp.data && resp.data.code === 200) {
  40. out += '[PASS] ' + name + '\n';
  41. } else {
  42. out += '[FAIL] ' + name + ': code=' + resp.status + ' msg=' + (resp.data ? (resp.data.message || JSON.stringify(resp.data)) : '') + '\n';
  43. }
  44. }
  45. function info(name, details) {
  46. out += '[INFO] ' + name + ': ' + details + '\n';
  47. }
  48. // ===================================================================
  49. // REGRESSION TESTS (from v9)
  50. // ===================================================================
  51. // NEW-01: Energy rule create
  52. const rc = 'CHK_' + Date.now();
  53. const er = await post('/api/admin/energy-rule/create', {
  54. ruleName: 'Test_' + Date.now(), dimensionCode: 'body', eventType: 'checkin',
  55. amount: 5, status: 1, action: 'increase', priority: 0, ruleCode: rc,
  56. }, { Authorization: 'Bearer ' + adminToken });
  57. check('REG Energy rule create', er);
  58. // NEW-02: SKU list
  59. const sku = await post('/api/admin/product/sku/list', { productId: 7 }, { Authorization: 'Bearer ' + adminToken });
  60. check('REG SKU list', sku);
  61. // NEW-03: Activity create + publish
  62. const act = await post('/api/activity/create', {
  63. title: 'Test_' + Date.now(), dimensionCode: 'body',
  64. startTime: new Date(Date.now() + 86400000 * 7).toISOString(),
  65. endTime: new Date(Date.now() + 86400000 * 8).toISOString(),
  66. location: 'test', maxParticipants: 10,
  67. }, { Authorization: 'Bearer ' + adminToken });
  68. const actId = act.data && act.data.data && (act.data.data.id || act.data.data);
  69. if (actId) {
  70. const pub = await post('/api/activity/publish', { id: actId }, { Authorization: 'Bearer ' + adminToken });
  71. check('REG Activity publish', pub);
  72. } else {
  73. info('REG Activity create', 'no id: ' + JSON.stringify(act.data));
  74. }
  75. // Public endpoints
  76. check('REG Articles list (public)', await post('/api/articles/list', { page: 1, size: 5 }));
  77. check('REG Activity list (public)', await post('/api/activity/list', { page: 1, size: 5 }));
  78. // Admin management
  79. check('REG User list', await post('/api/admin/users', { page: 1, size: 10 }, { Authorization: 'Bearer ' + adminToken }));
  80. check('REG Energy dims', await post('/api/admin/energy-rule/dimensions', {}, { Authorization: 'Bearer ' + adminToken }));
  81. check('REG Vendor list', await post('/api/admin/vendor/list', {}, { Authorization: 'Bearer ' + adminToken }));
  82. check('REG Admin articles', await post('/api/admin/articles/list', { page: 1, size: 5 }, { Authorization: 'Bearer ' + adminToken }));
  83. // Points & Energy
  84. check('REG Points balance', await post('/api/points/balance', { childId: 1 }, { Authorization: 'Bearer ' + adminToken }));
  85. check('REG Energy overview', await post('/api/energy/overview', { childId: 1 }, { Authorization: 'Bearer ' + adminToken }));
  86. // Article publish (specific ID)
  87. const ap = await post('/api/admin/articles/publish', { id: 13, status: 'published' }, { Authorization: 'Bearer ' + adminToken });
  88. check('REG Article publish', ap);
  89. // Create user
  90. const phone = '139' + String(Math.floor(Math.random() * 1e9)).padStart(9, '0');
  91. check('REG Create user', await post('/api/admin/users/create', { phone, password: 'Test123456', name: 'AutoTest', role: 'parent' }, { Authorization: 'Bearer ' + adminToken }));
  92. // Guide activity list
  93. const gaList = await post('/api/guide/activities/list', { status: '' }, { Authorization: 'Bearer ' + adminToken });
  94. if (gaList.status === 200 && gaList.data && gaList.data.code === 200) {
  95. out += '[PASS] REG Guide activity list (endpoint OK)\n';
  96. } else {
  97. info('REG Guide activity list', gaList.status + ' ' + (gaList.data ? (gaList.data.message || '') : ''));
  98. }
  99. // ===================================================================
  100. // ISSUE-001: Vendor login (with test-mode code bypass)
  101. // ===================================================================
  102. await post('/api/auth/send-code', { phone: '13800138001' });
  103. const vl = await post('/api/auth/phone-login', { phone: '13800138001', code: '123456' });
  104. if (vl.data && vl.data.data && vl.data.data.token) {
  105. out += '[PASS] ISSUE-001 Vendor login (with test-mode bypass)\n';
  106. const vs = await post('/api/product/shelve', { productId: 7, action: 'unshelve' }, { Authorization: 'Bearer ' + vl.data.data.token });
  107. check('ISSUE-001 Vendor unshelve', vs);
  108. } else {
  109. info('ISSUE-001 Vendor login', vl.status + ' ' + JSON.stringify(vl.data));
  110. }
  111. // ===================================================================
  112. // NEW FEATURE: Food Management (AdminFoodController)
  113. // ===================================================================
  114. // FOOD-01: Food list
  115. const foodList = await post('/api/admin/foods/list', { page: 1, size: 10 }, { Authorization: 'Bearer ' + adminToken });
  116. check('FOOD-01 Food list', foodList);
  117. // FOOD-02: Food create
  118. const foodCreate = await post('/api/admin/foods/create', {
  119. name: 'TestFood_' + Date.now(),
  120. category: 'vegetable',
  121. unit: '斤',
  122. nutrition: '{"calories":25,"protein":1.5}',
  123. }, { Authorization: 'Bearer ' + adminToken });
  124. check('FOOD-02 Food create', foodCreate);
  125. // FOOD-03: Food detail with months
  126. const foodId = foodCreate.data && foodCreate.data.data;
  127. if (foodId && typeof foodId !== 'string') {
  128. const foodDetail = await post('/api/admin/foods/detail', { id: foodId }, { Authorization: 'Bearer ' + adminToken });
  129. check('FOOD-03 Food detail', foodDetail);
  130. // FOOD-04: Save seasonal months
  131. const saveMonths = await post('/api/admin/foods/saveMonths', { id: foodId, months: [6, 7, 8] }, { Authorization: 'Bearer ' + adminToken });
  132. check('FOOD-04 Save months', saveMonths);
  133. // FOOD-05: Verify months after save
  134. const detail2 = await post('/api/admin/foods/detail', { id: foodId }, { Authorization: 'Bearer ' + adminToken });
  135. if (detail2.data && detail2.data.data && detail2.data.data.months) {
  136. const months = detail2.data.data.months;
  137. if (months.includes(6) && months.includes(7) && months.includes(8)) {
  138. out += '[PASS] FOOD-05 Verify months saved (6,7,8)\n';
  139. } else {
  140. info('FOOD-05 Verify months', 'expected 6,7,8 got ' + JSON.stringify(months));
  141. }
  142. } else {
  143. info('FOOD-05 Verify months', 'months not in response: ' + JSON.stringify(detail2.data));
  144. }
  145. // FOOD-06: Food delete
  146. check('FOOD-06 Food delete', await post('/api/admin/foods/delete', { id: foodId }, { Authorization: 'Bearer ' + adminToken }));
  147. } else {
  148. info('FOOD-03~06', 'food create no id: ' + JSON.stringify(foodCreate.data));
  149. }
  150. // ===================================================================
  151. // NEW FEATURE: Article Auto-Tagging
  152. // ===================================================================
  153. // =============================================
  154. // SUPPLY SYSTEM (供应商分销体系) v11新增
  155. // =============================================
  156. const sysName = 'AutoTestSystem_' + Date.now();
  157. const sysCreate = await post('/api/admin/supply-system/create', {
  158. name: sysName,
  159. settlementPeriodDays: 30,
  160. platformProfitRate: 0.1,
  161. description: 'Auto test supply system',
  162. status: 'ACTIVE',
  163. }, { Authorization: 'Bearer ' + adminToken });
  164. check('SUPPLY-01 SupplySystem create', sysCreate);
  165. const sysId = sysCreate.data && sysCreate.data.data && sysCreate.data.data.id;
  166. if (sysId) {
  167. const sysList = await post('/api/admin/supply-system/list', {}, { Authorization: 'Bearer ' + adminToken });
  168. check('SUPPLY-02 SupplySystem list', sysList);
  169. const sysToggle = await post('/api/admin/supply-system/toggle-status', { id: sysId }, { Authorization: 'Bearer ' + adminToken });
  170. check('SUPPLY-03 SupplySystem toggle status', sysToggle);
  171. // Create supplier under this system
  172. const supCreate = await post('/api/admin/supply-system/supplier/create', {
  173. name: 'AutoSupplier_' + Date.now(),
  174. contactName: '联系人',
  175. contactPhone: '13800138001',
  176. supplySystemId: sysId,
  177. commissionRate: 0.05,
  178. }, { Authorization: 'Bearer ' + adminToken });
  179. check('SUPPLY-04 Supplier create', supCreate);
  180. const supId = supCreate.data && supCreate.data.data && supCreate.data.data.id;
  181. if (supId) {
  182. const supList = await post('/api/admin/supply-system/supplier/list', { supplySystemId: sysId }, { Authorization: 'Bearer ' + adminToken });
  183. check('SUPPLY-05 Supplier list by system', supList);
  184. const supUpdate = await post('/api/admin/supply-system/supplier/update', {
  185. id: supId,
  186. name: 'AutoSupplier_Updated',
  187. contactName: '联系人_updated',
  188. }, { Authorization: 'Bearer ' + adminToken });
  189. check('SUPPLY-06 Supplier update', supUpdate);
  190. const supDelete = await post('/api/admin/supply-system/supplier/delete', { id: supId }, { Authorization: 'Bearer ' + adminToken });
  191. check('SUPPLY-07 Supplier delete', supDelete);
  192. }
  193. } else {
  194. info('SUPPLY-01', 'create no id: ' + JSON.stringify(sysCreate.data));
  195. }
  196. // =============================================
  197. // FOOD RECOMMEND (食材推荐) v11新增
  198. // =============================================
  199. check('FOOD-REC-01 Food recommend index (userId=1)', await post('/api/food/recommendation/index', { userId: 1 }, { Authorization: 'Bearer ' + adminToken }));
  200. check('FOOD-REC-02 Food recommend by-family (familyId=1)', await post('/api/food/recommendation/by-family', { familyId: 1 }, { Authorization: 'Bearer ' + adminToken }));
  201. // =============================================
  202. // ARTICLE WORKFLOW (submitForReview/withdraw/reDraft) v11新增
  203. // =============================================
  204. // Create a draft article for workflow test
  205. const wfArticleCreate = await post('/api/admin/articles/create', {
  206. title: 'WorkflowTest_' + Date.now(),
  207. content: '<p>工作流测试文章</p>',
  208. categoryId: 1,
  209. status: 'draft',
  210. coverImage: '',
  211. summary: 'Workflow test',
  212. }, { Authorization: 'Bearer ' + adminToken });
  213. if (wfArticleCreate.data && wfArticleCreate.data.data) {
  214. const wfId = wfArticleCreate.data.data.id || wfArticleCreate.data.data;
  215. out += '[PASS] ARTICLE-WF-00 Article created for workflow test\n';
  216. if (typeof wfId === 'number') {
  217. // submitForReview: draft -> pending
  218. const wfSubmit = await post('/api/admin/articles/submit-review', { id: wfId }, { Authorization: 'Bearer ' + adminToken });
  219. check('ARTICLE-WF-01 submitForReview (draft→pending)', wfSubmit);
  220. // Create another article for withdraw test (need published state - skip for now, requires approval flow)
  221. // For now just verify the endpoint exists
  222. out += '[INFO] ARTICLE-WF-02 withdraw: requires published state (approval flow needed)\n';
  223. out += '[INFO] ARTICLE-WF-03 reDraft: requires rejected state (approval flow needed)\n';
  224. // Cleanup
  225. await post('/api/admin/articles/delete', { id: wfId }, { Authorization: 'Bearer ' + adminToken });
  226. }
  227. } else {
  228. info('ARTICLE-WF-00', 'create failed: ' + JSON.stringify(wfArticleCreate.data));
  229. }
  230. // =============================================
  231. // AUTO TAG TEST (existing)
  232. // =============================================
  233. const articleCreate = await post('/api/admin/articles/create', {
  234. title: 'AutoTagTest_' + Date.now(),
  235. content: '今天天气很好,适合户外跑步锻炼身体。多吃水果蔬菜对健康有益。',
  236. categoryId: 1,
  237. status: 'draft',
  238. coverImage: '',
  239. summary: 'Auto tag test article',
  240. }, { Authorization: 'Bearer ' + adminToken });
  241. if (articleCreate.data && articleCreate.data.data) {
  242. const articleId = articleCreate.data.data.id || articleCreate.data.data;
  243. out += '[PASS] AUTO-TAG-01 Article create\n';
  244. if (typeof articleId === 'number') {
  245. // Check article detail to see auto-generated tags
  246. const articleDetail = await post('/api/admin/articles/detail', { id: articleId }, { Authorization: 'Bearer ' + adminToken });
  247. if (articleDetail.data && articleDetail.data.code === 200) {
  248. const article = articleDetail.data.data;
  249. if (article.tags && article.tags.length > 0) {
  250. out += '[PASS] AUTO-TAG-02 Tags auto-generated: ' + article.tags + '\n';
  251. } else {
  252. info('AUTO-TAG-02 Tags', 'no tags field in response: ' + JSON.stringify(article).slice(0, 200));
  253. }
  254. } else {
  255. info('AUTO-TAG-02 Tags detail', JSON.stringify(articleDetail.data));
  256. }
  257. // Cleanup
  258. await post('/api/admin/articles/delete', { id: articleId }, { Authorization: 'Bearer ' + adminToken });
  259. }
  260. } else {
  261. info('AUTO-TAG-01', 'create failed: ' + JSON.stringify(articleCreate.data));
  262. }
  263. // ===================================================================
  264. // SUMMARY
  265. // ===================================================================
  266. const lines = out.split('\n').filter(l => l.startsWith('[PASS]') || l.startsWith('[FAIL]') || l.startsWith('[SKIP]') || l.startsWith('[INFO]'));
  267. const passCount = lines.filter(l => l.startsWith('[PASS]')).length;
  268. const failCount = lines.filter(l => l.startsWith('[FAIL]')).length;
  269. const skipCount = lines.filter(l => l.startsWith('[SKIP]')).length;
  270. const infoCount = lines.filter(l => l.startsWith('[INFO]')).length;
  271. out += '\n=== Summary: ' + passCount + ' PASS / ' + failCount + ' FAIL / ' + skipCount + ' SKIP / ' + infoCount + ' INFO / ' + lines.length + ' Total ===\n';
  272. fs.writeFileSync(RESULT_FILE, out);
  273. console.log('V11_DONE - ' + RESULT_FILE);
  274. }
  275. main().catch(e => {
  276. fs.writeFileSync(RESULT_FILE, 'ERROR: ' + e.message);
  277. console.error('V11_ERROR: ' + e.message);
  278. });