"""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("SPA") (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 # --------------------------------------------------------------------------- # (1–3) Проверка расположения 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/" ) # --------------------------------------------------------------------------- # (4–6) 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}" ) # --------------------------------------------------------------------------- # (7–9) 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"