实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
279 lines
10 KiB
Python
279 lines
10 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,
|
|
)
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w27-27b001",
|
|
environment="隔离PG",
|
|
given="本作者完整目录、确切正文版本与独立隔离数据",
|
|
when="通过定稿、S02导出、跨章候选或连载反馈实际入口执行并检查失败恢复",
|
|
then=["计划日期不充当发布且前置章需要实际证据"],
|
|
contract="docs/系统架构/新版设计/模块设计/B08-交付连载.md",
|
|
)
|
|
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
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w27-27b002",
|
|
environment="隔离PG",
|
|
given="本作者完整目录、确切正文版本与独立隔离数据",
|
|
when="通过定稿、S02导出、跨章候选或连载反馈实际入口执行并检查失败恢复",
|
|
then=["反馈未关联不猜近期版本且调整只形成请求"],
|
|
contract="docs/系统架构/新版设计/模块设计/B08-交付连载.md",
|
|
)
|
|
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
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w27-27b003",
|
|
environment="隔离PG",
|
|
given="本作者完整目录、确切正文版本与独立隔离数据",
|
|
when="通过定稿、S02导出、跨章候选或连载反馈实际入口执行并检查失败恢复",
|
|
then=["修订发布产生新记录而旧反馈仍指向原稿"],
|
|
contract="docs/系统架构/新版设计/模块设计/B08-交付连载.md",
|
|
)
|
|
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)
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w27-27b004",
|
|
environment="隔离PG",
|
|
given="本作者完整目录、确切正文版本与独立隔离数据",
|
|
when="通过定稿、S02导出、跨章候选或连载反馈实际入口执行并检查失败恢复",
|
|
then=["HTTP从全书回看定稿到发布反馈真实读回"],
|
|
contract="docs/系统架构/新版设计/模块设计/B08-交付连载.md",
|
|
)
|
|
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"]
|