实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
243 lines
11 KiB
Python
243 lines
11 KiB
Python
"""资料导入与原文版本:幂等去重、版本链、证据校验、用途授权与清理对照。"""
|
|
|
|
import pytest
|
|
|
|
from muse.共享.调用身份 import 用途
|
|
from muse.资料研究.接口 import 资料服务
|
|
from muse.资料研究.模型 import 导入请求, 资料错误
|
|
|
|
pytestmark = pytest.mark.数据库
|
|
|
|
|
|
@pytest.fixture
|
|
def 服务(应用测试库):
|
|
return 资料服务()
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w17-w17a01",
|
|
environment="真实隔离 PG",
|
|
given="隔离数据库",
|
|
when="同源同内容幂等返回既有版本;同源异内容追加版本且授权只增不减",
|
|
then=["重复可辨认", "版本链完整", "授权持久"],
|
|
contract="docs/系统架构/新版设计/模块设计/B03-资料研究.md",
|
|
)
|
|
def test_导入去重与版本链__17a01a(服务, 应用测试库):
|
|
库 = 应用测试库[用途.生产]
|
|
请求 = 导入请求(
|
|
kind="reference",
|
|
title="封神演义",
|
|
origin="file:///资料/封神演义.txt",
|
|
content="第一回 纣王女娲宫进香。\n\n最新章节请访问 example.com\n",
|
|
authorized_uses=("analysis",),
|
|
)
|
|
with 库.连接() as 连, 连.transaction():
|
|
首次 = 服务.导入(连, "importer", 请求)
|
|
assert 首次.重复 is False and 首次.revision == 1
|
|
# 同源同内容:幂等返回既有版本,重复可辨认。
|
|
再次 = 服务.导入(连, "importer", 请求)
|
|
assert 再次.重复 is True and 再次.revision == 1 and 再次.source_id == 首次.source_id
|
|
# 同源异内容:追加新版本,旧版本仍可回读。
|
|
新版 = 服务.导入(
|
|
连,
|
|
"importer",
|
|
导入请求(
|
|
kind="reference",
|
|
title="封神演义(校对版)",
|
|
origin=请求.origin,
|
|
content="第一回 纣王女娲宫进香。(校对)\n",
|
|
authorized_uses=("analysis",),
|
|
),
|
|
)
|
|
assert 新版.重复 is False and 新版.revision == 2 and 新版.source_id == 首次.source_id
|
|
版本 = 服务.读取版本(连, 首次.source_id, 1)
|
|
assert "最新章节" in 版本["content"]
|
|
# 内容重导入不改变授权版本和用途。
|
|
源 = 服务.核对来源授权(连, 首次.source_id, "analysis")
|
|
assert 源["authorized_uses"] == ["analysis"]
|
|
assert 源["authorization_revision"] == 1
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w17-w17a02",
|
|
environment="真实隔离 PG",
|
|
given="隔离数据库",
|
|
when="按码点区间取原文片段并校验内容哈希;越界拒绝",
|
|
then=["片段正确", "越界拒绝"],
|
|
contract="docs/系统架构/新版设计/模块设计/B03-资料研究.md",
|
|
)
|
|
def test_证据读取与哈希校验__17a02b(服务, 应用测试库):
|
|
库 = 应用测试库[用途.生产]
|
|
文本 = "林深把伞收在了门边。雨还没停。"
|
|
with 库.连接() as 连, 连.transaction():
|
|
结果 = 服务.导入(
|
|
连,
|
|
"importer",
|
|
导入请求("note", "码头笔记", "note://local/1", 文本, ("analysis",)),
|
|
)
|
|
证据 = 服务.读取证据(连, 结果.source_id, 结果.revision, 0, 10)
|
|
assert 证据["片段"] == "林深把伞收在了门边。"
|
|
with pytest.raises(资料错误, match="区间越界"):
|
|
服务.读取证据(连, 结果.source_id, 结果.revision, 0, 999)
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w17-w17a03",
|
|
environment="真实隔离 PG",
|
|
given="隔离数据库",
|
|
when="来源未授权的用途被拒绝",
|
|
then=["未授权拒绝"],
|
|
contract="docs/系统架构/新版设计/模块设计/B03-资料研究.md",
|
|
)
|
|
def test_用途未授权拒绝消费__17a03c(服务, 应用测试库):
|
|
库 = 应用测试库[用途.生产]
|
|
with 库.连接() as 连, 连.transaction():
|
|
结果 = 服务.导入(
|
|
连,
|
|
"importer",
|
|
导入请求("web", "某网页", "https://example.com/a", "网页内容", ("reference_reading",)),
|
|
)
|
|
with pytest.raises(资料错误, match="未授权用途"):
|
|
服务.核对来源授权(连, 结果.source_id, "analysis")
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w17-w17a04",
|
|
environment="真实隔离 PG",
|
|
given="隔离数据库",
|
|
when="清理生成对照而不改写原文;处置不可改写",
|
|
then=["原文不变", "对照生成", "处置终态"],
|
|
contract="docs/系统架构/新版设计/模块设计/B03-资料研究.md",
|
|
)
|
|
def test_清理候选对照且原文不变__17a04d(服务, 应用测试库):
|
|
库 = 应用测试库[用途.生产]
|
|
原文 = "第一回 纣王女娲宫进香。\n最新章节请访问 example.com\n本章完。"
|
|
with 库.连接() as 连, 连.transaction():
|
|
结果 = 服务.导入(
|
|
连,
|
|
"importer",
|
|
导入请求("reference", "样章", "file:///样章.txt", 原文, ("analysis",)),
|
|
)
|
|
候选 = 服务.生成清理候选(连, 结果.source_id, 结果.revision)
|
|
assert 候选.removals and all(项["理由"] for 项 in 候选.removals)
|
|
应用后 = 服务.应用清理候选(连, 候选.candidate_id)
|
|
assert "最新章节" not in 应用后.cleaned_text
|
|
assert "第一回" in 应用后.cleaned_text
|
|
# 原文版本不受清理影响。
|
|
版本 = 服务.读取版本(连, 结果.source_id, 结果.revision)
|
|
assert 版本["content"] == 原文
|
|
# 重复应用与处置不可改写。
|
|
with pytest.raises(资料错误, match="已处置"):
|
|
服务.应用清理候选(连, 候选.candidate_id)
|
|
新候选 = 服务.生成清理候选(连, 结果.source_id, 结果.revision)
|
|
服务.拒绝清理候选(连, 新候选.candidate_id)
|
|
with pytest.raises(资料错误, match="已处置"):
|
|
服务.应用清理候选(连, 新候选.candidate_id)
|
|
|
|
|
|
@pytest.mark.case_id("NC-O09-PG-REVOKE")
|
|
def test_撤权命令幂等与历史内容保留(应用测试库):
|
|
from dataclasses import replace
|
|
|
|
from muse.共享.标识 import 任务标识
|
|
from muse.共享.调用身份 import 内容用途, 调用身份
|
|
from muse.正式变更.接口 import 参与者目录, 变更错误, 正式变更服务
|
|
from muse.资料研究.接口 import 来源用途变更, 登记分析参与者
|
|
|
|
库 = 应用测试库[用途.生产]
|
|
目录 = 参与者目录()
|
|
登记分析参与者(目录)
|
|
服务 = 资料服务(正式变更服务(库, 目录))
|
|
身份 = 调用身份("author", 任务标识("revoke"), 用途.生产, 内容用途.规划)
|
|
导入 = 导入请求("reference", "合成样本", "fixture://revoke", "合成文字", ("analysis", "sample"))
|
|
with 库.连接() as 连, 连.transaction():
|
|
旧 = 服务.导入(连, "author", 导入)
|
|
请求 = 来源用途变更(旧.source_id, 1, ("analysis",), "撤回样本")
|
|
回执 = 服务.变更来源用途(身份, "revoke", 请求)
|
|
assert 服务.变更来源用途(身份, "revoke", 请求) == 回执
|
|
with pytest.raises(变更错误):
|
|
服务.变更来源用途(身份, "revoke", replace(请求, uses=()))
|
|
with pytest.raises(变更错误):
|
|
服务.变更来源用途(身份, "stale", 请求)
|
|
with 库.连接() as 连, 连.transaction():
|
|
assert 服务.读取版本(连, 旧.source_id, 1)["content"] == "合成文字"
|
|
assert 服务.核对来源授权(连, 旧.source_id, "analysis")["authorization_revision"] == 2
|
|
with pytest.raises(资料错误):
|
|
服务.核对来源授权(连, 旧.source_id, "sample")
|
|
with pytest.raises(资料错误, match="重导入不能修改"):
|
|
服务.导入(连, "author", 导入)
|
|
片段 = 服务.读取案例证据(连, "author", 旧.source_id, 1, 0, 2)
|
|
assert 片段["excerpt"] is None and not 片段["text_allowed"]
|
|
assert (
|
|
连.execute(
|
|
"SELECT count(*) FROM muse_source_version WHERE source_id=%s", (旧.source_id,)
|
|
).fetchone()[0]
|
|
== 1
|
|
)
|
|
全撤 = replace(请求, expected_authorization_revision=2, uses=(), reason="撤回全部")
|
|
服务.变更来源用途(身份, "revoke-all", 全撤)
|
|
with 库.连接() as 连:
|
|
with pytest.raises(资料错误):
|
|
服务.读取版本(连, 旧.source_id, 1)
|
|
assert (
|
|
连.execute(
|
|
"SELECT content FROM muse_source_version WHERE source_id=%s", (旧.source_id,)
|
|
).fetchone()[0]
|
|
== "合成文字"
|
|
)
|
|
他人 = replace(身份, 作者="other")
|
|
with pytest.raises(资料错误):
|
|
服务.变更来源用途(他人, "other-grant", replace(请求, expected_authorization_revision=3))
|
|
|
|
|
|
@pytest.mark.case_id("NC-O09-PG-AUTH-RACE")
|
|
def test_并发授权变更只有一个当前版本成功(应用测试库):
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from threading import Barrier
|
|
|
|
from muse.共享.标识 import 任务标识
|
|
from muse.共享.调用身份 import 内容用途, 调用身份
|
|
from muse.正式变更.接口 import 参与者目录, 变更错误, 正式变更服务
|
|
from muse.资料研究.接口 import 来源用途变更, 登记分析参与者
|
|
|
|
库 = 应用测试库[用途.生产]
|
|
目录 = 参与者目录()
|
|
登记分析参与者(目录)
|
|
服务 = 资料服务(正式变更服务(库, 目录))
|
|
身份 = 调用身份("author", 任务标识("revoke-race"), 用途.生产, 内容用途.规划)
|
|
with 库.连接() as 连, 连.transaction():
|
|
源 = 服务.导入(
|
|
连,
|
|
"author",
|
|
导入请求(
|
|
"note", "并发合成", "fixture://revoke-race", "合成文字", ("analysis", "sample")
|
|
),
|
|
)
|
|
屏障 = Barrier(2)
|
|
|
|
def 改用途(name, uses):
|
|
屏障.wait(timeout=5)
|
|
try:
|
|
return 服务.变更来源用途(身份, name, 来源用途变更(源.source_id, 1, uses, name))
|
|
except (变更错误, 资料错误):
|
|
return None
|
|
|
|
with ThreadPoolExecutor(max_workers=2) as pool:
|
|
第一 = pool.submit(改用途, "revoke-sample", ("analysis",))
|
|
第二 = pool.submit(改用途, "revoke-all", ())
|
|
结果 = [第一.result(timeout=10), 第二.result(timeout=10)]
|
|
成功 = [r for r in 结果 if r is not None]
|
|
assert len(成功) == 1
|
|
with 库.连接() as 连:
|
|
行 = 连.execute(
|
|
"SELECT authorized_uses,authorization_revision FROM muse_source WHERE source_id=%s",
|
|
(源.source_id,),
|
|
).fetchone()
|
|
assert 行 == (成功[0]["results"][0]["authorized_uses"], 2)
|
|
assert (
|
|
连.execute(
|
|
"SELECT count(*) FROM muse_source_version WHERE source_id=%s", (源.source_id,)
|
|
).fetchone()[0]
|
|
== 1
|
|
)
|