2026-07-12 15:39:59 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
|
import path from 'node:path'
|
|
|
|
|
|
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
|
const sourceRoot = path.resolve(scriptDir, '../src')
|
|
|
|
|
const evalViewDir = path.join(sourceRoot, 'views/eval')
|
|
|
|
|
|
|
|
|
|
const [
|
|
|
|
|
createSource,
|
|
|
|
|
taskStepSource,
|
|
|
|
|
ruleStepSource,
|
2026-07-14 09:40:17 +08:00
|
|
|
basicMetricStepSource,
|
|
|
|
|
startStepSource,
|
2026-07-13 11:47:46 +08:00
|
|
|
dimensionFieldsSource,
|
2026-07-12 15:39:59 +08:00
|
|
|
listSource,
|
|
|
|
|
routerSource,
|
|
|
|
|
apiSource,
|
|
|
|
|
] = await Promise.all([
|
|
|
|
|
readFile(path.join(evalViewDir, 'EvalCreateView.vue'), 'utf8'),
|
|
|
|
|
readFile(path.join(evalViewDir, 'create/EvalTaskSetupStep.vue'), 'utf8'),
|
|
|
|
|
readFile(path.join(evalViewDir, 'create/EvalRuleSetupStep.vue'), 'utf8'),
|
2026-07-14 09:40:17 +08:00
|
|
|
readFile(path.join(evalViewDir, 'create/BasicMetricSetupStep.vue'), 'utf8'),
|
|
|
|
|
readFile(path.join(evalViewDir, 'create/StartEvalStep.vue'), 'utf8'),
|
2026-07-13 11:47:46 +08:00
|
|
|
readFile(path.join(evalViewDir, 'create/DimensionFormFields.vue'), 'utf8'),
|
2026-07-12 15:39:59 +08:00
|
|
|
readFile(path.join(evalViewDir, 'EvalView.vue'), 'utf8'),
|
|
|
|
|
readFile(path.join(sourceRoot, 'router/index.ts'), 'utf8'),
|
|
|
|
|
readFile(path.join(sourceRoot, 'api/modules/eval.ts'), 'utf8'),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
function extractRouteBlock(source, routePath) {
|
|
|
|
|
const pathPattern = new RegExp(`path:\\s*['"]${routePath.replaceAll('/', '\\/')}['"]`)
|
|
|
|
|
const pathIndex = source.search(pathPattern)
|
|
|
|
|
assert.notEqual(pathIndex, -1, `未找到路由 ${routePath}`)
|
|
|
|
|
|
|
|
|
|
const openBrace = source.lastIndexOf('{', pathIndex)
|
|
|
|
|
assert.notEqual(openBrace, -1, `路由 ${routePath} 缺少左花括号`)
|
|
|
|
|
|
|
|
|
|
let depth = 0
|
|
|
|
|
for (let index = openBrace; index < source.length; index += 1) {
|
|
|
|
|
if (source[index] === '{') depth += 1
|
|
|
|
|
if (source[index] === '}') depth -= 1
|
|
|
|
|
if (depth === 0) return source.slice(openBrace + 1, index)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert.fail(`路由 ${routePath} 缺少右花括号`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createDimensionRoute = extractRouteBlock(routerSource, 'model-eval/dimension/create')
|
|
|
|
|
assert.match(
|
|
|
|
|
createDimensionRoute,
|
|
|
|
|
/redirect:\s*['"]\/model-eval\/create['"]/,
|
|
|
|
|
'独立创建维度入口必须收敛到新建评测向导',
|
|
|
|
|
)
|
|
|
|
|
assert.doesNotMatch(
|
|
|
|
|
createDimensionRoute,
|
|
|
|
|
/DimensionCreateView/,
|
|
|
|
|
'独立创建维度路由不应继续加载旧创建页',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const editDimensionRoute = extractRouteBlock(routerSource, 'model-eval/dimension/:id/edit')
|
|
|
|
|
assert.match(
|
|
|
|
|
editDimensionRoute,
|
|
|
|
|
/DimensionCreateView\.vue/,
|
|
|
|
|
'编辑维度路由必须继续复用 DimensionCreateView.vue',
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-13 11:47:46 +08:00
|
|
|
assert.doesNotMatch(listSource, /评测维度/, '模型评测页不应保留评测维度页签及内容')
|
|
|
|
|
assert.doesNotMatch(listSource, /activeTab\s*===\s*['"]dimensions['"]/, '模型评测页不应保留评测维度状态分支')
|
|
|
|
|
assert.doesNotMatch(listSource, /getDimensionList|deleteDimension|dimensionList/, '模型评测页不应继续加载维度列表数据')
|
2026-07-12 15:39:59 +08:00
|
|
|
|
|
|
|
|
assert.match(
|
|
|
|
|
apiSource,
|
|
|
|
|
/createDimension\s*=\s*\([^)]*\)\s*=>\s*[\s\S]*?post<\{\s*id:\s*string\s*\|\s*number\s*\}>\(['"]\/dimension['"]\s*,\s*data\)/,
|
|
|
|
|
'createDimension 必须声明返回包含 string | number 类型 id',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.match(createSource, /EvalTaskSetupStep/, '向导缺少任务配置子组件')
|
|
|
|
|
assert.match(createSource, /EvalRuleSetupStep/, '向导缺少评测规则子组件')
|
2026-07-14 09:40:17 +08:00
|
|
|
assert.match(createSource, /BasicMetricSetupStep/, '向导缺少基础评测指标子组件')
|
|
|
|
|
assert.match(createSource, /StartEvalStep/, '向导缺少开始评测确认子组件')
|
2026-07-12 15:39:59 +08:00
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
2026-07-14 09:40:17 +08:00
|
|
|
/任务配置[\s\S]*?大模型评测指标[\s\S]*?基础评测指标[\s\S]*?开始评测/,
|
|
|
|
|
'评测创建向导必须按“任务配置、大模型评测指标、基础评测指标、开始评测”定义四个步骤',
|
2026-07-12 15:39:59 +08:00
|
|
|
)
|
2026-07-13 11:47:46 +08:00
|
|
|
assert.match(
|
|
|
|
|
ruleStepSource,
|
|
|
|
|
/:show-description=["']false["']/,
|
2026-07-14 09:40:17 +08:00
|
|
|
'大模型评测指标步骤不应显示重复的规则描述输入框',
|
|
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
ruleStepSource,
|
|
|
|
|
/:show-status-settings=["']false["']/,
|
|
|
|
|
'大模型评测指标步骤不应显示维度管理状态字段',
|
|
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
ruleStepSource,
|
|
|
|
|
/LLM_METRIC_TYPES[^=]*=\s*\[['"]classification['"],\s*['"]metric['"]\][\s\S]*?:allowed-types=["']LLM_METRIC_TYPES["']/,
|
|
|
|
|
'大模型评测指标步骤只应提供依赖大模型的分类与评分指标',
|
2026-07-13 11:47:46 +08:00
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
dimensionFieldsSource,
|
|
|
|
|
/v-if=["']showDescription["'][^>]*label=["']描述["']/,
|
|
|
|
|
'共享维度字段必须支持按场景隐藏描述输入框',
|
|
|
|
|
)
|
2026-07-14 09:40:17 +08:00
|
|
|
assert.match(
|
|
|
|
|
dimensionFieldsSource,
|
|
|
|
|
/score_min[\s\S]*?score_max[\s\S]*?pass_threshold[\s\S]*?Math\.min[\s\S]*?Math\.max/,
|
|
|
|
|
'指标型评分区间变化时必须把通过阈值约束在合法范围内',
|
|
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
dimensionFieldsSource,
|
|
|
|
|
/prop=["']score_min["'][\s\S]*?:max=["'][^"']*score_max[^"']*["'][\s\S]*?prop=["']score_max["'][\s\S]*?:min=["'][^"']*score_min[^"']*["']/,
|
|
|
|
|
'评分最小值和最大值输入必须具备交叉边界约束',
|
|
|
|
|
)
|
2026-07-12 15:39:59 +08:00
|
|
|
assert.match(createSource, /currentStep\s*=\s*ref\(0\)/, '评测创建向导缺少当前步骤状态')
|
|
|
|
|
assert.match(createSource, /class=["']custom-wizard-steps["']/, '评测向导未复用数据处理的自定义步骤条结构')
|
|
|
|
|
assert.match(createSource, /class=["']step-connector["']/, '评测向导步骤条缺少连接线')
|
|
|
|
|
assert.match(createSource, /:aria-current=/, '步骤条缺少当前步骤的可访问性绑定')
|
|
|
|
|
assert.match(createSource, /currentStep === index \? 'step' : undefined/, '步骤条当前步骤语义不正确')
|
|
|
|
|
assert.doesNotMatch(createSource, /<el-steps\b|<el-step\b/, '评测向导不应继续使用 Element Plus 默认步骤条')
|
|
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
|
|
|
|
/<EvalTaskSetupStep[\s\S]*?v-if=["'][^"']*currentStep[^"']*0[^"']*["']/,
|
|
|
|
|
'任务配置子组件没有绑定第一步',
|
|
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
2026-07-14 09:40:17 +08:00
|
|
|
/<EvalRuleSetupStep[\s\S]*?currentStep[^"']*1/,
|
|
|
|
|
'大模型评测指标子组件没有绑定第二步',
|
|
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
|
|
|
|
/<BasicMetricSetupStep[\s\S]*?currentStep[^"']*2/,
|
|
|
|
|
'基础评测指标子组件没有绑定第三步',
|
|
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
|
|
|
|
/<StartEvalStep[\s\S]*?v-else/,
|
|
|
|
|
'开始评测确认子组件没有绑定第四步',
|
2026-07-12 15:39:59 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.match(taskStepSource, /prop=["']dataset_id["']/, '数据集字段缺少表单校验标识')
|
|
|
|
|
assert.match(
|
|
|
|
|
`${createSource}\n${taskStepSource}`,
|
|
|
|
|
/data_source[\s\S]*?dataset[\s\S]*?dataset_id/,
|
|
|
|
|
'选择评测数据集时必须校验 dataset_id',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.match(
|
2026-07-14 09:40:17 +08:00
|
|
|
basicMetricStepSource,
|
|
|
|
|
/BLEU[\s\S]*?ROUGE[\s\S]*?(?:Cosine|余弦)/,
|
|
|
|
|
'基础评测指标步骤必须提供 BLEU、ROUGE 与余弦相似度配置',
|
2026-07-12 15:39:59 +08:00
|
|
|
)
|
|
|
|
|
assert.match(
|
2026-07-14 09:40:17 +08:00
|
|
|
basicMetricStepSource,
|
|
|
|
|
/bleu_enabled[\s\S]*?rouge_enabled[\s\S]*?cosine_enabled/,
|
|
|
|
|
'基础评测指标必须支持分别决定是否启用',
|
2026-07-12 15:39:59 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
|
|
|
|
/await\s+createDimension\([\s\S]*?await\s+startEval\(/,
|
|
|
|
|
'最终提交必须先创建新维度,再使用维度 id 启动评测',
|
|
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
|
|
|
|
/createdDimension(?:\.value)?\.id|createdDimensionId|dimensionResult\.id/,
|
|
|
|
|
'启动评测前必须读取新建维度返回的 id',
|
|
|
|
|
)
|
|
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
2026-07-14 09:40:17 +08:00
|
|
|
/basic_metrics[\s\S]*?bleu[\s\S]*?rouge[\s\S]*?cosine/,
|
|
|
|
|
'启动评测时必须提交基础评测指标配置',
|
2026-07-12 15:39:59 +08:00
|
|
|
)
|
|
|
|
|
assert.match(
|
2026-07-14 09:40:17 +08:00
|
|
|
startStepSource,
|
|
|
|
|
/任务信息[\s\S]*?大模型评测指标[\s\S]*?基础评测指标/,
|
|
|
|
|
'开始评测步骤必须展示前三步配置摘要',
|
2026-07-12 15:39:59 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert.match(createSource, /:loading=["']submitting["']/, '最终提交按钮缺少 loading 状态')
|
|
|
|
|
assert.match(createSource, /:disabled=["'][^"']*submitting/, '提交期间必须禁用重复操作')
|
2026-07-14 09:40:17 +08:00
|
|
|
assert.match(createSource, />\s*开始评测\s*</, '最终提交按钮必须命名为“开始评测”')
|
2026-07-12 15:39:59 +08:00
|
|
|
assert.match(createSource, /class=["'][^"']*wizard-footer/, '评测向导缺少统一底部操作栏')
|
|
|
|
|
assert.match(
|
|
|
|
|
createSource,
|
|
|
|
|
/\.wizard-footer\s*\{[\s\S]*?display:\s*flex[\s\S]*?justify-content:\s*flex-end/,
|
|
|
|
|
'底部操作栏必须保持企业表单常用的右对齐布局',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
console.log('评测创建向导回归检查通过')
|