Nightly TestManager failed because cleanHeadingTitle only stripped 一…十, leaving 十一、/十二、 unmatched against template 签署/说明.
535 lines
19 KiB
Go
535 lines
19 KiB
Go
package squad_test
|
||
|
||
import (
|
||
"context"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"chat-agent-system/internal/agent"
|
||
"chat-agent-system/internal/squad"
|
||
)
|
||
|
||
func TestQualityGate_Validate(t *testing.T) {
|
||
tmpDir := t.TempDir()
|
||
|
||
engine := squad.NewWorkspaceEngine()
|
||
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
|
||
WorkDir: tmpDir,
|
||
TaskKey: "intake",
|
||
StageTitle: "用户初始需求收集表",
|
||
ProjectName: "医疗设备",
|
||
TemplateContent: "# 用户初始需求收集表\n\n## 1. 客户与项目画像\n\n## 2. 预期用途\n\n## 3. 待确认清单\n",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("PrepareWorkspace failed: %v", err)
|
||
}
|
||
|
||
gate := squad.NewQualityGate()
|
||
|
||
// 1. 测试违规文本:包含敷衍词「不做展开」与缺少第 2 节
|
||
badContent := "# 用户初始需求收集表\n\n## 1. 客户与项目画像\n此处篇幅有限,不做展开。\n\n## 3. 待确认清单\n- 待确认事项\n"
|
||
report1, err := gate.Validate(ws.WorkDir, "intake", badContent)
|
||
if err != nil {
|
||
t.Fatalf("Validate failed: %v", err)
|
||
}
|
||
if report1.Passed {
|
||
t.Errorf("expected report1 to fail quality gate")
|
||
}
|
||
if len(report1.ProhibitedPhrases) == 0 {
|
||
t.Errorf("expected prohibited phrase '不做展开' to be detected")
|
||
}
|
||
if len(report1.MissingSections) == 0 {
|
||
t.Errorf("expected missing section '## 2. 预期用途' to be detected")
|
||
}
|
||
|
||
// 2. 测试合规文本:包含所有章节且无违规词
|
||
goodContent := `# 用户初始需求收集表
|
||
|
||
## 1. 客户与项目画像
|
||
本项目客户为专业医疗机构〔材料: 需求表 §1〕。
|
||
(四段式展开详细论述)
|
||
|
||
## 2. 预期用途
|
||
用于临床辅助诊断与生理信号监测。
|
||
|
||
## 3. 待确认清单
|
||
- 供电模块认证周期待窗口确认。
|
||
`
|
||
report2, err := gate.Validate(ws.WorkDir, "intake", goodContent)
|
||
if err != nil {
|
||
t.Fatalf("Validate failed: %v", err)
|
||
}
|
||
if !report2.Passed {
|
||
t.Errorf("expected report2 to pass quality gate, violations: %v", report2.Violations)
|
||
}
|
||
if report2.Score < 80.0 {
|
||
t.Errorf("expected high score, got %f", report2.Score)
|
||
}
|
||
|
||
// 3. 测试 Manifest 生成
|
||
manifest, err := gate.GenerateManifest(ws.WorkDir, "intake", *report2)
|
||
if err != nil {
|
||
t.Fatalf("GenerateManifest failed: %v", err)
|
||
}
|
||
if manifest.DocKey != "intake" {
|
||
t.Errorf("expected DocKey 'intake', got %s", manifest.DocKey)
|
||
}
|
||
if len(manifest.Files) == 0 {
|
||
t.Errorf("expected tracked files in manifest")
|
||
}
|
||
if _, err := os.Stat(filepath.Join(ws.WorkDir, "manifest.json")); err != nil {
|
||
t.Errorf("manifest.json not written to disk")
|
||
}
|
||
}
|
||
|
||
func TestQualityGate_AutoRepair(t *testing.T) {
|
||
tmpDir := t.TempDir()
|
||
|
||
engine := squad.NewWorkspaceEngine()
|
||
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
|
||
WorkDir: tmpDir,
|
||
TaskKey: "intake",
|
||
StageTitle: "用户初始需求收集表",
|
||
TemplateContent: "# 用户初始需求收集表\n\n## 1. 结论\n\n## 2. 详细方案\n",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("PrepareWorkspace failed: %v", err)
|
||
}
|
||
|
||
driver := squad.NewWorkflowDriver(engine)
|
||
gate := squad.NewQualityGate()
|
||
|
||
// 模拟不合格草稿
|
||
badDraft := "# 用户初始需求收集表\n\n## 1. 结论\n由于篇幅有限,不做展开。\n"
|
||
_ = os.WriteFile(filepath.Join(ws.WorkDir, "output.md"), []byte(badDraft), 0o644)
|
||
|
||
// 触发自动修复:门禁检测到不合格,向 workflow 追加修复步骤并执行
|
||
stubBackend := &agent.StubBackend{}
|
||
report, err := gate.EnsureQualityOrRepair(context.Background(), ws.WorkDir, "intake", driver, stubBackend, 1, squad.ExecuteOptions{
|
||
Timeout: 5 * time.Second,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("EnsureQualityOrRepair failed: %v", err)
|
||
}
|
||
|
||
// 验证工作流中已追加过修复步骤
|
||
wf, _ := driver.LoadWorkflow(ws.WorkDir)
|
||
hasRepairStep := false
|
||
for _, s := range wf.Steps {
|
||
if s.ID == "step_repair_1" {
|
||
hasRepairStep = true
|
||
break
|
||
}
|
||
}
|
||
if !hasRepairStep {
|
||
t.Errorf("expected step_repair_1 to be appended to workflow")
|
||
}
|
||
|
||
if report == nil {
|
||
t.Errorf("expected non-nil quality report")
|
||
}
|
||
}
|
||
|
||
func TestQualityGate_SystemReqsAndTestManager(t *testing.T) {
|
||
tmpDir := t.TempDir()
|
||
engine := squad.NewWorkspaceEngine()
|
||
gate := squad.NewQualityGate()
|
||
|
||
// 1. 测试 system_reqs
|
||
ws1, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
|
||
WorkDir: filepath.Join(tmpDir, "ws_reqs"),
|
||
TaskKey: "system_reqs",
|
||
StageTitle: "系统需求规格书",
|
||
ProjectName: "医疗监护仪",
|
||
TemplateContent: "# 系统需求规格书\n\n## 1. 结论\n\n## 2. 系统需求清单\n",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("PrepareWorkspace failed: %v", err)
|
||
}
|
||
|
||
// 缺少需求编号和验证方式
|
||
badReqContent := "# 系统需求规格书\n\n## 1. 结论\n结论完整\n\n## 2. 系统需求清单\n系统应支持心率采集。\n"
|
||
rep1, err := gate.Validate(ws1.WorkDir, "system_reqs", badReqContent)
|
||
if err != nil {
|
||
t.Fatalf("Validate failed: %v", err)
|
||
}
|
||
if rep1.Passed {
|
||
t.Errorf("expected system_reqs missing REQ- and verification to fail")
|
||
}
|
||
|
||
// 合规文本
|
||
goodReqContent := "# 系统需求规格书\n\n## 1. 结论\n结论完整\n\n## 2. 系统需求清单\n| REQ-01 | 系统应支持心率监测,测量误差不超过 ±2bpm | 测试 | 高 |\n"
|
||
rep2, err := gate.Validate(ws1.WorkDir, "system_reqs", goodReqContent)
|
||
if err != nil {
|
||
t.Fatalf("Validate failed: %v", err)
|
||
}
|
||
if !rep2.Passed {
|
||
t.Errorf("expected compliant system_reqs to pass, got violations: %v", rep2.Violations)
|
||
}
|
||
|
||
// 2. 测试 test_manager
|
||
ws2, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
|
||
WorkDir: filepath.Join(tmpDir, "ws_test"),
|
||
TaskKey: "test_manager",
|
||
StageTitle: "系统验证方案及计划",
|
||
ProjectName: "医疗监护仪",
|
||
TemplateContent: "# 系统验证方案及计划\n\n## 1. 结论\n\n## 2. 用例与验证规程\n",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("PrepareWorkspace failed: %v", err)
|
||
}
|
||
|
||
// 缺少用例和上游追溯
|
||
badTestContent := "# 系统验证方案及计划\n\n## 1. 结论\n结论\n\n## 2. 用例与验证规程\n验证功能正常。\n"
|
||
rep3, err := gate.Validate(ws2.WorkDir, "test_manager", badTestContent)
|
||
if err != nil {
|
||
t.Fatalf("Validate failed: %v", err)
|
||
}
|
||
if rep3.Passed {
|
||
t.Errorf("expected test_manager missing TC- and REQ- to fail")
|
||
}
|
||
|
||
// 合规文本
|
||
goodTestContent := "# 系统验证方案及计划\n\n## 1. 结论\n结论\n\n## 2. 用例与验证规程\n| TC-01 | 验证心率测量精度 | 追溯 REQ-01 | 判定准则误差 <= ±2bpm | 通过 |\n"
|
||
rep4, err := gate.Validate(ws2.WorkDir, "test_manager", goodTestContent)
|
||
if err != nil {
|
||
t.Fatalf("Validate failed: %v", err)
|
||
}
|
||
if !rep4.Passed {
|
||
t.Errorf("expected compliant test_manager to pass, got violations: %v", rep4.Violations)
|
||
}
|
||
}
|
||
|
||
// 现场证据 output.md:204 — 终稿自检表否定性引用禁词,不得误报为敷衍违规。
|
||
func TestQualityGate_NegatedSelfCheckCitation_NoFalsePositive(t *testing.T) {
|
||
tmpDir := t.TempDir()
|
||
engine := squad.NewWorkspaceEngine()
|
||
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
|
||
WorkDir: tmpDir,
|
||
TaskKey: "intake",
|
||
StageTitle: "用户初始需求收集表",
|
||
ProjectName: "医疗设备",
|
||
TemplateContent: "# 用户初始需求收集表\n\n## 1. 客户与项目画像\n\n## 2. 预期用途\n\n## 3. 待确认清单\n",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("PrepareWorkspace: %v", err)
|
||
}
|
||
gate := squad.NewQualityGate()
|
||
|
||
// 与 data/dev/workspaces/bizdev/intake_1789308825666278807/output.md 第204行同构
|
||
realChecklistLine := `| 写作禁令遵守情况 | ✅ 通过 | 无"不做展开""从略""篇幅有限"等敷衍套话 |`
|
||
content := `# 用户初始需求收集表
|
||
|
||
## 1. 客户与项目画像
|
||
本项目客户为专业医疗机构〔材料: 需求表 §1〕。四段式展开详细论述客户背景与项目边界。
|
||
|
||
## 2. 预期用途
|
||
用于临床辅助诊断与生理信号监测,覆盖病房与门诊场景。
|
||
|
||
## 3. 待确认清单
|
||
- 供电模块认证周期待窗口确认。
|
||
|
||
### 附录C:审核记录(Review Record)
|
||
|
||
| 审核项 | 审核结论 | 说明 |
|
||
|-------|---------|------|
|
||
| 全文逻辑一致性 | ✅ 通过 | 结论→正文→解读完整 |
|
||
` + realChecklistLine + "\n"
|
||
|
||
report, err := gate.Validate(ws.WorkDir, "intake", content)
|
||
if err != nil {
|
||
t.Fatalf("Validate: %v", err)
|
||
}
|
||
if !report.Passed {
|
||
t.Fatalf("negated self-check must not fail gate; violations=%v phrases=%v score=%.0f",
|
||
report.Violations, report.ProhibitedPhrases, report.Score)
|
||
}
|
||
if len(report.ProhibitedPhrases) != 0 {
|
||
t.Fatalf("expected no prohibited hits on meta citation, got %v", report.ProhibitedPhrases)
|
||
}
|
||
|
||
// 真敷衍仍须命中
|
||
hedging := "# 用户初始需求收集表\n\n## 1. 客户与项目画像\n此处篇幅有限,不做展开。\n\n## 2. 预期用途\nok\n\n## 3. 待确认清单\n- x\n"
|
||
bad, err := gate.Validate(ws.WorkDir, "intake", hedging)
|
||
if err != nil {
|
||
t.Fatalf("Validate hedging: %v", err)
|
||
}
|
||
if bad.Passed || len(bad.ProhibitedPhrases) == 0 {
|
||
t.Fatalf("real hedging must fail; passed=%v phrases=%v", bad.Passed, bad.ProhibitedPhrases)
|
||
}
|
||
for _, v := range bad.Violations {
|
||
for _, p := range squad.ProhibitedPhrases {
|
||
if strings.Contains(v, p) {
|
||
t.Fatalf("violation text must not echo banned phrase %q: %s", p, v)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 正文出现「无」但非自检引用:不得因 48 字窗口豁免
|
||
falseNeg := "# 用户初始需求收集表\n\n## 1. 客户与项目画像\n此处无更多数据,不做展开。\n\n## 2. 预期用途\nok\n\n## 3. 待确认清单\n- x\n"
|
||
fn, err := gate.Validate(ws.WorkDir, "intake", falseNeg)
|
||
if err != nil {
|
||
t.Fatalf("Validate false-neg: %v", err)
|
||
}
|
||
if fn.Passed || len(fn.ProhibitedPhrases) == 0 {
|
||
t.Fatalf("body hedging with 无 must still fail; passed=%v phrases=%v", fn.Passed, fn.ProhibitedPhrases)
|
||
}
|
||
|
||
// 仅有审核栏标题、禁词作正文省略:不得豁免
|
||
metaOnly := "# 用户初始需求收集表\n\n## 1. 客户与项目画像\n审核结论:本节不做展开。\n\n## 2. 预期用途\nok\n\n## 3. 待确认清单\n- x\n"
|
||
mo, err := gate.Validate(ws.WorkDir, "intake", metaOnly)
|
||
if err != nil {
|
||
t.Fatalf("Validate meta-only: %v", err)
|
||
}
|
||
if mo.Passed || len(mo.ProhibitedPhrases) == 0 {
|
||
t.Fatalf("meta heading plus real hedging must fail; passed=%v phrases=%v", mo.Passed, mo.ProhibitedPhrases)
|
||
}
|
||
}
|
||
|
||
func intakeTemplate(t *testing.T) (workDir string, gate *squad.QualityGate) {
|
||
t.Helper()
|
||
engine := squad.NewWorkspaceEngine()
|
||
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
|
||
WorkDir: t.TempDir(),
|
||
TaskKey: "intake",
|
||
StageTitle: "用户初始需求收集表",
|
||
ProjectName: "医疗设备",
|
||
TemplateContent: "# 用户初始需求收集表\n\n## 1. 客户与项目画像\n\n## 2. 预期用途\n\n## 3. 待确认清单\n",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("PrepareWorkspace: %v", err)
|
||
}
|
||
return ws.WorkDir, squad.NewQualityGate()
|
||
}
|
||
|
||
func wrapIntakeBody(body string) string {
|
||
return `# 用户初始需求收集表
|
||
|
||
## 1. 客户与项目画像
|
||
本项目客户为专业医疗机构。四段式展开客户背景、项目边界与采集依据。
|
||
|
||
## 2. 预期用途
|
||
用于临床辅助诊断与生理信号监测,覆盖病房与门诊场景。
|
||
|
||
## 3. 待确认清单
|
||
- 供电模块认证周期待窗口确认。
|
||
|
||
` + body
|
||
}
|
||
|
||
// 现场 intake_1789314411395330821 output.md:163-164 — 行动计划表独立状态「待续」不得误报。
|
||
func TestQualityGate_ActionPlanStatusCell_PendingNotHedging(t *testing.T) {
|
||
workDir, gate := intakeTemplate(t)
|
||
|
||
stillListed := false
|
||
for _, p := range squad.ProhibitedPhrases {
|
||
if p == "待续" {
|
||
stillListed = true
|
||
break
|
||
}
|
||
}
|
||
if !stillListed {
|
||
t.Fatal("待续 must remain in ProhibitedPhrases")
|
||
}
|
||
|
||
// 与 workspace 第163-164 行同构(下一步行动建议 / 阶段·行动项·负责方·触发条件)
|
||
actionPlan := `### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
|---|---|---|---|
|
||
| **立即** | 用户补充D-01(注册分类)、D-02(预期用途)两项高优先级缺口,提供分类依据或产品技术要求初稿。 | 北京水木东方 | 收到用户反馈后 |
|
||
| **待续** | 合规专员基于用户补充材料,评估GB 9706.1-2020符合性与软件合规要求,出具初步合规评估意见。 | 合规专员 | D-01至D-07全部确认后 |
|
||
| **待续** | 组织专员编制里程碑规划与检测排期方案,明确各阶段时间节点、责任部门及交付物要求。 | 组织专员 | 合规评估完成后 |
|
||
`
|
||
report, err := gate.Validate(workDir, "intake", wrapIntakeBody(actionPlan))
|
||
if err != nil {
|
||
t.Fatalf("Validate: %v", err)
|
||
}
|
||
if !report.Passed {
|
||
t.Fatalf("action-plan status cell must pass; violations=%v phrases=%v score=%.0f",
|
||
report.Violations, report.ProhibitedPhrases, report.Score)
|
||
}
|
||
if len(report.ProhibitedPhrases) != 0 {
|
||
t.Fatalf("expected no prohibited hits on action-plan status, got %v", report.ProhibitedPhrases)
|
||
}
|
||
}
|
||
|
||
func TestQualityGate_ActionPlanStatus_ReorderedColumnsPass(t *testing.T) {
|
||
workDir, gate := intakeTemplate(t)
|
||
reordered := `### 下一步行动建议
|
||
|
||
| 行动项 | 负责方 | 触发条件 | 阶段 |
|
||
|---|---|---|---|
|
||
| 合规专员基于用户补充材料评估GB 9706.1符合性并出具初步意见。 | 合规专员 | D-01至D-07全部确认后 | **待续** |
|
||
`
|
||
report, err := gate.Validate(workDir, "intake", wrapIntakeBody(reordered))
|
||
if err != nil {
|
||
t.Fatalf("Validate: %v", err)
|
||
}
|
||
if !report.Passed || len(report.ProhibitedPhrases) != 0 {
|
||
t.Fatalf("reordered status column must pass; violations=%v phrases=%v",
|
||
report.Violations, report.ProhibitedPhrases)
|
||
}
|
||
}
|
||
|
||
func TestQualityGate_PendingPhrase_HostileContextsStillFail(t *testing.T) {
|
||
workDir, gate := intakeTemplate(t)
|
||
|
||
cases := []struct {
|
||
name string
|
||
body string
|
||
}{
|
||
{"body_hedging", "后续章节待续,本节仅列框架。\n"},
|
||
{"heading", "## 后续章节待续\n正文已展开风险与电气安全条款适用性。\n"},
|
||
{"list", "- 后续章节待续\n"},
|
||
{"plain_two_col_table", "| 项目 | 内容 |\n|---|---|\n| 风险管理 | 待续 |\n"},
|
||
{"four_col_non_action_table", "| 章节 | 摘要 | 作者 | 备注 |\n|---|---|---|---|\n| 风险 | 待续 | 张三 | 无 |\n"},
|
||
{"labeled_pending_note", `### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
|---|---|---|---|
|
||
| 待续说明 | 合规专员基于用户补充材料评估符合性并出具初步意见。 | 合规专员 | D-01确认后 |
|
||
`},
|
||
{"incomplete_row", `### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
|---|---|---|---|
|
||
| **待续** | 合规专员基于用户补充材料评估符合性并出具初步意见。 | 合规专员 | |
|
||
`},
|
||
{"thin_action", `### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
|---|---|---|---|
|
||
| **待续** | 再议 | 合规专员 | 完成后 |
|
||
`},
|
||
{"owner_cell_pending", `### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
|---|---|---|---|
|
||
| 立即 | 合规专员基于用户补充材料评估符合性并出具初步意见。 | 待续 | D-01确认后 |
|
||
`},
|
||
{"trigger_cell_pending", `### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
|---|---|---|---|
|
||
| 立即 | 合规专员基于用户补充材料评估符合性并出具初步意见。 | 合规专员 | 待续 |
|
||
`},
|
||
{"no_header_separator", `### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
| **待续** | 合规专员基于用户补充材料评估符合性并出具初步意见。 | 合规专员 | D-01确认后 |
|
||
`},
|
||
{"header_4_data_5", `### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
|---|---|---|---|
|
||
| **待续** | 合规专员基于用户补充材料评估符合性并出具初步意见。 | 合规专员 | D-01确认后 | 额外列 |
|
||
`},
|
||
}
|
||
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
rep, err := gate.Validate(workDir, "intake", wrapIntakeBody(tc.body))
|
||
if err != nil {
|
||
t.Fatalf("Validate: %v", err)
|
||
}
|
||
if rep.Passed || len(rep.ProhibitedPhrases) == 0 {
|
||
t.Fatalf("must still fail; passed=%v phrases=%v", rep.Passed, rep.ProhibitedPhrases)
|
||
}
|
||
hit := false
|
||
for _, p := range rep.ProhibitedPhrases {
|
||
if p == "待续" {
|
||
hit = true
|
||
}
|
||
}
|
||
if !hit {
|
||
t.Fatalf("must record 待续, got %v", rep.ProhibitedPhrases)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestQualityGate_ActionPlanStatus_DoesNotTriggerRepair(t *testing.T) {
|
||
workDir, gate := intakeTemplate(t)
|
||
content := wrapIntakeBody(`### 下一步行动建议
|
||
|
||
| 阶段 | 行动项 | 负责方 | 触发条件 |
|
||
|---|---|---|---|
|
||
| **待续** | 合规专员基于用户补充材料评估电气安全符合性并出具意见。 | 合规专员 | D-01至D-07全部确认后 |
|
||
`)
|
||
if err := os.WriteFile(filepath.Join(workDir, "output.md"), []byte(content), 0o644); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
|
||
report, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &agent.StubBackend{}, 1, squad.ExecuteOptions{
|
||
Timeout: 5 * time.Second,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("EnsureQualityOrRepair: %v", err)
|
||
}
|
||
if report == nil || !report.Passed {
|
||
t.Fatalf("action-plan pending must pass without repair; report=%+v", report)
|
||
}
|
||
wf, _ := driver.LoadWorkflow(workDir)
|
||
for _, s := range wf.Steps {
|
||
if strings.HasPrefix(s.ID, "step_repair_") {
|
||
t.Fatalf("must not append repair for status-cell false positive, got %q", s.ID)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 现场 nightly TestManager:LLM 写「## 十一、签署 / ## 十二、说明」,旧清洗只剥到「十、」会误报缺章。
|
||
func TestCleanHeadingTitle_MultiDigitChineseOrdinal(t *testing.T) {
|
||
cases := []struct {
|
||
in, want string
|
||
}{
|
||
{"一、客户与项目画像", "客户与项目画像"},
|
||
{"十、附录", "附录"},
|
||
{"十一、签署", "签署"},
|
||
{"十二、说明", "说明"},
|
||
{"二十三、风险评估", "风险评估"},
|
||
{"1. 结论", "结论"},
|
||
{"签署", "签署"},
|
||
{"十一、签署(终稿)", "签署"},
|
||
}
|
||
for _, c := range cases {
|
||
got := squad.CleanHeadingTitleForTest(c.in)
|
||
if got != c.want {
|
||
t.Errorf("cleanHeadingTitle(%q) = %q, want %q", c.in, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 现场 nightly TestManager:LLM 写「## 十一、签署 / ## 十二、说明」,旧清洗只剥到「十、」会误报缺章。
|
||
func TestQualityGate_Validate_ChineseOrdinalElevenTwelve(t *testing.T) {
|
||
tmpDir := t.TempDir()
|
||
engine := squad.NewWorkspaceEngine()
|
||
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
|
||
WorkDir: filepath.Join(tmpDir, "ws_tm"),
|
||
TaskKey: "test_manager",
|
||
StageTitle: "系统验证方案及计划",
|
||
ProjectName: "医疗监护仪",
|
||
TemplateContent: "# 系统验证方案及计划\n\n" +
|
||
"## 结论\n\n## 用例与验证规程\n\n## 签署\n\n## 说明\n",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("PrepareWorkspace: %v", err)
|
||
}
|
||
gate := squad.NewQualityGate()
|
||
body := "# 系统验证方案及计划\n\n" +
|
||
"## 一、结论\n本方案覆盖核心验证路径,结论可交付。\n\n" +
|
||
"## 二、用例与验证规程\n| TC-01 | 验证心率测量精度 | 追溯 REQ-01 | 判定准则误差 <= ±2bpm | 通过 |\n\n" +
|
||
"## 十一、签署\n产品负责人签字确认。\n\n" +
|
||
"## 十二、说明\n本说明补充验证边界与假设。\n"
|
||
rep, err := gate.Validate(ws.WorkDir, "test_manager", body)
|
||
if err != nil {
|
||
t.Fatalf("Validate: %v", err)
|
||
}
|
||
if !rep.Passed {
|
||
t.Fatalf("expected eleven/twelve ordinal headings to match template, violations=%v", rep.Violations)
|
||
}
|
||
}
|