新增e2e测试脚本,修复部分问题
This commit was merged in pull request #51.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 教练端 HTTP API 测试工具
|
||||
* 支持 HMAC-SHA256 签名 + JWT Bearer Token
|
||||
*/
|
||||
|
||||
const http = require('http');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const BASE_URL = 'http://192.168.110.64:8084';
|
||||
const API_BASE = BASE_URL + '/api';
|
||||
const SIGNATURE_SECRET = 'NovalonManageSystemSecretKey2026';
|
||||
|
||||
/**
|
||||
* 生成 HMAC-SHA256 签名头
|
||||
*/
|
||||
function generateSignatureHeaders(method, path, bodyStr) {
|
||||
const timestamp = Date.now();
|
||||
const nonce = timestamp.toString(36) + '-' + Math.random().toString(36).substring(2, 8);
|
||||
const stringToSign = [method.toUpperCase(), path, '', bodyStr || '', String(timestamp), nonce].join('\n');
|
||||
const signature = crypto.createHmac('sha256', SIGNATURE_SECRET).update(stringToSign).digest('base64');
|
||||
return {
|
||||
'X-Signature': signature,
|
||||
'X-Timestamp': String(timestamp),
|
||||
'X-Nonce': nonce
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用 HTTP 请求
|
||||
*/
|
||||
function makeRequest(method, url, body, token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parsed = new URL(url);
|
||||
const bodyStr = body ? JSON.stringify(body) : '';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...generateSignatureHeaders(method, parsed.pathname + parsed.search, bodyStr)
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
|
||||
const options = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port,
|
||||
path: parsed.pathname + parsed.search,
|
||||
method: method,
|
||||
headers: headers,
|
||||
timeout: 15000
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
let parsedData;
|
||||
try { parsedData = JSON.parse(data); } catch (e) { parsedData = data; }
|
||||
resolve({ status: res.statusCode, data: parsedData });
|
||||
});
|
||||
});
|
||||
req.on('error', (e) => reject(e));
|
||||
if (bodyStr) req.write(bodyStr);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function isSuccess(res) {
|
||||
return res.status >= 200 && res.status < 300;
|
||||
}
|
||||
|
||||
// ── 教练端 API ──
|
||||
|
||||
/** 教练登录 */
|
||||
async function coachLogin(username, password) {
|
||||
console.log(`[API] 教练登录: ${username}`);
|
||||
const res = await makeRequest('POST', `${API_BASE}/auth/login`, { username, password });
|
||||
console.log(`[API] 登录响应: status=${res.status}`);
|
||||
if (isSuccess(res) && res.data) {
|
||||
return {
|
||||
token: res.data.token,
|
||||
userId: res.data.userId,
|
||||
username: res.data.username
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 获取教练的团课列表 */
|
||||
async function getCoachCourses(coachId, token) {
|
||||
console.log(`[API] 获取教练课程: coachId=${coachId}`);
|
||||
const res = await makeRequest('GET', `${API_BASE}/coach/${coachId}/courses`, null, token);
|
||||
console.log(`[API] 课程列表: status=${res.status}`);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** 获取团课详情 */
|
||||
async function getCourseDetail(courseId, token) {
|
||||
console.log(`[API] 获取课程详情: courseId=${courseId}`);
|
||||
const res = await makeRequest('GET', `${API_BASE}/groupCourse/${courseId}/detail`, null, token);
|
||||
console.log(`[API] 课程详情: status=${res.status}`);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** 手动开课 */
|
||||
async function startCourse(courseId, token) {
|
||||
console.log(`[API] 手动开课: courseId=${courseId}`);
|
||||
const res = await makeRequest('POST', `${API_BASE}/coach/courses/${courseId}/start`, {}, token);
|
||||
console.log(`[API] 开课结果: status=${res.status}`);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** 手动结课 */
|
||||
async function endCourse(courseId, token) {
|
||||
console.log(`[API] 手动结课: courseId=${courseId}`);
|
||||
const res = await makeRequest('POST', `${API_BASE}/coach/courses/${courseId}/end`, {}, token);
|
||||
console.log(`[API] 结课结果: status=${res.status}`);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** 获取课程预约列表 */
|
||||
async function getCourseBookings(courseId, token) {
|
||||
console.log(`[API] 获取预约列表: courseId=${courseId}`);
|
||||
const res = await makeRequest('GET', `${API_BASE}/groupCourse/bookings/course/${courseId}`, null, token);
|
||||
console.log(`[API] 预约列表: status=${res.status}`);
|
||||
return res;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
makeRequest,
|
||||
coachLogin,
|
||||
getCoachCourses,
|
||||
getCourseDetail,
|
||||
startCourse,
|
||||
endCourse,
|
||||
getCourseBookings,
|
||||
isSuccess,
|
||||
BASE_URL,
|
||||
API_BASE
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
const automator = require('miniprogram-automator');
|
||||
const path = require('path');
|
||||
|
||||
const PROJECT_PATH = path.resolve(__dirname, '../../unpackage/dist/dev/mp-weixin');
|
||||
const CLI_PATH = 'D:\\wechat-dev-tools\\cli.bat';
|
||||
const CLI_PORT = 45869;
|
||||
const BASE_URL = 'http://192.168.110.64:8084/api';
|
||||
|
||||
let miniProgram = null;
|
||||
|
||||
/**
|
||||
* 启动微信小程序
|
||||
*/
|
||||
async function launchMiniProgram() {
|
||||
miniProgram = await automator.launch({
|
||||
projectPath: PROJECT_PATH,
|
||||
cliPath: CLI_PATH,
|
||||
port: CLI_PORT
|
||||
});
|
||||
return miniProgram;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭小程序连接
|
||||
*/
|
||||
async function closeMiniProgram() {
|
||||
if (miniProgram) {
|
||||
try {
|
||||
await miniProgram.close();
|
||||
} catch (e) {
|
||||
console.warn('[automator] 关闭小程序失败:', e.message);
|
||||
}
|
||||
miniProgram = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 miniProgram 实例
|
||||
*/
|
||||
function getMiniProgram() {
|
||||
return miniProgram;
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待指定毫秒
|
||||
*/
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* 教练端通过 username/password 登录
|
||||
* @param {object} mp - miniProgram 实例
|
||||
* @param {string} username
|
||||
* @param {string} password
|
||||
*/
|
||||
async function loginAsCoach(mp, username = 'coach_test1', password = 'Test@123') {
|
||||
await sleep(1500);
|
||||
const page = await mp.currentPage();
|
||||
|
||||
// 等待登录表单渲染
|
||||
await page.waitFor('input', { timeout: 10000 });
|
||||
|
||||
// 填写用户名
|
||||
const inputs = await page.$$('input');
|
||||
if (inputs.length >= 2) {
|
||||
await inputs[0].input(username);
|
||||
await sleep(300);
|
||||
await inputs[1].input(password);
|
||||
await sleep(300);
|
||||
|
||||
// 点击登录按钮
|
||||
const loginBtn = await page.$('.login-btn');
|
||||
if (loginBtn) {
|
||||
await loginBtn.tap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待页面跳转(用于登录成功后等待首页加载)
|
||||
* @param {object} mp - miniProgram 实例
|
||||
* @param {string} targetPath - 目标页面路径,如 'pages/index/index'
|
||||
* @param {number} timeoutMs
|
||||
*/
|
||||
async function waitForPage(mp, targetPath, timeoutMs = 15000) {
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
try {
|
||||
const page = await mp.currentPage();
|
||||
if (page.path.includes(targetPath)) {
|
||||
return page;
|
||||
}
|
||||
} catch (e) {
|
||||
// 页面切换中,忽略错误
|
||||
}
|
||||
await sleep(500);
|
||||
}
|
||||
throw new Error(`等待页面 "${targetPath}" 超时 (${timeoutMs}ms)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航到指定页面
|
||||
* @param {object} mp - miniProgram 实例
|
||||
* @param {string} url - 小程序页面路径,如 '/pages/course-list/course-list'
|
||||
*/
|
||||
async function navigateTo(mp, url) {
|
||||
await mp.navigateTo(url);
|
||||
await sleep(1500);
|
||||
return mp.currentPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查后端服务可用性
|
||||
*/
|
||||
async function checkBackendHealth() {
|
||||
const http = require('http');
|
||||
return new Promise((resolve) => {
|
||||
const req = http.get(BASE_URL + '/auth/login', { timeout: 10000 }, (res) => {
|
||||
resolve(true);
|
||||
});
|
||||
req.on('error', () => {
|
||||
console.warn('[automator] 后端服务不可达:', BASE_URL);
|
||||
resolve(false);
|
||||
});
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
console.warn('[automator] 后端服务连接超时:', BASE_URL);
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
launchMiniProgram,
|
||||
closeMiniProgram,
|
||||
getMiniProgram,
|
||||
loginAsCoach,
|
||||
waitForPage,
|
||||
navigateTo,
|
||||
sleep,
|
||||
checkBackendHealth,
|
||||
BASE_URL,
|
||||
CLI_PORT
|
||||
};
|
||||
Reference in New Issue
Block a user