添加完整的微信插件测试框架,包含: - 微信插件源码(weixin-plugin/) - TypeScript 类型定义(types.ts) - 插件编译脚本(scripts/build-weixin-plugin.sh) - 简化测试脚本(scripts/test-weixin-simple.js) - 完整测试脚本(scripts/test-weixin-plugin.js) - 项目配置和依赖管理(package.json) - 使用说明文档(README.md) 主要功能: - 支持二维码登录认证 - HTTP 长轮询消息接收 - 文本、图片、视频、文件消息收发 - 自动回复测试 - 运行状态统计 Generated with [Claude Code](https://claude.com/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
429 lines
12 KiB
JavaScript
429 lines
12 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* 微信插件完整测试脚本
|
||
*
|
||
* 功能:
|
||
* 1. 二维码登录
|
||
* 2. 启动消息监听
|
||
* 3. 自动回复测试
|
||
* 4. 消息收发验证
|
||
*
|
||
* 使用方法:
|
||
* node scripts/test-weixin-plugin.js
|
||
* 或
|
||
* npm run test:full
|
||
*/
|
||
|
||
import path from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
import fs from 'fs';
|
||
|
||
// 获取项目根目录
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = path.dirname(__filename);
|
||
const projectRoot = path.resolve(__dirname, '..');
|
||
|
||
// 导入微信插件模块
|
||
const weixinPluginPath = path.join(projectRoot, 'weixin-plugin');
|
||
|
||
console.log('='.repeat(60));
|
||
console.log('微信插件完整测试脚本');
|
||
console.log('='.repeat(60));
|
||
console.log(`项目根目录: ${projectRoot}`);
|
||
console.log(`插件路径: ${weixinPluginPath}`);
|
||
console.log('');
|
||
|
||
// =============================================================================
|
||
// 颜色输出工具
|
||
// =============================================================================
|
||
|
||
const colors = {
|
||
reset: '\x1b[0m',
|
||
red: '\x1b[31m',
|
||
green: '\x1b[32m',
|
||
yellow: '\x1b[33m',
|
||
blue: '\x1b[34m',
|
||
magenta: '\x1b[35m',
|
||
cyan: '\x1b[36m',
|
||
};
|
||
|
||
function log(color, ...args) {
|
||
console.log(color + args.join(' ') + colors.reset);
|
||
}
|
||
|
||
function logInfo(...args) {
|
||
log(colors.cyan, '[INFO]', ...args);
|
||
}
|
||
|
||
function logSuccess(...args) {
|
||
log(colors.green, '[OK]', ...args);
|
||
}
|
||
|
||
function logError(...args) {
|
||
log(colors.red, '[ERROR]', ...args);
|
||
}
|
||
|
||
function logWarn(...args) {
|
||
log(colors.yellow, '[WARN]', ...args);
|
||
}
|
||
|
||
// =============================================================================
|
||
// 测试配置
|
||
// =============================================================================
|
||
|
||
const TEST_CONFIG = {
|
||
// 测试账号ID(留空使用默认)
|
||
accountId: process.env.WEIXIN_ACCOUNT_ID || '',
|
||
// 登录超时时间(毫秒)
|
||
loginTimeout: 5 * 60 * 1000,
|
||
// 消息监听超时(毫秒,0 表示不限制)
|
||
listenTimeout: 0,
|
||
// 是否启用自动回复
|
||
autoReply: true,
|
||
// 自动回复前缀
|
||
autoReplyPrefix: '[自动回复] ',
|
||
// 调试模式
|
||
debug: true,
|
||
};
|
||
|
||
// =============================================================================
|
||
// 导入微信插件模块
|
||
// =============================================================================
|
||
|
||
function resolveModulePath() {
|
||
const distPath = path.join(weixinPluginPath, 'dist', 'weixinGateway.js');
|
||
if (fs.existsSync(distPath)) {
|
||
return distPath;
|
||
}
|
||
const srcPath = path.join(weixinPluginPath, 'weixinGateway.ts');
|
||
if (fs.existsSync(srcPath)) {
|
||
return srcPath;
|
||
}
|
||
throw new Error('找不到 weixinGateway 模块文件');
|
||
}
|
||
|
||
let WeixinGateway, startWeixinLogin, waitWeixinLogin;
|
||
|
||
try {
|
||
const modulePath = resolveModulePath();
|
||
const weixinGatewayModule = await import(modulePath);
|
||
WeixinGateway = weixinGatewayModule.WeixinGateway;
|
||
startWeixinLogin = weixinGatewayModule.startWeixinLogin;
|
||
waitWeixinLogin = weixinGatewayModule.waitWeixinLogin;
|
||
|
||
logInfo('成功加载微信插件模块');
|
||
} catch (err) {
|
||
logError('加载微信插件模块失败:', err.message);
|
||
logInfo('请确保已编译插件: npm run build');
|
||
process.exit(1);
|
||
}
|
||
|
||
// =============================================================================
|
||
// 工具函数
|
||
// =============================================================================
|
||
|
||
/**
|
||
* 保存二维码到文件
|
||
*/
|
||
async function saveQRCode(qrcodeDataUrl, filename = 'weixin-qrcode.png') {
|
||
const matches = qrcodeDataUrl.match(/^data:image\/png;base64,(.+)$/);
|
||
if (!matches) {
|
||
throw new Error('无效的二维码数据');
|
||
}
|
||
|
||
const buffer = Buffer.from(matches[1], 'base64');
|
||
const filepath = path.join(projectRoot, filename);
|
||
|
||
fs.writeFileSync(filepath, buffer);
|
||
logSuccess(`二维码已保存到: ${filepath}`);
|
||
|
||
return filepath;
|
||
}
|
||
|
||
/**
|
||
* 格式化时间戳
|
||
*/
|
||
function formatTime(timestamp) {
|
||
return new Date(timestamp).toLocaleString('zh-CN');
|
||
}
|
||
|
||
/**
|
||
* 格式化持续时间
|
||
*/
|
||
function formatDuration(ms) {
|
||
const seconds = Math.floor(ms / 1000);
|
||
const minutes = Math.floor(seconds / 60);
|
||
const hours = Math.floor(minutes / 60);
|
||
|
||
if (hours > 0) {
|
||
return `${hours}小时${minutes % 60}分钟`;
|
||
} else if (minutes > 0) {
|
||
return `${minutes}分钟${seconds % 60}秒`;
|
||
} else {
|
||
return `${seconds}秒`;
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 测试流程
|
||
// =============================================================================
|
||
|
||
/**
|
||
* 步骤 1: 二维码登录
|
||
*/
|
||
async function testLogin() {
|
||
logInfo('开始二维码登录流程...');
|
||
console.log('');
|
||
|
||
try {
|
||
// 1. 获取二维码
|
||
logInfo('正在获取登录二维码...');
|
||
const { sessionKey, qrcodeUrl } = await startWeixinLogin(TEST_CONFIG.accountId || 'test-account');
|
||
|
||
if (!qrcodeUrl) {
|
||
throw new Error('获取二维码失败');
|
||
}
|
||
|
||
// 2. 保存二维码
|
||
await saveQRCode(qrcodeUrl);
|
||
logSuccess('二维码获取成功!');
|
||
console.log('');
|
||
|
||
// 3. 显示二维码
|
||
logInfo('请使用微信扫描以下二维码登录:');
|
||
console.log('');
|
||
console.log(qrcodeUrl);
|
||
console.log('');
|
||
|
||
logInfo('等待扫码登录...');
|
||
|
||
// 4. 等待登录
|
||
const loginResult = await waitWeixinLogin(sessionKey, TEST_CONFIG.loginTimeout);
|
||
|
||
if (!loginResult.connected) {
|
||
throw new Error(loginResult.error || '登录失败');
|
||
}
|
||
|
||
logSuccess('登录成功!');
|
||
console.log('');
|
||
logInfo('账号信息:');
|
||
logInfo(` - Account ID: ${loginResult.accountId}`);
|
||
logInfo(` - User ID: ${loginResult.userId}`);
|
||
logInfo(` - Base URL: ${loginResult.baseUrl}`);
|
||
console.log('');
|
||
|
||
return loginResult;
|
||
} catch (err) {
|
||
logError('登录失败:', err.message);
|
||
throw err;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 步骤 2: 启动网关
|
||
*/
|
||
async function testGateway() {
|
||
logInfo('初始化微信网关...');
|
||
console.log('');
|
||
|
||
const gateway = new WeixinGateway();
|
||
|
||
// 设置消息回调
|
||
let messageCount = 0;
|
||
let firstMessageTime = null;
|
||
|
||
gateway.setMessageCallback(async (message, replyFn) => {
|
||
messageCount++;
|
||
|
||
if (!firstMessageTime) {
|
||
firstMessageTime = Date.now();
|
||
}
|
||
|
||
logSuccess('收到消息!');
|
||
console.log('');
|
||
logInfo('消息详情:');
|
||
logInfo(` - 平台: ${message.platform}`);
|
||
logInfo(` - 发送者: ${message.senderId}`);
|
||
logInfo(` - 会话: ${message.conversationId}`);
|
||
logInfo(` - 类型: ${message.chatType}`);
|
||
logInfo(` - 内容: ${message.content}`);
|
||
logInfo(` - 时间: ${formatTime(message.timestamp)}`);
|
||
console.log('');
|
||
|
||
// 自动回复
|
||
if (TEST_CONFIG.autoReply) {
|
||
const replyText = `${TEST_CONFIG.autoReplyPrefix}已收到你的消息:"${message.content}"(第${messageCount}条)`;
|
||
logInfo(`发送自动回复: ${replyText}`);
|
||
|
||
try {
|
||
await replyFn(replyText);
|
||
logSuccess('自动回复发送成功');
|
||
} catch (err) {
|
||
logError('自动回复发送失败:', err.message);
|
||
}
|
||
console.log('');
|
||
}
|
||
});
|
||
|
||
// 监听连接事件
|
||
gateway.once('connected', () => {
|
||
logSuccess('网关已连接!');
|
||
const status = gateway.getStatus();
|
||
logInfo(`账号: ${status.accountId}`);
|
||
logInfo(`启动时间: ${formatTime(status.startedAt)}`);
|
||
console.log('');
|
||
});
|
||
|
||
gateway.on('error', (error) => {
|
||
logError('网关错误:', error.message);
|
||
});
|
||
|
||
gateway.on('message', (message) => {
|
||
// 消息已在回调中处理
|
||
});
|
||
|
||
// 启动网关
|
||
try {
|
||
await gateway.start({
|
||
enabled: true,
|
||
debug: TEST_CONFIG.debug,
|
||
});
|
||
|
||
// 等待连接
|
||
await new Promise((resolve, reject) => {
|
||
const timeout = setTimeout(() => {
|
||
gateway.off('connected');
|
||
gateway.off('error');
|
||
reject(new Error('连接超时'));
|
||
}, 10000);
|
||
|
||
gateway.once('connected', () => {
|
||
clearTimeout(timeout);
|
||
resolve();
|
||
});
|
||
|
||
gateway.once('error', (err) => {
|
||
clearTimeout(timeout);
|
||
reject(err);
|
||
});
|
||
});
|
||
|
||
return gateway;
|
||
} catch (err) {
|
||
logError('启动网关失败:', err.message);
|
||
throw err;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 步骤 3: 消息监听测试
|
||
*/
|
||
async function testMessageListening(gateway) {
|
||
logInfo('开始监听消息...');
|
||
logInfo('你可以向微信机器人发送消息进行测试');
|
||
logInfo('按 Ctrl+C 退出测试');
|
||
console.log('');
|
||
logInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
console.log('');
|
||
|
||
const startTime = Date.now();
|
||
let lastMessageCount = 0;
|
||
|
||
// 定时显示统计信息
|
||
const statsInterval = setInterval(() => {
|
||
const status = gateway.getStatus();
|
||
const elapsed = Date.now() - startTime;
|
||
|
||
console.log('');
|
||
logInfo('━━━━ 运行状态 ━━━━');
|
||
logInfo(`运行时长: ${formatDuration(elapsed)}`);
|
||
logInfo(`连接状态: ${status.connected ? '已连接' : '未连接'}`);
|
||
logInfo(`入站消息: ${status.lastInboundAt ? `最后于 ${formatTime(status.lastInboundAt)}` : '暂无'}`);
|
||
logInfo(`出站消息: ${status.lastOutboundAt ? `最后于 ${formatTime(status.lastOutboundAt)}` : '暂无'}`);
|
||
logInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
console.log('');
|
||
}, 30000);
|
||
|
||
// 等待用户中断
|
||
return new Promise((resolve) => {
|
||
process.on('SIGINT', () => {
|
||
console.log('');
|
||
logInfo('收到退出信号,正在停止...');
|
||
|
||
clearInterval(statsInterval);
|
||
|
||
const elapsed = Date.now() - startTime;
|
||
logInfo('━━━━ 测试统计 ━━━━');
|
||
logInfo(`运行时长: ${formatDuration(elapsed)}`);
|
||
logInfo(`接收消息数: ${lastMessageCount} 条`);
|
||
logInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
console.log('');
|
||
|
||
resolve();
|
||
});
|
||
|
||
// 监听消息事件来更新计数
|
||
gateway.on('message', () => {
|
||
lastMessageCount++;
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 主测试流程
|
||
*/
|
||
async function main() {
|
||
const startTime = Date.now();
|
||
|
||
try {
|
||
// 步骤 1: 登录
|
||
const loginResult = await testLogin();
|
||
|
||
// 步骤 2: 启动网关
|
||
const gateway = await testGateway();
|
||
|
||
// 步骤 3: 监听消息
|
||
await testMessageListening(gateway);
|
||
|
||
// 停止网关
|
||
logInfo('正在停止网关...');
|
||
await gateway.stop();
|
||
logSuccess('网关已停止');
|
||
|
||
// 完成
|
||
const elapsed = Date.now() - startTime;
|
||
console.log('');
|
||
logSuccess('测试完成!总耗时:', formatDuration(elapsed));
|
||
|
||
} catch (err) {
|
||
console.log('');
|
||
logError('测试失败:', err.message);
|
||
|
||
if (TEST_CONFIG.debug) {
|
||
console.log('');
|
||
console.error(err);
|
||
}
|
||
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 运行测试
|
||
// =============================================================================
|
||
|
||
console.log('测试配置:');
|
||
console.log(` - 账号ID: ${TEST_CONFIG.accountId || 'test-account (默认)'}`);
|
||
console.log(` - 登录超时: ${formatDuration(TEST_CONFIG.loginTimeout)}`);
|
||
console.log(` - 自动回复: ${TEST_CONFIG.autoReply ? '启用' : '禁用'}`);
|
||
console.log(` - 调试模式: ${TEST_CONFIG.debug ? '启用' : '禁用'}`);
|
||
console.log('');
|
||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||
console.log('');
|
||
|
||
main().catch((err) => {
|
||
logError('未处理的错误:', err);
|
||
process.exit(1);
|
||
});
|