- src/muse 新版全模块(装配/共享/上下文/任务运行/作品规划/故事世界/正文写作/审校修订/知识方法/作者经验/效果评测/交付连载/资料研究/正式变更/元数据/接入/基础设施/编排)+ 测试树(单元/契约/集成/架构/迁移/端到端/夹具) - 129 项功能全部实现与自动验证(功能覆盖.json/矩阵),含 W31 补齐的规则与代价/节奏安排/伏笔与承诺 - 旧实现按处置清单退出(702 条中 324 删,保护合同与未迁移条目留存有据);web/app.py 旧工作台退役,新工作台为唯一写入口 - 数据库/旧库迁移:真实旧库内容批次迁移链(端点守卫/PG作品正文映射/质量资产缺省投影) - 运行手册 docs/运行手册.md;W30 本机服务阶段一已运行(infra PG 为正式内容权威) - R2 执行证据与私有运行材料在 .agents.local/改造/R2-20260909/(不入库)
263 lines
10 KiB
Python
263 lines
10 KiB
Python
"""规则资格的真实接入;测试中的owned声明只验证工程链,不认证真实语料来源。"""
|
||
|
||
import json
|
||
import os
|
||
import socket
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
from dataclasses import replace
|
||
from datetime import UTC, datetime, timedelta
|
||
from pathlib import Path
|
||
from threading import Thread
|
||
|
||
import httpx
|
||
import pytest
|
||
import test_生产评测权限隔离 as 接入测试
|
||
import test_规则诊断资格与消费 as 规则测试
|
||
import uvicorn
|
||
from fastapi.testclient import TestClient
|
||
|
||
from muse.共享.调用身份 import 用途
|
||
from muse.接入.http.应用 import 创建应用
|
||
from muse.效果评测.接口 import 规则诊断数据集发布
|
||
from muse.资源加载 import 加载清单
|
||
from muse.配置 import 读取配置
|
||
|
||
pytestmark = pytest.mark.数据库
|
||
规则资格环境 = 规则测试.规则资格环境
|
||
|
||
|
||
def _配置(env, tmp_path):
|
||
configs = {}
|
||
for purpose in 用途:
|
||
path = 接入测试._配置文件(env["pools"][purpose], tmp_path)
|
||
path.write_text(
|
||
path.read_text()
|
||
.replace("eval-author", env["author"].作者)
|
||
.replace(
|
||
'"发布身份"="test"',
|
||
'"发布身份"=' + json.dumps(加载清单()["构建身份"]),
|
||
)
|
||
)
|
||
configs[purpose] = path
|
||
return configs
|
||
|
||
|
||
def _cli(tmp_path, config, command, action, target, *args):
|
||
process = subprocess.run(
|
||
[
|
||
sys.executable,
|
||
"-I",
|
||
"-m",
|
||
"muse",
|
||
command,
|
||
str(config),
|
||
action,
|
||
str(target),
|
||
*map(str, args),
|
||
],
|
||
cwd=tmp_path,
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=30,
|
||
)
|
||
assert process.returncode == 0, process.stderr
|
||
return json.loads(process.stdout)
|
||
|
||
|
||
def _登录(client):
|
||
response = client.post("/api/v1/session", json={"password": "synthetic-evaluation-only"})
|
||
assert response.status_code == 200, response.text
|
||
|
||
|
||
def test_规则验证真实CLI到默认HTTP审阅启停及读回__25d104(规则资格环境, tmp_path):
|
||
env = 规则资格环境
|
||
configs = _配置(env, tmp_path)
|
||
request = 规则诊断数据集发布(
|
||
dataset_id="rule-entry",
|
||
revision=1,
|
||
rule_id="l002",
|
||
rule_version=1,
|
||
approval_ref="test-author-declared-fixture",
|
||
expires_at=datetime.now(UTC) + timedelta(hours=2),
|
||
samples=规则测试._samples("owned", "entry"),
|
||
)
|
||
source_file = tmp_path / "samples.json"
|
||
source_file.write_text(request.model_dump_json())
|
||
dataset = _cli(
|
||
tmp_path,
|
||
configs[用途.维护],
|
||
"评测",
|
||
"发布规则样本",
|
||
source_file,
|
||
"--来源配置",
|
||
configs[用途.生产],
|
||
)
|
||
run = _cli(tmp_path, configs[用途.评测], "评测", "验证规则", dataset["version_id"])
|
||
assert run["assessment"]["status"] == "passed"
|
||
replay = _cli(tmp_path, configs[用途.评测], "评测", "规则验证报告", run["run_id"])
|
||
assert replay["assessment"] == run["assessment"]
|
||
export_file = tmp_path / "export.json"
|
||
export_file.write_text(
|
||
json.dumps(
|
||
{
|
||
"run_id": run["run_id"],
|
||
"approval_ref": "fixture-owner",
|
||
"valid_until": (datetime.now(UTC) + timedelta(hours=1)).isoformat(),
|
||
}
|
||
)
|
||
)
|
||
proof = _cli(tmp_path, configs[用途.维护], "评测", "导出规则凭据", export_file)
|
||
change = {
|
||
"rule_id": "l002",
|
||
"version": 1,
|
||
"action": "enable",
|
||
"expected_activation_revision": 0,
|
||
"credential_id": proof["receipt_id"],
|
||
}
|
||
config = 读取配置(configs[用途.生产])
|
||
with TestClient(创建应用(config), headers={"Origin": "http://testserver"}) as client:
|
||
_登录(client)
|
||
before = client.get("/api/v1/rules/l002/activation")
|
||
assert before.status_code == 200 and before.json()["current"] is None
|
||
malformed = {**change, "version": True}
|
||
assert (
|
||
client.post(
|
||
"/api/v1/rules/l002/activation/review", json={"request": malformed}
|
||
).status_code
|
||
== 422
|
||
)
|
||
opened = client.post("/api/v1/rules/l002/activation/review", json={"request": change})
|
||
assert opened.status_code == 200, opened.text
|
||
assert client.get("/api/v1/rules/l002/activation").json()["current"] is None
|
||
decision = {
|
||
"command_id": "rule-entry-adopt",
|
||
"request": change,
|
||
"action": "adopt",
|
||
"review_id": opened.json()["review_id"],
|
||
"review_hash": opened.json()["review_hash"],
|
||
}
|
||
response = client.post("/api/v1/rules/l002/activation/decisions", json=decision)
|
||
assert response.status_code == 200, response.text
|
||
assert (
|
||
client.post("/api/v1/rules/l002/activation/decisions", json=decision).json()
|
||
== response.json()
|
||
)
|
||
current = client.get("/api/v1/rules/l002/activation").json()
|
||
assert current["consumable"] and current["current"]["rule_version"] == 1
|
||
diagnosis = client.post("/api/v1/diagnoses", json={"target": env["target"]})
|
||
assert diagnosis.status_code == 201, diagnosis.text
|
||
assert diagnosis.json()["report"]["findings"][0]["rule_id"] == "l002"
|
||
assert client.get("/api/v1/evaluation/rule-diagnostics/" + run["run_id"]).status_code == 403
|
||
state = _cli(tmp_path, configs[用途.生产], "审校", "规则状态", "l002")
|
||
assert state["current"]["activation_revision"] == 1 and state["consumable"]
|
||
with TestClient(
|
||
创建应用(读取配置(configs[用途.评测])), headers={"Origin": "http://testserver"}
|
||
) as client:
|
||
_登录(client)
|
||
result = client.post(
|
||
"/api/v1/evaluation/rule-diagnostics",
|
||
json={"dataset_version_id": dataset["version_id"]},
|
||
)
|
||
assert result.status_code == 201 and result.json()["run_id"] == run["run_id"]
|
||
stopped = client.post(
|
||
"/api/v1/evaluation/rule-diagnostics/" + run["run_id"] + "/stop",
|
||
json={"command_id": "stop-rule-test"},
|
||
)
|
||
assert stopped.status_code == 200, stopped.text
|
||
state = _cli(tmp_path, configs[用途.生产], "审校", "规则状态", "l002")
|
||
assert state["current"]["state"] == "active" and not state["consumable"]
|
||
change.update(action="disable", credential_id="", expected_activation_revision=1)
|
||
review_file = tmp_path / "review.json"
|
||
review_file.write_text(json.dumps({"request": change}))
|
||
review = _cli(tmp_path, configs[用途.生产], "审校", "规则审阅", review_file)
|
||
decision_file = tmp_path / "decision.json"
|
||
decision_file.write_text(
|
||
json.dumps(
|
||
{
|
||
"request": change,
|
||
"command_id": "rule-entry-disable",
|
||
"review_id": review["review_id"],
|
||
"review_hash": review["review_hash"],
|
||
"action": "adopt",
|
||
}
|
||
)
|
||
)
|
||
_cli(tmp_path, configs[用途.生产], "审校", "规则决定", decision_file)
|
||
state = _cli(tmp_path, configs[用途.生产], "审校", "规则状态", "l002")
|
||
assert state["current"]["state"] == "disabled" and len(state["history"]) == 2
|
||
assert env["app"].要求正文().读取正文(env["author"], "rule-ch")["revision"] == 1
|
||
|
||
|
||
@pytest.mark.浏览器
|
||
def test_规则凭据经真实浏览器审阅启用刷新停用__25d105(规则资格环境, tmp_path):
|
||
if os.environ.get("MUSE_RUN_BROWSER") != "1":
|
||
pytest.skip("需显式启用浏览器")
|
||
env = 规则资格环境
|
||
configs = _配置(env, tmp_path)
|
||
published = 规则测试._publish(env, "browser-rule-fixture", "owned")
|
||
run = env["evaluate"].执行规则诊断验证(env["evaluation_identity"], published["version_id"])
|
||
proof = env["maintain"].导出规则诊断凭据(
|
||
env["maintenance_identity"],
|
||
run["run_id"],
|
||
批准引用="fixture-browser-proof",
|
||
有效期=datetime.now(UTC) + timedelta(hours=1),
|
||
)
|
||
with socket.socket() as sock:
|
||
sock.bind(("127.0.0.1", 0))
|
||
port = sock.getsockname()[1]
|
||
url = f"http://127.0.0.1:{port}"
|
||
config = 读取配置(configs[用途.生产])
|
||
assert config.HTTP is not None
|
||
config = replace(config, HTTP=replace(config.HTTP, 公开地址=url, 允许来源=(url,)))
|
||
server = uvicorn.Server(
|
||
uvicorn.Config(创建应用(config), host="127.0.0.1", port=port, log_level="warning")
|
||
)
|
||
thread = Thread(target=server.run, daemon=True)
|
||
thread.start()
|
||
output = Path(os.environ.get("MUSE_BROWSER_ARTIFACTS", str(tmp_path / "browser"))).resolve()
|
||
output.mkdir(parents=True)
|
||
try:
|
||
for _ in range(100):
|
||
if server.started:
|
||
break
|
||
time.sleep(0.05)
|
||
assert server.started and httpx.get(url + "/ready", trust_env=False).status_code == 200
|
||
environment = {
|
||
**os.environ,
|
||
"MUSE_WORKBENCH_URL": url,
|
||
"MUSE_AUTHOR_PASSWORD_FILE": config.HTTP.口令文件,
|
||
"MUSE_RULE_CREDENTIAL": proof["receipt_id"],
|
||
"MUSE_BROWSER_EXECUTABLE": (
|
||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||
),
|
||
}
|
||
with (output / "浏览器.json").open("w") as report:
|
||
process = subprocess.run(
|
||
[
|
||
"pnpm",
|
||
"exec",
|
||
"playwright",
|
||
"test",
|
||
"规则启用.spec.ts",
|
||
"--reporter=json",
|
||
"--output",
|
||
str(output / "产物"),
|
||
],
|
||
cwd=Path(__file__).resolve().parents[2] / "web",
|
||
env=environment,
|
||
stdout=report,
|
||
stderr=subprocess.STDOUT,
|
||
timeout=90,
|
||
)
|
||
assert process.returncode == 0, "浏览器未通过,见浏览器.json"
|
||
state = env["review"].读取规则状态(env["author"], "l002")
|
||
assert state["current"]["state"] == "disabled"
|
||
assert [r["action"] for r in state["history"]] == ["enable", "disable"]
|
||
assert env["app"].要求正文().读取正文(env["author"], "rule-ch")["revision"] == 1
|
||
finally:
|
||
server.should_exit = True
|
||
thread.join(10)
|
||
assert not thread.is_alive()
|