Files
MoviePilot/tests/test_db_error_diagnostics.py
freeman f3ad81b39e style: apply safe ruff auto-fixes and exempt startup late imports
Run `ruff check app tests scripts --fix` with the configured rule set
(E4/E7/E9/F/I): import sorting (I001), f-strings without placeholders
(F541), lambda/variable/unused-import cleanups that ruff marks safe.
Nine files whose only ruff debt sits next to pre-existing pylint
import-resolution errors (lazy exports, sys.path script imports) or
whose "unused" imports are reached through attribute access by tests are
left untouched so the changed-file pylint gate and the benchmark tests
stay green; their diagnostics remain in the baseline.

app/startup/lifecycle/__init__.py and app/main.py import business
modules after cache composition, import-path and stdio setup on purpose
(the source comments document the contract), so declare E402
per-file-ignores for them instead of adding per-line noqa.

Ruff ratchet watermark drops from 498 to 100; sync the baseline fixture
and the optimization checklist figure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 13:29:09 +08:00

112 lines
3.6 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.
import asyncio
import pytest
from sqlalchemy import create_engine, text
from sqlalchemy.exc import OperationalError
# 诊断实现已迁至 app.db.diagnostics;app.db 只做 re-export,私有符号不在其上
import app.db.diagnostics as db_module
# 用 getter 而不是旧名字 AsyncEngine:后者只为仓库外插件保留,模块级导入它会在 pytest
# 的收集期就把全局异步引擎建出来
from app.db.engine import get_global_async_engine
class _SqliteError(Exception):
"""模拟 sqlite3 异常暴露的扩展错误字段。"""
sqlite_errorcode = 266
sqlite_errorname = "SQLITE_IOERR_READ"
class _PsycopgError(Exception):
"""模拟 psycopg2 异常暴露的 SQLSTATE 字段。"""
pgcode = "40001"
class _AsyncpgError(Exception):
"""模拟 asyncpg 适配异常暴露的 SQLSTATE 字段。"""
sqlstate = "23505"
@pytest.mark.parametrize(
("error", "expected"),
[
(
_SqliteError("disk I/O error"),
{
"error_type": "_SqliteError",
"error_code": 266,
"error_name": "SQLITE_IOERR_READ",
},
),
(
_PsycopgError("serialization failure"),
{
"error_type": "_PsycopgError",
"sqlstate": "40001",
},
),
(
_AsyncpgError("duplicate key"),
{
"error_type": "_AsyncpgError",
"sqlstate": "23505",
},
),
],
)
def test_database_error_metadata_extracts_driver_codes(error, expected) -> None:
"""诊断元数据应兼容 SQLite、psycopg2 与 asyncpg 的稳定错误字段。"""
assert db_module._database_error_metadata(error) == expected
def test_database_error_listener_omits_statement_and_parameters(monkeypatch) -> None:
"""数据库错误日志不得包含 SQL、参数或驱动返回的原始消息。"""
messages = []
engine = create_engine("sqlite:///:memory:")
monkeypatch.setattr("app.db.diagnostics.logger.error", messages.append)
db_module._register_database_error_logging(engine)
with pytest.raises(OperationalError):
with engine.connect() as connection:
connection.execute(
text("SELECT * FROM missing_table WHERE token = :token"),
{"token": "private-token"},
)
assert len(messages) == 1
assert "database=sqlite" in messages[0]
assert "driver=pysqlite" in messages[0]
assert "error_code=1" in messages[0]
assert "error_name=SQLITE_ERROR" in messages[0]
assert "missing_table" not in messages[0]
assert "private-token" not in messages[0]
def test_async_database_engine_logs_driver_error_metadata(monkeypatch) -> None:
"""异步 Engine 应通过底层 sync engine 记录驱动错误码。"""
messages = []
monkeypatch.setattr("app.db.diagnostics.logger.error", messages.append)
async def query_missing_table() -> None:
async with get_global_async_engine().connect() as connection:
await connection.execute(text("SELECT * FROM async_missing_table"))
with pytest.raises(OperationalError):
asyncio.run(query_missing_table())
assert len(messages) == 1
assert "database=sqlite" in messages[0]
assert "driver=aiosqlite" in messages[0]
assert "error_code=1" in messages[0]
assert "error_name=SQLITE_ERROR" in messages[0]
assert "async_missing_table" not in messages[0]
def test_database_error_metadata_ignores_unclassified_errors() -> None:
"""没有驱动错误码时不应制造无效诊断日志。"""
assert db_module._database_error_metadata(RuntimeError("plain failure")) is None