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

247 lines
8.7 KiB
Python

"""计划与真实发布区分,反馈显式关联,旧记录不随修订改变。"""
from dataclasses import asdict, replace
from datetime import UTC, datetime, timedelta
from uuid import uuid4
import pytest
import test_定稿与导出 as 定稿测试
import test_生产评测权限隔离 as 接入测试
from fastapi.testclient import TestClient
from muse.交付连载.接口 import 发布请求, 读者反馈请求, 连载章, 连载请求
from muse.共享.错误 import Muse错误
from muse.接入.http.应用 import 创建应用
from muse.配置 import 读取配置
pytestmark = pytest.mark.数据库
候选环境 = 定稿测试.候选环境
交付环境 = 定稿测试.交付环境
时间 = datetime(2026, 9, 1, 12, tzinfo=UTC)
def _发布(req, **changes):
return replace(
发布请求(
str(uuid4()),
req.delivery_id,
(req.chapters[0].chapter_id,),
时间,
"合成平台",
"https://example.invalid/release/1",
),
**changes,
)
def test_计划日期不充当发布且前置章需要实际证据__27b001(交付环境):
app, author, _, req = 交付环境
svc = app.要求交付()
svc.冻结定稿(author, "freeze", req)
a, b = [x.chapter_id for x in req.chapters]
plan = 连载请求(
str(uuid4()),
req.delivery_id,
0,
(
连载章(a, 时间),
连载章(b, 时间 + timedelta(days=1), (a,)),
),
)
saved = svc.保存连载(author, "plan", plan)
assert svc.保存连载(author, "plan", plan) == saved
face = svc.连载工作面(author, req.work_id)
assert face["releases"] == []
assert [c["state"] for c in face["plans"][0]["chapters"]] == ["ready", "ready"]
with pytest.raises(Muse错误, match="前置"):
svc.登记发布(
author,
"too-early",
_发布(
req,
chapter_ids=(b,),
plan_id=plan.plan_id,
plan_revision=1,
),
)
first = _发布(req, plan_id=plan.plan_id, plan_revision=1)
svc.登记发布(author, "release-one", first)
svc.登记发布(
author,
"release-two",
_发布(
req,
chapter_ids=(b,),
published_at=时间 + timedelta(days=1),
plan_id=plan.plan_id,
plan_revision=1,
),
)
face = svc.连载工作面(author, req.work_id)
assert [c["state"] for c in face["plans"][0]["chapters"]] == ["published", "published"]
newer = replace(
plan,
expected_revision=1,
chapters=(
replace(plan.chapters[0], scheduled_at=时间 - timedelta(days=1)),
plan.chapters[1],
),
)
svc.保存连载(author, "reschedule", newer)
assert (
svc.读取连载(author, plan.plan_id, 1)["payload"]["chapters"][0]["scheduled_at"]
== 时间.isoformat()
)
assert svc.读取连载(author, plan.plan_id)["revision"] == 2
def test_反馈未关联不猜近期版本且调整只形成请求__27b002(交付环境):
app, author, pools, req = 交付环境
svc = app.要求交付()
svc.冻结定稿(author, "freeze", req)
release = _发布(req)
svc.登记发布(author, "release", release)
feedback = 读者反馈请求(
str(uuid4()),
req.work_id,
"合成读者来信",
"末段看不清是谁进门。",
时间,
)
svc.登记读者反馈(author, "unlinked", feedback)
linked = replace(
feedback,
feedback_id=str(uuid4()),
release_id=release.release_id,
chapter_id=req.chapters[0].chapter_id,
category="question",
adjustment="请在改稿候选中明确进门的人。",
)
svc.登记读者反馈(author, "linked", linked)
face = svc.连载工作面(author, req.work_id)
rows = {str(r["feedback_id"]): r["payload"] for r in face["feedback"]}
assert rows[feedback.feedback_id]["association"] == "unlinked"
assert rows[feedback.feedback_id]["chapter_versions"] == []
assert rows[linked.feedback_id]["chapter_versions"][0]["revision"] == 1
assert rows[linked.feedback_id]["causality"] == "not_established"
assert rows[linked.feedback_id]["adjustment_state"] == "requested"
with pytest.raises(Muse错误, match="章节"):
svc.登记读者反馈(
author,
"wrong-chapter",
replace(
linked,
feedback_id=str(uuid4()),
chapter_id=req.chapters[1].chapter_id,
),
)
with pools[author.用途].连接(只读=True) as conn:
assert conn.execute("SELECT count(*) FROM muse_document_version").fetchone()[0] == 2
assert conn.execute("SELECT count(*) FROM muse_candidate_decision").fetchone()[0] == 0
def test_修订发布产生新记录而旧反馈仍指向原稿__27b003(交付环境):
app, author, _, req = 交付环境
svc = app.要求交付()
svc.冻结定稿(author, "freeze", req)
release = _发布(req)
svc.登记发布(author, "release", release)
app.要求正文().保存人工(
author,
"edit",
req.chapters[0].chapter_id,
1,
定稿测试.正文测试.草稿("新版第一句"),
)
face = svc.全书回看(author, req.work_id)
newreq = replace(
req,
delivery_id=str(uuid4()),
chapters=(
replace(
req.chapters[0], revision=2, document_hash=face["chapters"][0]["document_hash"]
),
req.chapters[1],
),
)
svc.冻结定稿(author, "new-freeze", newreq)
correction = _发布(
newreq,
corrects_release_id=release.release_id,
published_at=时间 + timedelta(days=1),
)
svc.登记发布(author, "correction", correction)
feedback = 读者反馈请求(
str(uuid4()),
req.work_id,
"旧版读者",
"我看到的还是原版。",
时间 + timedelta(days=2),
release_id=release.release_id,
chapter_id=req.chapters[0].chapter_id,
)
svc.登记读者反馈(author, "feedback", feedback)
face = svc.连载工作面(author, req.work_id)
assert len(face["releases"]) == 2
assert face["feedback"][0]["payload"]["chapter_versions"][0]["revision"] == 1
with pytest.raises(Muse错误):
svc.登记发布(author, "release", correction)
def test_HTTP从全书回看定稿到发布反馈真实读回__27b004(交付环境, tmp_path):
app, author, pools, req = 交付环境
config = 接入测试._配置文件(pools[author.用途], tmp_path)
config.write_text(config.read_text().replace("eval-author", author.作者))
with TestClient(创建应用(读取配置(config)), headers={"origin": "http://testserver"}) as client:
assert client.get(f"/api/v1/works/{req.work_id}/delivery-draft").status_code == 401
assert (
client.post(
"/api/v1/session", json={"password": "synthetic-evaluation-only"}
).status_code
== 200
)
face = client.get(f"/api/v1/works/{req.work_id}/delivery-draft")
assert face.json()["can_freeze"] is True
assert face.json()["literary_status"] == "not_assessed"
response = client.post(
"/api/v1/deliveries",
json={
"command_id": "http-freeze",
"delivery": asdict(req),
},
)
assert response.status_code == 200, response.text
release = asdict(_发布(req))
release["published_at"] = 时间.isoformat()
response = client.post(
"/api/v1/releases",
json={
"command_id": "http-release",
"release": release,
},
)
assert response.status_code == 200, response.text
feedback = asdict(
读者反馈请求(
str(uuid4()),
req.work_id,
"合成留言",
"保留原句。",
时间,
release_id=release["release_id"],
chapter_id=req.chapters[0].chapter_id,
)
)
feedback["received_at"] = 时间.isoformat()
response = client.post(
"/api/v1/reader-feedback",
json={
"command_id": "http-reader",
"feedback": feedback,
},
)
assert response.status_code == 200, response.text
rows = client.get(f"/api/v1/works/{req.work_id}/serial").json()
assert rows["feedback"][0]["payload"]["release_id"] == release["release_id"]
assert app.要求交付().连载工作面(author, req.work_id)["feedback"]