import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const knowledgePagePath = path.join(adminRoot, 'src/pages/ai/knowledge/index.tsx'); const knowledgeLocalePath = path.join(adminRoot, 'src/pages/ai/knowledge/locale/index.ts'); test('1. 知识库页面必须包含召回沙盒调试入口与 Arco Drawer/Progress 组件', () => { assert.ok(fs.existsSync(knowledgePagePath), '知识库页面必须存在'); const content = fs.readFileSync(knowledgePagePath, 'utf-8'); assert.ok(content.includes('Drawer'), '必须使用 Arco Drawer 抽屉展示沙盒调试器'); assert.ok(content.includes('Progress'), '必须使用 Arco Progress 进度条展示置信度'); assert.ok( content.includes('admin/ai/knowledge/sandbox-test'), '必须调用 POST /api/admin/ai/knowledge/sandbox-test 调试接口' ); assert.ok( content.includes('KnowledgeChunk') || content.includes('KnowledgeSandboxResult'), '必须使用 KnowledgeChunk 或 KnowledgeSandboxResult 契约类型' ); assert.ok(content.includes('lightColors'), '必须引用 lightColors 设计 Token'); assert.ok(!/#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})\b/.test(content), '严禁硬编码十六进制颜色'); assert.ok(!/\bany\b/.test(content), '页面严禁使用 any'); // 严禁包含 emoji assert.ok(!/[\uD83C-\uDBFF\uDC00-\uDFFF\u2600-\u27BF]/.test(content), '严禁使用 emoji'); }); test('2. 召回沙盒支持输入测试提问、切片卡片渲染与置信度分数进度条', () => { const content = fs.readFileSync(knowledgePagePath, 'utf-8'); assert.ok(content.includes('sandboxVisible') || content.includes('sandboxDrawer') || content.includes('openSandbox'), '必须包含沙盒显示控制状态'); assert.ok(content.includes('query') || content.includes('sandboxQuery'), '必须支持测试提问 query 输入'); assert.ok(content.includes('recallList') || content.includes('recalledList') || content.includes('recallResults'), '必须维护召回切片结果列表'); assert.ok(content.includes('score'), '必须展示切片置信度 score'); assert.ok(content.includes('percent'), 'Progress 必须配置 percent 计算属性'); assert.ok(content.includes('chunkSize') || content.includes('topK'), '沙盒应支持调试参数配置'); }); test('3. 知识库多语言字典必须包含沙盒调试相关词条,且无 any 与无 emoji', () => { assert.ok(fs.existsSync(knowledgeLocalePath), '语言包必须存在'); const content = fs.readFileSync(knowledgeLocalePath, 'utf-8'); assert.ok(content.includes('aiKnowledge.sandbox.title'), '必须包含沙盒标题文案'); assert.ok(content.includes('aiKnowledge.sandbox.query'), '必须包含测试提问文案'); assert.ok(content.includes('aiKnowledge.sandbox.test'), '必须包含沙盒测试执行按钮文案'); assert.ok(content.includes('aiKnowledge.sandbox.score'), '必须包含置信度分数文案'); assert.ok(!/\bany\b/.test(content), '语言包严禁使用 any'); assert.ok(!/[\uD83C-\uDBFF\uDC00-\uDFFF\u2600-\u27BF]/.test(content), '语言包严禁使用 emoji'); }); test('4. 沙盒置信度分数与进度条数值转换契约测试', () => { // 模拟置信度归一化算法,验证 0~1 的 score 转换为 0~100 的 percent const calculatePercent = (score?: number): number => { if (score === undefined || Number.isNaN(score) || score <= 0) return 0; if (score >= 1) return 100; return Math.round(score * 100); }; assert.equal(calculatePercent(0), 0); assert.equal(calculatePercent(0.852), 85); assert.equal(calculatePercent(0.999), 100); assert.equal(calculatePercent(1.0), 100); assert.equal(calculatePercent(undefined), 0); });