Suite was uncollectable for a while → code evolved, mocks didn't (42 failed / 5 errors → 1687 passed / 0 failed / 0 errors). TEST-ONLY, no app code changed, no real bugs found (code-reviewed ✅ — no test weakened to fake-green). - best_layouts: sum_deals→deals_window rename; velocity-scaling direction for new SF-01 divisors (last_month=1.0/last_year=12.0) - quarter_dump_lookup: 7→10-tuple mock rows (risks/opportunity/red_lines cols); fix early-exit call-counts for the cad_zouit fallback (#232) - cadastre_bulk: add xmin/ymin/xmax/ymax to harvest-quarter db mock - nspd_client: add required QuarterDump.opportunity - TopLayoutRow: add required is_oversold - analyze_{market_price,recent_permits,inline_weights}: SQL-signature dispatch instead of fragile positional db.execute indices (SF-B5 query reorder; also fixes a double-POST geom-starvation false-negative) - custom_pois: move centroid >15km so center_bonus=0 isolates custom-POI delta - layout/report PDF: runtime WeasyPrint probe (skip macOS dev, RUN on CI) - mv_layout SQL: normalize +psycopg DSN + connectivity-probe skipif - admin token tests: skip (X-Admin-Token gate removed #437/#426; protection is Caddy basic_auth + RBAC, covered by test_rbac) Refs #944.
91 lines
3.6 KiB
Python
91 lines
3.6 KiB
Python
"""Тесты для POST /admin/scrape/ekburg-permits.
|
||
|
||
Проверяет:
|
||
- валидный запрос без year → scope all_years_2022_2026, task_id в ответе
|
||
- валидный запрос с year=2026 → scope year_2026
|
||
- year < 2022 или > 2030 → 422
|
||
- отсутствие X-Admin-Token → 401/503
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
import pytest
|
||
from fastapi.testclient import TestClient
|
||
|
||
from app.main import app
|
||
|
||
ADMIN_TOKEN = "test-admin-token"
|
||
ADMIN_HEADERS = {"X-Admin-Token": ADMIN_TOKEN}
|
||
ENDPOINT = "/api/v1/admin/scrape/ekburg-permits"
|
||
|
||
|
||
def _mock_task(task_id: str = "fake-task-id-123") -> MagicMock:
|
||
result = MagicMock()
|
||
result.id = task_id
|
||
return result
|
||
|
||
|
||
@patch("app.core.config.settings.scrape_admin_token", ADMIN_TOKEN)
|
||
def test_trigger_refresh_all_returns_task_id() -> None:
|
||
"""POST без year → refresh_all queued, scope=all_years_2022_2026."""
|
||
mock_result = _mock_task("task-all-001")
|
||
|
||
with (
|
||
patch("app.workers.tasks.ekburg_permits_sync.refresh_all") as mock_refresh_all,
|
||
patch("app.workers.tasks.ekburg_permits_sync.refresh_year"),
|
||
):
|
||
mock_refresh_all.apply_async.return_value = mock_result
|
||
client = TestClient(app)
|
||
response = client.post(ENDPOINT, json={}, headers=ADMIN_HEADERS)
|
||
|
||
assert response.status_code == 200, response.text
|
||
body = response.json()
|
||
assert body["task_id"] == "task-all-001"
|
||
assert body["scope"] == "all_years_2022_2026"
|
||
assert "queued_at" in body
|
||
|
||
|
||
@patch("app.core.config.settings.scrape_admin_token", ADMIN_TOKEN)
|
||
def test_trigger_refresh_year_returns_task_id() -> None:
|
||
"""POST year=2026 → refresh_year queued, scope=year_2026."""
|
||
mock_result = _mock_task("task-year-002")
|
||
|
||
with (
|
||
patch("app.workers.tasks.ekburg_permits_sync.refresh_year") as mock_refresh_year,
|
||
patch("app.workers.tasks.ekburg_permits_sync.refresh_all"),
|
||
):
|
||
mock_refresh_year.apply_async.return_value = mock_result
|
||
client = TestClient(app)
|
||
response = client.post(ENDPOINT, json={"year": 2026}, headers=ADMIN_HEADERS)
|
||
|
||
assert response.status_code == 200, response.text
|
||
body = response.json()
|
||
assert body["task_id"] == "task-year-002"
|
||
assert body["scope"] == "year_2026"
|
||
|
||
|
||
@pytest.mark.parametrize("bad_year", [2021, 2031, 1999, 9999])
|
||
@patch("app.core.config.settings.scrape_admin_token", ADMIN_TOKEN)
|
||
def test_trigger_invalid_year_returns_422(bad_year: int) -> None:
|
||
"""year вне диапазона [2022, 2030] → 422 Unprocessable Entity."""
|
||
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
|