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

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

445 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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.

"""合成文件的边界、字节保全和无损正文转换;不访问真实旧文件或数据库。"""
import hashlib
import json
import os
from dataclasses import asdict
from pathlib import Path
import pytest
from pydantic import TypeAdapter
from muse.正文写作.接口 import 可见文本, 正文草稿
from 建立映射 import 迁移错误
from 读取旧文件 import 核对文件保全, 解码文件, 读取文件集
from 转换正文 import 转换文件正文
def _文件(tmp_path: Path, 内容: bytes, *, 修订="1", 输出="保全"):
源根 = tmp_path / "源"
源根.mkdir(exist_ok=True)
(源根 / "章.txt").write_bytes(内容)
清单 = [{"relpath": "章.txt", "revision": 修订, "sha256": hashlib.sha256(内容).hexdigest()}]
目标 = tmp_path / 输出
快照 = 读取文件集(源根, 清单, 数据集="synthetic-files", 输出目录=目标)
return 快照[0], 目标, 清单
@pytest.mark.case_id(
"NC-w21-21e001",
environment="合成文件与明确私有目录;默认离线",
given="含BOM/混合换行/表情/组合字符/尾空白的明确文件清单与新私有根",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["源字节、命名空间、0700/0600权限、完整回执与同内容只读重放"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_文件字节私有保全与只读重放__21e001(tmp_path):
原文 = "\ufeff甲\r\n\r乙\n\n😀e\u0301\t末尾 \n".encode()
源, 目标, 清单 = _文件(tmp_path, 原文)
assert 解码文件(源) == 原文
assert (目标 / "原始文件/章.txt").read_bytes() == 原文
assert set(源.原行) == {"id", "revision", "relpath", "size", "sha256", "content_base64"}
assert "source_hash" in 源.导出() and 源.source.id == 源.原行["relpath"]
assert str(tmp_path) not in 源.source.记录键
for p in (目标, 目标 / "原始文件"):
assert p.stat().st_mode & 0o777 == 0o700
for p in (目标 / "快照.json", 目标 / "文件回执.json", 目标 / "原始文件/章.txt"):
assert p.stat().st_mode & 0o777 == 0o600
assert 读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=目标) == [源]
assert 核对文件保全(目标 / "文件回执.json", [源])["passed"] is True
assert (tmp_path / "源/章.txt").read_bytes() == 原文
@pytest.mark.case_id(
"NC-w21-21e002",
environment="合成文件与明确私有目录;默认离线",
given="含绝对地址、点段、空路径段或反斜线的清单项",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["绝对、越界、空段与反斜线路径在读取前拒绝"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize("相对", ["../越界", "/绝对", "a/../章.txt", "a//b", "a/ /b", "a\\b"])
def test_清单越界路径在读取前拒绝__21e002(tmp_path, 相对):
源根 = tmp_path / "源"
源根.mkdir()
with pytest.raises(迁移错误, match="file_path_denied"):
读取文件集(
源根,
[{"relpath": 相对, "revision": "1", "sha256": "0" * 64}],
数据集="synthetic",
输出目录=tmp_path / "保全",
)
assert not (tmp_path / "保全").exists()
@pytest.mark.case_id(
"NC-w21-21e003",
environment="合成文件与明确私有目录;默认离线",
given="源叶文件或父目录为符号链接,或源为FIFO",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["文件链接、父目录链接和FIFO均不读取,外部文件不变"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize("方式", ["文件链接", "父目录链接", "管道"])
def test_链接与非普通文件不越过目录描述符__21e003(tmp_path, 方式):
源根 = tmp_path / "源"
源根.mkdir()
外部 = tmp_path / "外部"
外部.mkdir()
(外部 / "输入").write_bytes(b"not-authorized")
相对 = "输入"
if 方式 == "文件链接":
(源根 / "输入").symlink_to(外部 / "输入")
elif 方式 == "父目录链接":
(源根 / "子").symlink_to(外部, target_is_directory=True)
相对 = "子/输入"
else:
os.mkfifo(源根 / "输入")
with pytest.raises(迁移错误):
读取文件集(
源根,
[
{
"relpath": 相对,
"revision": "1",
"sha256": hashlib.sha256(b"not-authorized").hexdigest(),
}
],
数据集="synthetic",
输出目录=tmp_path / "保全",
)
assert not (tmp_path / "保全").exists() and (外部 / "输入").read_bytes() == b"not-authorized"
@pytest.mark.case_id(
"NC-w21-21e004",
environment="合成文件与明确私有目录;默认离线",
given="输出根等于源根、在源根内或包含源根",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["源根和保全根双向互含均拒绝,原文件不变"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize("目标", ["源", "源/保全", "."])
def test_源和保全根双向互含均拒绝__21e004(tmp_path, 目标):
源, _, 清单 = _文件(tmp_path, b"original")
with pytest.raises(迁移错误, match="output_is_input"):
读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=tmp_path / 目标)
assert (tmp_path / "源/章.txt").read_bytes() == 解码文件(源)
@pytest.mark.case_id(
"NC-w21-21e005",
environment="合成文件与明确私有目录;默认离线",
given="既有保全根缺最终回执、副本被改或对应不同源内容",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["缺最终回执、篡改副本、不同输入均失败且不覆盖"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_缺最终回执篡改副本和不同输入均不能覆盖__21e005(tmp_path):
源, 目标, 清单 = _文件(tmp_path, b"original")
(目标 / "文件回执.json").unlink()
assert 核对文件保全(目标 / "文件回执.json", [源])["passed"] is False
with pytest.raises(迁移错误):
读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=目标)
源, 目标, 清单 = _文件(tmp_path, b"original", 输出="完整保全")
(目标 / "原始文件/章.txt").write_bytes(b"tampered")
assert 核对文件保全(目标 / "文件回执.json", [源])["issues"] == [
{"reason_code": "file_copy_mismatch"}
]
with pytest.raises(迁移错误, match="file_copy_mismatch"):
读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=目标)
_, 目标, _ = _文件(tmp_path, b"original", 输出="不覆盖")
(tmp_path / "源/章.txt").write_bytes(b"changed")
清单[0]["sha256"] = hashlib.sha256(b"changed").hexdigest()
with pytest.raises(迁移错误, match="output_conflict"):
读取文件集(tmp_path / "源", 清单, 数据集="synthetic-files", 输出目录=目标)
assert (目标 / "原始文件/章.txt").read_bytes() == b"original"
@pytest.mark.case_id(
"NC-w21-21e006",
environment="合成文件与明确私有目录;默认离线",
given="源文件超过8MiB、清单超过500项或base64快照超过16MiB",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["8MiB单文件、500项及16MiB总快照界限真实触发,不截断"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize("方式", ["单文件", "数量", "总字节"])
def test_文件界限真实触发不截断__21e006(tmp_path, 方式):
源根 = tmp_path / "源"
源根.mkdir()
清单 = [{"relpath": "a", "revision": "1", "sha256": "0" * 64}]
if 方式 == "单文件":
with (源根 / "a").open("wb") as f:
f.truncate(8 * 1024 * 1024 + 1)
elif 方式 == "数量":
清单 *= 501
else:
内容 = b"a" * (6 * 1024 * 1024)
清单 = []
for 名 in ("a", "b"):
(源根 / 名).write_bytes(内容)
清单.append(
{"relpath": 名, "revision": "1", "sha256": hashlib.sha256(内容).hexdigest()}
)
with pytest.raises(迁移错误):
读取文件集(源根, 清单, 数据集="synthetic", 输出目录=tmp_path / "保全")
assert not (tmp_path / "保全").exists()
@pytest.mark.case_id(
"NC-w21-21e007",
environment="合成文件与明确私有目录;默认离线",
given="空稿及各种换行/Unicode/空白文本,并含同对象另一个源版本",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["纯文本含空稿、CR/LF/CRLF、BOM、表情、组合字符无损,跨版段ID稳定"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize("文本", ["", "\n", "\r\n", "\ufeff甲\r\n乙\r丙\n", "😀e\u0301\t空白 \n\n"])
def test_纯文本逐码点保留及跨版段ID稳定__21e007(tmp_path, 文本):
源, _, _ = _文件(tmp_path, 文本.encode())
结果 = 转换文件正文(源, {"format": "text_utf8"})
草稿 = TypeAdapter(正文草稿).validate_python(结果["document"])
assert 可见文本(草稿) == 文本 and len(草稿.paragraphs) == 1
assert 结果["visible_text_hash"] == hashlib.sha256(文本.encode()).hexdigest()
新源, _, _ = _文件(tmp_path, (文本 + "新").encode(), 修订="2", 输出="新版本")
新 = 转换文件正文(新源, {"format": "text_utf8"})
assert 新["paragraph_map"][0]["paragraph_id"] == 结果["paragraph_map"][0]["paragraph_id"]
assert 解码文件(源) == 文本.encode()
@pytest.mark.case_id(
"NC-w21-21e008",
environment="合成文件与明确私有目录;默认离线",
given="明确的旧场景/段ID和恰有单LF间隔的码点范围",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["明确场景/段落来源对照,无损且不造正文额外场景字段"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_显式场景段落只保来源对照且无损__21e008(tmp_path):
源, _, _ = _文件(tmp_path, "甲😀\n乙\n".encode())
结果 = 转换文件正文(
源,
{
"format": "text_utf8",
"paragraphs": [
{"id": "p1", "scene_id": "旧场景", "start": 0, "end": 2},
{"id": "p2", "scene_id": "旧场景", "start": 3, "end": 5},
],
},
)
assert 可见文本(TypeAdapter(正文草稿).validate_python(结果["document"])) == "甲😀\n乙\n"
assert {m["source_scene_id"] for m in 结果["paragraph_map"]} == {"旧场景"}
assert set(结果["document"]) == {"paragraphs", "format_version"}
@pytest.mark.case_id(
"NC-w21-21e009",
environment="合成文件与明确私有目录;默认离线",
given="漏字、跨CRLF原子边界或含未知字段的分段表",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["范围遗漏、切开CRLF或未知分段字段拒绝,原字节保留"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"段表",
[
[{"id": "p", "start": 1, "end": 4}],
[{"id": "p1", "start": 0, "end": 2}, {"id": "p2", "start": 3, "end": 4}],
[{"id": "p1", "start": 0, "end": 1}, {"id": "p2", "start": 3, "end": 4}],
[{"id": "p", "start": 0, "end": 4, "unknown": 1}],
],
)
def test_显式分段不切CRLF或漏字__21e009(tmp_path, 段表):
源, _, _ = _文件(tmp_path, "甲\r\n乙".encode())
with pytest.raises(迁移错误, match="file_paragraph_invalid"):
转换文件正文(源, {"format": "text_utf8", "paragraphs": 段表})
assert 解码文件(源) == "甲\r\n乙".encode()
@pytest.mark.case_id(
"NC-w21-21e00a",
environment="合成文件与明确私有目录;默认离线",
given="正文格式1 JSON含原段ID与可合并的相邻文字节点",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["document_v1保原JSON字节和原段ID,结构节点规范化不冒充原文"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_正文JSON保原字节与段ID且不把语法当正文__21e00a(tmp_path):
数据 = {
"format_version": 1,
"paragraphs": [
{
"paragraph_id": "原段",
"content": [{"type": "text", "text": "甲"}, {"type": "text", "text": "乙"}],
}
],
}
原 = json.dumps(数据, ensure_ascii=False, indent=2).encode()
源, _, _ = _文件(tmp_path, 原)
结果 = 转换文件正文(源, {"format": "document_v1"})
草稿 = TypeAdapter(正文草稿).validate_python(结果["document"])
assert 可见文本(草稿) == "甲乙" and 草稿.paragraphs[0].paragraph_id == "原段"
assert len(草稿.paragraphs[0].content) == 1 and 解码文件(源) == 原
assert 结果["paragraph_map"][0]["coordinate_space"] == "document_visible_text"
assert asdict(草稿) == 结果["document"]
@pytest.mark.case_id(
"NC-w21-21e00b",
environment="合成文件与明确私有目录;默认离线",
given="坏UTF8、重复JSON键、非法版本、未知字段或重复/空段ID",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["坏编码/字段/版本/重复段ID/空段ID拒绝且不回显输入"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"输入,格式",
[
(b"\xff", "text_utf8"),
(b'{"format_version":1,"private":"secret-body"}', "document_v1"),
(b'{"a":1,"a":2}', "document_v1"),
(b'{"format_version":2,"paragraphs":[]}', "document_v1"),
(
b'{"paragraphs":[{"paragraph_id":"p","content":[]},{"paragraph_id":"p","content":[]}]}',
"document_v1",
),
(b'{"paragraphs":[{"paragraph_id":" ","content":[]}]}', "document_v1"),
],
)
def test_坏正文保持字节且异常不回显输入__21e00b(tmp_path, 输入, 格式):
源, _, _ = _文件(tmp_path, 输入)
with pytest.raises(迁移错误) as 拒绝:
转换文件正文(源, {"format": 格式})
assert "secret-body" not in str(拒绝.value) and 解码文件(源) == 输入
@pytest.mark.case_id(
"NC-w21-21e00c",
environment="合成文件与明确私有目录;默认离线",
given="完整文件清单/保全根,通过真实CLI读取和核对",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["读取文件和核对文件CLI真实往返,正文不进入标准输出"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_文件命令真实往返且不回显正文__21e00c(tmp_path):
import subprocess
import sys
源, 保全, 清单 = _文件(tmp_path, b"private-synthetic-body")
清单文件 = tmp_path / "清单.json"
清单文件.write_text(json.dumps(清单), encoding="utf-8")
入口 = Path(__file__).resolve().parents[2] / "数据库/旧库迁移/入口.py"
for 命令 in [
[
"读取文件",
"--源根",
str(tmp_path / "源"),
"--清单",
str(清单文件),
"--数据集",
"synthetic-files",
"--输出目录",
str(保全),
],
[
"核对文件",
"--文件回执",
str(保全 / "文件回执.json"),
"--输出",
str(tmp_path / "文件报告.json"),
],
]:
进程 = subprocess.run(
[sys.executable, str(入口), *命令], text=True, capture_output=True, timeout=20
)
assert 进程.returncode == 0, 进程.stderr
assert "private-synthetic-body" not in 进程.stdout + 进程.stderr
assert json.loads((tmp_path / "文件报告.json").read_text())["passed"] is True
assert 解码文件(源) == b"private-synthetic-body"
@pytest.mark.case_id(
"NC-w21-21e00d",
environment="合成文件与明确私有目录;默认离线",
given="文件路由缺失、重复、源哈希错误或含非法用途/字段",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["文件路由缺失、多义、漂移及未知用途/字段不猜补,原记录保留"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"方式,原因",
[
("缺路由", "file_route_missing"),
("重复", "file_route_ambiguous"),
("漂移", "file_route_drift"),
("多余字段", "file_route_invalid"),
("未知用途", "file_route_invalid"),
],
)
def test_文件路由不猜用途且保原记录__21e00d(tmp_path, 方式, 原因):
from 建立映射 import 映射配置
from 知识记录分流 import 分流批次
源, _, _ = _文件(tmp_path, b"raw bytes")
r = {"source_key": 源.source.记录键, "source_hash": 源.源哈希, "route": "archive"}
路由 = [r]
if 方式 == "缺路由":
路由 = []
elif 方式 == "重复":
路由 = [r, r]
elif 方式 == "漂移":
r["source_hash"] = "0" * 64
elif 方式 == "多余字段":
r["format"] = "text_utf8"
else:
r["route"] = "guess"
判定 = 分流批次([源], 映射配置({"works": [], "file_routes": 路由}))[0]
assert 判定.disposition == "隔离" and 判定.reason_code == 原因
assert 判定.original == 源.导出()
@pytest.mark.case_id(
"NC-w21-21e00e",
environment="合成文件与明确私有目录;默认离线",
given="子目录描述符刚打开后原目录路径被替换成外部链接",
when="按文件保全/正文合同执行真实受控入口并回查结果",
then=["源子目录被替换成外部链接后,已固定目录描述符仍只读原授权字节"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_源目录被换成链接后仍只读已授权目录描述符__21e00e(tmp_path, monkeypatch):
源根 = tmp_path / "源"
(源根 / "子").mkdir(parents=True)
(源根 / "子/输入").write_bytes(b"allowed")
外部 = tmp_path / "外部"
外部.mkdir()
(外部 / "输入").write_bytes(b"forbidden")
原打开 = os.open
已替换 = False
def 打开(路径, *args, **kwargs):
nonlocal 已替换
fd = 原打开(路径, *args, **kwargs)
if 路径 == "子" and not 已替换:
已替换 = True
(源根 / "子").rename(源根 / "旧目录")
(源根 / "子").symlink_to(外部, target_is_directory=True)
return fd
with monkeypatch.context() as m:
m.setattr(os, "open", 打开)
快照 = 读取文件集(
源根,
[
{
"relpath": "子/输入",
"revision": "1",
"sha256": hashlib.sha256(b"allowed").hexdigest(),
}
],
数据集="synthetic-race",
输出目录=tmp_path / "保全",
)
assert 已替换 and 解码文件(快照[0]) == b"allowed"
assert (外部 / "输入").read_bytes() == b"forbidden"