| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- #!/usr/bin/env node
- 'use strict';
- const assert = require('assert');
- const fs = require('fs');
- const path = require('path');
- const { normalizeMessage, detectMessageType } = require('../mcp/src/core/message-normalizer');
- const { sanitizePayload } = require('../mcp/src/providers/fmode-wecom-gateway');
- const samples = [
- ['text', 2, { content: 'hello' }, 'hello'],
- ['image', 101, { filename: 'sample.png' }, '[图片] sample.png'],
- ['voice', 16, { duration: 3 }, '[语音] 3 秒'],
- ['video', 103, { filename: 'sample.mp4' }, '[视频] sample.mp4'],
- ['file', 20, { filename: 'document.pdf' }, '[文件] document.pdf'],
- ['weapp', 78, { title: '服务小程序' }, '[小程序] 服务小程序'],
- ['meeting', 0, { title: '项目例会' }, '[会议邀请] 项目例会'],
- ['location', 6, { title: '会议室', latitude: 1, longitude: 2 }, '[位置] 会议室'],
- ['link', 13, { title: '帮助中心', url: 'https://example.invalid/help' }, '[链接] 帮助中心 · https://example.invalid/help'],
- ['card', 41, { displayName: '测试联系人' }, '[名片] 测试联系人'],
- ['emotion', 104, { title: '微笑' }, '[表情] 微笑'],
- ['revoke', 2063, { title: '消息已撤回' }, '[撤回消息] 消息已撤回'],
- ['agree', 0, { title: '同意存档' }, '[同意会话存档] 同意存档'],
- ['disagree', 0, { title: '不同意存档' }, '[不同意会话存档] 不同意存档'],
- ['chatrecord', 4, { title: '会话记录', itemCount: 2 }, '[会话记录] 会话记录 · 2 条'],
- ['todo', 0, { title: '跟进事项' }, '[待办] 跟进事项'],
- ['vote', 0, { title: '方案投票' }, '[投票] 方案投票'],
- ['collect', 0, { title: '信息登记' }, '[填表] 信息登记'],
- ['redpacket', 26, { title: '红包' }, '[红包] 红包'],
- ['external_redpacket', 0, { title: '互通红包' }, '[互通红包] 互通红包'],
- ['transfer', 588, { title: '转账' }, '[转账] 转账'],
- ['meeting_notification', 0, { title: '会议开始' }, '[会议控制] 会议开始'],
- ['docmsg', 0, { title: '在线文档' }, '[在线文档] 在线文档'],
- ['markdown', 2, { content: '**Markdown**' }, '**Markdown**'],
- ['news', 0, { title: '图文消息' }, '[图文消息] 图文消息'],
- ['calendar', 0, { title: '日程' }, '[日程] 日程'],
- ['mixed', 0, { title: '混合内容' }, '[混合消息] 混合内容'],
- ['meeting_voice_call', 0, { title: '会议音频' }, '[音频存档] 会议音频'],
- ['voip_doc_share', 0, { title: '共享文档' }, '[音频共享文档] 共享文档'],
- ['sphfeed', 141, { title: '视频号内容' }, '[视频号] 视频号内容'],
- ['voiptext', 0, { callDuration: 12 }, '[音视频通话] 12 秒'],
- ['qydiskfile', 15, { filename: '微盘文件.pdf' }, '[微盘文件] 微盘文件.pdf'],
- ['solitaire', 0, { title: '接龙' }, '[接龙] 接龙'],
- ['note', 0, { title: '笔记' }, '[笔记] 笔记'],
- ];
- for (const [type, msgType, msgData, expected] of samples) {
- const normalized = normalizeMessage({ msgType, msgData: { type, ...msgData } });
- assert.strictEqual(normalized.type, type, `${type} should retain its declared type`);
- assert.strictEqual(normalized.content, expected, `${type} should produce stable display content`);
- }
- assert.strictEqual(detectMessageType({ msgType: 16, msgData: { fileId: 'voice-file' } }), 'voice');
- assert.strictEqual(detectMessageType({ msgType: 103, msgData: { fileId: 'video-file' } }), 'video');
- assert.strictEqual(detectMessageType({ msgType: 104, msgData: { fileId: 'emotion-file' } }), 'emotion');
- const redacted = sanitizePayload('SERVICE_PASSWORD=example-secret\n"masterKey": "example-key"\nsk-example1234567890');
- assert.ok(!redacted.includes('example-secret') && !redacted.includes('example-key') && !redacted.includes('sk-example'), 'public message text should redact credential assignments');
- const dashboardRoot = path.join(__dirname, '..', 'mcp', 'src', 'dashboard');
- const app = fs.readFileSync(path.join(dashboardRoot, 'app.js'), 'utf8');
- const styles = fs.readFileSync(path.join(dashboardRoot, 'styles.css'), 'utf8');
- const server = fs.readFileSync(path.join(dashboardRoot, 'server.js'), 'utf8');
- for (const contract of [
- 'agentCardMessageHtml',
- 'agentReplyQuoteHtml',
- 'agentFinancialMessageHtml',
- 'agentChatrecordItemText',
- 'safeAgentMediaUrl',
- 'agentMessageData(message).reply',
- 'data.cards',
- 'subMsgTimestamp',
- 'data-agent-chatrecord-open',
- 'agent-video-play',
- 'openSessionConversation',
- 'view-session',
- ]) {
- assert.ok(app.includes(contract), `dashboard app should include ${contract}`);
- }
- const richRenderer = app.slice(app.indexOf('function agentCardMessageHtml'), app.indexOf('function renderAgentMessage'));
- const messageRenderer = app.slice(app.indexOf('function renderAgentMessage'), app.indexOf('function agentModeLabel'));
- for (const type of [
- 'image', 'video', 'file', 'weapp', 'meeting', 'location', 'link', 'card', 'emotion', 'chatrecord',
- 'todo', 'vote', 'collect', 'redpacket', 'external_redpacket', 'transfer', 'docmsg', 'news', 'calendar',
- 'mixed', 'meeting_voice_call', 'voip_doc_share', 'sphfeed', 'voiptext', 'qydiskfile', 'solitaire', 'note',
- ]) {
- assert.ok(richRenderer.includes(`'${type}'`), `${type} should have a rich-message render path`);
- }
- for (const type of ['revoke', 'meeting_notification', 'agree', 'disagree']) {
- assert.ok(messageRenderer.includes(type), `${type} should have a system-message render path`);
- }
- assert.ok(messageRenderer.includes("type === 'voice'"), 'voice should have an audio render path');
- assert.ok(messageRenderer.includes('agentTextMessageHtml(message)'), 'text and markdown should retain quoted-message rendering');
- for (const contract of [
- '.agent-message-quote',
- '.agent-financial-foot',
- '.agent-file-icon',
- '.agent-chatrecord-avatar',
- '.agent-feed-placeholder',
- '.agent-video-play',
- ]) {
- assert.ok(styles.includes(contract), `dashboard styles should include ${contract}`);
- }
- for (const route of ['session-transcript', '/media', '/open', 'wechat-emojis']) {
- assert.ok(server.includes(route), `dashboard server should include ${route}`);
- }
- console.log(`message types smoke passed (${samples.length} declared types + 3 numeric precedence checks + public redaction)`);
|