kin/tests/test_kin_fix_024_regression.py
2026-03-19 13:28:30 +02:00

154 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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"