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