fix(reports): HEAD-поддержка на /report/download — qa поймал 405
All checks were successful
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m35s
CI / backend-tests (pull_request) Successful in 14m43s
All checks were successful
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m35s
CI / backend-tests (pull_request) Successful in 14m43s
qa-смоук #2338: FastAPI НЕ добавляет HEAD к @router.get автоматически — фронтовый HEAD-preflight DOCX получал 405 (allow: GET), abортился, и UI молча не скачивал файл вовсе (curl GET при этом отдавал валидный DOCX 2.08 МБ). Явный @router.head (include_in_schema=False): FileResponse при HEAD нативно отдаёт только заголовки. +2 теста (200 headers-only, 404 симметрично GET).
This commit is contained in:
parent
1d9e25ce07
commit
747d752b2d
2 changed files with 57 additions and 0 deletions
|
|
@ -1699,6 +1699,10 @@ def parcel_report_status(
|
||||||
_REPORT_DOCX_MEDIA_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
_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(
|
@router.get(
|
||||||
"/{cad_num}/report/download",
|
"/{cad_num}/report/download",
|
||||||
summary="Скачать готовый полный отчёт участка — PDF или DOCX (#2259 PR-D/PR-F)",
|
summary="Скачать готовый полный отчёт участка — PDF или DOCX (#2259 PR-D/PR-F)",
|
||||||
|
|
|
||||||
|
|
@ -422,3 +422,56 @@ def test_download_invalid_format_returns_422() -> None:
|
||||||
assert resp.status_code == 422, resp.text
|
assert resp.status_code == 422, resp.text
|
||||||
finally:
|
finally:
|
||||||
app.dependency_overrides.clear()
|
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()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue