实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
251 lines
11 KiB
Python
251 lines
11 KiB
Python
"""具体规则改进通过真实CLI验证,再由默认HTTP作者审阅并提交owner决定。"""
|
||
|
||
import json
|
||
import os
|
||
import socket
|
||
import subprocess
|
||
import time
|
||
from dataclasses import asdict, replace
|
||
from datetime import UTC, datetime, timedelta
|
||
from pathlib import Path
|
||
from threading import Thread
|
||
from uuid import uuid4
|
||
|
||
import httpx
|
||
import pytest
|
||
import test_规则启用接入 as 接入测试
|
||
import test_规则改进与回执 as 改进测试
|
||
import test_规则诊断资格与消费 as 规则测试
|
||
import uvicorn
|
||
from fastapi.testclient import TestClient
|
||
|
||
from muse.共享.调用身份 import 用途
|
||
from muse.接入.http.应用 import 创建应用
|
||
from muse.配置 import 读取配置
|
||
|
||
pytestmark = pytest.mark.数据库
|
||
规则资格环境 = 规则测试.规则资格环境
|
||
|
||
|
||
def _规范时刻(value):
|
||
# CLI与HTTP保留同一数据库时刻,但JSON分别使用空格和T分隔。
|
||
if isinstance(value, dict):
|
||
return {key: _规范时刻(item) for key, item in value.items()}
|
||
if isinstance(value, list):
|
||
return [_规范时刻(item) for item in value]
|
||
if isinstance(value, str):
|
||
try:
|
||
return datetime.fromisoformat(value)
|
||
except ValueError:
|
||
pass
|
||
return value
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w26-26d104",
|
||
environment="隔离PG/合成HTTP及浏览器",
|
||
given="固定请求、来源与作者;按用例环境准备独立测试输入",
|
||
when="通过实际公开入口执行并核对拒绝、状态及持久结果",
|
||
then=["规则改进CLI验证与HTTP作者启停读回"],
|
||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#有来源的运行观察",
|
||
)
|
||
def test_规则改进CLI验证与HTTP作者启停读回__26d104(规则资格环境, tmp_path):
|
||
env = 规则资格环境
|
||
service, seed, _ = 改进测试._规则改进(env)
|
||
proposal = replace(seed, improvement_id=str(uuid4()))
|
||
configs = 接入测试._配置(env, tmp_path)
|
||
published = 规则测试._publish(env, "rule-improvement-entry", "owned")
|
||
with TestClient(
|
||
创建应用(读取配置(configs[用途.生产])), headers={"Origin": "http://testserver"}
|
||
) as client:
|
||
接入测试._登录(client)
|
||
target = client.get("/api/v1/rules/l002/versions/1/target")
|
||
assert target.status_code == 200, target.text
|
||
assert target.json()["target"]["rule_hash"] == proposal.rule_hash
|
||
created = client.post("/api/v1/rule-improvements", json={"proposal": asdict(proposal)})
|
||
assert created.status_code == 201, created.text
|
||
assert "method_version_id" not in created.json()["payload"]
|
||
point = {
|
||
"improvement_id": proposal.improvement_id,
|
||
"revision": created.json()["revision"],
|
||
"content_hash": created.json()["content_hash"],
|
||
}
|
||
path = "/api/v1/improvements/" + proposal.improvement_id
|
||
assert client.get(path).json()["actions"] == []
|
||
assert client.get("/api/v1/rules/l002/activation").json()["current"] is None
|
||
|
||
request = {
|
||
"command_id": "rule-improvement-entry-validation",
|
||
"selection": point,
|
||
"request": {
|
||
"dataset_version_id": published["version_id"],
|
||
"rule_id": proposal.rule_id,
|
||
"rule_version": proposal.rule_version,
|
||
"rule_hash": proposal.rule_hash,
|
||
},
|
||
}
|
||
request_file = tmp_path / "规则验证请求.json"
|
||
request_file.write_text(json.dumps(request, ensure_ascii=False))
|
||
validated = 接入测试._cli(
|
||
tmp_path,
|
||
configs[用途.生产],
|
||
"经验",
|
||
"申请规则验证",
|
||
request_file,
|
||
"--评测配置",
|
||
configs[用途.评测],
|
||
)
|
||
assert set(validated["receipt"]) == {"run_id", "target", "result_hash"}
|
||
export_file = tmp_path / "规则凭据导出.json"
|
||
export_file.write_text(
|
||
json.dumps(
|
||
{
|
||
"run_id": validated["receipt"]["run_id"],
|
||
"approval_ref": "fixture-rule-entry",
|
||
"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"],
|
||
}
|
||
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-improvement-entry-enable",
|
||
"selection": point,
|
||
"request": {
|
||
**{k: v for k, v in change.items() if k != "version"},
|
||
"rule_version": 1,
|
||
"rule_hash": proposal.rule_hash,
|
||
"review_id": opened.json()["review_id"],
|
||
"review_hash": opened.json()["review_hash"],
|
||
},
|
||
}
|
||
missing_review = {**decision, "request": {**decision["request"], "review_id": ""}}
|
||
assert client.post(path + "/rule-state", json=missing_review).status_code == 422
|
||
wrong_path = client.post(
|
||
"/api/v1/improvements/" + str(uuid4()) + "/rule-state", json=decision
|
||
)
|
||
assert wrong_path.status_code == 403
|
||
assert client.get("/api/v1/rules/l002/activation").json()["current"] is None
|
||
enabled = client.post(path + "/rule-state", json=decision)
|
||
assert enabled.status_code == 200, enabled.text
|
||
assert client.post(path + "/rule-state", json=decision).json() == enabled.json()
|
||
detail = client.get(path).json()
|
||
assert detail["target_state"] == "active" and detail["consumption"] == []
|
||
diagnosis = client.post("/api/v1/diagnoses", json={"target": env["target"]})
|
||
assert diagnosis.status_code == 201, diagnosis.text
|
||
detail = client.get(path).json()
|
||
assert len(detail["consumption"]) == 1
|
||
assert detail["consumption"][0]["diagnosis_id"] == diagnosis.json()["diagnosis_id"]
|
||
change.update(action="disable", credential_id="", expected_activation_revision=1)
|
||
opened = client.post("/api/v1/rules/l002/activation/review", json={"request": change})
|
||
assert opened.status_code == 200, opened.text
|
||
decision.update(command_id="rule-improvement-entry-disable")
|
||
decision["request"].update(
|
||
action="disable",
|
||
credential_id="",
|
||
expected_activation_revision=1,
|
||
review_id=opened.json()["review_id"],
|
||
review_hash=opened.json()["review_hash"],
|
||
)
|
||
decision_file = tmp_path / "规则停用请求.json"
|
||
decision_file.write_text(json.dumps(decision))
|
||
disabled = 接入测试._cli(tmp_path, configs[用途.生产], "经验", "规则启停", decision_file)
|
||
assert disabled["receipt"]["results"][0]["state"] == "disabled"
|
||
restored = 接入测试._cli(
|
||
tmp_path, configs[用途.生产], "经验", "读取改进", proposal.improvement_id
|
||
)
|
||
assert _规范时刻(restored) == _规范时刻(client.get(path).json())
|
||
assert restored["target_state"] == "disabled" and len(restored["consumption"]) == 1
|
||
assert len(restored["actions"]) == 3
|
||
assert service.读取改进(env["author"], proposal.improvement_id)["target_state"] == "disabled"
|
||
assert env["app"].要求正文().读取正文(env["author"], "rule-ch")["revision"] == 1
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w26-26d105",
|
||
environment="隔离PG/合成HTTP及浏览器",
|
||
given="固定请求、来源与作者;按用例环境准备独立测试输入",
|
||
when="通过实际公开入口执行并核对拒绝、状态及持久结果",
|
||
then=["规则改进浏览器提案验证与启停消费"],
|
||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#有来源的运行观察",
|
||
)
|
||
@pytest.mark.浏览器
|
||
def test_规则改进浏览器提案验证与启停消费__26d105(规则资格环境, tmp_path):
|
||
if os.environ.get("MUSE_RUN_BROWSER") != "1":
|
||
pytest.skip("需显式启用浏览器")
|
||
env = 规则资格环境
|
||
_, seed, _ = 改进测试._规则改进(env)
|
||
configs = 接入测试._配置(env, tmp_path)
|
||
published = 规则测试._publish(env, "rule-improvement-browser", "owned")
|
||
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_FEEDBACK_ID": seed.feedback_ids[0],
|
||
"MUSE_RULE_DATASET_ID": published["version_id"],
|
||
"MUSE_RULE_BODY_TARGET": json.dumps(env["target"]),
|
||
"MUSE_TEST_PYTHON": os.sys.executable,
|
||
"MUSE_PRODUCTION_CONFIG": str(configs[用途.生产]),
|
||
"MUSE_EVALUATION_CONFIG": str(configs[用途.评测]),
|
||
"MUSE_MAINTENANCE_CONFIG": str(configs[用途.维护]),
|
||
"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=120,
|
||
)
|
||
assert process.returncode == 0, "浏览器未通过,见浏览器.json"
|
||
state = env["review"].读取规则状态(env["author"], "l002")
|
||
assert state["current"]["state"] == "disabled"
|
||
assert [row["action"] for row 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()
|