feat(KIN-010): implement rebuild-frontend post-pipeline hook
- scripts/rebuild-frontend.sh: builds Vue 3 frontend and restarts uvicorn API - cli/main.py: hook group with add/list/remove/logs/setup commands; `hook setup` idempotently registers rebuild-frontend for a project - agents/runner.py: call run_hooks(event="pipeline_completed") after successful pipeline; wrap in try/except so hook errors never block results - tests: 3 tests for hook_setup CLI + 3 tests for pipeline→hooks integration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6705b302f7
commit
01b269e2b8
6 changed files with 355 additions and 2 deletions
112
tests/test_no_connection_artifacts.py
Normal file
112
tests/test_no_connection_artifacts.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"""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) передаётся вместо пути к файлу — это создаёт файл-артефакт!"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue