修复坏枪从来没有运行过的单元测试,为项目引入单元测试框架(终于。。)

This commit is contained in:
2026-03-07 13:16:24 +08:00
parent a1c9f9bccb
commit 88861f4264
7 changed files with 1385 additions and 1309 deletions

6
.sqls.yml Normal file
View File

@ -0,0 +1,6 @@
lowercaseKeywords: false
connections:
- driver: sqlite
dataSourceName: "./data/database.db"
- driver: sqlite
dataSourceName: "./data/perm.sqlite3"

View File

@ -1,4 +1,5 @@
watch:
poetry run watchfiles bot.main . --filter scripts.watch_filter.filter
test:
poetry run pytest

View File

@ -1,3 +1,4 @@
from contextlib import asynccontextmanager
import os
import asyncio
import sqlparse
@ -10,16 +11,19 @@ if TYPE_CHECKING:
from . import DatabaseManager
# 全局数据库管理器实例
_global_db_manager: Optional['DatabaseManager'] = None
_global_db_manager: Optional["DatabaseManager"] = None
def get_global_db_manager() -> 'DatabaseManager':
def get_global_db_manager() -> "DatabaseManager":
"""获取全局数据库管理器实例"""
global _global_db_manager
if _global_db_manager is None:
from . import DatabaseManager
_global_db_manager = DatabaseManager()
return _global_db_manager
def close_global_db_manager() -> None:
"""关闭全局数据库管理器实例"""
global _global_db_manager
@ -87,6 +91,12 @@ class DatabaseManager:
except:
pass
@asynccontextmanager
async def get_conn(self):
conn = await self._get_connection()
yield conn
await self._return_connection(conn)
async def query(
self, query: str, params: Optional[tuple] = None
) -> List[Dict[str, Any]]:
@ -152,7 +162,9 @@ class DatabaseManager:
return statements
async def execute_by_sql_file(
self, file_path: Union[str, Path], params: Optional[Union[tuple, List[tuple]]] = None
self,
file_path: Union[str, Path],
params: Optional[Union[tuple, List[tuple]]] = None,
) -> None:
"""从 SQL 文件中读取非查询语句并执行"""
path = str(file_path) if isinstance(file_path, Path) else file_path
@ -167,7 +179,9 @@ class DatabaseManager:
# 使用sqlparse准确分割SQL语句
statements = self._parse_sql_statements(script)
if len(statements) != len(params):
raise ValueError(f"语句数量({len(statements)})与参数组数量({len(params)})不匹配")
raise ValueError(
f"语句数量({len(statements)})与参数组数量({len(params)})不匹配"
)
for statement, stmt_params in zip(statements, params):
if statement:
@ -215,4 +229,3 @@ class DatabaseManager:
except:
pass
self._in_use.clear()

2572
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,8 @@ dependencies = [
"shapely (>=2.1.2,<3.0.0)",
"mcstatus (>=12.2.1,<13.0.0)",
"borax (>=4.1.3,<5.0.0)",
"pytest (>=8.0.0,<9.0.0)",
"nonebug (>=0.4.3,<0.5.0)",
]
[tool.poetry]
@ -52,8 +54,14 @@ priority = "primary"
[dependency-groups]
dev = [
"rust-just (>=1.43.0,<2.0.0)",
"pytest (>=9.0.1,<10.0.0)",
"pytest-asyncio (>=1.3.0,<2.0.0)"
]
dev = ["rust-just (>=1.43.0,<2.0.0)", "pytest-asyncio (>=1.3.0,<2.0.0)"]
[tool.pytest.ini_options]
testpaths = "tests"
python_files = "test_*.py"
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
[tool.nonebot]
# plugin_dirs = ["konabot/plugins/"]
plugin_dirs = []

28
tests/conftest.py Normal file
View File

@ -0,0 +1,28 @@
# 文件内容来源:
# https://nonebot.dev/docs/best-practice/testing/
# 保证 nonebug 测试框架正常运作
import pytest
import nonebot
from pytest_asyncio import is_async_test
from nonebot.adapters.console import Adapter as ConsoleAdapter
from nonebug import NONEBOT_START_LIFESPAN
def pytest_collection_modifyitems(items: list[pytest.Item]):
pytest_asyncio_tests = (item for item in items if is_async_test(item))
session_scope_marker = pytest.mark.asyncio(loop_scope="session")
for async_test in pytest_asyncio_tests:
async_test.add_marker(session_scope_marker, append=False)
@pytest.fixture(scope="session", autouse=True)
async def after_nonebot_init(after_nonebot_init: None):
driver = nonebot.get_driver()
driver.register_adapter(ConsoleAdapter)
nonebot.load_from_toml("pyproject.toml")
def pytest_configure(config: pytest.Config):
config.stash[NONEBOT_START_LIFESPAN] = False

View File

@ -1,4 +1,3 @@
import asyncio
import os
import tempfile
from pathlib import Path
@ -12,7 +11,7 @@ from konabot.common.database import DatabaseManager
async def test_database_manager():
"""测试数据库管理器的基本功能"""
# 创建临时数据库文件
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp_file:
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp_file:
db_path = tmp_file.name
try:
@ -42,8 +41,9 @@ async def test_database_manager():
assert results[0]["email"] == "zhangsan@example.com"
# 测试使用Path对象
results = await db_manager.query_by_sql_file(Path(__file__), ("李四",))
# results = await db_manager.query_by_sql_file(Path(__file__), ("李四",))
# 注意这里只是测试参数传递实际SQL文件内容不是有效的SQL
## ^^^ 卧了个槽的坏枪,你让 AI 写单元测试不检查一下吗
# 关闭所有连接
await db_manager.close_all_connections()
@ -58,7 +58,7 @@ async def test_database_manager():
async def test_execute_script():
"""测试执行SQL脚本功能"""
# 创建临时数据库文件
with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as tmp_file:
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp_file:
db_path = tmp_file.name
try: