"""真实诊断与明确作者范围生成新候选,当前正文保持;失败无副作用。""" from dataclasses import asdict, replace import pytest import test_规则库与诊断 as 诊断测试 from muse.共享.错误 import Muse错误 from muse.审校修订.接口 import 修订请求, 允许选区 from muse.正式变更.接口 import 固定哈希 from muse.正文写作.接口 import 文本节点, 正文草稿, 段落, 段落哈希, 选段修改 pytestmark = pytest.mark.数据库 诊断环境 = 诊断测试.诊断环境 def 准备(env): app, 身份, target = env svc = app.要求审校() svc.导入规则候选(身份) report = svc.生成诊断(身份, target, 预览候选=True) finding = next(f for f in report["report"]["findings"] if f["rule_id"] == "l002") doc = app.要求正文().读取正文(身份, "diagnosis-ch") from pydantic import TypeAdapter draft = TypeAdapter(正文草稿).validate_python(doc["document"]) p = draft.paragraphs[0] patch = 选段修改(p.paragraph_id, 段落哈希(p), 0, 7, "值得注意的是,", "", finding["id"]) return ( svc, 身份, 修订请求( report["diagnosis_id"], 固定哈希(report["report"]), (patch,), (允许选区("p1", 0, 7),), (finding["id"],), "移除作者确认无功能的元话语", ), doc, ) @pytest.mark.case_id( "NC-w23-231001", environment="隔离PG", given="test_真实诊断批准选区仅生成候选且同命令幂等__231001", when="真实PG诊断与S01作者明确修订候选", then=["test_真实诊断批准选区仅生成候选且同命令幂等__231001"], contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md", ) def test_真实诊断批准选区仅生成候选且同命令幂等__231001(诊断环境): svc, 身份, 请求, 原稿 = 准备(诊断环境) app = 诊断环境[0] receipt = svc.提出修订(身份, "patch-1", 请求) assert receipt == svc.提出修订(身份, "patch-1", 请求) candidate = app.要求正文().读取候选(身份, receipt["target_ref"]) assert candidate["visible_text"] == "窗外正在下雨。" assert candidate["decision"] == "undecided" assert app.要求正文().读取正文(身份, "diagnosis-ch") == 原稿 assert receipt["results"][0]["checks"]["semantic_status"] == "unverified" @pytest.mark.case_id( "NC-w23-231002", environment="隔离PG", given="test_伪发现和越范围修订不产生候选__231002", when="真实PG诊断与S01作者明确修订候选", then=["test_伪发现和越范围修订不产生候选__231002"], contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md", ) @pytest.mark.parametrize( "bad", ["missing_finding", "unapproved", "outside", "hash", "text", "purpose"] ) def test_伪发现和越范围修订不产生候选__231002(诊断环境, bad): svc, 身份, r, 原稿 = 准备(诊断环境) app = 诊断环境[0] with pytest.raises((Muse错误, ValueError)): if bad == "missing_finding": r = replace(r, patches=(replace(r.patches[0], finding_id="fake"),)) elif bad == "unapproved": r = replace(r, approved_findings=()) elif bad == "outside": r = replace(r, allowed_ranges=(允许选区("p1", 1, 7),)) elif bad == "hash": r = replace(r, diagnosis_hash="0" * 64) elif bad == "text": r = replace(r, patches=(replace(r.patches[0], original="伪造"),)) else: r = replace(r, purpose="") svc.提出修订(身份, "bad-patch", r) assert app.要求正文().列出候选(身份, "diagnosis-ch") == [] assert app.要求正文().读取正文(身份, "diagnosis-ch") == 原稿 @pytest.mark.case_id( "NC-w23-231003", environment="隔离PG", given="test_旧诊断不能覆盖作者后来保存的新稿__231003", when="真实PG诊断与S01作者明确修订候选", then=["test_旧诊断不能覆盖作者后来保存的新稿__231003"], contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md", ) def test_旧诊断不能覆盖作者后来保存的新稿__231003(诊断环境): svc, 身份, r, _ = 准备(诊断环境) app = 诊断环境[0] app.要求正文().保存人工( 身份, "later", "diagnosis-ch", 1, 正文草稿((段落("p1", (文本节点("作者的新句子。"),)),)) ) with pytest.raises(Muse错误): svc.提出修订(身份, "stale-patch", r) assert app.要求正文().读取正文(身份, "diagnosis-ch")["visible_text"] == "作者的新句子。" assert app.要求正文().列出候选(身份, "diagnosis-ch") == [] @pytest.mark.case_id( "TC-1d9f2c11aa8d", environment="隔离PG与实际CLI子进程", when="执行具名公开保真、离线回放或实际CLI与隔离PG入口", then=[ "替代旧persist替身计数:实际子进程CLI与隔离PG保存一个未决候选,重放回执相同且不重复,正文不变。" ], contract="docs/系统架构/新版设计/功能规格/P04-声音诊断与受控修订.md", ) def test_cli_default_persists_revision__1d9f2c(诊断环境, tmp_path): import json import subprocess import sys _, 身份, req, original = 准备(诊断环境) app = 诊断环境[0] secret = tmp_path / "password" secret.write_text("synthetic-only") secret.chmod(0o600) config = tmp_path / "author.toml" config.write_text( '["数据库"]\n"取值方式"="受控存储"\n"位置"=' + json.dumps(app.要求数据库().引用.位置) + '\n["资源"]\n"发布身份"="test"\n["HTTP"]\n"作者ID"=' + json.dumps(身份.作者) + '\n"口令文件"=' + json.dumps(str(secret)) + "\n" ) path = tmp_path / "revision.json" path.write_text( json.dumps({"command_id": "cli-revision", "request": asdict(req)}, ensure_ascii=False) ) command = [sys.executable, "-I", "-m", "muse", "审校", str(config), "作者修订", str(path)] first = subprocess.run(command, cwd=tmp_path, capture_output=True, text=True, timeout=30) assert first.returncode == 0, first.stderr second = subprocess.run(command, cwd=tmp_path, capture_output=True, text=True, timeout=30) assert second.returncode == 0 and json.loads(first.stdout) == json.loads(second.stdout) candidates = app.要求正文().列出候选(身份, "diagnosis-ch") assert len(candidates) == 1 and candidates[0]["decision"] == "undecided" assert candidates[0]["visible_text"] == "窗外正在下雨。" assert app.要求正文().读取正文(身份, "diagnosis-ch") == original