2026-07-28 02:12:46 +08:00
|
|
|
|
# ============================================================
|
2026-08-11 00:54:05 +08:00
|
|
|
|
# 测试默认启用演示种子,并把所有持久化状态隔离到进程级临时目录
|
2026-07-28 02:12:46 +08:00
|
|
|
|
# ============================================================
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
2026-08-11 00:54:05 +08:00
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
import tempfile
|
|
|
|
|
|
from pathlib import Path
|
2026-07-28 02:12:46 +08:00
|
|
|
|
|
2026-08-11 00:54:05 +08:00
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
# conftest 在测试模块收集前导入:这里必须立即覆盖持久化环境,避免模块级
|
|
|
|
|
|
# 单例在 session fixture 执行前连接仓库内 server/data 或现场数据库。
|
|
|
|
|
|
_SESSION_TEMP = tempfile.TemporaryDirectory(prefix="aps-agent-tests-")
|
|
|
|
|
|
_SESSION_ROOT = Path(_SESSION_TEMP.name).resolve()
|
|
|
|
|
|
_SESSION_HOME = _SESSION_ROOT / "home"
|
|
|
|
|
|
_SESSION_DATA = _SESSION_HOME / "data"
|
|
|
|
|
|
|
|
|
|
|
|
_PERSISTENCE_ENV = {
|
|
|
|
|
|
"APS_HOME": _SESSION_HOME,
|
|
|
|
|
|
"APS_DATA_DIR": _SESSION_DATA,
|
|
|
|
|
|
"APS_DB_PATH": _SESSION_DATA / "master.db",
|
|
|
|
|
|
"APS_WORLD_PATH": _SESSION_DATA / "world.json",
|
|
|
|
|
|
"APS_PROJECTS_PATH": _SESSION_HOME / "sessions" / "workspace.json",
|
|
|
|
|
|
"APS_KNOWLEDGE_PATH": _SESSION_DATA / "knowledge.json",
|
|
|
|
|
|
"APS_CHECKPOINT_PATH": _SESSION_DATA / "checkpoints.json",
|
|
|
|
|
|
"APS_PREFERENCE_PATH": _SESSION_DATA / "preferences.json",
|
|
|
|
|
|
"APS_EMBEDDINGS_PATH": _SESSION_DATA / "embeddings.json",
|
|
|
|
|
|
"APS_APPROVAL_PATH": _SESSION_DATA / "approvals.json",
|
|
|
|
|
|
"APS_AUDIT_LEDGER_DIR": _SESSION_DATA / "audit-ledger",
|
|
|
|
|
|
"APS_AUDIT_MIRROR_DIR": _SESSION_DATA / "audit-mirror",
|
|
|
|
|
|
"APS_AUTOMATION_STATE_PATH": _SESSION_DATA / "automation.json",
|
|
|
|
|
|
"APS_MCP_BUS_PATH": _SESSION_DATA / "mcp-bus.json",
|
|
|
|
|
|
"APS_BRANCH_PATH": _SESSION_DATA / "branches.json",
|
|
|
|
|
|
"APS_BRANCH_DIR": _SESSION_DATA / "branches",
|
|
|
|
|
|
"APS_GOLDEN_CACHE": _SESSION_DATA / "golden-tests.json",
|
|
|
|
|
|
}
|
|
|
|
|
|
for _name, _path in _PERSISTENCE_ENV.items():
|
|
|
|
|
|
os.environ[_name] = str(_path)
|
|
|
|
|
|
|
|
|
|
|
|
# 任何外部数据库 URL 都会绕过 APS_DB_PATH;测试默认必须 fail-closed。
|
|
|
|
|
|
os.environ.pop("APS_DATABASE_URL", None)
|
|
|
|
|
|
os.environ["APS_APPROVAL_BACKEND"] = "file"
|
|
|
|
|
|
# Test authentication must not inherit a delayed dotenv load from the developer machine.
|
|
|
|
|
|
os.environ["APS_AUTH_ENABLED"] = "1"
|
|
|
|
|
|
os.environ.setdefault("APS_MODE", "web")
|
|
|
|
|
|
# 黄金测试依赖 build_demo_world 的自洽数据;生产默认 APS_SEED_DEMO 关闭。
|
2026-07-28 02:12:46 +08:00
|
|
|
|
os.environ.setdefault("APS_SEED_DEMO", "1")
|
2026-08-11 00:54:05 +08:00
|
|
|
|
|
|
|
|
|
|
# 本轮系统验收日期固定为 2026-08-02;黄金测试不得因宿主机跨午夜漂移。
|
|
|
|
|
|
import server.timeutil as _timeutil
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _fixed_today0() -> datetime:
|
|
|
|
|
|
return datetime(2026, 8, 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_timeutil.today0 = _fixed_today0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _reset_runtime_caches() -> None:
|
|
|
|
|
|
"""丢弃所有依赖持久化路径的进程级缓存,防止测试间串目录。"""
|
|
|
|
|
|
from server.db.database import reset_engine
|
|
|
|
|
|
|
|
|
|
|
|
reset_engine()
|
|
|
|
|
|
|
|
|
|
|
|
from server.state import store as world_store
|
|
|
|
|
|
|
|
|
|
|
|
with world_store._stores_lock:
|
|
|
|
|
|
world_store._stores.clear()
|
|
|
|
|
|
|
|
|
|
|
|
from server.state.branches import reset_branch_stores
|
|
|
|
|
|
|
|
|
|
|
|
reset_branch_stores()
|
|
|
|
|
|
|
|
|
|
|
|
from server.agent_core.plan_runtime import reset_plan_stores
|
|
|
|
|
|
|
|
|
|
|
|
reset_plan_stores()
|
|
|
|
|
|
|
|
|
|
|
|
from server.agent_core.mcp_bus import reset_mcp_bus
|
|
|
|
|
|
|
|
|
|
|
|
reset_mcp_bus()
|
|
|
|
|
|
|
|
|
|
|
|
from server.knowledge import assets, embedding, preferences
|
|
|
|
|
|
|
|
|
|
|
|
with assets._stores_lock:
|
|
|
|
|
|
assets._stores.clear()
|
|
|
|
|
|
with preferences._stores_lock:
|
|
|
|
|
|
preferences._stores.clear()
|
|
|
|
|
|
with embedding._stores_lock:
|
|
|
|
|
|
embedding._stores.clear()
|
|
|
|
|
|
embedding._provider = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
|
|
|
|
def _isolated_persistence_session():
|
|
|
|
|
|
"""保证整个 pytest session 只在临时 APS_HOME 下持久化。"""
|
|
|
|
|
|
for path in _PERSISTENCE_ENV.values():
|
|
|
|
|
|
assert Path(path).resolve().is_relative_to(_SESSION_ROOT)
|
|
|
|
|
|
assert not os.environ.get("APS_DATABASE_URL")
|
|
|
|
|
|
|
|
|
|
|
|
_reset_runtime_caches()
|
|
|
|
|
|
yield _SESSION_ROOT
|
|
|
|
|
|
_reset_runtime_caches()
|
|
|
|
|
|
_SESSION_TEMP.cleanup()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
|
|
def _isolated_approval_store(tmp_path):
|
|
|
|
|
|
"""每条用例隔离审批状态,并在前后清空所有路径相关缓存。"""
|
|
|
|
|
|
from server.agent_core import harness
|
|
|
|
|
|
|
|
|
|
|
|
_reset_runtime_caches()
|
|
|
|
|
|
store = harness.configure_approval_store(str(tmp_path / "approvals.json"))
|
|
|
|
|
|
yield
|
|
|
|
|
|
store.clear()
|
|
|
|
|
|
_reset_runtime_caches()
|