From 9289c3d69d6f0b24d825eac0471c3382df255856 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Fri, 3 Jul 2026 23:13:23 +0000 Subject: [PATCH] =?UTF-8?q?fix(reports):=20HEAD-=D0=BF=D0=BE=D0=B4=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20=D0=BD=D0=B0=20/report/downloa?= =?UTF-8?q?d=20=E2=80=94=20qa=20=D0=BF=D0=BE=D0=B9=D0=BC=D0=B0=D0=BB=20405?= =?UTF-8?q?,=20DOCX=20=D0=BD=D0=B5=20=D1=81=D0=BA=D0=B0=D1=87=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=D1=81=D1=8F=20=D0=B8=D0=B7=20UI=20(#2343)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/parcels.py | 4 ++ backend/tests/api/v1/test_parcel_report.py | 53 ++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/backend/app/api/v1/parcels.py b/backend/app/api/v1/parcels.py index 1d8fedf5..da20595d 100644 --- a/backend/app/api/v1/parcels.py +++ b/backend/app/api/v1/parcels.py @@ -1699,6 +1699,10 @@ def parcel_report_status( _REPORT_DOCX_MEDIA_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" +# HEAD нужен фронтовому preflight'у (#2338): FastAPI НЕ добавляет HEAD к @router.get +# автоматически (прод отдавал 405 → UI не скачивал DOCX вовсе). FileResponse при +# HEAD нативно отдаёт только заголовки (Starlette send_header_only). +@router.head("/{cad_num}/report/download", include_in_schema=False) @router.get( "/{cad_num}/report/download", summary="Скачать готовый полный отчёт участка — PDF или DOCX (#2259 PR-D/PR-F)", diff --git a/backend/tests/api/v1/test_parcel_report.py b/backend/tests/api/v1/test_parcel_report.py index ea51004d..deb74db4 100644 --- a/backend/tests/api/v1/test_parcel_report.py +++ b/backend/tests/api/v1/test_parcel_report.py @@ -422,3 +422,56 @@ def test_download_invalid_format_returns_422() -> None: assert resp.status_code == 422, resp.text finally: app.dependency_overrides.clear() + + +def test_download_head_returns_headers_only(tmp_path: Path) -> None: + """HEAD на download — 200 с заголовками БЕЗ тела (preflight фронта, #2338). + + FastAPI не добавляет HEAD к @router.get автоматически — прод отдавал 405, + HEAD-preflight абортился и UI молча не скачивал DOCX. Явный @router.head чинит. + """ + from app.core.db import get_db + + pdf = tmp_path / "report.pdf" + pdf.write_bytes(b"%PDF-1.7 payload") + docx = tmp_path / "report.docx" + docx.write_bytes(b"PK\x03\x04 fake-docx") + report_row = _run( + 510, + { + "pdf_path": str(pdf), + "docx_path": str(docx), + "analyze_run_id": 101, + "forecast_run_id": 202, + "generated_at": "2026-07-01T09:00:00+00:00", + }, + ) + rows = {REPORT_SCHEMA_VERSION: report_row} + db = MagicMock() + app.dependency_overrides[get_db] = _override_db(db) + try: + with patch("app.api.v1.parcels.latest_run_for", side_effect=_lrf_router(rows)): + client = TestClient(app) + resp = client.head(f"/api/v1/parcels/{_CAD}/report/download?format=docx") + assert resp.status_code == 200, resp.text + assert resp.headers["content-type"] == ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ) + assert resp.content == b"" + finally: + app.dependency_overrides.clear() + + +def test_download_head_404_when_not_ready() -> None: + """HEAD для участка без готового отчёта — 404 (симметрично GET).""" + from app.core.db import get_db + + db = MagicMock() + app.dependency_overrides[get_db] = _override_db(db) + try: + with patch("app.api.v1.parcels.latest_run_for", side_effect=_lrf_router({})): + client = TestClient(app) + resp = client.head(f"/api/v1/parcels/{_CAD}/report/download") + assert resp.status_code == 404 + finally: + app.dependency_overrides.clear()