kin/tests/test_kin_fix_024_regression.py

155 lines
6.4 KiB
Python
Raw Permalink Normal View History

2026-03-19 13:28:30 +02:00
"""Regression tests for KIN-FIX-024 — dist из worktree вручную скопирован в основной проект.
Acceptance criteria:
- после `git pull` + `npm run build` в main деплой работает без ручного копирования dist/
Проверяем:
(1) DIST path в api.py указывает на web/frontend/dist (относительно api.py)
(2) В проекте нет dist/ в корне (stray artifact)
(3) В web/ нет dist/ вне web/frontend/dist/ (stray artifact)
(4) tsconfig.app.json исключает *.test.ts из сборки
(5) tsconfig.app.json исключает *.spec.ts из сборки
(6) tsconfig.app.json исключает __tests__/ директории
(7) GET / возвращает index.html из dist/ (SPA root)
(8) GET /some/nested/route SPA fallback на index.html
(9) GET /favicon.ico конкретный файл из dist/ (не index.html)
"""
import json
import re
import pytest
from pathlib import Path
PROJECT_ROOT = Path(__file__).parent.parent
WEB_API_FILE = PROJECT_ROOT / "web" / "api.py"
TSCONFIG_APP = PROJECT_ROOT / "web" / "frontend" / "tsconfig.app.json"
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def spa_client(tmp_path):
"""TestClient с изолированной БД и фейковым dist/ для тестов SPA endpoint."""
import web.api as api_module
db_path = tmp_path / "test.db"
api_module.DB_PATH = db_path
# Создаём фейковый dist/
dist_dir = tmp_path / "dist"
dist_dir.mkdir()
(dist_dir / "index.html").write_text("<html><body>SPA</body></html>")
(dist_dir / "favicon.ico").write_bytes(b"ICON_DATA")
original_dist = api_module.DIST
api_module.DIST = dist_dir
from web.api import app
from fastapi.testclient import TestClient
client = TestClient(app)
yield client
api_module.DIST = original_dist
# ---------------------------------------------------------------------------
# (13) Проверка расположения dist/ в проекте
# ---------------------------------------------------------------------------
def test_dist_path_points_to_web_frontend_dist():
"""api.py DIST должен резолвиться в web/frontend/dist относительно api.py."""
import web.api as api_module
expected = (WEB_API_FILE.parent / "frontend" / "dist").resolve()
actual = api_module.DIST.resolve()
assert actual == expected, (
f"DIST указывает на {actual}, ожидалось {expected}. "
"Возможна ошибка пути — деплой без ручного копирования не будет работать."
)
def test_no_stray_dist_in_project_root():
"""dist/ не должен существовать в корне проекта (признак ручного копирования)."""
root_dist = PROJECT_ROOT / "dist"
assert not root_dist.exists(), (
f"Обнаружен stray dist/ в корне проекта: {root_dist}. "
"Это признак ручного копирования из worktree."
)
def test_no_stray_dist_in_web_root():
"""dist/ не должен существовать в web/ (только в web/frontend/dist/)."""
web_dist = PROJECT_ROOT / "web" / "dist"
assert not web_dist.exists(), (
f"Обнаружен stray dist/ в web/: {web_dist}. "
"Правильное место: web/frontend/dist/"
)
# ---------------------------------------------------------------------------
# (46) tsconfig.app.json — exclude для тест-файлов
# ---------------------------------------------------------------------------
def _load_tsconfig_exclude() -> list:
"""Парсим tsconfig.app.json как JSONC (может содержать /* */ и // комментарии)."""
content = TSCONFIG_APP.read_text()
# Стрипаем блочные комментарии /* ... */
content = re.sub(r"/\*.*?\*/", "", content, flags=re.DOTALL)
# Стрипаем строчные комментарии // ...
content = re.sub(r"//[^\n]*", "", content)
data = json.loads(content)
return data.get("exclude", [])
def test_tsconfig_app_excludes_test_ts():
"""tsconfig.app.json должен исключать src/**/*.test.ts из сборки."""
exclude = _load_tsconfig_exclude()
assert any("test.ts" in e for e in exclude), (
f"tsconfig.app.json не исключает *.test.ts — тест-файлы попадут в сборку. "
f"exclude={exclude}"
)
def test_tsconfig_app_excludes_spec_ts():
"""tsconfig.app.json должен исключать src/**/*.spec.ts из сборки."""
exclude = _load_tsconfig_exclude()
assert any("spec.ts" in e for e in exclude), (
f"tsconfig.app.json не исключает *.spec.ts — тест-файлы попадут в сборку. "
f"exclude={exclude}"
)
def test_tsconfig_app_excludes_tests_directory():
"""tsconfig.app.json должен исключать src/**/__tests__/ из сборки."""
exclude = _load_tsconfig_exclude()
assert any("__tests__" in e for e in exclude), (
f"tsconfig.app.json не исключает __tests__/ директории. "
f"exclude={exclude}"
)
# ---------------------------------------------------------------------------
# (79) SPA endpoint — serve_spa обслуживает из корректного dist/
# ---------------------------------------------------------------------------
def test_serve_spa_root_returns_index_html(spa_client):
"""GET / → index.html из dist/ (SPA entry point)."""
r = spa_client.get("/")
assert r.status_code == 200
assert "SPA" in r.text
def test_serve_spa_unknown_route_returns_index_html(spa_client):
"""GET /projects/kin/tasks → SPA fallback (index.html), не 404."""
r = spa_client.get("/projects/kin/tasks")
assert r.status_code == 200
assert "SPA" in r.text
def test_serve_spa_existing_file_returned_directly(spa_client):
"""GET /favicon.ico → конкретный файл из dist/, а не index.html."""
r = spa_client.get("/favicon.ico")
assert r.status_code == 200
assert r.content == b"ICON_DATA"