fix(ptica): бан НСПД по WAF больше не глотается сборщиком кварталов
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m58s
CI / backend-tests (pull_request) Successful in 16m9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m58s
CI / backend-tests (pull_request) Successful in 16m9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
harvest_quarter в докстроке обещает `Raises: NspdBulkWafError — caller не retry`, но три голых `except Exception` это обещание отменяли: WAF-бан превращался в «этот cell/слой не дошёл», обход продолжался и job доходил до status='done' с частичными данными. Прод-замер 13.08 по cadastre_jobs: 23 job'а, суммарно 50 WAF-блоков, упавших — 0. То есть бан ни разу не остановил сбор, как обещано. Три сайта: per-category probe (search_by_quarter), territorial_zones и цикл ячеек в _grid_walk_category. Везде добавлен `except (NspdBulkWafError, NspdBulkRateLimitError): raise` ПЕРЕД широкими обработчиками — зеркало уже исправленных nspd_bulk_client.get_features_in_bbox_grid и nspd_client (#2464-G). Продолжать обход после 403 вредно вдвойне: углубляет бан и дописывает в БД ложный нулевой слой, неотличимый от честного «здесь ничего нет». Тесты двусторонние: против bulk_harvest из main падают ровно два новых (waf, rate_limit), третий — контроль «сетевая ошибка ячейки обход НЕ роняет и НЕ поднимает layer_failed» — зелёный с обеих сторон. Refs #2464
This commit is contained in:
parent
f600663aa0
commit
d2cf7456d9
2 changed files with 87 additions and 1 deletions
|
|
@ -30,7 +30,12 @@ from sqlalchemy import text
|
|||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.schemas.nspd_bulk import NSPDBulkFeature, QuarterSnapshot
|
||||
from app.scrapers.nspd_bulk_client import NSPDBulkClient, NspdBulkServerError
|
||||
from app.scrapers.nspd_bulk_client import (
|
||||
NSPDBulkClient,
|
||||
NspdBulkRateLimitError,
|
||||
NspdBulkServerError,
|
||||
NspdBulkWafError,
|
||||
)
|
||||
from app.services.cadastre.grid_geometry import generate_grid_click_points, quarter_bbox_3857
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -182,6 +187,13 @@ async def harvest_quarter(
|
|||
try:
|
||||
cat_snapshot = await client.search_by_quarter(quarter, category_id=cat_id)
|
||||
result.snapshot_requests += 1
|
||||
except (NspdBulkWafError, NspdBulkRateLimitError):
|
||||
# #2464-A: бан IP / исчерпанные ретраи — НЕ «этот cat не дошёл».
|
||||
# Контракт harvest_quarter (Raises:) обещает пробросить их наверх,
|
||||
# а голый except ниже их глотал: прогон доходил до status='done'
|
||||
# с частичными данными. Прод-замер 13.08: 23 job'а, 50 WAF-блоков,
|
||||
# 0 упавших — то есть бан ни разу не остановил сбор.
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"harvest_quarter: per-cat probe failed cat=%d quarter=%s: %s",
|
||||
|
|
@ -279,6 +291,9 @@ async def harvest_quarter(
|
|||
logger.info(
|
||||
"harvest_quarter: territorial_zones quarter=%s upserted=%d", quarter, tz_count
|
||||
)
|
||||
except (NspdBulkWafError, NspdBulkRateLimitError):
|
||||
# #2464-A: см. выше — бан пробрасываем, а не превращаем в «слой пуст».
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.warning("harvest_quarter: territorial_zones failed quarter=%s: %s", quarter, e)
|
||||
|
||||
|
|
@ -399,6 +414,18 @@ async def _grid_walk_category(
|
|||
requests += 1
|
||||
server_errors += 1
|
||||
continue
|
||||
except (NspdBulkWafError, NspdBulkRateLimitError):
|
||||
# #2464-A: 403 WAF — бан IP, а не «этот cell не дошёл». Продолжать
|
||||
# обход значит углублять бан и дописать в БД ложный нулевой слой.
|
||||
# Зеркало уже исправленных nspd_bulk_client.get_features_in_bbox_grid
|
||||
# и nspd_client.get_features_in_bbox_grid (#2464-G).
|
||||
logger.warning(
|
||||
"_grid_walk_category: WAF/rate-limit layer=%d quarter=%s cell=%d — прерываем",
|
||||
layer_id,
|
||||
quarter,
|
||||
idx,
|
||||
)
|
||||
raise
|
||||
except Exception as e:
|
||||
# Прочие (сетевые / parse) ошибки одного cell — тоже не валим квартал,
|
||||
# но это НЕ server-side 500 → не учитываем в server_errors (иначе сеть
|
||||
|
|
|
|||
|
|
@ -1393,6 +1393,65 @@ async def test_grid_walk_marks_layer_failed_when_all_cells_500() -> None:
|
|||
assert layer_failed is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_grid_walk_reraises_waf_instead_of_swallowing() -> None:
|
||||
"""#2464-A: 403 WAF прерывает обход, а не превращается в «cell не дошёл».
|
||||
|
||||
Контракт harvest_quarter (Raises:) обещает пробросить NspdBulkWafError, но
|
||||
голый `except Exception` в цикле ячеек его глотал. Прод-замер 13.08:
|
||||
23 job'а в cadastre_jobs, суммарно 50 WAF-блоков — и НИ ОДНОГО упавшего
|
||||
job'а. То есть бан ни разу не остановил сбор, как обещано.
|
||||
"""
|
||||
from app.scrapers.nspd_bulk_client import NspdBulkWafError
|
||||
from app.services.cadastre.bulk_harvest import _grid_walk_category
|
||||
|
||||
db = _mock_db_grid_bbox()
|
||||
client = AsyncMock()
|
||||
client.wms_feature_info = AsyncMock(side_effect=NspdBulkWafError("HTTP 403 WAF"))
|
||||
|
||||
with pytest.raises(NspdBulkWafError):
|
||||
await _grid_walk_category(
|
||||
db=db, client=client, quarter="66:41:0303161", layer_id=36368, grid_size=3
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_grid_walk_reraises_rate_limit() -> None:
|
||||
"""#2464-A: исчерпанные ретраи — тоже не «пустой слой» (caller может retry)."""
|
||||
from app.scrapers.nspd_bulk_client import NspdBulkRateLimitError
|
||||
from app.services.cadastre.bulk_harvest import _grid_walk_category
|
||||
|
||||
db = _mock_db_grid_bbox()
|
||||
client = AsyncMock()
|
||||
client.wms_feature_info = AsyncMock(side_effect=NspdBulkRateLimitError("429"))
|
||||
|
||||
with pytest.raises(NspdBulkRateLimitError):
|
||||
await _grid_walk_category(
|
||||
db=db, client=client, quarter="66:41:0303161", layer_id=36368, grid_size=3
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_grid_walk_still_tolerates_network_error_per_cell() -> None:
|
||||
"""Контроль обратной крайности: сетевая ошибка ячейки обход НЕ роняет.
|
||||
|
||||
Зелёный с обеих сторон правки — проверяет, что #2464-A не превратил любое
|
||||
исключение в отказ квартала.
|
||||
"""
|
||||
from app.services.cadastre.bulk_harvest import _grid_walk_category
|
||||
|
||||
db = _mock_db_grid_bbox()
|
||||
client = AsyncMock()
|
||||
client.wms_feature_info = AsyncMock(side_effect=OSError("connection reset"))
|
||||
|
||||
upserted, requests, layer_failed = await _grid_walk_category(
|
||||
db=db, client=client, quarter="66:41:0303161", layer_id=36368, grid_size=3
|
||||
)
|
||||
assert upserted == 0
|
||||
assert requests == 9
|
||||
assert layer_failed is False, "сетевые сбои НЕ должны поднимать layer_failed"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_grid_walk_layer_not_failed_when_some_cells_ok() -> None:
|
||||
"""Issue #252: если хоть один cell прошёл — layer_failed=False (слой жив, просто пуст)."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue