- src/muse 新版全模块(装配/共享/上下文/任务运行/作品规划/故事世界/正文写作/审校修订/知识方法/作者经验/效果评测/交付连载/资料研究/正式变更/元数据/接入/基础设施/编排)+ 测试树(单元/契约/集成/架构/迁移/端到端/夹具) - 129 项功能全部实现与自动验证(功能覆盖.json/矩阵),含 W31 补齐的规则与代价/节奏安排/伏笔与承诺 - 旧实现按处置清单退出(702 条中 324 删,保护合同与未迁移条目留存有据);web/app.py 旧工作台退役,新工作台为唯一写入口 - 数据库/旧库迁移:真实旧库内容批次迁移链(端点守卫/PG作品正文映射/质量资产缺省投影) - 运行手册 docs/运行手册.md;W30 本机服务阶段一已运行(infra PG 为正式内容权威) - R2 执行证据与私有运行材料在 .agents.local/改造/R2-20260909/(不入库)
181 lines
7.2 KiB
Python
181 lines
7.2 KiB
Python
"""真实任务尝试经CLI/HTTP记录观察;不向请求方披露原始输入或派发模型。"""
|
||
|
||
import json
|
||
import os
|
||
import socket
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
from dataclasses import replace
|
||
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.接入.http.应用 import 创建应用
|
||
from muse.资源加载 import 加载清单
|
||
from muse.配置 import 读取配置
|
||
|
||
pytestmark = pytest.mark.数据库
|
||
观察环境 = 观察测试.观察环境
|
||
|
||
|
||
def _配置(env, tmp_path):
|
||
path = 接入测试._配置文件(env["pool"], tmp_path)
|
||
path.write_text(
|
||
path.read_text()
|
||
.replace("eval-author", env["author"].作者)
|
||
.replace(
|
||
'"发布身份"="test"',
|
||
'"发布身份"=' + json.dumps(加载清单()["构建身份"]),
|
||
)
|
||
)
|
||
return path
|
||
|
||
|
||
def _cli(tmp_path, config, action, *args):
|
||
response = subprocess.run(
|
||
[sys.executable, "-I", "-m", "muse", "经验", str(config), action, *map(str, args)],
|
||
cwd=tmp_path,
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=30,
|
||
)
|
||
assert response.returncode == 0, response.stderr
|
||
return json.loads(response.stdout)
|
||
|
||
|
||
def test_真实CLI及HTTP按原任务用途保存观察且读取不重发__26e101(观察环境, tmp_path):
|
||
env = 观察环境
|
||
task_id, claim, _ = 观察测试._完成(env)
|
||
before = env["run"].读取任务(task_id)
|
||
config = _配置(env, tmp_path)
|
||
source_request = {"task_id": task_id, "attempt_id": claim.尝试ID}
|
||
source_file = tmp_path / "source.json"
|
||
source_file.write_text(json.dumps(source_request))
|
||
source = _cli(tmp_path, config, "运行来源", source_file)
|
||
assert source["runtime"]["content_purpose"] == "generation"
|
||
assert "不应进入运行观察的正文" not in json.dumps(source, ensure_ascii=False)
|
||
body = {
|
||
"command_id": "entry-observation",
|
||
"observation": {
|
||
"sources": [source["locator"]],
|
||
"phenomenon": "实际尝试有结果",
|
||
"quote": "保留这次的观察。",
|
||
"background": "固定合成任务。",
|
||
"issue_key": "同类结果",
|
||
},
|
||
}
|
||
with TestClient(创建应用(读取配置(config)), headers={"Origin": "http://testserver"}) as client:
|
||
assert (
|
||
client.post(
|
||
"/api/v1/session", json={"password": "synthetic-evaluation-only"}
|
||
).status_code
|
||
== 200
|
||
)
|
||
task = client.get("/api/v1/tasks/" + task_id)
|
||
assert task.status_code == 200 and task.json()["steps"][0]["attempt_id"] == claim.尝试ID
|
||
reread = client.post("/api/v1/run-observation-sources", json=source_request)
|
||
assert reread.status_code == 200 and reread.json() == source
|
||
assert client.get("/api/v1/run-observations", params={"task_id": task_id}).json() == []
|
||
bad = {**body, "observation": {**body["observation"], "state": "promoted"}}
|
||
assert client.post("/api/v1/run-observations", json=bad).status_code == 422
|
||
saved = client.post("/api/v1/run-observations", json=body)
|
||
assert saved.status_code == 201, saved.text
|
||
oid = saved.json()["observation_id"]
|
||
assert (
|
||
client.get("/api/v1/run-observations/" + oid).json()["payload"]["quote"]
|
||
== "保留这次的观察。"
|
||
)
|
||
repeats = client.get("/api/v1/recurring-issues", params={"issue_key": "同类结果"})
|
||
assert repeats.status_code == 200, repeats.text
|
||
write_file = tmp_path / "observation.json"
|
||
write_file.write_text(json.dumps(body))
|
||
replay = _cli(tmp_path, config, "记录运行观察", write_file)
|
||
assert replay["deduped"] and replay["observation_id"] == oid
|
||
assert (
|
||
_cli(tmp_path, config, "运行观察", oid)["payload"]["sources"][0]["runtime"]["attempt_id"]
|
||
== claim.尝试ID
|
||
)
|
||
assert len(_cli(tmp_path, config, "运行观察清单")) == 1
|
||
assert env["run"].读取任务(task_id) == before
|
||
other = tmp_path / "other.toml"
|
||
other.write_text(config.read_text().replace(env["author"].作者, "other-observer"))
|
||
with TestClient(创建应用(读取配置(other)), headers={"Origin": "http://testserver"}) as client:
|
||
client.post("/api/v1/session", json={"password": "synthetic-evaluation-only"})
|
||
assert (
|
||
client.post("/api/v1/run-observation-sources", json=source_request).status_code == 403
|
||
)
|
||
assert client.get("/api/v1/run-observations").json() == []
|
||
|
||
|
||
@pytest.mark.浏览器
|
||
def test_任务页真实执行观察保存刷新且运行本身不变__26e102(观察环境, tmp_path):
|
||
if os.environ.get("MUSE_RUN_BROWSER") != "1":
|
||
pytest.skip("需显式启用浏览器")
|
||
env = 观察环境
|
||
task_id, claim, _ = 观察测试._完成(env)
|
||
before = env["run"].读取任务(task_id)
|
||
config_file = _配置(env, tmp_path)
|
||
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 = 读取配置(config_file)
|
||
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_OBSERVATION_TASK": task_id,
|
||
"MUSE_OBSERVATION_ATTEMPT": claim.尝试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"
|
||
rows = env["service"].列出运行观察(env["author"], 任务ID=task_id)
|
||
assert len(rows) == 1 and rows[0]["payload"]["quote"] == "先把这次的现象记下来。"
|
||
assert env["run"].读取任务(task_id) == before
|
||
finally:
|
||
server.should_exit = True
|
||
thread.join(10)
|
||
assert not thread.is_alive()
|