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

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

206 lines
9.0 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.

"""探索方向的真实作者动作与PG回执;选择方向不确认正式规划。"""
import json
from dataclasses import replace
import pytest
from muse.作品规划.接口 import 作品错误, 探索选择, 档案保存
from muse.共享.调用身份 import 内容用途, 用途, 调用身份
from muse.启动 import 构建
from muse.资源加载 import 打开资源
from muse.配置 import 应用配置
pytestmark = pytest.mark.数据库
@pytest.fixture
def 交接环境(内置结构测试库, tmp_path):
应用测试库 = 内置结构测试库
app = 构建(
应用配置(应用测试库[用途.生产].引用, "synthetic", 探索草稿=str(tmp_path / "私人探索"))
)
with app.生命周期():
actor = 调用身份("author", None, 用途.生产, 内容用途.规划)
profile = app.要求作品().新档案结构(actor, "selection-work", "work_core", 1)
app.要求作品().保存档案(
actor, "create-work", 档案保存("selection-work", 0, {"名称": "合成作品"}, profile)
)
tree = json.loads(打开资源("规划/探索交付结构.json"))
draft = "# 浮岛|作品探索\n\n" + "\n\n".join(
"## " + h + "\n未探索:先保留空中城镇停电的画面。"
for h in ["根设定", *[c["heading"] for c in tree["chapters"]], "待决与取舍"]
)
saved = app.要求探索().保存(actor, "island", "浮岛", draft, None)
yield app, actor, saved, 应用测试库
@pytest.mark.case_id(
"NC-exploration-selection-snapshot",
environment="真实隔离PostgreSQL与临时私人目录",
given="有已保存的完整探索稿和本书身份",
when="明确选定后修改私人稿并重放原命令",
then=["所选原话成为不可变快照,重复命令回原回执,陈旧新请求拒绝"],
contract="docs/系统架构/新版设计/模块设计/B01-作品规划.md",
)
def test_选定稿冻结来源且重复请求回原回执__ad1201(交接环境):
app, actor, draft, _ = 交接环境
works = app.要求作品()
assert works.读取探索选定(actor, "selection-work")["revision"] == 0
req = 探索选择("selection-work", "island", draft["draft_hash"], 0)
receipt = works.选定探索稿(actor, "select-direction", req)
chosen = works.读取探索选定(actor, "selection-work")
assert chosen["revision"] == 1 and chosen["selection"]["snapshot"] == draft
assert chosen["selection"]["selected_by"] == "author"
app.要求探索().保存(
actor, "island", "浮岛", draft["content"] + "\n新的分歧,暂不选。", draft["draft_hash"]
)
assert works.读取探索选定(actor, "selection-work") == chosen
assert works.选定探索稿(actor, "select-direction", req) == receipt
with pytest.raises(作品错误) as 错:
works.选定探索稿(actor, "select-stale", replace(req, expected_revision=1))
assert 错.value.错误码 == "REVISION_CONFLICT"
@pytest.mark.case_id(
"NC-exploration-selection-rollback",
environment="真实隔离PostgreSQL与临时私人目录",
given="本书尚未选定方向",
when="实际PG触发器使S01回执保存失败,移除后重试",
then=["选择指针与快照完整回滚,私人稿保持,同命令可恢复"],
contract="docs/系统架构/新版设计/模块设计/B01-作品规划.md",
)
def test_选定失败完整回滚且私人稿保留__ad1202(交接环境):
import psycopg
app, actor, draft, db = 交接环境
with db[用途.维护].连接() as 连, 连.transaction():
连.execute(
"CREATE FUNCTION reject_selection_receipt() RETURNS trigger LANGUAGE plpgsql AS $$ "
"BEGIN RAISE EXCEPTION 'synthetic receipt failure'; END $$"
)
连.execute(
"CREATE TRIGGER reject_selection BEFORE UPDATE OF receipt ON muse_change_command "
"FOR EACH ROW EXECUTE FUNCTION reject_selection_receipt()"
)
request = 探索选择("selection-work", "island", draft["draft_hash"], 0)
with pytest.raises(psycopg.Error):
app.要求作品().选定探索稿(actor, "retry-selection", request)
assert app.要求作品().读取探索选定(actor, "selection-work")["revision"] == 0
assert app.要求探索().读取(actor, "island") == draft
with db[用途.维护].连接() as 连, 连.transaction():
连.execute("DROP TRIGGER reject_selection ON muse_change_command")
assert (
app.要求作品().选定探索稿(actor, "retry-selection", request)["results"][0]["revision"] == 1
)
@pytest.mark.case_id(
"TC-bfcbc2348505",
environment="真实隔离PostgreSQL、HTTP作者会话和CLI",
when="验证最终交接与规划字段。",
contract="docs/系统架构/新版设计/功能规格/P01-作品创建与连续写作.md",
)
def test_作者实际选定经HTTP与CLI回查来源__bfcbc2(交接环境, tmp_path):
import subprocess
import sys
from datetime import datetime
from fastapi.testclient import TestClient
from muse.接入.http.应用 import 创建应用
from muse.配置 import 读取配置
app, actor, draft, db = 交接环境
password = tmp_path / "口令.txt"
password.write_text("synthetic-selection-password")
password.chmod(0o600)
cfg = tmp_path / "选定.toml"
cfg.write_text(
'["数据库"]\n"取值方式"="受控存储"\n"位置"='
+ json.dumps(db[用途.生产].引用.位置)
+ '\n["资源"]\n"发布身份"="synthetic"\n[HTTP]\n"口令文件"="口令.txt"\n"作者ID"="author"\n"公开地址"="http://testserver"\n"允许来源"=["http://testserver"]\n["文件"]\n"探索草稿"='
+ json.dumps(app.配置.探索草稿)
+ "\n"
)
path = "/api/v1/works/selection-work/exploration-selection"
with TestClient(创建应用(读取配置(cfg)), headers={"origin": "http://testserver"}) as client:
assert client.get(path).status_code == 401
client.post("/api/v1/session", json={"password": "synthetic-selection-password"})
request = {
"command_id": "http-select",
"selection": {
"work_id": "selection-work",
"draft_id": "island",
"expected_hash": draft["draft_hash"],
"expected_revision": 0,
},
}
assert (
client.post(
path,
json={
**request,
"confirmedByUser": True,
"confirmedAt": "2026-08-20T12:00:00+08:00",
},
).status_code
== 422
)
result = client.post(path, json=request)
assert result.status_code == 200, result.text
assert client.post(path, json=request).json() == result.json()
selected = client.get(path).json()
assert selected["selection"]["snapshot"] == draft
assert selected["selection"]["selected_by"] == "author"
assert datetime.fromisoformat(selected["selection"]["selected_at"]).tzinfo is not None
probe = subprocess.run(
[sys.executable, "-m", "muse", "探索", str(cfg), "查看选定", "selection-work"],
text=True,
capture_output=True,
)
assert probe.returncode == 0, probe.stderr
assert json.loads(probe.stdout) == selected
with pytest.raises(作品错误) as 错:
app.要求作品().读取探索选定(replace(actor, 作者="another-author"), "selection-work")
assert 错.value.错误码 == "SCOPE_DENIED"
@pytest.mark.case_id(
"NC-exploration-selection-race",
environment="真实隔离PostgreSQL与临时私人目录",
given="同一作品有两个不同方向的最终稿",
when="两个PG连接以同一选定基线同时交接",
then=["仅一个当前版本推进,另一请求冲突,两个私人稿均保留"],
contract="docs/系统架构/新版设计/模块设计/B01-作品规划.md",
)
def test_不同方向同时交接只推进一个选定版本__ad1203(交接环境):
from concurrent.futures import ThreadPoolExecutor
from threading import Barrier
from muse.共享.错误 import Muse错误
app, actor, one, _ = 交接环境
two = app.要求探索().保存(
actor, "village", "山村", one["content"].replace("空中城镇停电", "山村渡口停航"), None
)
start = Barrier(2)
def choose(draft):
start.wait(3)
try:
return app.要求作品().选定探索稿(
actor,
"choose-" + draft["draft_id"],
探索选择("selection-work", draft["draft_id"], draft["draft_hash"], 0),
)
except Muse错误 as exc:
return exc.错误码
with ThreadPoolExecutor(2) as pool:
results = list(pool.map(choose, [one, two]))
assert sum(isinstance(r, dict) for r in results) == 1
# 预读时已变化为REVISION_CONFLICT;等待S01依赖锁后变化为SOURCE_STALE。
assert any(r in {"REVISION_CONFLICT", "SOURCE_STALE"} for r in results if isinstance(r, str))
assert app.要求作品().读取探索选定(actor, "selection-work")["revision"] == 1