Files
MoviePilot/app/testing/network.py
jxxghp 6d4ce83837 fix(types): clear host mypy debt and prevent typing regressions
严格检查全部宿主源码,清零类型错误预算并移除按模块类型豁免。
按原因、所属方法、错误码及语法指纹锁定必要兼容例外和站点类型桩,
CI 阻断新增忽略、检查范围退化、本地桩替代真实接口及新的类型错误。

修复空值、异步和供应方类型边界,保持插件与 API 兼容合同,
并补充存储、订阅、通知、Agent 和工作流边界的回归覆盖。
2026-10-21 22:03:18 +08:00

97 lines
4.0 KiB
Python
Raw Permalink 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.
"""
主程序与插件仓共享的测试网络守卫。
在 conftest 中导入 block_real_network 即可使 pytest 自动应用该夹具,拦截非本地主机的
DNS 查询与套接字连接,并在用例结束时检查是否发生了被业务代码吞掉的出站尝试。
本模块仅用于测试,不参与应用运行。
"""
from __future__ import annotations
import ipaddress
from collections.abc import Iterator
from typing import Any, NoReturn
import pytest
# 本地回环/通配地址放行,其余主机一律视为真实出站;getaddrinfo 的 host 可能为 str 或 bytes
_ALLOWED_NETWORK_HOSTS = {"127.0.0.1", "::1", "localhost", "0.0.0.0", "::", ""}
@pytest.fixture(autouse=True)
def block_real_network(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
"""
拦截测试中的非本地主机访问,并在用例结束时复核违规尝试。
允许回环地址、未指定地址和本地套接字;外部依赖仍应由用例在边界处替换。
守卫只覆盖这里包装的 DNS 查询和连接接口,不替代外部依赖隔离。
:param monkeypatch: pytest 提供的临时替换夹具,用例结束后恢复 socket 接口
:yields: 网络接口已安装守卫的测试执行阶段,不提供额外值
"""
import socket
_real_getaddrinfo = socket.getaddrinfo
_real_connect = socket.socket.connect
attempts: list[tuple[str, object]] = []
def _is_allowed_host(host: object) -> bool:
"""
判断主机参数是否属于无需外部出站的本地地址。
:param host: DNS 或套接字主机参数,可为文本、字节或 None
:return: 回环、未指定地址或允许的本地名称为 True,其他主机为 False
"""
normalized = host.decode() if isinstance(host, (bytes, bytearray)) else host
if normalized is None or normalized in _ALLOWED_NETWORK_HOSTS:
return True
try:
address = ipaddress.ip_address(str(normalized).split("%", 1)[0])
return address.is_loopback or address.is_unspecified
except ValueError:
return False
def _blocked(operation: str, host: object) -> NoReturn:
"""
记录违规访问并立即抛出异常,使收尾也能发现被吞掉的尝试。
:param operation: 发起访问的接口类别
:param host: 违规目标主机
:raises RuntimeError: 发现测试不允许的真实出站访问
"""
attempts.append((operation, host))
raise RuntimeError(
f"测试禁止真实出站网络:尝试通过 {operation} 访问 {host!r};请 mock 对应外部依赖"
)
def _guarded_getaddrinfo(host: Any, *args: Any, **kwargs: Any) -> list[tuple[Any, ...]]:
"""
先检查主机范围,再委托原 DNS 解析函数。
:param host: 待解析主机
:param args: 原解析函数的位置参数
:param kwargs: 原解析函数的关键字参数
:return: 原 DNS 解析结果;非本地主机访问抛出异常
"""
if not _is_allowed_host(host):
_blocked("DNS", host)
return _real_getaddrinfo(host, *args, **kwargs)
def _guarded_connect(sock: socket.socket, address: Any) -> None:
"""
检查网络地址元组中的主机,允许本地套接字继续连接。
:param sock: 发起连接的套接字实例
:param address: 连接地址;网络地址元组的首项作为主机检查
:return: 原套接字连接函数的返回值
"""
if isinstance(address, tuple) and address and not _is_allowed_host(address[0]):
_blocked("socket", address[0])
return _real_connect(sock, address)
monkeypatch.setattr(socket, "getaddrinfo", _guarded_getaddrinfo)
monkeypatch.setattr(socket.socket, "connect", _guarded_connect)
yield
if attempts:
details = ", ".join(f"{operation}:{host}" for operation, host in attempts)
pytest.fail(f"测试期间发生真实出站网络尝试:{details}")