fix(tests): починить и вычистить проверки, которые не исполнялись ни разу (#2744)
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy Trade-In / changes (push) Successful in 11s
Deploy / build-frontend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy / build-backend (push) Successful in 32s
Deploy / build-worker (push) Successful in 32s
Deploy / deploy (push) Successful in 1m8s
Deploy Trade-In / test (push) Successful in 3m5s
Deploy Trade-In / build-backend (push) Successful in 29s
Deploy Trade-In / deploy (push) Successful in 1m5s
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy Trade-In / changes (push) Successful in 11s
Deploy / build-frontend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy / build-backend (push) Successful in 32s
Deploy / build-worker (push) Successful in 32s
Deploy / deploy (push) Successful in 1m8s
Deploy Trade-In / test (push) Successful in 3m5s
Deploy Trade-In / build-backend (push) Successful in 29s
Deploy Trade-In / deploy (push) Successful in 1m5s
This commit is contained in:
parent
9f51c98ff4
commit
a091da1aa8
5 changed files with 24 additions and 104 deletions
|
|
@ -5,32 +5,16 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
# scrape_cadastre зависит от app.scrapers.nspd_bulk_client (PR 2/5).
|
||||
# Используем importlib.util.find_spec вместо прямого import — иначе
|
||||
# `import app.workers...` пересоздаёт `app` как Python package и
|
||||
# перебивает FastAPI instance, привязанный в строке выше → AttributeError
|
||||
# на app.dependency_overrides.
|
||||
import importlib.util
|
||||
# NB: `import app.workers...` здесь запрещён — он пересоздаёт `app` как Python
|
||||
# package и перебивает FastAPI instance, привязанный ниже → AttributeError на
|
||||
# app.dependency_overrides. Патчим воркер строкой, через patch("app.workers...").
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
|
||||
_SCRAPE_CADASTRE_AVAILABLE = (
|
||||
importlib.util.find_spec("app.workers.tasks.scrape_cadastre") is not None
|
||||
)
|
||||
|
||||
requires_scrape_cadastre = pytest.mark.skipif(
|
||||
not _SCRAPE_CADASTRE_AVAILABLE,
|
||||
reason=(
|
||||
"app.scrapers.nspd_bulk_client отсутствует — нужно смержить main (PR #170). "
|
||||
"Команда: git rebase origin/main"
|
||||
),
|
||||
)
|
||||
|
||||
# Токен для тестов (не реальный)
|
||||
ADMIN_TOKEN = "test-admin-token"
|
||||
ADMIN_HEADERS = {"X-Admin-Token": ADMIN_TOKEN}
|
||||
|
|
@ -113,7 +97,6 @@ def _make_sample_job_row() -> dict[str, Any]:
|
|||
# ── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@requires_scrape_cadastre
|
||||
@patch("app.core.config.settings.scrape_admin_token", ADMIN_TOKEN)
|
||||
def test_create_job_pilot_returns_job_id() -> None:
|
||||
"""POST /cadastre/jobs scope=pilot → job_id + targets_total."""
|
||||
|
|
@ -144,7 +127,6 @@ def test_create_job_pilot_returns_job_id() -> None:
|
|||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
@requires_scrape_cadastre
|
||||
@patch("app.core.config.settings.scrape_admin_token", ADMIN_TOKEN)
|
||||
def test_create_job_manual_list() -> None:
|
||||
"""POST /cadastre/jobs scope=manual_list с явным списком."""
|
||||
|
|
@ -174,7 +156,6 @@ def test_create_job_manual_list() -> None:
|
|||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
@requires_scrape_cadastre
|
||||
@patch("app.core.config.settings.scrape_admin_token", ADMIN_TOKEN)
|
||||
def test_create_job_manual_list_empty_quarters_returns_400() -> None:
|
||||
"""scope=manual_list без quarters → 400."""
|
||||
|
|
@ -294,22 +275,3 @@ def test_cancel_job_not_found_returns_404() -> None:
|
|||
assert response.status_code == 404
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason=(
|
||||
"X-Admin-Token gate удалён в #437 (refactor(security): убрать X-Admin-Token — "
|
||||
"Caddy basic_auth + RBAC middleware достаточны). Admin endpoint'ы больше не "
|
||||
"несут verify_admin_token dependency; защита — на уровне Caddy/RBAC. В test-mode "
|
||||
"RBAC bypass'ится (CI-rehab 1/3), поэтому 401/503 здесь больше недостижим без "
|
||||
"реверта security-решения #437. Тест проверял удалённое поведение."
|
||||
)
|
||||
)
|
||||
def test_create_job_no_token_returns_401() -> None:
|
||||
"""Без X-Admin-Token → 401/503 (устарело: токен-гейт удалён в #437)."""
|
||||
client = TestClient(app)
|
||||
response = client.post(
|
||||
"/api/v1/admin/cadastre/jobs",
|
||||
json={"scope": "pilot"},
|
||||
)
|
||||
assert response.status_code in (401, 503)
|
||||
|
|
|
|||
|
|
@ -73,19 +73,3 @@ def test_trigger_invalid_year_returns_422(bad_year: int) -> None:
|
|||
client = TestClient(app)
|
||||
response = client.post(ENDPOINT, json={"year": bad_year}, headers=ADMIN_HEADERS)
|
||||
assert response.status_code == 422, f"year={bad_year} должен возвращать 422"
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason=(
|
||||
"X-Admin-Token gate удалён в #437 (refactor(security): убрать X-Admin-Token — "
|
||||
"Caddy basic_auth + RBAC middleware достаточны). trigger_ekburg_permits больше не "
|
||||
"несёт verify_admin_token dependency; защита — на уровне Caddy/RBAC. В test-mode "
|
||||
"RBAC bypass'ится (CI-rehab 1/3), поэтому 401/503 здесь больше недостижим без "
|
||||
"реверта security-решения #437. Тест проверял удалённое поведение."
|
||||
)
|
||||
)
|
||||
def test_trigger_no_token_returns_401_or_503() -> None:
|
||||
"""Без X-Admin-Token → 401 или 503 (устарело: токен-гейт удалён в #437)."""
|
||||
client = TestClient(app)
|
||||
response = client.post(ENDPOINT, json={})
|
||||
assert response.status_code in (401, 503), response.text
|
||||
|
|
|
|||
|
|
@ -202,9 +202,7 @@ class TestParseTable11:
|
|||
# Реальная зона должна быть в результате
|
||||
assert "Зона жилой застройки" in zone_names
|
||||
# Артефакты (zone_name пуст, не is_total) отброшены
|
||||
non_total_no_zone = [
|
||||
r for r in result if not r["zone_name"] and not r.get("is_total")
|
||||
]
|
||||
non_total_no_zone = [r for r in result if not r["zone_name"] and not r.get("is_total")]
|
||||
assert non_total_no_zone == []
|
||||
# Итого-строка осталась
|
||||
assert any(r.get("is_total") for r in result)
|
||||
|
|
@ -483,15 +481,23 @@ class TestParsePptTepMocked:
|
|||
|
||||
|
||||
# ── Тест на реальном PDF-образце (skip в CI) ─────────────────────────────────
|
||||
|
||||
_REAL_PDF_PATH = Path(
|
||||
"C:/Users/user/source/repos/gendesign/.playwright-mcp/ekb-docs/ppt2018_22823_poyasnit_PPT.pdf"
|
||||
#
|
||||
# Путь repo-relative, а не абсолютный `C:/Users/user/source/repos/...`: с
|
||||
# Windows-путём условие пропуска не выполнялось НИ НА ОДНОЙ машине кроме одного
|
||||
# старого дев-бокса, т.е. эти 6 тестов были не «пропущены по среде», а мертвы.
|
||||
# Образец не в git (untracked `.playwright-mcp/`), поэтому в CI пропуск остаётся —
|
||||
# но теперь он снимается тем, что файл кладут в репо-каталог на любой ОС.
|
||||
_REAL_PDF_PATH = (
|
||||
Path(__file__).resolve().parents[4]
|
||||
/ ".playwright-mcp"
|
||||
/ "ekb-docs"
|
||||
/ "ppt2018_22823_poyasnit_PPT.pdf"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not _REAL_PDF_PATH.exists(),
|
||||
reason="Реальный PDF-образец отсутствует (только для локального запуска)",
|
||||
reason=f"Реальный PDF-образец отсутствует ({_REAL_PDF_PATH}) — только локальный запуск",
|
||||
)
|
||||
class TestParsePptTepRealPdf:
|
||||
"""Интеграционные тесты на реальном образце ppt2018_22823."""
|
||||
|
|
|
|||
|
|
@ -399,41 +399,3 @@ def test_list_profiles_with_system_service(monkeypatch: pytest.MonkeyPatch) -> N
|
|||
assert len(captured_params) == 1
|
||||
assert captured_params[0]["system_user_id"] == SYSTEM_USER_ID
|
||||
assert captured_params[0]["user_id"] == "user-test"
|
||||
|
||||
|
||||
# ── Auth ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
_TOKEN_REMOVED_REASON = (
|
||||
"App-level X-Admin-Token header удалён 2026-05-23 (см. docstring "
|
||||
"app/api/v1/admin_weight_profiles.py: Caddy basic_auth PR #426 + RBAC достаточны, "
|
||||
"двойная auth избыточна). Endpoint больше не несёт verify_admin_token dependency. "
|
||||
"В test-mode RBAC bypass'ится (CI-rehab 1/3) → 401 здесь недостижим без реверта "
|
||||
"security-решения. Тест проверял удалённое поведение."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason=_TOKEN_REMOVED_REASON)
|
||||
def test_unauthorized_no_token(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Запрос без X-Admin-Token → 401 (устарело: токен-гейт удалён 2026-05-23)."""
|
||||
monkeypatch.setattr("app.core.config.settings.scrape_admin_token", _ADMIN_TOKEN)
|
||||
client = TestClient(app)
|
||||
r = client.get(
|
||||
"/api/v1/admin/site-finder/weight-profiles",
|
||||
params={"user_id": "user-1"},
|
||||
# без headers — нет X-Admin-Token
|
||||
)
|
||||
assert r.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.skip(reason=_TOKEN_REMOVED_REASON)
|
||||
def test_unauthorized_wrong_token(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Неверный X-Admin-Token → 401 (устарело: токен-гейт удалён 2026-05-23)."""
|
||||
monkeypatch.setattr("app.core.config.settings.scrape_admin_token", _ADMIN_TOKEN)
|
||||
client = TestClient(app)
|
||||
r = client.get(
|
||||
"/api/v1/admin/site-finder/weight-profiles",
|
||||
params={"user_id": "user-1"},
|
||||
headers={"X-Admin-Token": "wrong-token"},
|
||||
)
|
||||
assert r.status_code == 401
|
||||
|
|
|
|||
|
|
@ -449,8 +449,14 @@ def test_upsert_and_canon_match_populates_gar_flat_count(gar_dir: str) -> None:
|
|||
db = _live_session()
|
||||
assert db is not None
|
||||
try:
|
||||
db.execute(text(_NORMALIZER_FN))
|
||||
db.execute(text(_CANON_FN))
|
||||
# exec_driver_sql, НЕ text(): в обеих функциях есть regexp-группы `(?:...)`,
|
||||
# а `text()` читает `:Россия` / `:кв` / `:асть` как bind-параметры и падает
|
||||
# с InvalidRequestError ещё до первого утверждения. Драйверный путь ничего
|
||||
# не парсит. (Тест жил с 2026-06-28 и до #2741 не исполнялся ни разу — под
|
||||
# skipif «нет БД» падение было не видно.)
|
||||
raw = db.connection()
|
||||
raw.exec_driver_sql(_NORMALIZER_FN)
|
||||
raw.exec_driver_sql(_CANON_FN)
|
||||
db.execute(
|
||||
text(
|
||||
"CREATE TEMP TABLE gar_house_flats ("
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue