- src/muse 新版全模块(装配/共享/上下文/任务运行/作品规划/故事世界/正文写作/审校修订/知识方法/作者经验/效果评测/交付连载/资料研究/正式变更/元数据/接入/基础设施/编排)+ 测试树(单元/契约/集成/架构/迁移/端到端/夹具) - 129 项功能全部实现与自动验证(功能覆盖.json/矩阵),含 W31 补齐的规则与代价/节奏安排/伏笔与承诺 - 旧实现按处置清单退出(702 条中 324 删,保护合同与未迁移条目留存有据);web/app.py 旧工作台退役,新工作台为唯一写入口 - 数据库/旧库迁移:真实旧库内容批次迁移链(端点守卫/PG作品正文映射/质量资产缺省投影) - 运行手册 docs/运行手册.md;W30 本机服务阶段一已运行(infra PG 为正式内容权威) - R2 执行证据与私有运行材料在 .agents.local/改造/R2-20260909/(不入库)
138 lines
5.9 KiB
Python
138 lines
5.9 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.数据库
|
||
原文环境 = 原文测试.原文环境
|
||
|
||
|
||
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)
|
||
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 pytest.raises(配置错误, match="范围已变化"):
|
||
推进维护任务(wrong, "author", task_id)
|
||
assert app.任务运行.读取任务(task_id).状态 == 任务状态.已失败
|
||
fresh = 构建(config)
|
||
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"]
|
||
|
||
|
||
def test_归档损坏不伪称通过且修复后原任务可恢复__29c002(应用测试库, tmp_path):
|
||
root = tmp_path / "backups"
|
||
root.mkdir(mode=0o700)
|
||
manifest = 创建备份(
|
||
应用配置(应用测试库[用途.维护].引用, 加载清单()["构建身份"], 运行用途=用途.维护),
|
||
root / "one",
|
||
)
|
||
row = 列出备份(str(root))["items"][0]
|
||
app = 构建(应用配置(应用测试库[用途.生产].引用, 加载清单()["构建身份"], 备份目录=str(root)))
|
||
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"
|
||
|
||
|
||
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,
|
||
)
|
||
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"]
|
||
output = 推进维护任务(app, "author", clean)["steps"][-1]["result"]
|
||
assert output["cleaned_orphan_ids"] == [orphan]
|
||
receipt = raw.读取孤儿清理回执(orphan)
|
||
assert receipt["state"] == "completed" and receipt["receipt_id"] in output["receipts"]
|
||
assert raw.读取(original_task, lease, digest) == data
|
||
assert 推进维护任务(app, "author", clean)["steps"][-1]["result"] == output
|