muse-agent-example/tests/集成/test_作品与人工正文.py
zizi d909d1bd1b 后端实现与用例身份:19 包集成落地并修复收尾缺陷
实现侧:
- 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。
- 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。
- 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。
- 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。
- 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。
- 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。
- 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。
- 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。

用例侧:
- 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存;
- 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
2026-09-18 01:15:00 +08:00

318 lines
15 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

"""人工创作入口使用真实owner、元数据、S01和隔离PG;不需要模型评分。"""
import pytest
from muse.作品规划.接口 import 作品服务, 档案保存, 登记作品参与者
from muse.元数据.接口 import 元数据服务
from muse.共享.标识 import 任务标识
from muse.共享.调用身份 import 内容用途, 用途, 调用身份
from muse.正式变更.接口 import 参与者目录, 正式变更服务
pytestmark = pytest.mark.数据库
@pytest.fixture
def 作品环境(内置结构测试库):
应用测试库 = 内置结构测试库
目录 = 参与者目录()
from muse.任务运行.接口 import 任务服务, 登记任务参与者
from muse.编排.接口 import 流程登记
登记任务参与者(目录, 任务服务(应用测试库[用途.生产], 流程登记()))
登记作品参与者(目录)
正式 = 正式变更服务(应用测试库[用途.生产], 目录)
服务 = 作品服务(应用测试库[用途.生产], 正式)
作者 = 调用身份("author", 任务标识("manual-create"), 用途.生产, 内容用途.规划)
结构 = 服务.新档案结构(作者, "work-new", "work_core", 1)
输入 = 档案保存(
"work-new", 0, {"名称": "新故事", "题材定位": "未定题材", "主题立意": "关于选择"}, 结构
)
return 服务, 作者, 输入, 应用测试库
@pytest.mark.case_id(
"NC-work-profile-atomic",
environment="隔离 PostgreSQL、真实业务owner,CLI例使用子进程和任意cwd",
given="内置结构已发布,作者创建具有动态档案的作品",
when="经S01保存及重试后读回",
then=["档案值由B01写入,S04初始绑定同事务,命令重试不多写版本"],
contract="docs/系统架构/新版设计/接口契约/正文结构与中文选区.md",
)
def test_创建动态作品档案经S01且同命令不重复__aa1101(作品环境):
服务, 作者, 输入, 应用测试库 = 作品环境
回执 = 服务.保存档案(作者, "create-work", 输入)
assert 服务.保存档案(作者, "create-work", 输入) == 回执
作品 = 服务.读取作品(作者, "work-new")
assert 作品["revision"] == 1
assert 作品["content"] == {"名称": "新故事", "题材定位": "未定题材", "主题立意": "关于选择"}
assert 作品["schema_binding"]["schema_id"] == "work_core"
with 应用测试库[用途.生产].连接() as 连:
assert 连.execute("SELECT count(*) FROM muse_work_version").fetchone()[0] == 1
@pytest.mark.case_id(
"NC-work-directory-stable",
environment="隔离 PostgreSQL、真实业务owner,CLI例使用子进程和任意cwd",
given="作者作品及两个稳定身份的章节",
when="重排全部章节后重试",
then=["新目录版本和原章节身份正确,保留历史目录,不重复更新"],
contract="docs/系统架构/新版设计/接口契约/正文结构与中文选区.md",
)
def test_章节重排只改目录顺序保留稳定身份__aa1102(作品环境):
服务, 作者, 输入, _ = 作品环境
服务.保存档案(作者, "create-work", 输入)
服务.添加章节(作者, "chapter-A", "work-new", "chapter-a", "开篇", 预期目录版本=0)
服务.添加章节(作者, "chapter-B", "work-new", "chapter-b", "随后", 预期目录版本=1)
回执 = 服务.重排章节(作者, "reorder", "work-new", ("chapter-b", "chapter-a"), 预期目录版本=2)
assert (
服务.重排章节(作者, "reorder", "work-new", ("chapter-b", "chapter-a"), 预期目录版本=2)
== 回执
)
目录 = 服务.读取目录(作者, "work-new")
assert 目录["revision"] == 3
assert [(v["chapter_id"], v["title"], v["position"]) for v in 目录["chapters"]] == [
("chapter-b", "随后", 1),
("chapter-a", "开篇", 2),
]
@pytest.mark.case_id(
"NC-document-manual-history",
environment="隔离 PostgreSQL、真实业务owner,CLI例使用子进程和任意cwd",
given="真实作品章节与带组合字符、emoji、空白的文稿",
when="两次人工保存、重开、回退、旧基线再次保存",
then=["中文与段落身份原样,回退形成第三版,第二版仍可读,旧基线拒绝"],
contract="docs/系统架构/新版设计/接口契约/正文结构与中文选区.md",
)
def test_人工正文保存重开与回退保留中文和历史__aa1103(作品环境):
from dataclasses import replace
from muse.正式变更.接口 import 变更错误
from muse.正文写作.接口 import 可见文本, 文本节点, 正文服务, 正文草稿, 段落, 登记正文参与者
作品, 作者, 输入, 库 = 作品环境
登记正文参与者(作品.正式.目录)
作品.保存档案(作者, "create-work", 输入)
作品.添加章节(作者, "chapter-A", "work-new", "chapter-a", "开篇", 预期目录版本=0)
正文 = 正文服务(库[用途.生产], 作品.正式)
草稿 = 正文草稿(
(
段落("p1", (文本节点(" 「中文」 e\u0301 🧑‍🏽 "),)),
段落("p2", ()),
段落("p3", (文本节点("最后一段。"),)),
)
)
首次 = 正文.保存人工(作者, "save-1", "chapter-a", 0, 草稿)
assert 正文.保存人工(作者, "save-1", "chapter-a", 0, 草稿) == 首次
重开 = 正文.读取正文(作者, "chapter-a")
assert 重开["revision"] == 1 and 重开["visible_text"] == 可见文本(草稿)
assert [p["paragraph_id"] for p in 重开["document"]["paragraphs"]] == ["p1", "p2", "p3"]
第二稿 = replace(
草稿, paragraphs=(*草稿.paragraphs[:2], 段落("p3", (文本节点("作者更新的末段。"),)))
)
正文.保存人工(作者, "save-2", "chapter-a", 1, 第二稿)
回退 = 正文.回退正文(作者, "restore-1", "chapter-a", 2, 1)
assert 回退["results"][0]["revision"] == 3
assert 正文.读取正文(作者, "chapter-a")["visible_text"] == 可见文本(草稿)
assert 正文.读取正文(作者, "chapter-a", 版本=2)["visible_text"] == 可见文本(第二稿)
with pytest.raises(变更错误) as 捕获:
正文.保存人工(作者, "stale-save", "chapter-a", 1, 第二稿)
assert 捕获.value.错误码 == "REVISION_CONFLICT"
assert 正文.读取正文(作者, "chapter-a")["revision"] == 3
@pytest.mark.case_id(
"NC-document-cli-owner",
environment="隔离 PostgreSQL、真实业务owner,CLI例使用子进程和任意cwd",
given="真实作品章节和本地作者配置",
when="仓库外cwd用python -m muse保存正文、重试和读回",
then=["CLI实际装配真实B01/B05/S01,结果幂等,中文和末尾空段落不损坏"],
contract="docs/系统架构/新版设计/接口契约/正文结构与中文选区.md",
)
def test_人工正文CLI使用实际装配并返回相同文稿__aa1104(作品环境, tmp_path):
文稿 = _核对CLI正文(作品环境, tmp_path, " CLI 原文「保持不变」🧑‍🏽\n")
assert 文稿["visible_text"] == " CLI 原文「保持不变」🧑‍🏽\n"
@pytest.mark.case_id(
"TC-125b0ca098ec",
environment="隔离PostgreSQL与仓内Python真实CLI",
given="显式大文本输入:中文、双引号、LF和反斜杠组合重复5000次",
when="经具名正文CLI请求文件保存到隔离PG,重复命令后重新读取",
then=["全文逐字符相同,重复命令不新增版本;不恢复通用SQL写入口"],
contract="docs/系统架构/新版设计/接口契约/正文结构与中文选区.md",
)
def test_大段中文引号换行与反斜杠经CLI和PG往返__125b0c(作品环境, tmp_path):
big = '中文"引号"\n换行\\反斜杠' * 5000
文稿 = _核对CLI正文(作品环境, tmp_path, big)
assert 文稿["visible_text"] == big
def _核对CLI正文(作品环境, tmp_path, 原文):
import json
import subprocess
import sys
from dataclasses import asdict
from muse.正文写作.接口 import 文本节点, 正文草稿, 段落
作品, 作者, 输入, 库 = 作品环境
作品.保存档案(作者, "create-work", 输入)
作品.添加章节(作者, "chapter-A", "work-new", "chapter-a", "开篇", 预期目录版本=0)
配置文件 = tmp_path / "人工.toml"
配置文件.write_text(
'["数据库"]\n"取值方式"="受控存储"\n"位置"='
+ json.dumps(库[用途.生产].引用.位置)
+ """
["资源"]
"发布身份"="test"
["HTTP"]
"口令文件"="作者口令.txt"
"作者ID"="author"
"""
)
草稿 = 正文草稿(
tuple(段落(f"cli-p{i}", (文本节点(行),)) for i, 行 in enumerate(原文.split("\n")))
)
请求文件 = tmp_path / "正文请求.json"
请求文件.write_text(
json.dumps(
{
"command_id": "cli-save",
"chapter_id": "chapter-a",
"expected_revision": 0,
"document": asdict(草稿),
},
ensure_ascii=False,
)
)
base = [sys.executable, "-m", "muse", "作品", str(配置文件)]
def 执行(*args):
p = subprocess.run([*base, *args], cwd="/", capture_output=True, text=True, timeout=15)
assert p.returncode == 0, p.stderr
return json.loads(p.stdout)
回执 = 执行("保存正文", str(请求文件))
assert 执行("保存正文", str(请求文件)) == 回执
文稿 = 执行("正文", "chapter-a")
assert 文稿["revision"] == 1
return 文稿
@pytest.mark.case_id(
"NC-work-current-binding",
environment="隔离 PostgreSQL;结构升级为明确的已完成状态夹具,不代替作者升级旅程",
given="作品值按结构1保存,兼容升级已将当前绑定切到结构2",
when="重新打开档案并填写新增字段",
then=["表单使用当前结构2和投影,原值版本仍1;新增字段通过S01保存为新数据版本"],
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
)
def test_档案读取当前绑定而不沿用上次写值结构__aa1105(作品环境):
from dataclasses import replace
from muse.作品规划.接口 import 档案保存, 档案结构
from muse.元数据.接口 import 值规则, 启用命令, 字段定义
作品, 作者, 输入, 库 = 作品环境
作品.保存档案(作者, "create-work", 输入)
# 这里只设置已完成的兼容升级;作者升级确认旅程由W11另验。
with 库[用途.维护].连接() as 连, 连.transaction():
元数据 = 元数据服务(连)
旧 = 元数据.读取结构("work_core", 1)
新 = replace(
旧,
schema_version=2,
父版本=1,
字段=(*旧.字段, 字段定义("work_core.读者约定", "读者约定", "读者约定", 值规则("text"))),
)
元数据.登记结构候选(新)
元数据.发布结构("work_core", 2, 新.内容哈希)
有效 = 元数据.读取有效结构("work_core", 2)
元数据.启用绑定(
"work:work-new",
"work-new",
有效,
启用命令(
"synthetic-upgrade",
有效.effective_schema_hash,
1,
"synthetic-reviewed-upgrade",
"work:work-new",
),
)
当前 = 作品.读取作品(作者, "work-new")
assert 当前["revision"] == 1 and 当前["schema_binding"]["base_version"] == 2
frame = 当前["schema_binding"]
结构 = 档案结构(
frame["schema_id"],
frame["base_version"],
frame["effective_schema_hash"],
当前["projection_version"],
frame["extension_version"],
)
作品.保存档案(
作者, "save-new-field", 档案保存("work-new", 1, {"读者约定": "先抛问题,再给答案"}, 结构)
)
assert 作品.读取作品(作者, "work-new")["content"]["读者约定"] == "先抛问题,再给答案"
@pytest.mark.case_id("NC-O09-PG-PROFILE")
def test_无变化保存保留回执不消耗版本且旧标签仍可真修改(作品环境):
from dataclasses import replace
from muse.正式变更.接口 import 变更错误
服务, 作者, 输入, 应用测试库 = 作品环境
服务.保存档案(作者, "create-no-change", 输入)
同值 = replace(输入, expected_revision=1)
回执 = 服务.保存档案(作者, "same-new-command", 同值)
assert 回执["no_change"] and not 回执["did_change"]
assert 回执["results"][0]["revision"] == 1 and 回执["impact_ids"] == []
assert 服务.正式.读取回执(作者, "same-new-command") == 回执
assert 服务.保存档案(作者, "same-new-command", 同值) == 回执
with pytest.raises(变更错误):
服务.保存档案(作者, "same-new-command", replace(同值, content={"名称": "B"}))
with pytest.raises(变更错误):
服务.保存档案(作者, "old-cas", 输入)
服务.保存档案(作者, "tab-B", replace(同值, content={"名称": "B"}))
服务.保存档案(作者, "return-A", replace(同值, expected_revision=2))
with 应用测试库[用途.生产].连接() as 连:
assert 连.execute("SELECT count(*) FROM muse_work_version").fetchone()[0] == 3
assert (
连.execute(
"SELECT count(*) FROM muse_change_impact WHERE reason='profile_changed'"
).fetchone()[0]
== 3
)
@pytest.mark.case_id("NC-O06-PROGRESS-PG-SEMANTICS")
def test_进度批量摘要与正文候选实际语义一致(作品环境):
from muse.任务运行.接口 import 任务服务
from muse.作品规划.接口 import 创作进度服务
from muse.正文写作.接口 import 文本节点, 正文服务, 正文草稿, 段落, 登记正文参与者
from muse.编排.接口 import 流程登记
作品, 作者, 输入, 库 = 作品环境
登记正文参与者(作品.正式.目录)
作品.保存档案(作者, "o06-create", 输入)
作品.添加章节(作者, "o06-chapter", "work-new", "chapter-a", "章", 预期目录版本=0)
正文 = 正文服务(库[用途.生产], 作品.正式)
草稿 = 正文草稿((段落("p1", (文本节点("中文 e\u0301 🧑‍🏽"),)), 段落("p2", ())))
正文.保存人工(作者, "o06-save", "chapter-a", 0, 草稿)
正文.创建人工候选(作者, "o06-candidate", "chapter-a", 1, 草稿)
进度 = 创作进度服务(作品, 正文, 任务服务(库[用途.生产], 流程登记()))
expected = 正文.读取正文(作者, "chapter-a")
candidates = 正文.列出候选(作者, "chapter-a")
result = 进度.查询(作者, "work-new")["chapters"][0]
assert result["document_hash"] == expected["document_hash"]
assert result["visible_characters"] == len(expected["visible_text"])
assert result["candidates"] == [
{key: candidate[key] for key in ("candidate_id", "revision", "decision", "stale")}
for candidate in candidates
]
第二稿 = 正文草稿((段落("p1", (文本节点("正文版本已改变。"),)),))
正文.保存人工(作者, "o06-save2", "chapter-a", 1, 第二稿)
assert 进度.查询(作者, "work-new")["chapters"][0]["candidates"][0]["stale"]