"""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)) создаст файл с именем ''. Этот тест перехватывает вызов и проверяет тип аргумента напрямую. """ 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) передаётся вместо пути к файлу — это создаёт файл-артефакт!" )