实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
169 lines
7.7 KiB
Python
169 lines
7.7 KiB
Python
"""维护流程使用真实S02状态与owner回执,关闭会话不会取消已登记工作。"""
|
||
|
||
from dataclasses import replace
|
||
from datetime import timedelta
|
||
from uuid import uuid4
|
||
|
||
import pytest
|
||
import test_原文归档与恢复 as 原文测试
|
||
|
||
from muse.任务运行.接口 import 任务状态
|
||
from muse.共享.调用身份 import 用途
|
||
from muse.共享.错误 import 配置错误
|
||
from muse.启动 import 构建
|
||
from muse.基础设施.备份恢复 import 列出备份, 创建备份, 备份恢复错误
|
||
from muse.编排.数据维护 import 发起维护任务, 推进维护任务
|
||
from muse.资源加载 import 加载清单
|
||
from muse.配置 import 应用配置
|
||
|
||
pytestmark = pytest.mark.数据库
|
||
原文环境 = 原文测试.原文环境
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w29-29c001",
|
||
environment="隔离PG",
|
||
given="显式隔离配置、合成数据与已知发布构建",
|
||
when="经真实维护、业务或浏览器入口执行并读取持久结果",
|
||
then=["备份核对计划跨装配继续且冻结目录与命令范围"],
|
||
contract="docs/系统架构/新版设计/迁移与验收.md#备份与恢复入口",
|
||
)
|
||
def test_备份核对计划跨装配继续且冻结目录与命令范围__29c001(应用测试库, tmp_path):
|
||
root = tmp_path / "backups"
|
||
root.mkdir(mode=0o700)
|
||
manifest = 创建备份(
|
||
应用配置(应用测试库[用途.维护].引用, 加载清单()["构建身份"], 运行用途=用途.维护),
|
||
root / "one",
|
||
)
|
||
row = 列出备份(str(root))["items"][0]
|
||
config = 应用配置(应用测试库[用途.生产].引用, 加载清单()["构建身份"], 备份目录=str(root))
|
||
app = 构建(config)
|
||
with app.生命周期():
|
||
request = {
|
||
"action": "verify_backup",
|
||
"entry_id": row["entry_id"],
|
||
"backup_id": manifest["backup_id"],
|
||
}
|
||
created = 发起维护任务(app, "author", "check-one", request)
|
||
task_id = created["task_id"]
|
||
assert 发起维护任务(app, "author", "check-one", request) == created
|
||
with pytest.raises(配置错误, match="重放"):
|
||
发起维护任务(app, "author", "check-one", {**request, "backup_id": "another"})
|
||
claim = app.任务运行.领取步骤(
|
||
"before-restart", ["maintenance.verify"], 任务ID=task_id, 作者="author"
|
||
)
|
||
assert claim is not None
|
||
app.任务运行.执行一步(claim)
|
||
checkpoint = app.任务运行.读取任务(task_id).步骤[0]["checkpoint"]
|
||
assert checkpoint["scope_hash"]
|
||
# 每个装配各有一条常驻连接池,跨装配继续必须各自进入生命周期。
|
||
wrong = 构建(replace(config, 备份目录=str(tmp_path / "other")))
|
||
with wrong.生命周期(), pytest.raises(配置错误, match="范围已变化"):
|
||
推进维护任务(wrong, "author", task_id)
|
||
assert app.任务运行.读取任务(task_id).状态 == 任务状态.已失败
|
||
fresh = 构建(config)
|
||
with fresh.生命周期():
|
||
fresh.任务运行.控制任务(task_id, "author", 任务状态.已失败, "恢复", 命令ID="resume")
|
||
with pytest.raises(配置错误, match="当前作者"):
|
||
推进维护任务(fresh, "another", task_id)
|
||
finished = 推进维护任务(fresh, "author", task_id)
|
||
assert finished["state"] == "completed"
|
||
assert finished["steps"][0]["checkpoint"] == checkpoint
|
||
result = finished["steps"][-1]["result"]
|
||
assert result["backup_id"] == manifest["backup_id"]
|
||
assert result["archive_verified"] and not result["restore_verified"]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w29-29c002",
|
||
environment="隔离PG",
|
||
given="显式隔离配置、合成数据与已知发布构建",
|
||
when="经真实维护、业务或浏览器入口执行并读取持久结果",
|
||
then=["归档损坏不伪称通过且修复后原任务可恢复"],
|
||
contract="docs/系统架构/新版设计/迁移与验收.md#备份与恢复入口",
|
||
)
|
||
def test_归档损坏不伪称通过且修复后原任务可恢复__29c002(应用测试库, tmp_path):
|
||
root = tmp_path / "backups"
|
||
root.mkdir(mode=0o700)
|
||
manifest = 创建备份(
|
||
应用配置(应用测试库[用途.维护].引用, 加载清单()["构建身份"], 运行用途=用途.维护),
|
||
root / "one",
|
||
)
|
||
row = 列出备份(str(root))["items"][0]
|
||
app = 构建(应用配置(应用测试库[用途.生产].引用, 加载清单()["构建身份"], 备份目录=str(root)))
|
||
with app.生命周期():
|
||
task_id = 发起维护任务(
|
||
app,
|
||
"author",
|
||
"check-bad",
|
||
{
|
||
"action": "verify_backup",
|
||
"entry_id": row["entry_id"],
|
||
"backup_id": manifest["backup_id"],
|
||
},
|
||
)["task_id"]
|
||
dump = root / "one/database.dump"
|
||
original = dump.read_bytes()
|
||
dump.write_bytes(original + b"bad")
|
||
with pytest.raises(备份恢复错误):
|
||
推进维护任务(app, "author", task_id)
|
||
failed = app.任务运行.读取任务(task_id)
|
||
assert failed.状态 == 任务状态.已失败
|
||
assert not failed.步骤[-1]["result"]
|
||
dump.write_bytes(original)
|
||
app.任务运行.控制任务(task_id, "author", 任务状态.已失败, "恢复", 命令ID="repaired")
|
||
assert 推进维护任务(app, "author", task_id)["state"] == "completed"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w29-29c003",
|
||
environment="隔离PG",
|
||
given="显式隔离配置、合成数据与已知发布构建",
|
||
when="经真实维护、业务或浏览器入口执行并读取持久结果",
|
||
then=["原文维护保留有效租约并保留孤儿清理真实回执"],
|
||
contract="docs/系统架构/新版设计/迁移与验收.md#备份与恢复入口",
|
||
)
|
||
def test_原文维护保留有效租约并保留孤儿清理真实回执__29c003(原文环境):
|
||
raw, original_task, authorization, data, digest, clock, pools = 原文环境
|
||
lease = raw.创建租约(
|
||
original_task, authorization, "keep", clock.现在() + timedelta(hours=1), 最少剩余秒=0
|
||
)
|
||
raw.写入(original_task, lease, digest, data)
|
||
orphan = str(uuid4())
|
||
raw.文件.写入(orphan, digest, data)
|
||
app = 构建(
|
||
应用配置(pools[用途.生产].引用, 加载清单()["构建身份"], 原文暂存=str(raw.文件.根)),
|
||
时钟=clock,
|
||
)
|
||
with app.生命周期():
|
||
recover = 发起维护任务(
|
||
app,
|
||
"author",
|
||
"raw-recover",
|
||
{
|
||
"action": "recover_raw",
|
||
"target_task_id": original_task,
|
||
},
|
||
)["task_id"]
|
||
done = 推进维护任务(app, "author", recover)
|
||
assert done["steps"][-1]["result"]["items"] == [{"lease_id": lease, "action": "retained"}]
|
||
with pytest.raises(配置错误, match="不属于"):
|
||
发起维护任务(
|
||
app,
|
||
"other",
|
||
"wrong-owner",
|
||
{
|
||
"action": "recover_raw",
|
||
"target_task_id": original_task,
|
||
},
|
||
)
|
||
clean = 发起维护任务(app, "author", "orphan-clean", {"action": "clean_orphans"})["task_id"]
|
||
# 无租约只证明缺登记,不证明可删:维护入口盘点并列出待人工核对对象。
|
||
with pytest.raises(配置错误, match="部分原文尚待恢复"):
|
||
推进维护任务(app, "author", clean)
|
||
checkpoint = app.任务运行.读取任务(clean).步骤[-1]["checkpoint"]
|
||
assert checkpoint["preview_only"] is True and checkpoint["needs_recovery"] == [orphan]
|
||
assert checkpoint["cleaned_orphan_ids"] == []
|
||
assert (raw.文件.根 / orphan).exists()
|
||
assert raw.读取(original_task, lease, digest) == data
|