113 lines
5 KiB
Python
113 lines
5 KiB
Python
|
|
"""Regression test — KIN-009.
|
|||
|
|
|
|||
|
|
Проверяет, что в рабочей директории проекта НЕ создаются файлы с именами,
|
|||
|
|
содержащими 'sqlite3.Connection'. Такие артефакты появляются, если путь к БД
|
|||
|
|
формируется передачей объекта sqlite3.Connection вместо строки/Path в
|
|||
|
|
sqlite3.connect().
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import sqlite3
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
|
|||
|
|
# Корень проекта — три уровня вверх от этого файла (tests/ → kin/)
|
|||
|
|
PROJECT_ROOT = Path(__file__).parent.parent
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _find_connection_artifacts(search_dir: Path) -> list[Path]:
|
|||
|
|
"""Рекурсивно ищет файлы, чьё имя содержит 'sqlite3.Connection'."""
|
|||
|
|
found = []
|
|||
|
|
try:
|
|||
|
|
for entry in search_dir.rglob("*"):
|
|||
|
|
if entry.is_file() and "sqlite3.Connection" in entry.name:
|
|||
|
|
found.append(entry)
|
|||
|
|
except PermissionError:
|
|||
|
|
pass
|
|||
|
|
return found
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# Тест 1: статическая проверка — артефактов нет прямо сейчас
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
def test_no_connection_artifact_files_in_project():
|
|||
|
|
"""В рабочей директории проекта не должно быть файлов с 'sqlite3.Connection' в имени."""
|
|||
|
|
artifacts = _find_connection_artifacts(PROJECT_ROOT)
|
|||
|
|
assert artifacts == [], (
|
|||
|
|
f"Найдены файлы-артефакты sqlite3.Connection:\n"
|
|||
|
|
+ "\n".join(f" {p}" for p in artifacts)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_no_connection_artifact_files_in_kin_home():
|
|||
|
|
"""В ~/.kin/ тоже не должно быть таких файлов."""
|
|||
|
|
kin_home = Path.home() / ".kin"
|
|||
|
|
if not kin_home.exists():
|
|||
|
|
pytest.skip("~/.kin не существует")
|
|||
|
|
artifacts = _find_connection_artifacts(kin_home)
|
|||
|
|
assert artifacts == [], (
|
|||
|
|
f"Найдены файлы-артефакты sqlite3.Connection в ~/.kin:\n"
|
|||
|
|
+ "\n".join(f" {p}" for p in artifacts)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# Тест 2: динамическая проверка — init_db не создаёт артефактов в tmp_path
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
def test_init_db_does_not_create_connection_artifact(tmp_path):
|
|||
|
|
"""init_db() должен создавать файл с нормальным именем, а не 'sqlite3.Connection ...'."""
|
|||
|
|
from core.db import init_db
|
|||
|
|
|
|||
|
|
db_file = tmp_path / "test.db"
|
|||
|
|
conn = init_db(db_file)
|
|||
|
|
conn.close()
|
|||
|
|
|
|||
|
|
artifacts = _find_connection_artifacts(tmp_path)
|
|||
|
|
assert artifacts == [], (
|
|||
|
|
"init_db() создал файл с именем, содержащим 'sqlite3.Connection':\n"
|
|||
|
|
+ "\n".join(f" {p}" for p in artifacts)
|
|||
|
|
)
|
|||
|
|
# Убедимся, что файл БД реально создан с правильным именем
|
|||
|
|
assert db_file.exists(), "Файл БД должен существовать"
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
# Тест 3: воспроизводит сценарий утечки — connect(conn) вместо connect(path)
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
def test_init_db_passes_string_to_sqlite_connect(tmp_path, monkeypatch):
|
|||
|
|
"""core/db.init_db() должен вызывать sqlite3.connect() со строкой пути, а НЕ с объектом Connection.
|
|||
|
|
|
|||
|
|
Баг-сценарий: если где-то в коде путь к БД перепутан с объектом conn,
|
|||
|
|
sqlite3.connect(str(conn)) создаст файл с именем '<sqlite3.Connection object at 0x...>'.
|
|||
|
|
Этот тест перехватывает вызов и проверяет тип аргумента напрямую.
|
|||
|
|
"""
|
|||
|
|
import core.db as db_module
|
|||
|
|
|
|||
|
|
connect_calls: list = []
|
|||
|
|
real_connect = sqlite3.connect
|
|||
|
|
|
|||
|
|
def mock_connect(database, *args, **kwargs):
|
|||
|
|
connect_calls.append(database)
|
|||
|
|
return real_connect(database, *args, **kwargs)
|
|||
|
|
|
|||
|
|
monkeypatch.setattr(db_module.sqlite3, "connect", mock_connect)
|
|||
|
|
|
|||
|
|
db_file = tmp_path / "test.db"
|
|||
|
|
conn = db_module.init_db(db_file)
|
|||
|
|
conn.close()
|
|||
|
|
|
|||
|
|
assert connect_calls, "sqlite3.connect() должен быть вызван хотя бы один раз"
|
|||
|
|
|
|||
|
|
for call_arg in connect_calls:
|
|||
|
|
assert isinstance(call_arg, str), (
|
|||
|
|
f"sqlite3.connect() получил не строку: {type(call_arg).__name__!r} = {call_arg!r}"
|
|||
|
|
)
|
|||
|
|
assert "sqlite3.Connection" not in call_arg, (
|
|||
|
|
f"sqlite3.connect() получил строку объекта Connection: {call_arg!r}\n"
|
|||
|
|
"Баг: str(conn) передаётся вместо пути к файлу — это создаёт файл-артефакт!"
|
|||
|
|
)
|