Files
digital-me-history/digital-me-backend/test/assistant.test.js
T
admin 0593ff7e62
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
数字我 Digital Me:全量入库(大文件走 Git LFS)
2026-10-10 16:24:43 +08:00

78 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 能力中枢助手:确定性部分的测试(检索 / 语音清洗 / 无 key 降级 / 总览)。
// 不设 DASHSCOPE / ANTHROPIC key,走 fallback 分支,无需网络。
delete process.env.DASHSCOPE_API_KEY;
delete process.env.ANTHROPIC_API_KEY;
import { test } from 'node:test';
import assert from 'node:assert/strict';
const { searchCapabilities, CAPABILITIES, getCapability, listCapabilities } = await import('../src/capabilities.js');
const { speechClean, toSegments, answer, overviewText } = await import('../src/services/assistant.js');
test('检索:口语提问能命中对应功能', () => {
assert.equal(searchCapabilities('怎么克隆我的声音')[0].id, 'voice');
assert.equal(searchCapabilities('想让分身出镜说话拍视频')[0].id, 'talkvideo');
assert.equal(searchCapabilities('只想发给特定的人不公开')[0].id, 'share');
assert.equal(searchCapabilities('养得好不好怎么看')[0].id, 'grounding');
});
// 目录只能列【页面上真的存在】的功能。收费模式受 FEATURE_MARKETPLACE 门控、默认关闭——
// 关着的时候它必须彻底检索不到,否则助手会把用户送去一个页面上根本没有的按钮。
test('门控:marketplace 关闭时,收费模式不出现在目录/检索里', () => {
assert.equal(searchCapabilities('怎么用分身挣钱').length, 0);
assert.equal(getCapability('paid'), null);
assert.ok(!listCapabilities().some((c) => c.id === 'paid'));
});
test('语音清洗:去掉 Markdown / 网址 / 表情 / 序号', () => {
const dirty = '# 标题\n1. 第一步 **加粗** `代码` 😀 看 https://x.com/y [链接](https://z.com)';
const clean = speechClean(dirty);
assert.doesNotMatch(clean, /[#*`]/, '不应残留 Markdown 符号');
assert.doesNotMatch(clean, /https?:\/\//, '不应残留网址');
assert.doesNotMatch(clean, /😀/, '不应残留表情');
assert.match(clean, /链接/, '链接文字应保留');
});
test('分句:按句末标点切分', () => {
const segs = toSegments('第一句。第二句!第三句?');
assert.equal(segs.length, 3);
assert.equal(segs[0], '第一句。');
});
test('无 key 降级:命中功能时产出可朗读话术(不含 Markdown)', async () => {
const r = await answer('怎么克隆声音');
assert.equal(r.source, 'fallback');
assert.ok(r.spoken.length > 0);
assert.equal(r.matched[0].id, 'voice');
assert.ok(r.segments.length >= 1);
assert.doesNotMatch(r.spoken, /[#*`]|https?:\/\//);
// 韵律/情感输出:ssml(语调)+ style(情感风格)供合成网关使用。
assert.match(r.ssml, /^<speak[\s\S]*<prosody/, 'ssml 应含 speak + prosody');
assert.equal(r.style, 'friendly');
});
test('多句话术的 ssml 含句间停顿 break', async () => {
const r = await answer('你好呀今天天气不错'); // 走总览(多句)
assert.match(r.ssml, /<break/, '多句 ssml 应含句间停顿');
});
test('无 key 降级:没命中时给平台总览引导', async () => {
const r = await answer('你好呀今天天气不错');
assert.equal(r.source, 'fallback');
assert.match(r.spoken, /能力助手|数字分身|想先了解/);
});
test('总览话术非空且可朗读', () => {
const t = overviewText();
assert.ok(t.length > 20);
assert.doesNotMatch(t, /[#*`]/);
});
test('每条能力都具备助手所需字段', () => {
for (const c of CAPABILITIES) {
for (const k of ['id', 'name', 'category', 'summary', 'howto', 'where', 'speak']) {
assert.ok(c[k] !== undefined && c[k] !== '', `能力 ${c.id} 缺字段 ${k}`);
}
}
});