muse-agent-example/tests/集成/test_作者偏好与反馈.py
zizi 9e6f1c4481 R2 改造交付:新版模块化单体全量成果
- src/muse 新版全模块(装配/共享/上下文/任务运行/作品规划/故事世界/正文写作/审校修订/知识方法/作者经验/效果评测/交付连载/资料研究/正式变更/元数据/接入/基础设施/编排)+ 测试树(单元/契约/集成/架构/迁移/端到端/夹具)
- 129 项功能全部实现与自动验证(功能覆盖.json/矩阵),含 W31 补齐的规则与代价/节奏安排/伏笔与承诺
- 旧实现按处置清单退出(702 条中 324 删,保护合同与未迁移条目留存有据);web/app.py 旧工作台退役,新工作台为唯一写入口
- 数据库/旧库迁移:真实旧库内容批次迁移链(端点守卫/PG作品正文映射/质量资产缺省投影)
- 运行手册 docs/运行手册.md;W30 本机服务阶段一已运行(infra PG 为正式内容权威)
- R2 执行证据与私有运行材料在 .agents.local/改造/R2-20260909/(不入库)
2026-09-15 12:47:42 +08:00

178 lines
7.3 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.

"""B07真实版本反馈、作者审阅及撤回;无模型响应或私表代写业务结果。"""
from dataclasses import replace
from uuid import uuid4
import psycopg
import pytest
import test_正文候选与作者决定 as 正文测试
from muse.作者经验.接口 import (
作者经验服务,
偏好决定,
偏好范围,
偏好请求,
反馈版本,
反馈请求,
读取偏好材料,
)
from muse.共享.调用身份 import 用途
from muse.共享.错误 import Muse错误
from muse.正文写作.接口 import 读取候选依据
pytestmark = pytest.mark.数据库
候选环境 = 正文测试.候选环境
@pytest.fixture
def 环境(候选环境):
body, author, pools = 候选环境
service = 作者经验服务(pools[用途.生产], body.正式)
_, candidate, _ = 正文测试.新候选(body, author)
original = body.读取正文(author, "chapter-candidate")
with pools[用途.生产].连接(只读=True) as conn:
updated = 读取候选依据(conn, author.作者, candidate["candidate_id"], 1)
feedback = 反馈请求(
"work-candidate",
反馈版本("document", "chapter-candidate", 1, original["document_hash"]),
反馈版本("candidate", candidate["candidate_id"], 1, updated["document_hash"]),
"这一次末段不要解释人物感受。",
"revise",
)
return service, author, pools, feedback
def _请求(**changes):
return 偏好请求(
**{
"preference_id": str(uuid4()),
"topic": "心理描写",
"text": "不解释人物感受。",
"quote": "今后不解释人物感受。",
"scope": 偏好范围(),
**changes,
}
)
def _定位(result, replacements=()):
return 偏好决定(
result["preference_id"], result["revision"], result["content_hash"], replacements
)
def _消费(pools, author):
with pools[用途.生产].连接(只读=True) as conn:
return 读取偏好材料(conn, author.作者, "work-candidate")
def test_反馈保留确切原版本且不能冒充候选采纳__26a001(环境):
service, author, pools, request = 环境
saved = service.记录反馈(author, "feedback", request)
assert service.记录反馈(author, "feedback", request) == saved
read = service.读取反馈(author, str(saved["feedback_id"]))
assert "原句「保留」" in read["before_text"]
assert "改过的首句" in read["after_text"]
assert read["payload"]["quote"] == request.quote
with pytest.raises(Muse错误):
service.记录反馈(author, "feedback", replace(request, quote="改了原话"))
with pytest.raises(Muse错误):
service.记录反馈(
author,
"bad-hash",
replace(request, before=replace(request.before, content_hash="0" * 64)),
)
with pools[用途.生产].连接(只读=True) as conn:
assert conn.execute("SELECT count(*) FROM muse_candidate_decision").fetchone()[0] == 0
assert service.列出偏好(author) == []
def test_推断偏好待作者审阅确认且撤回保留历史__26a002(环境):
service, author, pools, feedback = 环境
fid = service.记录反馈(author, "feedback", feedback)["feedback_id"]
request = _请求(origin="inferred", feedback_id=str(fid), quote=feedback.quote)
saved = service.保存偏好(author, "infer", request)["results"][0]
assert saved["state"] == "pending" and _消费(pools, author) == []
point = _定位(saved)
review = service.打开偏好审阅(author, point)
confirmed = service.确认偏好(
author, "confirm", point, 审阅ID=review["review_id"], 审阅哈希=review["review_hash"]
)
active = confirmed["results"][0]
assert active["state"] == "active" and _消费(pools, author)[0]["text"] == request.text
service.撤回偏好(author, "revoke", _定位(active))
assert _消费(pools, author) == []
assert service.读取偏好(author, request.preference_id, 2)["state"] == "active"
assert service.读取偏好(author, request.preference_id, 1)["payload"]["quote"] == feedback.quote
assert (
service.确认偏好(
author, "confirm", point, 审阅ID=review["review_id"], 审阅哈希=review["review_hash"]
)
== confirmed
)
def test_显式范围冲突须点名旧版本而非自动覆盖__26a003(环境):
service, author, pools, _ = 环境
first = service.保存偏好(author, "first", _请求())["results"][0]
other = _请求(
text="允许解释人物感受。",
quote="这本书允许解释。",
scope=偏好范围("work", "work-candidate"),
)
second = service.保存偏好(author, "second", other)["results"][0]
assert first["state"] == "active" and second["state"] == "pending"
assert _消费(pools, author)[0]["preference_id"] == first["preference_id"]
point = _定位(second)
review = service.打开偏好审阅(author, point)
with pytest.raises(Muse错误, match="冲突"):
service.确认偏好(
author, "bad-confirm", point, 审阅ID=review["review_id"], 审阅哈希=review["review_hash"]
)
point = _定位(second, ((first["preference_id"], first["revision"]),))
review = service.打开偏好审阅(author, point)
service.确认偏好(
author, "confirm", point, 审阅ID=review["review_id"], 审阅哈希=review["review_hash"]
)
assert [x["text"] for x in _消费(pools, author)] == [other.text]
assert service.读取偏好(author, first["preference_id"])["state"] == "withdrawn"
def test_审阅后偏好变化拒绝旧批准和旧修改__26a004(环境):
service, author, _, feedback = 环境
fid = service.记录反馈(author, "feedback", feedback)["feedback_id"]
req = _请求(origin="inferred", feedback_id=str(fid), quote=feedback.quote)
first = service.保存偏好(author, "infer", req)["results"][0]
point = _定位(first)
review = service.打开偏好审阅(author, point)
service.保存偏好(author, "edit", replace(req, text="保留必要解释。", expected_revision=1))
with pytest.raises(Muse错误):
service.确认偏好(
author, "stale", point, 审阅ID=review["review_id"], 审阅哈希=review["review_hash"]
)
with pytest.raises(Muse错误):
service.保存偏好(author, "stale-edit", replace(req, expected_revision=1))
def test_偏好及反馈拒绝异作者及评测用途__26a005(环境):
service, author, pools, request = 环境
fid = str(service.记录反馈(author, "feedback", request)["feedback_id"])
pref = service.保存偏好(author, "pref", _请求())["results"][0]
stranger = replace(author, 作者="stranger")
for action in [
lambda: service.读取反馈(stranger, fid),
lambda: service.读取偏好(stranger, pref["preference_id"]),
lambda: service.保存偏好(replace(author, 用途=用途.评测), "x", _请求()),
]:
with pytest.raises(Muse错误):
action()
with pools[用途.评测].连接() as conn, pytest.raises(psycopg.errors.InsufficientPrivilege):
conn.execute("SELECT * FROM muse_author_feedback")
def test_单次要求不被登记为作者长期偏好__26a006(环境):
with pytest.raises(Muse错误, match="一次性"):
_请求(duration="request")
with pytest.raises(Muse错误, match="反馈"):
_请求(origin="inferred")