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

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

118 lines
5.6 KiB
Python

"""隔离模型任务在原库断开后,从完整备份续接已批准任务且不重发旧回合。"""
from dataclasses import replace
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from pathlib import Path
import psycopg
import pytest
import test_两阶段写手 as 写手测试
from psycopg import sql
from psycopg.conninfo import conninfo_to_dict, make_conninfo
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-29f005",
environment="隔离PG",
given="显式作者、隔离配置、合成数据与已知发布构建",
when="经真实维护、业务或浏览器入口执行并读取持久结果",
then=["断源恢复模型任务只补未执行步骤并保持原预算与候选"],
contract="docs/系统架构/新版设计/迁移与验收.md#备份与恢复入口",
)
def test_断源恢复模型任务只补未执行步骤并保持原预算与候选__29f005(
生成环境, 新空目标库, 隔离数据库URL, tmp_path
):
env = 生成环境
config = env["装配"].配置
app = 构建默认运行(config, 计价=写手测试.合成计价())
author = env["作者"]
# 第二个装配另有常驻池,必须自己进入生命周期。
with app.生命周期():
original = app.要求正文().读取正文(author, "ch-3")
task_id = 发起生成正文(
app,
author,
"model-backup",
"new_chapter",
work_id="gen-work",
chapter_id="ch-3",
配置ID="gen-config",
)["task_id"]
预算管理(env["库"], "synthetic").登记策略(额度策略("synthetic", "1", Decimal("20"), 40))
app.任务运行.批准作者预算(
task_id,
author.作者,
任务预算计划(
Decimal("10"),
(角色预算("writer", 8, 8, Decimal("1")),),
"synthetic-backup-budget",
datetime.now(UTC) + timedelta(minutes=10),
),
)
budget = app.任务运行.读取作者预算(task_id, author.作者)
env["剧本"].extend(
写手测试.正常剧本(
[
"雨点敲着铁皮屋顶,林深把退回的信压在掌心。",
"他沿跳板走向仓库,门后传来一声应答。",
]
)
)
assert 推进作者任务(app, author.作者, task_id)["executed_step"]["step_id"] == "预组装"
assert 推进作者任务(app, author.作者, task_id)["executed_step"]["step_id"] == "受限探索"
assert len(env["收到"]) == 4
source_steps = app.任务运行.读取任务(task_id).步骤
backup_config = replace(config, 数据库=env["库组"][用途.维护].引用, 运行用途=用途.维护)
archive = 创建备份(backup_config, tmp_path / "backup")
source_name = conninfo_to_dict(Path(config.数据库.位置).read_text())["dbname"]
with psycopg.connect(隔离数据库URL, autocommit=True) as admin:
admin.execute(
sql.SQL("ALTER DATABASE {} ALLOW_CONNECTIONS false").format(
sql.Identifier(source_name)
)
)
# 停用本例生成的旧暂存目录,恢复必须使用新目录;供应方凭据仍在受控存储。
raw = Path(config.原文暂存)
raw.rename(tmp_path / "offline-original-raw")
restored_raw = tmp_path / "restored-raw"
restore_config = replace(
backup_config,
数据库=新空目标库.工厂.引用,
原文暂存=str(restored_raw),
)
restored = 恢复备份(tmp_path / "backup", restore_config, archive["backup_id"])
assert restored["status"] == "verified" and not restored["services_started"]
params = conninfo_to_dict(Path(新空目标库.工厂.引用.位置).read_text())
params["user"] = "muse_app"
secret = tmp_path / "restored-app.txt"
secret.write_text(make_conninfo(**params))
secret.chmod(0o600)
fresh = 构建默认运行(
replace(config, 数据库=数据库引用("受控存储", str(secret)), 原文暂存=str(restored_raw)),
计价=写手测试.合成计价(),
)
with fresh.生命周期():
assert fresh.任务运行.读取作者预算(task_id, author.作者) == budget
assert fresh.任务运行.读取任务(task_id).步骤 == source_steps
assert len(env["收到"]) == 4
for expected in ("冻结回放", "无工具写作", "检查与候选"):
result = 推进作者任务(fresh, author.作者, task_id)
assert result["executed_step"]["step_id"] == expected
assert result["state"] == "completed" and len(env["收到"]) == 5
final = next(s["result"] for s in result["steps"] if s["step_id"] == "检查与候选")
candidate = fresh.要求正文().读取候选(author, final["candidate_id"])
assert candidate["origin"] == "model" and candidate["decision"] == "undecided"
assert fresh.要求正文().读取正文(author, "ch-3") == original