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

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

321 lines
13 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.

"""旧源只读加载:仅合成SQLite和显式隔离PG;不访问真实旧库或导入业务目标。"""
from __future__ import annotations
import base64
import hashlib
import json
import sqlite3
import subprocess
import sys
from pathlib import Path
from uuid import uuid4
import psycopg
import pytest
from psycopg import sql
from 建立映射 import 迁移错误
from 读取旧PG import _打开只读连接 as 打开只读PG
from 读取旧PG import 读取旧库 as 读取PG
from 读取旧SQLite import _打开只读连接 as 打开只读SQLite
from 读取旧SQLite import 读取旧库 as 读取SQLite
@pytest.fixture
def sqlite副本(tmp_path):
路径 = tmp_path / "合成旧库.db"
with sqlite3.connect(路径) as 连:
连.executescript("""
CREATE TABLE runs(id TEXT PRIMARY KEY, output_text TEXT NOT NULL);
CREATE TABLE events(run_id TEXT NOT NULL,seq INTEGER NOT NULL,
payload_json TEXT,PRIMARY KEY(run_id,seq));
CREATE TABLE cards(id TEXT PRIMARY KEY,kind TEXT,payload_json TEXT,embedding BLOB);
INSERT INTO runs VALUES('r1','仅供测试的原运行输出');
INSERT INTO events VALUES('r1',1,'{"片段":"甲"}'),('r1',2,'{"片段":"乙"}');
""")
连.execute("INSERT INTO cards VALUES(?,?,?,?)", ("c1", "embedding", "{}", b"\x00\xff"))
return 路径
def _sqlite(路径, **参数):
return 读取SQLite(
路径, 数据集="合成旧源", 表=("runs", "events", "cards"), 快照版本="export-v1", **参数
)
@pytest.mark.case_id(
"NC-w21-21b003",
environment="离线合成JSON/SQLite与CLI;不含目标PG导入",
given="合成SQLite运行、复合键事件及二进制镜像",
when="对明确合成源执行只读加载、纯分流、身份核对或独立CLI",
then=["原行、复合主键及字节可恢复,只读连接拒绝写入,源文件不变"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_SQLite原行复合主键字节与只读性__21b003(sqlite副本):
原哈希 = hashlib.sha256(sqlite副本.read_bytes()).hexdigest()
结果 = _sqlite(sqlite副本)
assert len(结果) == 4
事件 = [r for r in 结果 if r.source.table == "events"]
assert [json.loads(r.source.id) for r in 事件] == [
{"run_id": "r1", "seq": 1},
{"run_id": "r1", "seq": 2},
]
assert all(r.source.revision == "export-v1" for r in 结果)
assert all(str(sqlite副本) not in r.source.database for r in 结果)
卡 = next(r for r in 结果 if r.source.table == "cards")
assert base64.b64decode(卡.原行["embedding"]["__bytea_base64__"]) == b"\x00\xff"
with 打开只读SQLite(sqlite副本) as 连:
assert 连.execute("PRAGMA query_only").fetchone()[0] == 1
with pytest.raises(sqlite3.OperationalError, match="readonly"):
连.execute("DELETE FROM runs")
assert hashlib.sha256(sqlite副本.read_bytes()).hexdigest() == 原哈希
@pytest.mark.case_id(
"NC-w21-21b004",
environment="离线合成JSON/SQLite与CLI;不含目标PG导入",
given="缺失副本、未登记表或视图",
when="对明确合成源执行只读加载、纯分流、身份核对或独立CLI",
then=["读取明确失败,不创建文件或暗读未允许来源"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_SQLite缺文件未知表和视图不能暗读__21b004(sqlite副本, tmp_path):
缺失 = tmp_path / "不存在.db"
with pytest.raises(FileNotFoundError):
_sqlite(缺失)
assert not 缺失.exists()
with pytest.raises(迁移错误, match="source_table_denied"):
读取SQLite(sqlite副本, 数据集="合成旧源", 表=("private_keys",), 快照版本="x")
with sqlite3.connect(sqlite副本) as 连:
连.execute("DROP TABLE cards")
连.execute("CREATE VIEW cards AS SELECT id FROM runs")
with pytest.raises(迁移错误, match="source_table_denied"):
_sqlite(sqlite副本)
@pytest.mark.case_id(
"NC-w21-21b005",
environment="离线合成JSON/SQLite与CLI;不含目标PG导入",
given="复合主键范围或超出快照配额",
when="对明确合成源执行只读加载、纯分流、身份核对或独立CLI",
then=["只读指定范围,错误范围拒绝,行数和字节超限不截断"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_SQLite范围行数与字节上限不静默截断__21b005(sqlite副本):
结果 = 读取SQLite(
sqlite副本,
数据集="合成旧源",
表=("events",),
快照版本="x",
范围={"events": {"start": ["r1", 2], "end": ["r1", 3]}},
)
assert len(结果) == 1 and 结果[0].原行["seq"] == 2
with pytest.raises(迁移错误, match="source_range_invalid"):
_sqlite(sqlite副本, 范围={"events": {"start": [1]}})
with pytest.raises(迁移错误, match="snapshot_limit"):
_sqlite(sqlite副本, 最大行数=1)
with sqlite3.connect(sqlite副本) as 连:
连.execute("UPDATE runs SET output_text=?", ("x" * (16 * 1024 * 1024),))
with pytest.raises(迁移错误, match="snapshot_limit"):
_sqlite(sqlite副本)
@pytest.mark.case_id(
"NC-w21-21b006",
environment="离线合成JSON/SQLite与CLI;不含目标PG导入",
given="CLI从合成SQLite导出并交给分流",
when="对明确合成源执行只读加载、纯分流、身份核对或独立CLI",
then=["快照可重放且私有,不覆盖副本;运行原文不进stdout,分流仅保历史"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_读取命令接纯分流且不覆盖源__21b006(sqlite副本, tmp_path):
脚本 = Path(__file__).resolve().parents[2] / "数据库/旧库迁移/入口.py"
输出, 映射, 计划 = [tmp_path / n for n in ("快照.json", "映射.json", "计划.json")]
命令 = [
sys.executable,
str(脚本),
"读取SQLite",
"--副本",
str(sqlite副本),
"--数据集",
"合成旧源",
"--表",
"runs",
"--表",
"events",
"--快照版本",
"export-v1",
"--输出",
str(输出),
]
for _ in range(2):
运行 = subprocess.run(命令, capture_output=True, text=True, timeout=15)
assert 运行.returncode == 0, 运行.stderr
assert "仅供测试的原运行输出" not in 运行.stdout
assert len(json.loads(输出.read_text())) == 3 and 输出.stat().st_mode & 0o777 == 0o600
映射.write_text('{"works": []}', encoding="utf-8")
运行 = subprocess.run(
[
sys.executable,
str(脚本),
"分流",
"--快照",
str(输出),
"--映射",
str(映射),
"--输出",
str(计划),
],
capture_output=True,
text=True,
timeout=15,
)
assert 运行.returncode == 0, 运行.stderr
assert json.loads(计划.read_text())["dispositions"] == {"历史保留": 3}
哈希 = hashlib.sha256(sqlite副本.read_bytes()).hexdigest()
assert subprocess.run([*命令[:-1], str(sqlite副本)], capture_output=True).returncode == 1
assert hashlib.sha256(sqlite副本.read_bytes()).hexdigest() == 哈希
@pytest.fixture
def pg旧源(隔离数据库URL, tmp_path):
schema = "w21_source_" + uuid4().hex
with psycopg.connect(隔离数据库URL) as 连:
连.execute(sql.SQL("CREATE SCHEMA {}").format(sql.Identifier(schema)))
连.execute(
sql.SQL(
"CREATE TABLE {}(id bigint PRIMARY KEY, tenant_id bigint NOT NULL, "
"revision int NOT NULL, description text)"
).format(sql.Identifier(schema, "muse_knowledge_entity"))
)
连.execute(
sql.SQL(
"INSERT INTO {} VALUES (1,7,3,'合成甲'),(2,8,4,'合成乙'),(3,7,5,'合成丙')"
).format(sql.Identifier(schema, "muse_knowledge_entity"))
)
连.execute(
sql.SQL(
"CREATE TABLE {}(run_id text, seq int, data text, PRIMARY KEY(run_id,seq))"
).format(sql.Identifier(schema, "example_run_receipt"))
)
连.execute(
sql.SQL("INSERT INTO {} VALUES ('r1',1,'合成事件')").format(
sql.Identifier(schema, "example_run_receipt")
)
)
文件 = tmp_path / "连接说明.txt"
文件.write_text(隔离数据库URL, encoding="utf-8")
文件.chmod(0o600)
try:
yield 文件, schema
finally:
with psycopg.connect(隔离数据库URL) as 连:
连.execute(sql.SQL("DROP SCHEMA {} CASCADE").format(sql.Identifier(schema)))
@pytest.mark.case_id(
"NC-w21-21b007",
environment="隔离PG合成源;不含真实旧库或目标业务导入",
given="隔离PG中不同租户、真实修订和复合主键源",
when="对明确合成源执行只读加载、纯分流、身份核对或独立CLI",
then=["读取前按租户限定,真实修订不替换,范围与缺修订快照版本可核对"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.数据库
def test_PG按租户范围读取且保留真实修订__21b007(pg旧源):
文件, schema = pg旧源
参数 = {
"数据集": "合成PG",
"schema": schema,
"表": ("muse_knowledge_entity",),
"快照版本": "export-v1",
}
一 = 读取PG(文件, 租户=7, **参数)
二 = 读取PG(文件, 租户=8, **参数)
assert [r.原行["id"] for r in 一] == [1, 3] and [r.原行["id"] for r in 二] == [2]
assert [r.source.revision for r in 一] == ["3", "5"]
assert 一[0].source.database != 二[0].source.database
限定 = 读取PG(文件, 租户=7, 范围={"muse_knowledge_entity": {"start": [3], "end": [4]}}, **参数)
assert len(限定) == 1 and 限定[0].原行["id"] == 3
with pytest.raises(迁移错误, match="source_scope_invalid"):
读取PG(文件, 租户=None, **参数)
with pytest.raises(迁移错误, match="snapshot_limit"):
读取PG(文件, 租户=7, 最大行数=1, **参数)
全局 = 读取PG(
文件,
数据集="合成PG",
schema=schema,
租户=None,
表=("example_run_receipt",),
快照版本="export-v1",
)
assert json.loads(全局[0].source.id) == {"run_id": "r1", "seq": 1}
assert 全局[0].source.revision == "export-v1"
@pytest.mark.case_id(
"NC-w21-21b008",
environment="隔离PG合成源;不含真实旧库或目标业务导入",
given="隔离PG服务器只读事务",
when="对明确合成源执行只读加载、纯分流、身份核对或独立CLI",
then=["服务器实际拒绝删除,隔离级别固定;视图不读取"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
@pytest.mark.数据库
def test_PG源事务真正只读且声明错误不读取__21b008(pg旧源):
文件, schema = pg旧源
with pytest.raises(迁移错误, match="25006"):
with 打开只读PG(文件) as 连:
assert (
连.execute("SHOW transaction_read_only").fetchone()["transaction_read_only"] == "on"
)
assert (
连.execute("SHOW transaction_isolation").fetchone()["transaction_isolation"]
== "repeatable read"
)
连.execute(
sql.SQL("DELETE FROM {}").format(sql.Identifier(schema, "muse_knowledge_entity"))
)
with psycopg.connect(文件.read_text()) as 连:
assert (
连.execute(
sql.SQL("SELECT count(*) FROM {}").format(
sql.Identifier(schema, "muse_knowledge_entity")
)
).fetchone()[0]
== 3
)
连.execute(
sql.SQL("CREATE VIEW {} AS SELECT 1 AS id").format(
sql.Identifier(schema, "example_run")
)
)
with pytest.raises(迁移错误, match="source_table_denied"):
读取PG(文件, 数据集="合成PG", schema=schema, 租户=None, 表=("example_run",), 快照版本="v1")
@pytest.mark.case_id(
"NC-w21-21b009",
environment="离线合成JSON/SQLite与CLI;不含目标PG导入",
given="连接说明缺明确参数或含无效敏感内容",
when="对明确合成源执行只读加载、纯分流、身份核对或独立CLI",
then=["不连接默认环境,不把驱动原错误中的凭据写出"],
contract="docs/系统架构/新版设计/数据模型/旧知识记录分流.md",
)
def test_连接说明不回落环境且错误不泄露凭据__21b009(tmp_path, monkeypatch):
文件 = tmp_path / "连接说明.txt"
monkeypatch.setattr(
psycopg.Connection, "connect", lambda *a, **k: pytest.fail("配置非法时不得连接")
)
for 内容 in ("", "dbname=somewhere", "host=localhost user=someone"):
文件.write_text(内容, encoding="utf-8")
with pytest.raises(迁移错误, match="source_connection_invalid"):
with 打开只读PG(文件):
pytest.fail("不得取得连接")
文件.write_text("password='never-print-this", encoding="utf-8")
with pytest.raises(迁移错误) as 错:
with 打开只读PG(文件):
pytest.fail("不得取得连接")
assert "never-print-this" not in str(错.value)