import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { classifyWechatIntent, isPageGenerateIntent } from './intent/classifier.mjs'; import { resolveWechatSessionAction } from './intent/session-action.mjs'; import { filterSendableHtmlArtifacts, isStubPublicHtmlContent, selectSendableHtmlArtifacts, verifyPageArtifactContent, } from './verify/page-artifact.mjs'; import { guardScheduleConfirmationReply, looksLikeScheduleConfirmation } from './handlers/schedule-guard.mjs'; import { resolvePageGenerateOutcome } from './handlers/page-generate.mjs'; import { buildGreetingText, resolveSyncReply } from './handlers/sync-replies.mjs'; import { buildWechatAgentPrompt } from './prompts/chat-general.mjs'; import { hasMindspaceCoverMeta, hasPlatformBrandMarker, hasShareDescription, verifyArtifactSharePreview, verifySharePreviewMeta, } from './verify/share-preview.mjs'; test('classifyWechatIntent detects page.generate', () => { const intent = classifyWechatIntent({ msgType: 'text', agentText: '帮我生成一个唐诗页面,放一首诗', }); assert.equal(intent.kind, 'page.generate'); assert.match(intent.topic, /唐诗页面/); assert.equal(isPageGenerateIntent({ msgType: 'text', agentText: intent.topic }), true); }); test('classifyWechatIntent detects session.reset', () => { assert.equal(classifyWechatIntent({ msgType: 'text', agentText: '换话题' }).kind, 'session.reset'); assert.equal(classifyWechatIntent({ msgType: 'text', agentText: '换新会话' }).kind, 'session.reset'); }); test('resolveWechatSessionAction recognizes natural new-session expressions', async () => { for (const text of ['换新会话', '新开一个会话', '我们重新开始吧', '从头来']) { const result = await resolveWechatSessionAction(text); assert.equal(result.action, 'reset', text); } }); test('resolveWechatSessionAction separates ignore-context from reset', async () => { assert.equal((await resolveWechatSessionAction('不要参考刚才关于医联体的内容')).action, 'ignore_previous'); assert.equal((await resolveWechatSessionAction('继续刚才的话题')).action, 'continue'); }); test('resolveWechatSessionAction uses an injected semantic classifier for unclear text', async () => { const result = await resolveWechatSessionAction('我们换个思路聊', { semanticClassifier: async () => ({ action: 'reset', confidence: 0.88, reason: '新对话意图' }), }); assert.equal(result.action, 'reset'); assert.equal(result.source, 'semantic_model'); }); test('selectSendableHtmlArtifacts never returns stub html', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-stub-')); const stubPath = path.join(dir, 'tang-poem.html'); fs.writeFileSync( stubPath, '服务号自动补出简版页面', 'utf8', ); const realPath = path.join(dir, 'real.html'); fs.writeFileSync( realPath, `
${'x'.repeat(600)}
`, 'utf8', ); const artifacts = [ { localPath: stubPath, relativePath: 'public/tang-poem.html', url: 'https://example/tang-poem.html' }, { localPath: realPath, relativePath: 'public/real.html', url: 'https://example/real.html' }, ]; const sendable = selectSendableHtmlArtifacts({ confirmedArtifacts: artifacts }); assert.equal(sendable.length, 1); assert.match(sendable[0].relativePath, /real\.html/); assert.equal(isStubPublicHtmlContent('服务号自动补出简版页面'), true); }); test('resolveSyncReply handles greeting and status probes', () => { const user = { nickname: '唐' }; assert.match( resolveSyncReply(classifyWechatIntent({ msgType: 'text', agentText: '你好' }), user, { statusFallbackText: '处理中', }), /唐/, ); assert.match( resolveSyncReply(classifyWechatIntent({ msgType: 'text', agentText: '??' }), user, { statusFallbackText: '处理中', }), /我在这边/, ); }); test('buildWechatAgentPrompt lives in wechat prompts package', () => { const prompt = buildWechatAgentPrompt({ msgType: 'text', agentText: '明天早上 6 点跑步,5 点半提醒我', }); assert.match(prompt, /schedule-assistant/); }); test('buildWechatAgentPrompt carries ignore-previous context boundary', () => { const prompt = buildWechatAgentPrompt({ msgType: 'text', agentText: '请分析这段新内容', sessionAction: 'ignore_previous', }); assert.match(prompt, /忽略此前相关内容/); }); test('schedule confirmation guard blocks false positives', async () => { assert.equal(looksLikeScheduleConfirmation('已经帮你设置好了待办提醒'), true); const guarded = await guardScheduleConfirmationReply({ replyText: '已经帮你设置好了待办提醒', scheduleService: { listItemsBySourceMessage: async () => [] }, userId: 'user-1', sourceMessageId: 'msg-1', logger: null, }); assert.match(guarded, /不能算设置成功/); }); test('resolvePageGenerateOutcome fails when only stub artifacts exist', () => { const outcome = resolvePageGenerateOutcome({ reply: { text: '页面已生成 https://m.tkmind.cn/MindSpace/u/public/tang-poem.html' }, confirmedArtifacts: [ { localPath: '/tmp/stub.html', relativePath: 'public/tang-poem.html', }, ], htmlGenerationNeedsRetry: true, replyHasPublicLinks: true, }); assert.equal(outcome.action, 'fail'); assert.match(outcome.failureText, /服务号页面技能/); }); test('verifyPageArtifactContent rejects small stub files', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-verify-')); const file = path.join(dir, 'page.html'); fs.writeFileSync(file, '服务号兜底', 'utf8'); const artifact = { localPath: file, relativePath: 'public/page.html' }; assert.equal(verifyPageArtifactContent(artifact).ok, false); assert.equal(filterSendableHtmlArtifacts([artifact]).length, 0); }); function writePreviewReadyHtml(file, { withCover = true, withBrand = true } = {}) { const coverMeta = withCover ? '' : ''; const brandMeta = withBrand ? '

TKMind · 智趣

' : ''; fs.writeFileSync( file, ` ${coverMeta}
${'x'.repeat(600)}
${brandMeta}`, 'utf8', ); } test('verifySharePreviewMeta requires mindspace-cover, description, and platform brand', () => { const withAll = '

TKMind · 智趣

'; assert.equal(verifySharePreviewMeta(withAll).ok, true); assert.equal(verifySharePreviewMeta('').ok, false); assert.equal(hasMindspaceCoverMeta(withAll), true); assert.equal(hasShareDescription(withAll), true); assert.equal(hasPlatformBrandMarker(withAll), true); }); test('resolvePageGenerateOutcome fails when html lacks share preview meta', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-preview-')); const file = path.join(dir, 'page.html'); writePreviewReadyHtml(file, { withCover: false }); const outcome = resolvePageGenerateOutcome({ reply: { text: '页面已生成 https://m.tkmind.cn/MindSpace/u/public/page.html' }, confirmedArtifacts: [{ localPath: file, relativePath: 'public/page.html' }], }); assert.equal(outcome.action, 'fail'); assert.equal(outcome.reason, 'missing_mindspace_cover'); assert.match(outcome.failureText, /预览元数据/); }); test('resolvePageGenerateOutcome fails when html lacks platform brand marker', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-brand-')); const file = path.join(dir, 'page.html'); writePreviewReadyHtml(file, { withBrand: false }); const outcome = resolvePageGenerateOutcome({ reply: { text: '页面已生成' }, confirmedArtifacts: [{ localPath: file, relativePath: 'public/page.html' }], }); assert.equal(outcome.action, 'fail'); assert.equal(outcome.reason, 'missing_platform_brand'); assert.match(outcome.failureText, /平台品牌/); }); test('resolvePageGenerateOutcome sends when share preview meta is present', () => { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-preview-ok-')); const file = path.join(dir, 'page.html'); writePreviewReadyHtml(file); const artifact = { localPath: file, relativePath: 'public/page.html' }; assert.equal(verifyArtifactSharePreview(artifact).ok, true); const outcome = resolvePageGenerateOutcome({ reply: { text: '页面已生成' }, confirmedArtifacts: [artifact], }); assert.equal(outcome.action, 'send'); assert.equal(outcome.artifacts.length, 1); });