|
@@ -0,0 +1,226 @@
|
|
|
|
|
+const http = require('http');
|
|
|
|
|
+const fs = require('fs');
|
|
|
|
|
+const path = require('path');
|
|
|
|
|
+
|
|
|
|
|
+const BASE = 'cfc.iwintrue.com';
|
|
|
|
|
+const PORT = 80;
|
|
|
|
|
+const RESULT_FILE = path.join(__dirname, 'v11_result.txt');
|
|
|
|
|
+let out = '';
|
|
|
|
|
+let adminToken = null;
|
|
|
|
|
+
|
|
|
|
|
+function post(path, body, headers) {
|
|
|
|
|
+ return new Promise(resolve => {
|
|
|
|
|
+ const data = JSON.stringify(body);
|
|
|
|
|
+ const req = http.request({ hostname: BASE, port: PORT, path, method: 'POST', headers: { 'Content-Type': 'application/json', ...headers, 'Content-Length': Buffer.byteLength(data) } }, res => {
|
|
|
|
|
+ let d = '';
|
|
|
|
|
+ res.on('data', c => d += c);
|
|
|
|
|
+ res.on('end', () => {
|
|
|
|
|
+ try { resolve({ status: res.statusCode, data: JSON.parse(d) }); }
|
|
|
|
|
+ catch { resolve({ status: res.statusCode, raw: d.slice(0, 300) }); }
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ req.on('error', e => resolve({ error: e.message }));
|
|
|
|
|
+ req.write(data); req.end();
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function main() {
|
|
|
|
|
+ out += '=== CFC API v11 Results ===\n';
|
|
|
|
|
+ out += 'Time: ' + new Date().toISOString() + '\n\n';
|
|
|
|
|
+
|
|
|
|
|
+ // ===== Admin Login =====
|
|
|
|
|
+ await post('/api/admin-auth/send-code', { phone: '13800138000' });
|
|
|
|
|
+ const lr = await post('/api/admin-auth/login', { phone: '13800138000', code: '123456' });
|
|
|
|
|
+ if (lr.data && lr.data.data && lr.data.data.token) {
|
|
|
|
|
+ adminToken = lr.data.data.token;
|
|
|
|
|
+ out += '[PASS] Admin login userId=' + (lr.data.data.adminId || 7) + '\n';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ out += '[FAIL] Admin login: status=' + lr.status + ' raw=' + (lr.raw || JSON.stringify(lr.data)) + '\n';
|
|
|
|
|
+ fs.writeFileSync(RESULT_FILE, out);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function check(name, resp) {
|
|
|
|
|
+ if (resp.status === 200 && resp.data && resp.data.code === 200) {
|
|
|
|
|
+ out += '[PASS] ' + name + '\n';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ out += '[FAIL] ' + name + ': code=' + resp.status + ' msg=' + (resp.data ? (resp.data.message || JSON.stringify(resp.data)) : '') + '\n';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function info(name, details) {
|
|
|
|
|
+ out += '[INFO] ' + name + ': ' + details + '\n';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+ // REGRESSION TESTS (from v9)
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+
|
|
|
|
|
+ // NEW-01: Energy rule create
|
|
|
|
|
+ const rc = 'CHK_' + Date.now();
|
|
|
|
|
+ const er = await post('/api/admin/energy-rule/create', {
|
|
|
|
|
+ ruleName: 'Test_' + Date.now(), dimensionCode: 'body', eventType: 'checkin',
|
|
|
|
|
+ amount: 5, status: 1, action: 'increase', priority: 0, ruleCode: rc,
|
|
|
|
|
+ }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ check('REG Energy rule create', er);
|
|
|
|
|
+
|
|
|
|
|
+ // NEW-02: SKU list
|
|
|
|
|
+ const sku = await post('/api/admin/product/sku/list', { productId: 7 }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ check('REG SKU list', sku);
|
|
|
|
|
+
|
|
|
|
|
+ // NEW-03: Activity create + publish
|
|
|
|
|
+ const act = await post('/api/activity/create', {
|
|
|
|
|
+ title: 'Test_' + Date.now(), dimensionCode: 'body',
|
|
|
|
|
+ startTime: new Date(Date.now() + 86400000 * 7).toISOString(),
|
|
|
|
|
+ endTime: new Date(Date.now() + 86400000 * 8).toISOString(),
|
|
|
|
|
+ location: 'test', maxParticipants: 10,
|
|
|
|
|
+ }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ const actId = act.data && act.data.data && (act.data.data.id || act.data.data);
|
|
|
|
|
+ if (actId) {
|
|
|
|
|
+ const pub = await post('/api/activity/publish', { id: actId }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ check('REG Activity publish', pub);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('REG Activity create', 'no id: ' + JSON.stringify(act.data));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Public endpoints
|
|
|
|
|
+ check('REG Articles list (public)', await post('/api/articles/list', { page: 1, size: 5 }));
|
|
|
|
|
+ check('REG Activity list (public)', await post('/api/activity/list', { page: 1, size: 5 }));
|
|
|
|
|
+
|
|
|
|
|
+ // Admin management
|
|
|
|
|
+ check('REG User list', await post('/api/admin/users', { page: 1, size: 10 }, { Authorization: 'Bearer ' + adminToken }));
|
|
|
|
|
+ check('REG Energy dims', await post('/api/admin/energy-rule/dimensions', {}, { Authorization: 'Bearer ' + adminToken }));
|
|
|
|
|
+ check('REG Vendor list', await post('/api/admin/vendor/list', {}, { Authorization: 'Bearer ' + adminToken }));
|
|
|
|
|
+ check('REG Admin articles', await post('/api/admin/articles/list', { page: 1, size: 5 }, { Authorization: 'Bearer ' + adminToken }));
|
|
|
|
|
+
|
|
|
|
|
+ // Points & Energy
|
|
|
|
|
+ check('REG Points balance', await post('/api/points/balance', { childId: 1 }, { Authorization: 'Bearer ' + adminToken }));
|
|
|
|
|
+ check('REG Energy overview', await post('/api/energy/overview', { childId: 1 }, { Authorization: 'Bearer ' + adminToken }));
|
|
|
|
|
+
|
|
|
|
|
+ // Article publish (specific ID)
|
|
|
|
|
+ const ap = await post('/api/admin/articles/publish', { id: 13, status: 'published' }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ check('REG Article publish', ap);
|
|
|
|
|
+
|
|
|
|
|
+ // Create user
|
|
|
|
|
+ const phone = '139' + String(Math.floor(Math.random() * 1e9)).padStart(9, '0');
|
|
|
|
|
+ check('REG Create user', await post('/api/admin/users/create', { phone, password: 'Test123456', name: 'AutoTest', role: 'parent' }, { Authorization: 'Bearer ' + adminToken }));
|
|
|
|
|
+
|
|
|
|
|
+ // Guide activity list
|
|
|
|
|
+ const gaList = await post('/api/guide/activities/list', { status: '' }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ if (gaList.status === 200 && gaList.data && gaList.data.code === 200) {
|
|
|
|
|
+ out += '[PASS] REG Guide activity list (endpoint OK)\n';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('REG Guide activity list', gaList.status + ' ' + (gaList.data ? (gaList.data.message || '') : ''));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+ // ISSUE-001: Vendor login (with test-mode code bypass)
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+ await post('/api/auth/send-code', { phone: '13800138001' });
|
|
|
|
|
+ const vl = await post('/api/auth/phone-login', { phone: '13800138001', code: '123456' });
|
|
|
|
|
+ if (vl.data && vl.data.data && vl.data.data.token) {
|
|
|
|
|
+ out += '[PASS] ISSUE-001 Vendor login (with test-mode bypass)\n';
|
|
|
|
|
+ const vs = await post('/api/product/shelve', { productId: 7, action: 'unshelve' }, { Authorization: 'Bearer ' + vl.data.data.token });
|
|
|
|
|
+ check('ISSUE-001 Vendor unshelve', vs);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('ISSUE-001 Vendor login', vl.status + ' ' + JSON.stringify(vl.data));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+ // NEW FEATURE: Food Management (AdminFoodController)
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+
|
|
|
|
|
+ // FOOD-01: Food list
|
|
|
|
|
+ const foodList = await post('/api/admin/foods/list', { page: 1, size: 10 }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ check('FOOD-01 Food list', foodList);
|
|
|
|
|
+
|
|
|
|
|
+ // FOOD-02: Food create
|
|
|
|
|
+ const foodCreate = await post('/api/admin/foods/create', {
|
|
|
|
|
+ name: 'TestFood_' + Date.now(),
|
|
|
|
|
+ category: 'vegetable',
|
|
|
|
|
+ unit: '斤',
|
|
|
|
|
+ nutrition: '{"calories":25,"protein":1.5}',
|
|
|
|
|
+ }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ check('FOOD-02 Food create', foodCreate);
|
|
|
|
|
+
|
|
|
|
|
+ // FOOD-03: Food detail with months
|
|
|
|
|
+ const foodId = foodCreate.data && foodCreate.data.data;
|
|
|
|
|
+ if (foodId && typeof foodId !== 'string') {
|
|
|
|
|
+ const foodDetail = await post('/api/admin/foods/detail', { id: foodId }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ check('FOOD-03 Food detail', foodDetail);
|
|
|
|
|
+
|
|
|
|
|
+ // FOOD-04: Save seasonal months
|
|
|
|
|
+ const saveMonths = await post('/api/admin/foods/saveMonths', { id: foodId, months: [6, 7, 8] }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ check('FOOD-04 Save months', saveMonths);
|
|
|
|
|
+
|
|
|
|
|
+ // FOOD-05: Verify months after save
|
|
|
|
|
+ const detail2 = await post('/api/admin/foods/detail', { id: foodId }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ if (detail2.data && detail2.data.data && detail2.data.data.months) {
|
|
|
|
|
+ const months = detail2.data.data.months;
|
|
|
|
|
+ if (months.includes(6) && months.includes(7) && months.includes(8)) {
|
|
|
|
|
+ out += '[PASS] FOOD-05 Verify months saved (6,7,8)\n';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('FOOD-05 Verify months', 'expected 6,7,8 got ' + JSON.stringify(months));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('FOOD-05 Verify months', 'months not in response: ' + JSON.stringify(detail2.data));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // FOOD-06: Food delete
|
|
|
|
|
+ check('FOOD-06 Food delete', await post('/api/admin/foods/delete', { id: foodId }, { Authorization: 'Bearer ' + adminToken }));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('FOOD-03~06', 'food create no id: ' + JSON.stringify(foodCreate.data));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+ // NEW FEATURE: Article Auto-Tagging
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+ const articleCreate = await post('/api/admin/articles/create', {
|
|
|
|
|
+ title: 'AutoTagTest_' + Date.now(),
|
|
|
|
|
+ content: '今天天气很好,适合户外跑步锻炼身体。多吃水果蔬菜对健康有益。',
|
|
|
|
|
+ categoryId: 1,
|
|
|
|
|
+ status: 'draft',
|
|
|
|
|
+ coverImage: '',
|
|
|
|
|
+ summary: 'Auto tag test article',
|
|
|
|
|
+ }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ if (articleCreate.data && articleCreate.data.data) {
|
|
|
|
|
+ const articleId = articleCreate.data.data.id || articleCreate.data.data;
|
|
|
|
|
+ out += '[PASS] AUTO-TAG-01 Article create\n';
|
|
|
|
|
+ if (typeof articleId === 'number') {
|
|
|
|
|
+ // Check article detail to see auto-generated tags
|
|
|
|
|
+ const articleDetail = await post('/api/admin/articles/detail', { id: articleId }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ if (articleDetail.data && articleDetail.data.code === 200) {
|
|
|
|
|
+ const article = articleDetail.data.data;
|
|
|
|
|
+ if (article.tags && article.tags.length > 0) {
|
|
|
|
|
+ out += '[PASS] AUTO-TAG-02 Tags auto-generated: ' + article.tags + '\n';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('AUTO-TAG-02 Tags', 'no tags field in response: ' + JSON.stringify(article).slice(0, 200));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('AUTO-TAG-02 Tags detail', JSON.stringify(articleDetail.data));
|
|
|
|
|
+ }
|
|
|
|
|
+ // Cleanup
|
|
|
|
|
+ await post('/api/admin/articles/delete', { id: articleId }, { Authorization: 'Bearer ' + adminToken });
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info('AUTO-TAG-01', 'create failed: ' + JSON.stringify(articleCreate.data));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+ // SUMMARY
|
|
|
|
|
+ // ===================================================================
|
|
|
|
|
+ const lines = out.split('\n').filter(l => l.startsWith('[PASS]') || l.startsWith('[FAIL]') || l.startsWith('[SKIP]') || l.startsWith('[INFO]'));
|
|
|
|
|
+ const passCount = lines.filter(l => l.startsWith('[PASS]')).length;
|
|
|
|
|
+ const failCount = lines.filter(l => l.startsWith('[FAIL]')).length;
|
|
|
|
|
+ const skipCount = lines.filter(l => l.startsWith('[SKIP]')).length;
|
|
|
|
|
+ const infoCount = lines.filter(l => l.startsWith('[INFO]')).length;
|
|
|
|
|
+ out += '\n=== Summary: ' + passCount + ' PASS / ' + failCount + ' FAIL / ' + skipCount + ' SKIP / ' + infoCount + ' INFO / ' + lines.length + ' Total ===\n';
|
|
|
|
|
+
|
|
|
|
|
+ fs.writeFileSync(RESULT_FILE, out);
|
|
|
|
|
+ console.log('V11_DONE - ' + RESULT_FILE);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+main().catch(e => {
|
|
|
|
|
+ fs.writeFileSync(RESULT_FILE, 'ERROR: ' + e.message);
|
|
|
|
|
+ console.error('V11_ERROR: ' + e.message);
|
|
|
|
|
+});
|