muse-agent-example/tests/迁移/test_目标端点守卫.py
zizi d909d1bd1b 后端实现与用例身份:19 包集成落地并修复收尾缺陷
实现侧:
- 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。
- 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。
- 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。
- 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。
- 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。
- 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。
- 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。
- 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。

用例侧:
- 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存;
- 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
2026-09-18 01:15:00 +08:00

587 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""端点与身份合同的离线验证;PG协议替身不代表infra实测或真实迁移通过。"""
from __future__ import annotations
import json
from pathlib import Path
from uuid import UUID
import psycopg
import pytest
from psycopg.conninfo import conninfo_to_dict, make_conninfo
from muse.共享.调用身份 import 用途
from muse.基础设施.数据库.连接 import 数据库工厂
from muse.配置 import 数据库引用
from 入口 import main
from 导入新库 import _允许配置, _连接配置, 初始化目标, 核对隔离目标, 检查实例
from 建立映射 import 迁移错误
from 用例身份 import 原样参数ID
# 文档保留地址与合成指纹仅供离线合同测试,不是infra允许记录。
TCP端点 = {"endpoint_type": "tcp", "host": "192.0.2.8", "port": 5433}
合成指纹 = {"system_identifier": "123456789", "data_directory": "/synthetic/pgdata"}
@pytest.fixture(autouse=True)
def 无连接环境覆盖(monkeypatch):
for 名 in ("PGHOSTADDR", "PGSERVICE", "PGSERVICEFILE", "PGOPTIONS", "PGPORT"):
monkeypatch.delenv(名, raising=False)
def _JSON(tmp_path: Path, 名: str, 值: dict) -> Path:
文件 = tmp_path / 名
文件.write_text(json.dumps(值), encoding="utf-8")
return 文件
def _引用(tmp_path: Path, 串: str, 名: str = "连接.txt") -> 数据库引用:
文件 = tmp_path / 名
文件.write_text(串, encoding="utf-8")
文件.chmod(0o600)
return 数据库引用("受控存储", str(文件))
def _连接串(**参数) -> str:
return make_conninfo(
**{"host": TCP端点["host"], "port": "5433", "dbname": "muse-example", "user": "admin"}
| 参数
)
@pytest.mark.case_id(
"NC-mig-7e034f",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="明确endpoint_type配置与旧本地socket夹具",
when="解析端点配置",
then=["合法配置被接受,合同边界不放宽"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"端点",
[
TCP端点,
{"endpoint_type": "unix", "host": "/synthetic/socket", "port": 5433},
{"socket": "/synthetic/socket", "port": 5433},
],
ids=["explicit-tcp", "explicit-unix", "legacy-unix"],
)
def test_明确端点与旧本地夹具合法配置合同__3945a2(tmp_path, 端点):
允许 = _允许配置(_JSON(tmp_path, "允许.json", 端点 | 合成指纹))
主机 = 端点.get("host", 端点.get("socket"))
引用 = _引用(tmp_path, _连接串(host=主机, user="muse_app", sslmode="require"))
参数 = conninfo_to_dict(_连接配置(引用, 允许, 角色="muse_app"))
assert 参数["host"] == 主机 and 参数["port"] == "5433"
assert 允许["endpoint_type"] == ("unix" if str(主机).startswith("/") else "tcp")
@pytest.mark.case_id(
"NC-mig-072793",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="未明确批准或歧义的实例记录",
when="尝试作为目标",
then=["拒绝而非猜测核准"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"修改",
[
{"endpoint_type": None},
{"endpoint_type": []},
{"endpoint_type": "socket"},
{"host": "infra.example"},
{"host": "192.0.2.8,192.0.2.9"},
{"host": "0.0.0.0"},
{"host": "::"},
{"host": "192.0.2.8 "},
{"host": "/synthetic/socket"},
{"port": "5433"},
{"port": True},
{"port": 0},
{"port": 65536},
{"port": "5433,5434"},
{"socket": "/synthetic/socket"},
{"system_identifier": None},
{"system_identifier": 123456789},
{"data_directory": "relative"},
],
ids=[
原样参数ID("修改0"),
原样参数ID("修改1"),
原样参数ID("修改2"),
原样参数ID("修改3"),
原样参数ID("修改4"),
原样参数ID("修改5"),
原样参数ID("修改6"),
原样参数ID("修改7"),
原样参数ID("修改8"),
原样参数ID("修改9"),
原样参数ID("修改10"),
原样参数ID("修改11"),
原样参数ID("修改12"),
原样参数ID("修改13"),
原样参数ID("修改14"),
原样参数ID("修改15"),
原样参数ID("修改16"),
原样参数ID("修改17"),
],
)
def test_未明确批准或含歧义的实例记录拒绝__01cb75(tmp_path, 修改):
文件 = _JSON(tmp_path, "允许.json", TCP端点 | 合成指纹 | 修改)
with pytest.raises(迁移错误, match="target_denied"):
_允许配置(文件)
@pytest.mark.case_id(
"NC-mig-075a2a",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="仅含旧socket字段的记录",
when="混入TCP端点值",
then=["不能暗中升级为TCP授权"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_旧socket字段不能暗中变成TCP授权__1102ac(tmp_path):
文件 = _JSON(tmp_path, "允许.json", 合成指纹 | {"socket": "192.0.2.8", "port": 5433})
with pytest.raises(迁移错误, match="target_denied"):
_允许配置(文件)
@pytest.mark.case_id(
"NC-mig-3436bd",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="带hostaddr/service/options等覆盖或错误角色的连接串",
when="建立连接",
then=["连接前即拒绝,不触达网络"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"参数",
[
{"host": "192.0.2.9"},
{"host": "192.0.2.8,192.0.2.9"},
{"port": "5432"},
{"port": "5433,5433"},
{"hostaddr": "192.0.2.9"},
{"hostaddr": ""},
{"service": "other-instance"},
{"service": ""},
{"options": "-c role=admin"},
{"load_balance_hosts": "random"},
{"dbname": "host=192.0.2.9 dbname=muse-example"},
{"dbname": "postgresql://192.0.2.9/muse-example"},
{"dbname": ""},
{"user": "muse_maint"},
],
ids=[
原样参数ID("参数0"),
原样参数ID("参数1"),
原样参数ID("参数2"),
原样参数ID("参数3"),
原样参数ID("参数4"),
原样参数ID("参数5"),
原样参数ID("参数6"),
原样参数ID("参数7"),
原样参数ID("参数8"),
原样参数ID("参数9"),
原样参数ID("参数10"),
原样参数ID("参数11"),
原样参数ID("参数12"),
原样参数ID("参数13"),
],
)
def test_端点覆盖和错误角色在连接前拒绝__ef2ba0(tmp_path, monkeypatch, 参数):
调用 = []
monkeypatch.setattr(psycopg, "connect", lambda *a, **k: 调用.append((a, k)))
引用 = _引用(tmp_path, _连接串(**({"user": "muse_app"} | 参数)))
with pytest.raises(迁移错误, match="target_denied"):
_连接配置(引用, TCP端点, 角色="muse_app")
assert 调用 == []
@pytest.mark.case_id(
"NC-mig-c87591",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="进程环境含PG默认变量",
when="建立连接",
then=["不继承环境默认端点、库或角色"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"缺失", ["host", "port", "dbname", "user"], ids=["host", "port", "dbname", "user"]
)
def test_连接不能继承环境默认端点库或角色__35995e(tmp_path, 缺失):
参数 = conninfo_to_dict(_连接串())
参数.pop(缺失)
引用 = _引用(tmp_path, make_conninfo(**参数))
with pytest.raises(迁移错误, match="target_denied"):
_连接配置(引用, TCP端点)
@pytest.mark.case_id(
"NC-mig-7418b0",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="libpq环境覆盖变量",
when="建立连接",
then=[
"PGHOSTADDR、PGSERVICE、PGSERVICEFILE、PGOPTIONS及PGPORT环境覆盖均在连接前拒绝且不回显覆盖值"
],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"环境",
["PGHOSTADDR", "PGSERVICE", "PGSERVICEFILE", "PGOPTIONS", "PGPORT"],
ids=["PGHOSTADDR", "PGSERVICE", "PGSERVICEFILE", "PGOPTIONS", "PGPORT"],
)
def test_libpq环境覆盖也在连接前拒绝__dfe18e(tmp_path, monkeypatch, 环境):
monkeypatch.setenv(环境, "不得回显的覆盖值")
引用 = _引用(tmp_path, _连接串())
with pytest.raises(迁移错误, match="target_denied") as 错:
_连接配置(引用, TCP端点)
assert "不得回显" not in str(错.value)
@pytest.mark.case_id(
"NC-mig-07747d",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="URI形式连接串",
when="解析并连接",
then=["与key=value同等核对端点与覆盖参数"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_URI连接同样核对解析后的端点与覆盖参数__456216(tmp_path):
URI = "postgresql://muse_app:synthetic-secret@192.0.2.8:5433/muse-example"
引用 = _引用(tmp_path, URI)
assert conninfo_to_dict(_连接配置(引用, TCP端点, 角色="muse_app"))["host"] == TCP端点["host"]
引用 = _引用(tmp_path, URI + "?hostaddr=192.0.2.9")
with pytest.raises(迁移错误, match="target_denied") as 错:
_连接配置(引用, TCP端点)
assert "synthetic-secret" not in str(错.value)
class 目录协议替身:
"""只允许固定目录查询,断言执行前已设置只读;不提供业务表或写语句。"""
def __init__(self):
self.read_only = False
self.语句 = []
self.行 = []
def __enter__(self):
return self
def __exit__(self, *args):
return False
def execute(self, 语句):
assert self.read_only
self.语句.append(语句)
if 语句 == "SHOW data_directory":
self.行 = [(合成指纹["data_directory"],)]
elif 语句 == "SELECT system_identifier FROM pg_catalog.pg_control_system()":
self.行 = [(合成指纹["system_identifier"],)]
elif 语句 == "SHOW server_version":
self.行 = [("17.synthetic",)]
elif 语句 == "SELECT current_database(), current_user":
self.行 = [("postgres", "admin")]
elif 语句 == (
"SELECT datname, oid, pg_catalog.pg_get_userbyid(datdba), datistemplate "
"FROM pg_catalog.pg_database ORDER BY datname"
):
self.行 = [
("muse-example", 123, "legacy_owner", False),
("postgres", 5, "admin", False),
]
else:
pytest.fail("只读入口执行了合同外查询")
return self
def fetchone(self):
return self.行[0]
def fetchall(self):
return self.行
@pytest.mark.case_id(
"NC-mig-016cd8",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="目录探测协议替身",
when="执行只读检查",
then=["返回身份信息但不自动核准为目标"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_只读目录协议替身返回身份但不自动核准__cc8a7f(tmp_path, monkeypatch):
连 = 目录协议替身()
monkeypatch.setattr(psycopg, "connect", lambda *a, **k: 连)
结果 = 检查实例(
端点记录=_JSON(tmp_path, "端点.json", TCP端点),
管理引用=_引用(tmp_path, _连接串(password="synthetic-secret")),
)
assert 结果["instance"] == TCP端点 | 合成指纹
assert 结果["databases"][0] == {
"database": "muse-example",
"database_oid": 123,
"owner": "legacy_owner",
"is_template": False,
}
assert 结果["current_database"] == "postgres"
assert 结果["migration_authorized"] is False
assert len(连.语句) == 5
assert "synthetic-secret" not in json.dumps(结果)
@pytest.mark.case_id(
"NC-mig-c8a454",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="只读检查CLI",
when="输出检查结果",
then=["私有信息0600落盘,不回显秘密"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_只读检查CLI协议替身私有落盘且不回显秘密__7d5dae(tmp_path, monkeypatch, capsys):
monkeypatch.setattr(psycopg, "connect", lambda *a, **k: 目录协议替身())
端点 = _JSON(tmp_path, "端点.json", TCP端点)
引用 = _引用(tmp_path, _连接串(password="synthetic-secret"))
输出 = tmp_path / "检查.json"
参数 = ["检查实例", "--端点记录", str(端点), "--管理引用", 引用.位置, "--输出", str(输出)]
assert main(参数) == 0
assert 输出.stat().st_mode & 0o777 == 0o600
assert json.loads(输出.read_text())["migration_authorized"] is False
assert "synthetic-secret" not in capsys.readouterr().out
原凭据 = Path(引用.位置).read_bytes()
assert main(参数[:-1] + [引用.位置]) == 1
assert Path(引用.位置).read_bytes() == 原凭据
assert "output_is_input" in capsys.readouterr().err
@pytest.mark.case_id(
"NC-mig-866106",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="连接失败的驱动报错",
when="捕获并呈现",
then=["不泄漏驱动错误原文中的连接细节"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_只读检查连接失败不泄漏驱动原文__6ddc5f(tmp_path, monkeypatch, capsys):
def 失败(*args, **kwargs):
raise psycopg.OperationalError("dsn=secret password=synthetic-secret")
monkeypatch.setattr(psycopg, "connect", 失败)
端点 = _JSON(tmp_path, "端点.json", TCP端点)
引用 = _引用(tmp_path, _连接串())
输出 = tmp_path / "检查.json"
assert (
main(["检查实例", "--端点记录", str(端点), "--管理引用", 引用.位置, "--输出", str(输出)])
== 1
)
错误 = capsys.readouterr().err
assert "target_inspect_failed" in 错误 and "synthetic-secret" not in 错误
assert not 输出.exists()
def _合成回执() -> dict:
身份 = UUID("01234567-89ab-4def-8123-456789abcdef")
return {
"version": 1,
"target_id": str(身份),
**合成指纹,
"host": TCP端点["host"],
"port": TCP端点["port"],
"database": "muse_migration_" + 身份.hex,
"database_oid": 321,
"role": "muse_app",
"run_purpose": "production",
"created_at": "2026-09-01T00:00:00+00:00",
}
@pytest.mark.case_id(
"NC-mig-c8aee1",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="源库或已有业务库的回执",
when="尝试初始化为目标",
then=["拒绝作为迁移目标"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"库名",
["muse-example", "postgres", "existing-business"],
ids=["muse-example", "postgres", "existing-business"],
)
def test_源库和已有业务库不能凭回执作目标__f7801f(tmp_path, monkeypatch, 库名):
调用 = []
monkeypatch.setattr(psycopg, "connect", lambda *a, **k: 调用.append((a, k)))
允许 = _JSON(tmp_path, "允许.json", TCP端点 | 合成指纹)
回执 = _JSON(tmp_path, "回执.json", _合成回执() | {"database": 库名})
引用 = _引用(tmp_path, _连接串(user="muse_app"))
with pytest.raises(迁移错误, match="target_denied"):
核对隔离目标(数据库工厂(引用, 用途.生产), 允许实例=允许, 管理引用=引用, 目标回执=回执)
assert 调用 == []
class 初始化管理协议替身:
"""模拟CREATE成功/失败与目录OID漂移,仅验证本次清理边界,不创建数据库。"""
def __init__(self, *, 创建失败=False, 清理OID=321, 系统号=None):
self.创建失败 = 创建失败
self.清理OID = 清理OID
self.系统号 = 系统号 or 合成指纹["system_identifier"]
self.语句 = []
self.库名 = None
self.OID读取次数 = 0
self.行 = None
def __enter__(self):
return self
def __exit__(self, *args):
return False
def execute(self, 语句, 参数=None):
文本 = 语句 if isinstance(语句, str) else 语句.as_string()
self.语句.append(文本)
if 文本 == "SHOW data_directory":
self.行 = (合成指纹["data_directory"],)
elif 文本 == "SELECT system_identifier FROM pg_catalog.pg_control_system()":
self.行 = (self.系统号,)
elif 文本.startswith("CREATE DATABASE"):
if self.创建失败:
raise psycopg.errors.DuplicateDatabase("已存在,不能当作本次创建")
self.库名 = 文本.split('"')[1]
elif 文本 == "SELECT oid FROM pg_catalog.pg_database WHERE datname=%s":
assert 参数 == (self.库名,)
self.OID读取次数 += 1
self.行 = (321 if self.OID读取次数 == 1 else self.清理OID,)
elif 文本.startswith("DROP DATABASE"):
assert self.库名 and self.库名 in 文本
else:
pytest.fail("初始化执行了未登记的目录语句")
return self
def fetchone(self):
return self.行
@pytest.mark.case_id(
"NC-mig-fb5c10",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="初始化中途失败的新库",
when="执行清理",
then=["仅清理本次创建且身份未变的库,其他库不动"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"创建失败,清理OID,系统号,应删除,应保留定位",
[
(False, 321, None, True, False),
(False, 999, None, False, True),
(True, 321, None, False, False),
(False, 321, "987654321", False, False),
],
ids=["own-new-db", "oid-changed", "existing-db", "wrong-instance"],
)
def test_初始化失败协议替身只清理本次创建且身份未变的新库__f56478(
tmp_path, monkeypatch, 创建失败, 清理OID, 系统号, 应删除, 应保留定位
):
管理 = 初始化管理协议替身(创建失败=创建失败, 清理OID=清理OID, 系统号=系统号)
monkeypatch.setattr(psycopg, "connect", lambda *a, **k: 管理)
维护连接 = []
def 迁移失败(self, **kwargs):
参数 = conninfo_to_dict(Path(self.引用.位置).read_text())
维护连接.append(参数)
raise 迁移错误("synthetic_migration_failed", "协议替身中止初始化")
monkeypatch.setattr(数据库工厂, "连接", 迁移失败)
允许 = _JSON(tmp_path, "允许.json", TCP端点 | 合成指纹)
输出 = tmp_path / "新目标"
with pytest.raises(迁移错误):
初始化目标(
允许实例=允许,
管理引用=_引用(tmp_path, _连接串(), "管理.txt"),
维护模板=_引用(tmp_path, _连接串(user="muse_maint"), "维护.txt"),
应用模板=_引用(tmp_path, _连接串(user="muse_app"), "应用.txt"),
输出目录=输出,
)
assert any(s.startswith("DROP DATABASE") for s in 管理.语句) == 应删除
assert 输出.exists() == 应保留定位
if 管理.库名:
身份 = UUID(管理.库名.removeprefix("muse_migration_"))
assert 管理.库名 == "muse_migration_" + 身份.hex
assert 管理.库名 != "muse-example"
assert 维护连接[0]["dbname"] == 管理.库名
assert 维护连接[0]["host"] == TCP端点["host"]
if 应保留定位:
assert (输出 / "维护连接.txt").is_file()
assert not (输出 / "目标回执.json").exists()
class 目标标记协议替身:
"""只提供真实入口要求的目录与库内标记形状,不是数据库实测。"""
def __init__(self, 标记, OID, 角色):
self.标记, self.OID, self.角色 = 标记, OID, 角色
self.行 = None
def __enter__(self):
return self
def __exit__(self, *args):
return False
def execute(self, 语句):
if 语句.startswith("SELECT oid,current_user FROM pg_catalog.pg_database"):
self.行 = (self.OID, self.角色)
elif 语句.startswith("SELECT jsonb_build_object("):
self.行 = (dict(self.标记),)
else:
pytest.fail("目标核对出现合同外查询")
return self
def fetchone(self):
return self.行
@pytest.mark.case_id(
"NC-mig-11355f",
environment="离线迁移测试;协议替身与临时目录,不连真实数据库",
given="TCP目标协议替身",
when="核对目标",
then=["绑定外部回执、库内标记、OID和角色四重身份"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.parametrize(
"标记修改,OID,角色,通过",
[
({}, 321, "muse_app", True),
({}, 999, "muse_app", False),
({}, 321, "muse_maint", False),
({"target_id": "ffffffff-ffff-4fff-8fff-ffffffffffff"}, 321, "muse_app", False),
({"host": "192.0.2.9"}, 321, "muse_app", False),
({"system_identifier": "999"}, 321, "muse_app", False),
],
ids=["bound-tcp", "wrong-oid", "wrong-role", "wrong-id", "wrong-host", "wrong-system"],
)
def test_TCP目标核对协议替身仍绑定外部回执库内标记OID和角色__763bbe(
tmp_path, monkeypatch, 标记修改, OID, 角色, 通过
):
回执 = _合成回执()
monkeypatch.setattr(psycopg, "connect", lambda *a, **k: 目录协议替身())
def 应用连接(self, *, 只读=False):
assert 只读
return 目标标记协议替身(回执 | 标记修改, OID, 角色)
monkeypatch.setattr(数据库工厂, "连接", 应用连接)
应用 = _引用(tmp_path, _连接串(user="muse_app", dbname=回执["database"]), "应用.txt")
参数 = {
"允许实例": _JSON(tmp_path, "允许.json", TCP端点 | 合成指纹),
"管理引用": _引用(tmp_path, _连接串(), "管理.txt"),
"目标回执": _JSON(tmp_path, "回执.json", 回执),
}
if 通过:
assert 核对隔离目标(数据库工厂(应用, 用途.生产), **参数) == 回执
else:
with pytest.raises(迁移错误, match="target_denied"):
核对隔离目标(数据库工厂(应用, 用途.生产), **参数)